根本原因是参数绑定方式错配:前端用application/json发送,后端却用c.DefaultQuery()读URL参数;应统一用c.ShouldBindJSON()或c.ShouldBind(),并确保struct tag匹配。

为什么 gin.Context 里拿不到前端传的经纬度?
常见现象是后端日志显示 lat 和 lng 为 0 或空,但前端确认已发送。根本原因通常是参数绑定方式错配:前端用 application/json 发送对象,后端却用 c.DefaultQuery() 去取 —— Query 只读 URL 参数,不解析请求体。
实操建议:
- 统一用
c.ShouldBindJSON(&req)接收结构体,前提是 Content-Type 是application/json,且 JSON key 与 struct tag 匹配(如`json:"lat"`) - 若前端必须用表单提交(
application/x-www-form-urlencoded),改用c.ShouldBind(&req),并确保 struct tag 同时含form:"lat"和json:"lat" - 别依赖
c.PostForm("lat")手动取值 —— 它不自动类型转换,返回的是字符串,容易因格式错误导致strconv.ParseFloatpanic
如何校验定位坐标是否在有效地理围栏内?
企业考勤常要求“仅允许在办公园区 500 米内打卡”,不能只比对经纬度数值大小(地球是球面)。必须用 Haversine 公式或调用轻量级库算球面距离。
实操建议:
- 避免手写 Haversine —— 浮点精度、单位换算(弧度/角度)、边界 case(跨国际日期变更线)易出错
- 推荐直接用
github.com/kellydunn/golang-geo:创建geo.Point{Lat: lat, Lon: lng},再调point.Distance(officePoint),单位是米 - 校验逻辑要前置:在 Gin 中间件或 handler 开头就计算距离,超阈值立即
c.AbortWithStatusJSON(400, gin.H{"error": "超出打卡范围"}),不进业务逻辑 - 注意缓存办公点坐标:别每次请求都从 DB 查,应初始化时加载到内存变量中
time.Now().In(location) 为什么返回的时间还是 UTC?
典型错误是写了 loc, _ := time.LoadLocation("Asia/Shanghai"),但没把 loc 传给 In(),或者 LoadLocation 失败返回了 nil 导致默认用 UTC。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
实操建议:
- 强制检查错误:
loc, err := time.LoadLocation("Asia/Shanghai"); if err != nil { /* log and return */ } - 打卡时间必须用本地时区生成,否则数据库存的“9:00”实际是 UTC 的 9:00,和员工感知的上班时间对不上
- 存数据库时,建议统一用
time.Time类型(PostgreSQL 的timestamptz),而非字符串 —— 避免时区解析歧义 - 如果用 MySQL,确认字段类型是
TIMESTAMP(自动转 server 时区)而非DATETIME(无时区信息)
并发打卡时 Redis 锁为什么没拦住重复请求?
用户连点两次“打卡”,后端收到两个几乎同时的请求,Redis SETNX 返回 true 两次,导致同一个人同一天打了两次卡。
实操建议:
- 锁 key 必须带业务唯一标识,例如
"checkin:20240615:uid_123",不能只用"checkin_lock" - SETNX 必须设过期时间(
SETEX或SET ... PX ... NX),否则崩溃后锁永远不释放 - 更稳妥的做法是:先用 Redis 原子操作
INCR统计当日打卡次数,再判断是否 >1;或用 Lua 脚本把“查+设+判”打包成原子操作 - 别在锁内做耗时操作(如调第三方地图 API)—— 锁粒度越小越好,校验通过后再去查围栏、写 DB
地理围栏计算和时区处理是考勤接口里最容易被跳过的两步,漏掉任意一个,上线后都会导致打卡成功但数据不可信。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










