Gin POST接口收不到JSON数据主因是请求体被提前读取或前端格式不匹配;打卡时间须用服务端time.Now().UTC().Unix()而非客户端timestamp;频率限制宜用Redis中间件;INSERT IGNORE更适考勤“只认首次”场景。

为什么 Gin 的 POST 接口收不到前端传的 JSON 数据
常见现象是后端日志里 c.ShouldBindJSON(&req) 报错 invalid character ... 或直接返回 400,但前端确认已发 Content-Type: application/json。根本原因往往不是 Gin 本身,而是请求体被中间件或代理提前读取过(比如用了 gin.Logger() 后又手动调 c.Request.Body),或者前端发的是表单格式却用 ShouldBindJSON 去解析。
- 确保前端发送时 header 确实包含
Content-Type: application/json,且 payload 是合法 JSON(如{"userId":"U123","timestamp":1715823456}) - Gin 默认不自动解析
application/x-www-form-urlencoded为 struct,若前端用FormData发送,得改用c.ShouldBind(&req)或显式声明formtag - 避免在绑定前调用
c.Request.Body—— 一旦读取,Body 就成空流,ShouldBindJSON必然失败;调试时用c.Request.Header和c.Request.ContentLength辅助判断即可
time.Now().Unix() 在打卡接口里为什么不能直接当“打卡时间”用
考勤场景下,“打卡时间”必须是服务端可信时间,而非客户端传来的 timestamp。否则用户手机时间调快/调慢会导致作弊。Gin 接口里拿到的 timestamp 字段只能用于校验(比如是否在允许浮动范围内),不能入库作为打卡时间。
- 入库字段必须用
time.Now().UTC().Unix()(推荐 UTC 避免时区歧义),而不是依赖任何请求参数 - 若需支持“补卡”或“延迟打卡”,应单独设计状态字段(如
status为"normal"/"late"/"absent"),并用服务端时间计算迟到分钟数:int(time.Since(checkInTime).Minutes()) - 注意 MySQL 的
TIMESTAMP类型默认存 UTC,而DATETIME存本地时间 —— Go 的time.Time写入前建议统一转为 UTC,避免时区混乱
如何用 Gin 中间件做打卡频率限制(比如每人每天最多打 4 次)
不能只靠数据库唯一索引(如 user_id + date),因为早退、补卡等业务需要更灵活的次数控制。Gin 中间件适合做前置拦截,但要注意并发安全和存储选型。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用 Redis 实现计数:key 为
checkin:2024-05-15:U123,每次请求INCR并EXPIRE到当天结束时间,再GET判断是否 ≤4 - 避免用内存 map 做计数 —— 多实例部署时失效,且无法自动过期
- 中间件里不要阻塞等待 Redis 响应,超时应降级为放行(记录 warn 日志),否则影响接口可用性
- 注意 Redis 返回
nil时要判空,redis.IntCmd.Val()可能 panic,务必用cmd.Result()+ error 检查
MySQL 插入打卡记录时,为什么 INSERT IGNORE 比 ON DUPLICATE KEY UPDATE 更合适
考勤系统要求“同一天同一人只保留最早一次打卡”,但业务上可能允许上午打一次、下午再打一次(如外勤回公司)。所以主键或唯一索引设计很关键 —— 如果只建 user_id + date 唯一索引,那第二次打卡就会被忽略;但如果建 user_id + date + type(type=“in”/“out”),就需更精细控制。
立即学习“go语言免费学习笔记(深入)”;
- 推荐组合唯一索引:
UNIQUE KEY uk_user_date_type (user_id, DATE(checkin_time), type),这样上下班打卡可共存 -
INSERT IGNORE在冲突时不报错、不更新,适合“只认第一次”的场景;而ON DUPLICATE KEY UPDATE会触发更新,可能覆盖掉更早的有效打卡时间 - 如果真要更新(比如补卡修正时间),应用层先
SELECT判断是否存在,再决定INSERT或UPDATE,避免竞态
真实考勤逻辑远不止插入一条记录:涉及班次规则匹配、节假日判定、地理位置校验、设备指纹绑定……这些都得在 ShouldBind 之后、DB 操作之前完成校验,顺序错了容易绕过风控。

















