gin项目不能只靠gin.default()搭,因其仅解决路由通路问题,真实项目需应对配置分散、初始化混乱、中间件耦合、依赖错乱等首日即现的工程化难题。

直接用 gin.Default() 启动单文件服务能跑通,但一旦加路由、连数据库、写中间件,就容易变成“所有逻辑挤在 main.go 里”,后续改一个字段要翻十页,加个新接口得手动复制粘贴三处代码——这不是脚手架,是胶水堆。
为什么不能只靠 gin.Default() 搭项目
gin.Default() 确实开箱即用,但它只解决了“HTTP 路由通了”这一个问题。真实项目要面对:
- 配置分散:数据库地址硬编码在
main.go里,换环境就得改代码 - 初始化顺序混乱:MySQL 连上了,Redis 还没连,
defer mysql.Close()可能 panic - 中间件耦合:日志、CORS、JWT 校验全塞进
r.Use(...)链,调试时看不出哪一层挂了 - 包依赖错乱:
router层直接 importdao/mysql,而dao又 importconfig,稍不注意就循环引用
这些不是“以后再重构”的问题,是第一天写第二行业务代码时就会踩的坑。
main.go 只该做三件事:加载配置、初始化依赖、启动服务
把 main.go 当成“装配线总控台”,而不是“流水线工位”。它不处理业务,也不写 SQL,只负责把各模块按顺序接起来。
- 第一行必须是
setting.Init("conf/config.yaml")—— 配置没加载完,其他一切都不该开始 - 数据库、Redis、日志等初始化函数必须返回
error,且在main中显式检查,失败立刻os.Exit(1) - 最后才调用
router.Setup(r)和r.Run(),确保所有依赖已就绪 - 不要在
main.go里写任何c.JSON或c.Param—— 那是controller的事
目录结构必须让“新增一个接口”变成三步操作
真正省时间的脚手架,不是功能多,而是新增接口时路径清晰、无歧义、不翻旧代码。
- 路由定义统一放在
router/route.go,只负责r.POST("/user", user.Create)这类声明,不写实现 - 具体逻辑写在
controller/user.go,函数签名固定为func(c *gin.Context),只调用service.UserCreate(...) - 业务规则集中在
service/user.go,它只依赖repository和model,不碰 HTTP 上下文 - 数据访问收口到
repository/user.go,里面只有Insert(...)、FindByID(...)这类纯 DB 操作
这种分层不是教条,是防止你某天想给用户加个“最近登录 IP”字段时,不用在 main.go、router.go、handler.go 里各改一处,最后漏掉一个导致 500。
最容易被忽略的初始化细节:配置热加载与关闭顺序
很多脚手架跑测试没问题,一上生产就内存泄漏或连接超时,问题常出在两个地方:
-
viper.WatchConfig()必须在setting.Init()之后立即注册,否则配置变更不会触发重载;但 watch 回调里不能直接改全局变量,得加锁或用 channel 异步通知 -
defer mysql.Close()放在main()函数末尾是错的——如果r.Run()启动后进程被 kill,defer不会执行;正确做法是在signal.Notify()捕获os.Interrupt后,同步调用所有Close()方法 - Redis 连接池的
pool_size和 MySQL 的max_open_conns不能设成一样数值,前者建议是后者的 2–3 倍,否则高并发时 Redis 先耗尽连接











