main.go 应放在 cmd/ 目录下按服务拆分,如 cmd/api/main.go;internal/ 用于模块内复用且对外隔离的代码;config/ 存项目级配置逻辑,pkg/ 存可跨项目复用的组件;HTTP 处理层推荐用 http/ 或 api/,避免 routes/、web/ 等模糊命名。

main.go 放哪?别塞进根目录
Go 项目启动入口 main.go 必须放在可执行包(package main)里,但不建议直接丢在项目根目录。根目录塞太多东西会干扰 go mod init 和 IDE 识别——比如 go list ./... 可能意外包含测试工具或配置脚本。
推荐做法是建一个 cmd/ 目录,按服务拆分子命令:
-
cmd/api/main.go—— HTTP 服务主入口 -
cmd/migrate/main.go—— 数据库迁移工具 -
cmd/admin/main.go—— 后台管理 CLI
这样 go build -o bin/api cmd/api 路径清晰,也方便 CI 分别构建不同二进制。
internal/ 不是摆设,它管的是“不能被外部 import”的代码
internal/ 是 Go 官方约定的私有包路径,任何在 internal/ 下的包,只有其父目录或同级目录的模块才能导入。比如 github.com/user/project/internal/handler 只能被 github.com/user/project/cmd/api 或 github.com/user/project/internal/service 导入,第三方无法 import 它。
立即学习“go语言免费学习笔记(深入)”;
常见误用:
- 把通用工具函数(如
utils.StringToTime)放internal/utils→ 应该挪到pkg/utils或直接开独立小模块 - 在
internal/里写数据库模型(model.User),结果cmd/api和cmd/migrate都要重复定义 → 模型应统一放在internal/model,由各 cmd 显式引用
记住:internal/ 的边界是「模块内复用 + 对外隔离」,不是「随便藏代码的地方」。
config/ 和 pkg/ 怎么分?看是否跨项目复用
config/ 存当前项目的加载逻辑:解析 config.yaml、绑定环境变量、校验必填字段。它的结构通常紧贴运行时需求,比如:
config/ ├── config.go // Load() + Validate() ├── config_test.go └── sample.yaml
pkg/ 则是真正可剥离的、带明确接口契约的组件。例如:
-
pkg/logger—— 封装zerolog,暴露Logger接口和NewLogger() -
pkg/db—— 封装sqlx初始化、连接池配置,不暴露具体 driver -
pkg/httpclient—— 带默认超时、重试、metrics 上报的 client 工厂
如果某段代码只在这个项目里用,且不打算抽成独立 GitHub repo,就别硬塞 pkg/;反过来,如果已经写了 pkg/cache 但发现其他项目也要用,那就该单独 go mod init github.com/user/cache 拆出去。
handlers/ 和 api/ 目录名之争:优先选语义明确的
HTTP 路由处理层命名混乱很常见:handlers/、controllers/、api/、http/……实际没标准,但要注意两点:
- 名字得让新成员一眼看出「这里只做请求接收、参数解析、响应包装,不写业务逻辑」
- 避免和已有 Go 生态术语冲突,比如
controllers/容易让人联想到 Gin 的gin.HandlerFunc,但如果你用的是net/http原生路由,反而造成认知偏差
更稳妥的选择是:
-
http/—— 表明技术栈,适合强调协议层职责(如中间件、路由注册、错误转 HTTP 状态码) -
api/—— 表明领域,适合已划分清楚「API 层 vs Service 层」的团队,且 API 版本管理(v1/v2)会自然落到这个目录下
别用 routes/ —— 它只是个注册动作,不是职责主体;也别用 web/ —— 太宽泛,CLI 或 gRPC 服务也可能跑在 web server 上。










