go项目目录结构直接影响构建、导入和协作:go.mod必须在根目录;cmd/下按可执行名建子目录,main.go仅初始化;internal/为编译器级私有边界,pkg/为可复用公共包;config/、api/等非源码目录需确保运行时路径正确。

Go 项目不是随便建个文件夹就能跑起来的,目录结构直接决定后续能否顺利用 go build、go run、模块导入和依赖管理。错放一个 main.go 或搞混 internal 和 pkg,轻则包导入失败,重则本地能跑、CI 构建报 import path does not exist。
go.mod 必须在项目根目录,且不能嵌套在子目录里
go.mod 是 Go Modules 的锚点,Go 工具链靠它识别模块边界。如果把它放在 src/ 或 cmd/app/ 里,go list ./... 就会漏掉其他包,go get 也会找不到依赖上下文。
- 正确:项目根目录下有
go.mod和go.sum,所有子目录都属于该模块 - 错误:把
go.mod放进cmd/myapp/,此时internal/service被视为“外部路径”,无法被cmd/myapp导入 - 验证方式:运行
go list -m,输出应为你的模块名(如github.com/user/myapp),而不是command-line-arguments
cmd/ 目录只放 main.go,每个可执行文件一个子目录
cmd/ 是存放入口程序的地方,不是“放所有 Go 文件”的垃圾桶。每个独立二进制(比如 myapp-server、myapp-cli)应各自建子目录,里面只放 main.go 和必要配置初始化逻辑。
-
cmd/server/main.go中package main,导入myapp/internal/handler启动 HTTP 服务 -
cmd/cli/main.go中package main,导入myapp/pkg/utils提供命令行工具 - 别把业务逻辑写进
main.go——它只负责组装、启动、退出,否则后期没法复用或测试 - 构建命令也得对应:想编译服务端就
go build -o myapp-server ./cmd/server
internal/ 和 pkg/ 的权限边界必须严格区分
internal/ 下的包只能被同一模块内代码导入,pkg/ 则是设计为对外暴露的公共组件。Go 编译器会强制检查这个规则,不是约定而是硬约束。
-
internal/auth可被cmd/server和internal/handler导入,但github.com/other/repo绝对导不进来 -
pkg/validation可被任何外部项目import "github.com/user/myapp/pkg/validation"使用 - 常见翻车点:把数据库模型(
model)全扔进internal/,结果 CLI 工具要读取同样结构的数据却无法复用——这时应该拆出pkg/model或pkg/types - 别为了“图省事”把
internal/改成pkg/——一旦暴露,就等于承诺 API 兼容性,改字段就得发 v2
config/ 和 api/ 这类非 Go 源码目录,别放进 GOPATH 时代的老习惯
Go Modules 下,config/、api/、docs/ 等纯资源目录不需要参与编译,但必须保证运行时能被二进制正确读取。它们的位置不影响 go build,但影响部署路径和加载逻辑。
-
config/config.yaml在开发时用os.ReadFile("config/config.yaml")加载,但上线后二进制默认从当前工作目录找——所以部署脚本得确保它和二进制在同一级,或用-config参数指定绝对路径 -
api/helloworld/v1/greeter.proto是给protoc用的,生成的*.pb.go文件必须放在api/下同级或internal/api,否则go generate会找不到目标包 - 别把
config/放进internal/——它不是 Go 包,也不该被 import;更别放进vendor/(Modules 已弃用 vendor)
真正容易被忽略的,是 go.mod 初始化时机和 internal/ 的导入路径拼写——少一个斜杠、多一层目录,Go 就认不出这是“自己人”。结构不是越深越好,而是每层目录都得有明确的职责边界和导入约束。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











