Go 项目目录结构设计标准:深入解析 project-layout 的 Standard Go Project Layout
本文基于 golang-standards/project-layout 仓库的核心文档 README.md,系统讲解 Go 生态中最通用的项目目录布局:从 /cmd、/internal、/pkg 三大核心 Go 目录的取舍,到 /api、/web、/deployments 等应用级目录的分工,并结合仓库内 go.mod、Makefile 与各目录说明文件,给出可直接落地到真实项目的结构方案与反模式规避指南。
一、定位与适用边界:它不是官方标准,而是一套社区共识
理解这套布局之前,必须先明确它的三个定位(均来自 README.md 的 Overview 章节):
- Basic(基础级):它只关注"项目长什么样",不关注目录里写什么。比如它不覆盖 Clean Architecture 等更细粒度的架构分层,这类更深入的架构选择需要另外设计。
- 非官方标准:这不是 Go 核心团队定义的官方标准,而是 Go 生态中"历史上存在且正在涌现"的目录模式集合。其中一些模式比另一些更流行,但没有任何一个模式适用于所有项目——连
/vendor模式也不是普遍适用的。 - 故意保持通用:该布局刻意不强制任何特定的 Go 包结构,属于社区共建成果,README 也明确邀请开发者提交 Issue 来补充新模式或修正旧模式。
什么时候不该用它
README 中有一段加粗的关键提示,值得原样继承:
如果你在学 Go,或者只是在做一个 PoC / 简单项目,这套布局是过度设计。从一个单独的
main.go文件加一个go.mod开始就够了。
正确的演进路径是:项目长大后,保证代码结构良好(否则你会得到一堆隐藏依赖和全局状态的混乱代码);多人协作时,引入统一的包管理方式;当项目开源、或有其他项目 import 你的代码时,才需要认真考虑 /internal 私有包机制。
官方 Go 团队在 Organizing a Go module 文档中也提供了组织 Go 模块的通用准则,包含 internal 与 cmd 两类目录模式,与本布局高度一致。
仓库自身的 go.mod:模块路径的取值约束
仓库根目录的 go.mod 是一个最小可用的模板:
module github.com/YOUR-USER-OR-ORG-NAME/YOUR-REPO-NAME
go 1.19
对照 README 的说明,这里有两个实操要点:
- 基本
go.mod假设项目托管在 GitHub 上,但这不是强制要求——模块路径可以是任意值; - 模块路径的第一段通常需要包含点号(如
github.com/...)。当前版本的 Go 已不再强制这一点,但如果使用稍旧的 Go 版本,缺少点号可能导致构建失败,遇到此类报错应检查模块路径格式。
Go 1.14 起 Go Modules 已可用于生产环境:除非有特定原因,应优先使用 Go Modules,这样就不必再关心 $GOPATH 与项目放置位置的问题。
二、目录总览
下表汇总了 README.md 中定义的全部目录及其职责,仓库内对应目录大多已创建并附带说明文件:
| 目录 | 职责 | 仓库内说明文件 |
|---|---|---|
/cmd |
可执行主程序入口,目录名即二进制名 | cmd/README.md |
/internal |
私有应用与库代码(编译器强制不可外部 import) | internal/README.md |
/pkg |
可供外部项目 import 的公开库代码 | pkg/README.md |
/api |
OpenAPI/Swagger 规格、JSON Schema、协议定义文件 | api/README.md |
/web |
静态资源、服务端模板、SPA 等 Web 组件 | web/README.md |
/configs |
配置文件模板与默认配置 | configs/README.md |
/init |
systemd/upstart/sysv 与进程管理器配置 | init/README.md |
/scripts |
构建、安装、分析等脚本 | scripts/README.md |
/build |
打包与 CI 配置(文档定义,仓库模板未包含) | — |
/deployments |
IaaS/PaaS/容器编排部署配置(docker-compose、k8s/helm、terraform) | deployments/README.md |
/test |
额外的外部测试应用与测试数据 | test/README.md |
/docs |
设计与用户文档(godoc 之外的部分) | docs/README.md |
/tools |
项目配套工具(可 import /pkg 与 /internal 代码) |
tools/README.md |
/examples |
应用或公开库的示例 | examples/README.md |
/third_party |
外部辅助工具、fork 代码、第三方工具(如 Swagger UI) | third_party/README.md |
/githooks |
Git hooks | githooks/README.md |
/assets |
其他资产(图片、logo 等) | assets/README.md |
/website |
不使用 GitHub Pages 时的项目网站数据 | website/README.md |
此外还有一个不应出现的反模式目录:/src,后文专门讨论。
三、核心 Go 目录详解
3.1 /cmd:每个可执行程序一个子目录
规则(见 cmd/README.md):
- 项目的所有主应用放在
/cmd下,每个应用的子目录名必须等于你想要的可执行文件名(例如/cmd/myapp构建出myapp); - 应用目录里不要堆大量代码。判断标准很直接:这段代码能否被其他项目 import 复用?
- 能 → 放到
/pkg; - 不能复用、或不希望被复用 → 放到
/internal; - README 特别强调"要显式表达你的意图"——别人会做出你想不到的事情;
- 能 → 放到
- 典型的
main函数应当很小:只 import 并调用/internal与/pkg中的代码,不包含其他逻辑。
仓库模板中已预留 cmd/_your_app_/ 占位目录(下划线前缀会让 Go 工具忽略该目录,避免占位内容干扰构建)。
3.2 /internal:由 Go 编译器强制执行的私有包
这是整套布局中唯一由语言机制兜底的目录模式(见 internal/README.md):
-
/internal下的代码不允许项目外的代码 import,这一约束由 Go 编译器本身强制执行(该模式自 Go 1.4 引入),不依赖团队纪律; -
internal不需要只在顶层出现——项目树的任何层级都可以有多个internal目录,粒度完全由你自己控制; -
可选的二级结构用于区分"应用专属代码"与"应用间共享代码":
/internal/app/<应用名>:某个应用自身的代码(如/internal/app/myapp);/internal/pkg/<库名>:多个应用共享的内部库(如/internal/pkg/myprivlib)。
仓库模板中对应的
internal/app/_your_app_/与internal/pkg/_your_private_lib_/两个占位目录正是这一推荐结构; -
这套二级结构不是必须的,小项目不必引入,但它提供了"代码用途如何"的视觉线索。
判断一段代码该不该进 internal 的核心问题是:你是否希望别的项目 import 它?不希望,就放进 internal,让编译器替你把关。
3.3 /pkg:公开库代码的显式承诺
/pkg 的定位与 internal 恰好互补(见 pkg/README.md):
/pkg放允许外部应用使用的库代码(如/pkg/mypubliclib)。外部项目 import 这里时"期望它一直能工作",因此往里放任何东西之前都要三思;internal在强制私有性上更可靠(编译器执行),而/pkg的价值在于显式沟通:目录名本身就是"这些代码可以安全地被别人使用"的声明;- 另一个实际收益:当根目录里堆满大量非 Go 组件时,
/pkg把所有 Go 库代码集中到一处,方便运行各类 Go 工具; - 这个模式并非社区共识——README 坦承"它被广泛使用,但并非被普遍接受,Go 社区中有人不推荐它"。对很小的应用项目,多一层嵌套可能没有价值;当根目录开始变得杂乱(尤其是非 Go 组件很多时)再考虑引入;
- 历史渊源:早期 Go 源码仓库自己就用
pkg组织包,社区项目随后大量模仿了这一模式。
仓库模板中以 pkg/_your_public_lib_/ 占位,与 internal 形成"公开 vs 私有"的对照结构。
3.4 /vendor:依赖目录与模块代理
关于依赖管理,README.md 给出了明确的演进路线:
go mod vendor命令会为项目生成/vendor目录;若 Go 版本低于 1.14,go build时可能需显式加-mod=vendor标志;- 构建库(library)时不要提交应用依赖——这是明确写出的规则;
- 自 Go 1.13 起,模块代理(默认使用官方 Go 模块代理服务器)已启用。如果代理满足你的需求与约束,可以完全不需要
vendor目录。
四、服务应用与 Web 应用目录
4.1 /api:接口契约的定义层
存放 OpenAPI/Swagger 规格文件、JSON Schema 文件、协议定义文件(见 api/README.md)。将接口契约文件独立成目录的好处是:契约与实现分离,代码生成、多语言客户端、API 网关配置等都以 /api 下的文件为单一事实来源。
4.2 /web:前端与模板资产
存放 Web 应用专属组件:静态 Web 资源、服务端模板、SPA(见 web/README.md)。仓库模板进一步给出了推荐的三段式子结构:
web/
├── app/ # Web 应用代码(如 SPA 构建产物/前端源码)
├── static/ # 静态资源(CSS、JS、图片等)
└── template/ # 服务端模板
这为"服务端渲染模板 + 静态资源 + 独立 SPA"三种常见形态各留了位置,可按需裁剪。
五、通用应用目录:配置、脚本与部署
5.1 /configs:配置模板与默认配置
放配置文件模板或默认配置;README 特别指出 confd 或 consul-template 的模板文件也应放在这里。仓库中 configs/README.md 与主文档一致。
5.2 /init:系统启动与进程管理
放系统 init 配置(systemd、upstart、sysv)与进程管理器/监管器配置(runit、supervisord)(见 init/README.md)。这类文件通常不随代码构建,但对部署交付不可或缺,集中管理可避免散落在仓库各处。
5.3 /scripts:把根 Makefile 变薄
/scripts 存放执行构建、安装、分析等各类操作的脚本(见 scripts/README.md)。README 给出了这一目录的核心价值主张:
这些脚本让根目录的 Makefile 保持小而简单。
仓库的 Makefile 本身就是这一理念的实证——整个文件只有一行注释:
# note: call scripts from /scripts
也就是说,所有实际逻辑都下沉到 /scripts 中的独立脚本,根 Makefile 只做薄薄一层转发。这种"Makefile 即目录"的做法让 CI 与本地开发都能直接调用 scripts/ 下的脚本,无需解析复杂 target 依赖。
5.4 /build:打包与 CI(文档定义目录)
/build 用于打包和持续集成,README 进一步细分为两个子目录:
/build/package:云镜像(AMI)、容器(Docker)、操作系统包(deb、rpm、pkg)的打包配置与脚本;/build/ci:CI(travis、circle、drone 等)配置与脚本。注意部分 CI 工具对配置文件位置很挑剔,可行时应把配置放在/build/ci并链接(link)到工具期望的位置。
需要说明的是:当前仓库的目录模板中并未创建 /build,它属于文档层面推荐的可选结构。
5.5 /deployments:编排与 IaC 配置
存放 IaaS、PaaS、系统级与容器编排的部署配置和模板,README 列举的典型工具包括 docker-compose、kubernetes/helm、terraform。deployments/README.md 中还额外列出了 mesos 与 bosh。README 同时提醒:部分仓库(尤其是以 kubernetes 部署的应用)把这个目录叫 /deploy,阅读他人项目时两个名字都需认识。
5.6 /test:外部测试应用与测试数据
/test 用于存放额外的外部测试应用与测试数据,结构完全自由(见 test/README.md)。大项目建议设一个数据子目录。两个值得记住的 Go 工具行为:
- 用
/test/data或/test/testdata可以让 Go 忽略其中内容(testdata是 Go 工具约定的忽略目录名); - 以
.或_开头的目录/文件同样会被 Go 忽略——这也是仓库模板用cmd/_your_app_/这类下划线占位目录的原因。
六、其余支撑目录
| 目录 | 说明(源自 README.md) |
|---|---|
/docs |
设计与用户文档,与 godoc 自动生成的文档互补(docs/README.md) |
/tools |
项目配套工具;注意这些工具可以 import /pkg 与 /internal 中的代码(tools/README.md) |
/examples |
应用和/或公开库的示例代码(examples/README.md) |
/third_party |
外部辅助工具、fork 代码及其他第三方工具(如 Swagger UI)(third_party/README.md) |
/githooks |
Git hooks(githooks/README.md) |
/assets |
随仓库走的资产:图片、logo 等(assets/README.md) |
/website |
不使用 GitHub Pages 时,项目网站数据的存放地(website/README.md) |
其中 /tools 与 /cmd 的边界值得注意:cmd 是对外发布的可执行程序,而 tools 是服务于本项目开发/运维的辅助程序,且被允许依赖私有代码——这正是 internal 编译器约束下少数合法的内部消费场景。
七、反模式:为什么项目里不该有 /src
README.md 专设 "Directories You Shouldn't Have" 一节解释了这个常见误用:
- Go 项目里出现
src目录,通常是因为开发者从 Java 世界迁移过来习惯了该模式。结论很直接:不要让 Go 项目长得像 Java; - 不要与 Go 工作区(workspace)里的
/src混淆:$GOPATH(非 Windows 系统下默认$HOME/go)指向工作区,工作区顶层包含/pkg、/bin、/src三个目录,项目本身是src下的子目录。若你的项目里再套一层src,最终路径会变成/some/path/to/workspace/src/your_project/src/your_code.go这种双重嵌套; - 自 Go 1.11 起项目可以放在
GOPATH之外,但这不意味着src目录模式变得合理。
判断规则:如果仓库顶层出现了 /src,几乎可以断定是误用。
八、落地方式:克隆模板,按需裁剪
这套布局的官方使用方式在 README 中一句话概括:
Clone the repository, keep what you need and delete everything else! Just because it's there doesn't mean you have to use it all.
即:克隆本仓库作为起点,保留自己需要的目录,删掉其余部分。"目录存在"不等于"必须使用"——pkg、internal 甚至 vendor 都没有在每个项目中出现。结合 Makefile 的薄转发设计与 go.mod 的占位模块路径,一个可执行的落地流程是:
- 克隆仓库后修改 go.mod 中的模块路径(第一段需含点号以兼容旧版本 Go 构建);
- 按
README.md的目录表逐项裁剪:只做单一 CLI 工具的项目可只保留cmd+internal;开源库则保留pkg与docs; - 把构建逻辑写入
/scripts,让根 Makefile 保持一行注释加少量转发; - 命名与风格上,README 建议从
gofmt与staticcheck两个工具入手(前者负责格式,后者为 golint 弃用后推荐的受维护 linter)。
README 末尾还提示:一个更"有主张"的项目模板(带示例配置、脚本与代码)当时仍处于开发中,因此本布局的定位始终是"目录骨架"而非"代码骨架"。
九、小结:一张决策清单
把 README.md 的要点压缩成可复用的判断顺序:
- 学 Go / PoC / 小项目 → 单文件
main.go+go.mod,跳过本文全部结构; - 代码不想被外部 import → 放
internal,编译器替你强制; - 代码明确作为公开 API 对外提供 → 放
pkg,并承担"外部依赖它一直可用"的承诺; - 多可执行程序 → 一个程序一个
cmd/<name>子目录,main保持极小; - 接口契约、前端资产、配置、init、部署、CI、脚本、文档、示例、工具各自独立成目录,根目录只留 go.mod、Makefile、LICENSE.md 与少量顶层 README;
- 顶层绝不出现代
src。
这套布局的价值不在于它强制了什么,而在于它为 Go 社区提供了一套共同词汇:无论团队最终采用其中多少目录,cmd / internal / pkg 的语义在几乎所有 Go 项目中含义一致,这本身就是协作成本的大幅降低。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00