生产环境禁用 gin.default() 因其默认开启 debugmode 会暴露路由树和 panic 堆栈,应改用 gin.new() 手动注册中间件;字段校验需结合 binding:"required" 与自定义逻辑;权限查询应按角色动态生成 where 条件;时间存储优先使用 time.time 和数据库原生时间类型。

为什么用 gin.Default() 会暴露调试信息?
生产环境直接用 gin.Default() 启动服务,会导致 404 页面显示路由树、中间件列表,甚至可能泄露 debug 模式下的 panic 堆栈。这不是 Gin 的 bug,而是它默认开启 gin.DebugMode —— 只要环境变量 GIN_MODE 未设为 release,就自动启用。
实操建议:
- 启动前显式设置
os.Setenv("GIN_MODE", "release"),或在部署时通过系统环境变量控制 - 避免在代码里写
gin.SetMode(gin.ReleaseMode)后再调gin.Default(),因为Default()内部会重新读取环境变量并覆盖设置 - 更稳妥的做法是改用
gin.New()+ 手动注册必要中间件(如gin.Logger()、gin.Recovery()),完全绕过默认行为
打卡审批接口的请求体该怎么校验才不漏掉字段?
用 json.Unmarshal 直接解到 struct 后不做校验,很容易让空字符串、零值字段悄悄入库,比如 user_id 是空串、status 是 0(但合法值只有 1/2/3)。
实操建议:
- 用
binding:"required"标签做基础非空校验,但注意:它只对指针和非零值类型生效,string字段即使为空也不会触发 error - 对关键字段(如
user_id、apply_time)额外加自定义校验逻辑,例如:if req.UserID == "" { c.AbortWithStatusJSON(400, gin.H{"error": "user_id required"}) } - 状态字段(如
status)别只依赖前端传值,应在 handler 里映射成内部枚举:switch req.Status { case 1: status = "pending"; case 2: status = "approved"; default: ... }
怎么让审批记录支持「查自己+查下属」但又不写两套 SQL?
权限控制如果硬编码成 WHERE user_id = ? OR manager_id = ?,容易越权——比如 A 是 B 的上级,B 提交的申请被 A 查到没问题;但如果 C 也是 A 的下属,A 就不该看到 C 审批过的记录,除非 C 是申请人或审批人。
实操建议:
- 把查询逻辑收拢到一个函数,入参是当前用户 ID 和角色(
"employee"/"manager"),返回动态WHERE条件和参数 slice - 员工只能查自己的记录:
WHERE applicant_id = ?;经理可查自己提交的 + 自己审批的 + 下属提交的:WHERE applicant_id IN (SELECT id FROM users WHERE manager_id = ?) OR approver_id = ? OR applicant_id = ? - 务必对
manager_id字段建索引,否则子查询会慢;同时避免用IN (SELECT ...)查大表,可先查出下属 ID 列表再拼IN (?)
time.Now().Unix() 存时间戳还是用 time.Time 存数据库?
存整型时间戳看似省空间、好比较,但在时区处理和可读性上埋坑:MySQL 的 DATETIME 默认按系统时区解析,PostgreSQL 的 TIMESTAMP WITH TIME ZONE 会自动转换,而 Unix() 返回的是 UTC 秒数,没带时区上下文。
实操建议:
- 数据库字段类型优先选
TIMESTAMP(PostgreSQL)或DATETIME(MySQL),Go struct 中对应字段用time.Time - 插入前统一转成 UTC:
req.ApplyTime = req.ApplyTime.UTC(),避免本地时区干扰 - 如果必须用时间戳(比如对接第三方 API),至少用
time.Now().UnixMilli()而不是Unix(),防止秒级精度丢失导致并发重复提交判定失败
c.Keys,后续所有 handler 都从这里取,而不是每次重新解析 token 或查用户表。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











