internal目录是go编译器强制的私有包边界,仅允许同模块内导入,外部引用直接报错;它不是约定而是硬性访问控制机制,用于隔离业务实现、防止误引用。

Go 项目一旦超过 5 个接口,main.go 就会迅速变成“上帝文件”——路由、校验、SQL、返回体全挤在一起,改一个字段要翻三页,加个中间件得全局搜 gin.Engine。这不是写代码,是在考古。
为什么 internal 目录不是可选项,而是编译级强制项
Go 的 internal 是语言层机制:任何包路径中含 /internal/ 的目录,外部模块无法 import。这直接封死了“业务逻辑被其他项目误引用”的可能。
常见错误现象:import "myproject/internal/handler" 在测试包或 cmd 外调用时编译报错 use of internal package not allowed ——这不是 bug,是保护。
实操建议:
- 所有业务代码(
handler、service、repository、model)必须放在internal/下,且只允许单向依赖:handler → service → repository → model - 禁止在
service层 importgin.Context或调用c.JSON();禁止在repository层写if user.Status == 0这类业务判断 -
cmd/api/main.go只做四件事:加载配置、初始化 DB/Redis/Logger、注册 router、调用r.Run()
router 和 handler 怎么分工才不打架
路由层(router)只负责“把请求交给谁”,控制器层(handler)只负责“这个请求我收下了,参数校验完就转给 service”。两者职责一旦模糊,就会出现“路由里写鉴权逻辑”或“handler 里拼 SQL”的反模式。
使用场景举例:用户登录接口
-
router/v1/auth.go中只写:r.POST("/login", authHandler.Login),不加任何中间件逻辑 -
internal/handler/auth.go中只做:c.ShouldBind(&req)+if !validator.IsValid(req) { c.AbortWithStatusJSON(400, ...) }+resp, err := s.Login(req)+c.JSON(200, resp) - JWT 鉴权中间件必须独立成
middleware/auth.go,通过r.Use(auth.Middleware())注册,而非塞进某个 handler 函数里
repository 层命名和方法设计的硬约束
Repository 不是“DAO 的别名”,它是数据访问契约。名字不规范、方法越界,会导致 service 层被迫处理数据库细节,破坏分层边界。
参数差异与性能影响:
- 方法名必须以动词开头:
FindByID、CreateUser、UpdateStatus;禁止叫GetUser或UserQuery - 禁止在
repository里调用service或拼接业务条件:WHERE status = 1 AND deleted_at IS NULL可以,但WHERE invite_code = ? AND created_at > ?(涉及邀请时效规则)必须上提到service - 如果某查询需 JOIN 三张表并做聚合,不要强行塞进
repository.UserRepo;应新建repository/analytics.go,由对应 service 调用
configs 加载顺序错一位,dev 环境就丢超时设置
viper 默认不会自动合并多文件配置。如果先读 dev.yaml 再读 app.yaml,dev.yaml 里的 db.timeout: 3s 会被 app.yaml 的 db.timeout: 30s 覆盖,结果 dev 环境反而用了生产超时值。
实操建议:
- 加载顺序必须是:
viper.SetConfigName("app")→viper.AddConfigPath("configs")→viper.ReadInConfig()→viper.SetConfigName(env)→viper.MergeInConfig() - 配置结构体定义统一放在
pkg/config/config.go,用NewConfig()返回指针;禁止在main.go或db/init.go里重复定义 struct -
configs/dev.yaml只写差异化项,比如log.level: debug,其余继承app.yaml,避免配置漂移
最常被忽略的一点:model 层 struct 字段必须带 GORM 标签,但不能带任何方法或嵌套逻辑;哪怕只是加个 func (u *User) FullName() string,都会让 internal/model 无意中依赖 internal/service,破坏单向依赖规则。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











