企业级gin项目必须分层:路由层只注册路由和中间件,控制器层接收参数并调用服务,服务层处理业务逻辑和数据库操作,模型层定义结构体;目录按router、controller、service、model拆分,彻底解耦。

路由注册别写在 main.go 里
把所有 router.GET、router.POST 堆进 main.go,短期看着快,两周后加个中间件就得全局 grep。Gin 的路由树本身不重,但人脑维护成本高。
- 按业务域拆包,比如
user、order各自定义SetupRouter函数,接收*gin.Engine或*gin.RouterGroup - 避免用字符串拼接路径,如
"/api/v1/" + "user"—— 路径前缀应由engine.Group("/api/v1")统一管理 - 不要在路由闭包里直接写业务逻辑,哪怕只有一行;封装成独立函数,函数名能体现意图,比如
handleUserCreate而非func(c *gin.Context) { ... }
中间件要分层,别堆在一个文件里
常见错误是把 JWTAuth、Logger、Recovery、RateLimit 全塞进 middleware.go,然后靠注释区分“全局”和“局部”。结果改一个日志格式,全量重启测试。
- 按作用域组织:全局中间件(如
Recovery)在main.go注册;接口级中间件(如RequireRole("admin"))绑定到具体RouterGroup - 中间件函数返回值必须是
gin.HandlerFunc,别为了省一行而写func(c *gin.Context) { ... }()这种立即执行——它根本不是中间件 - 带参数的中间件(如
Timeout(5 * time.Second))务必检查参数合法性,0或负数会导致 panic,Gin 不校验
请求绑定和校验别跳过 ShouldBind 系列方法
有人用 c.PostForm("name") 手动取字段,再自己 if 判空,既漏类型转换又绕过结构体验证规则。Gin 的 ShouldBind 不只是方便,它联动了 tag 校验、JSON/XML/Query 多格式自动适配。
- 统一用
c.ShouldBind(&req),结构体字段加binding:"required,email"等 tag,错误统一由c.Error(err)记录,别用log.Print - 区分
ShouldBind和Bind:ShouldBind出错只记录 error 并继续执行,适合需要兜底逻辑的场景;Bind出错自动返回 400,适合严格校验 - 嵌套结构体校验要显式加
binding:"required",否则外层 struct 非 nil 就算通过,内层字段为空也不报错
错误处理别只写 c.JSON(500, gin.H{"error": err.Error()})
生产环境暴露原始 err.Error() 是安全风险,且无法区分是数据库超时、第三方服务不可达还是代码 panic。Gin 默认 recovery 中间件只打印堆栈,不控制响应体。
- 定义错误码枚举(如
ErrUserNotFound = 1001),配合status.HTTP404NotFound返回标准状态码,前端靠 status 判断,不靠 message 字符串匹配 - 数据库错误(如
sql.ErrNoRows)别直接透传,用errors.Is(err, sql.ErrNoRows)捕获后转成业务错误 - panic 场景下,
RecoveryWithWriter可以重定向日志输出,但别试图在 recover 里重写 response body —— Gin 已经写了 header,再写会 panic
真正难的不是写对一个 handler,而是当新增三个接口、两个中间件、四种错误分支时,还能一眼看出数据流向和错误出口在哪。结构松散比语法错误更难 debug。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











