Standard Go Project Layout:基于 project-layout 仓库的 Go 项目目录结构规范详解
本文以 Go 社区事实标准仓库 project-layout 的说明文档为主线,逐目录讲解 /cmd、/internal、/pkg、/vendor 等核心目录与 /api、/configs、/scripts、/deployments 等辅助目录的职责、适用场景与取舍原则,并结合仓库中真实的目录骨架(占位目录、go.mod、Makefile)说明如何把这套布局落地到自己的 Go 项目中。
一、总览:这是一套"基本骨架",而不是官方标准
project-layout 提供的是一套基本(basic)的 Go 应用项目目录规划,其"基本"体现在两个层面:
- 只关注整体 layout,不关注目录里装什么——它刻意保持高层抽象,不会深入到诸如 Clean Architecture 之类更细粒度的项目内部结构;
- 不施加任何特定的 Go 包结构——它是有意保持通用(intentionally generic)的,目的是给出一组被广泛接受的"目录语言",让团队成员对新项目的组织方式有共同预期。
文档同时明确强调:这套布局不是 Go 核心团队官方定义的标准("NOT an official standard defined by the core Go dev team"),而是 Go 生态中常见的历史与新兴项目布局模式的集合。其中部分模式(如 cmd、internal)比其他模式更受欢迎,并附带了一些面向足够大的真实应用的支撑目录。Go 核心团队在官方文档的 "Organizing a Go module" 页面中也提供了关于 Go 项目组织方式的通用指南(包含下文将讲的 internal 与 cmd 目录模式),两套资料可以互相印证。
什么时候该用、什么时候不该用
文档给出了非常明确的适用边界:
- 正在学 Go、做 PoC 或给自己写小工具时,这套布局是"过度设计"(overkill)——一个
main.go加一个go.mod就足够了; - 随着项目增长,如果不注意代码组织,最终会得到一堆"隐藏依赖(hidden dependencies)+ 大量全局状态(global state)"的混乱代码;
- 协作人数增加后需要更强的结构,这时应引入管理包/库的共同方式;
- 当项目开源、或知道有其他项目会 import 你的代码时,
internal私有包和私有代码才真正重要。
文档推荐的使用姿势是一句话:"克隆本仓库,保留你需要的部分,删掉其余的!"(Clone the repository, keep what you need and delete everything else!)——目录存在不代表必须全部使用,没有任何一种模式适用于每个项目,甚至连 vendor 模式都不是万能的。
Go Modules:布局成立的前提
自 Go 1.14 起,Go Modules 已"production ready"。文档的建议是:除非有特定理由不用,否则一律使用 Go Modules;使用它之后,就不必再关心 $GOPATH 以及项目应该放在哪里。
仓库自带的 go.mod(见 go.mod)印证了这一点,内容非常精简:
module github.com/YOUR-USER-OR-ORG-NAME/YOUR-REPO-NAME
go 1.19
关于 module path 的几个注意点(以当前仓库实际内容为准):
- 这份
go.mod假设项目托管在 GitHub,但这不是强制要求,module path 可以任意; - module path 的第一段(first component)应包含一个点——当前版本的 Go 已不再强制检查,但如果你使用稍旧的 Go 版本,构建失败时不必惊讶;
- 文档提示可查阅 golang/go 的 Issue #37554 与 #32819 了解该规则的来龙去脉。
命名、格式化与风格检查
文档建议:遇到命名、格式化与风格问题,先运行 gofmt 与 staticcheck。原来的标准 linter golint 已废弃且不再维护,文档推荐使用仍在维护的 linter(如 staticcheck)。此外还列出了一批 Go 代码风格参考资料("Names" 演讲幻灯片、Effective Go 的命名章节、Package Names 博客、Code Review Comments wiki、rakyll 的 Go 包风格指南)以及 GopherCon 系列演讲(Peter Bourgon《Best Practices for Industrial Programming》、Edward Muller《Go Anti-Patterns》、Kat Zien《How Do You Structure Your Go Apps》等),这里只保留主题线索,读者可按名称自行检索原文。
二、仓库结构一览:占位目录即"可复制的模板"
理解这套布局最直观的方式,是看 project-layout 仓库本身的目录树。当前仓库(即 README_fa.md 所在的仓库)正是按其描述自洽组织的"活模板":
.
├── api/ # OpenAPI/Swagger 规格、JSON Schema、协议定义
├── assets/ # 图片、logo 等仓库级静态资源
├── cmd/
│ └── _your_app_/ # 占位目录:每个可执行程序一个子目录
├── configs/ # 配置文件模板 / 默认配置
├── deployments/ # IaaS/PaaS/容器编排部署配置
├── docs/ # 设计与用户文档
├── examples/ # 应用与公开库的示例
├── githooks/ # Git hooks
├── init/ # systemd/upstart/sysv、runit/supervisord 配置
├── internal/
│ ├── app/
│ │ └── _your_app_/ # 私有应用代码
│ └── pkg/
│ └── _your_private_lib_/ # 私有共享库代码
├── pkg/
│ └── _your_public_lib_/ # 对外公开的库代码
├── scripts/ # build/install/analysis 脚本
├── test/ # 外部测试程序与测试数据
├── third_party/ # fork 的代码与第三方工具
├── tools/ # 项目辅助工具
├── web/
│ ├── app/
│ ├── static/
│ └── template/ # Web 应用:SPA、静态资源、服务端模板
├── website/ # 项目站点数据
├── LICENSE.md
├── Makefile # 刻意保持极简
├── go.mod # module 占位声明
└── README.md(及 17 种语言的译本,含 README_fa.md)
从源码结构看,几个占位目录(如 cmd/your_app、pkg/your_public_lib、internal/app/your_app、internal/pkg/your_private_lib、web/static)内部只放了一个 .keep 空文件——它们的作用就是在克隆后告诉你"这里应该放什么",保留自己需要的部分、删除其余,正是文档建议的落地方式。
两个细节值得注意:
- Makefile 的全部内容只有一行注释
# note: call scripts from /scripts。这与下文/scripts章节的设计意图直接对应:根级 Makefile 保持小而简单,具体操作脚本下沉到/scripts目录; - 仓库没有
vendor/目录,与"vendor不是万能的、使用 module proxy 时甚至可以完全不需要它"的说法一致。
三、Go 核心目录
3.1 /cmd:主应用入口,可执行文件名 = 子目录名
职责:存放本项目的所有主应用。规则是:每个应用对应一个子目录,目录名必须与你想要的可执行文件名一致(例如 /cmd/myapp 产出可执行文件 myapp)。
文档的核心告诫:
- 不要在应用目录里堆大量代码。如果你认为某段代码可以被其他项目 import 使用,它就应放进
/pkg;如果代码不可复用、或你不想让别人复用,就放进/internal。"你会惊讶于别人会拿你的代码做什么,所以请把你的意图表达得足够明确!" - 常见的做法是:
main函数非常小,只做一件事——import 并调用/internal与/pkg中的代码,不再做别的。
仓库中的 cmd/README.md 给出了同一规则的完整版,并列举了 velero、moby、prometheus、influxdb、kubernetes、dapr、go-ethereum 等主流项目均按此模式组织 cmd/,可作旁证。
3.2 /internal:由 Go 编译器强制执行的"私有包"
职责:私有应用与私有库代码,即你不希望其他项目 import 的代码。
这条规则的权威性来自 Go 语言本身:从源码结构看,internal 的可见性约束由 Go 编译器直接强制执行(Go 1.4 release notes 首次引入 internal packages 机制)——把包放进 internal 目录后,其他包只有在与它"拥有共同祖先(common ancestor)"时才能 import 它。internal 也是 Go 官方文档唯一点名、并享有特殊编译器处理的目录。
使用要点(与 internal/README.md 一致):
-
你不限于顶层一个
internal:可以在项目树的任意层级、放置多个internal目录; -
可选的二级结构:为区分"共享的私有代码"与"非共享的私有应用代码",可以按仓库的示例组织为
/internal/app/<应用名>—— 具体应用代码(本仓库占位为internal/app/_your_app_);/internal/pkg/<私有库名>—— 被这些应用共享的私有代码(本仓库占位为internal/pkg/_your_private_lib_)。
文档强调这种细分不是强制的(小项目尤其不必),但它提供了"这个包预期如何使用"的视觉线索。
3.3 /pkg:对外承诺"安全可用"的库代码
职责:允许外部应用使用的库代码(例如 /pkg/mypubliclib)。
文档的态度很克制,有三层含义:
- 其他项目会 import 这里的库并预期它们能正常工作,所以放入之前"想三遍"——这里代表了一种事实上的对外 API 承诺;
internal才是更强保障:确保私有包不被 import 的更好手段是internal(因为由 Go 语言机制强制执行);/pkg的价值在于显式地(explicitly)告诉外部使用者"这里的代码供你安全使用";/pkg还有工程层面的理由:当根目录混入大量非 Go 组件和目录时,把 Go 代码聚拢到一个地方,方便运行各类 Go 工具(GopherCon 2018 的 Best Practices for Industrial Programming、Kat Zien 的演讲、GoLab 2018 的 Project layout patterns in Go 都提到过这一点)。
仓库中的 pkg/README.md 进一步说明:pkg 目录不是被普遍接受的模式——"对每个用它的主流仓库,你都能找到十个不用的",用不用取决于你自己;但即便如此,懂得这个约定的人仍然占多数。该文件还列出了 containerd、jaeger、istio、helm、k3s、kubernetes、grafana、influxdb、cockroach 等大量采用该模式的知名仓库清单。
起源考据:早期 Go 源码自身就用 pkg 存放包,社区项目随后效仿这一模式(Brad Fitzpatrick 曾在推文中解释过这一背景)。
适用建议:如果项目真的很小、多一层嵌套没有收益,不用 pkg 完全没问题;等根目录变得拥挤(尤其有大量非 Go 组件)时再考虑引入。
3.4 /vendor:依赖的落地方式
职责:存放应用依赖(手动管理,或由依赖管理工具管理,如今内置的 Go Modules 就是标准答案)。
关键操作与版本边界:
go mod vendor命令会为项目创建/vendor目录;- 如果使用的 Go 版本不是 1.14 及以上(1.14 起默认启用 vendor 自动检测),可能需要给
go build显式加上-mod=vendor标志; - 如果你在构建一个库(library),不要 commit 你的应用依赖;
- 自 Go 1.13 起,module proxy 特性启用(默认使用官方 proxy.golang.org 作为模块代理服务器)。文档建议读者自行评估代理方案是否满足自身要求与限制——如果满足,你甚至完全不需要
vendor目录。
四、服务类应用目录:/api
职责:存放服务接口的定义性文件——OpenAPI/Swagger 规格、JSON Schema 文件、协议定义文件。
api/README.md 中列举了 kubernetes、moby 等项目把 API 定义集中放在 api/ 的做法。对提供 API 的服务而言,这一目录让"接口契约"与实现代码物理分离,便于文档生成与跨语言消费。
五、Web 应用目录:/web
职责:Web 应用特有组件——静态 Web 资源(static assets)、服务端模板(server-side templates)与单页应用(SPA)。
当前仓库的 web/ 目录恰好给出了三分法的占位实现:web/app(SPA 构建产物或入口)、web/static(CSS/JS/图片等静态资源)、web/template(服务端渲染模板)。三个子目录内同样只含 .keep 占位文件,克隆后可按需要保留或删除。
六、通用应用目录
6.1 /configs:配置文件模板与默认值
存放配置文件的模板或默认配置。文档特别提示:confd 与 consul-template 的模板文件应放在这里——也就是说 /configs 同时承担"仓库内默认配置"与"渲染型配置模板"两种角色。见 configs/README.md。
6.2 /init:系统初始化与进程守护配置
存放系统 init(systemd、upstart、sysv)以及进程管理器/守护器(runit、supervisord)的配置。适合有"系统服务"交付形态的项目。
6.3 /scripts:操作脚本,让根 Makefile 保持简单
职责:执行各种 build、install、analysis 等操作的脚本。
设计意图:这些脚本让根级 Makefile 保持小且简单。仓库自身的 Makefile 就是这一理念的最纯粹示范——整份文件只有一行 # note: call scripts from /scripts,所有实际操作都被委托给 scripts/ 目录。scripts/README.md 列举了 kubernetes/helm、cockroachdb/cockroach、hashicorp/terraform 等项目的同类做法(文档中还以 Terraform 的根 Makefile 作为"保持精简"的参照)。
6.4 /build:打包(Packaging)与持续集成(CI)
文档将 /build 拆为两块职责(原文为两个同名单元,含义互补):
- 打包:把云镜像(AMI)、容器(Docker)、操作系统包(deb、rpm、pkg)的打包配置与脚本放在
/build/package; - 持续集成:把 CI(travis、circle、drone 等)的配置与脚本放在
/build/ci。文档特别提醒:部分 CI 工具(如 Travis CI)对配置文件的存放位置非常挑剔,优先把配置统一放在/build/ci,再在工具期望的位置建立软链接指回它(在允许的情况下)。
6.5 /deployments:部署与编排
存放 IaaS、PaaS、系统与容器编排的部署配置和模板,典型如 docker-compose、kubernetes/helm、terraform。文档注意:有些仓库(尤其是以 kubernetes 部署的应用)习惯把该目录命名为 deploy/——理解他人代码时要意识到这种命名变体。
6.6 /test:外部测试程序与测试数据
职责:额外的外部测试应用与测试数据(区别于包内单元测试)。使用规则:
/test的内部结构可自由组织;- 大项目建议设一个数据子目录,例如
test/data或test/testdata——testdata是 Go 工具链约定忽略的目录名; - 补充:Go 同样会忽略以
.或_开头的目录/文件,因此命名测试数据目录的余地比想象中大。
test/README.md 以 openshift/origin 的 test/(测试数据位于 testdata 子目录)为例。
七、其他辅助目录
| 目录 | 职责 | 仓库内文档 |
|---|---|---|
/docs |
设计与用户文档(与 godoc 生成的 API 文档互补) | docs/README.md |
/tools |
本项目的辅助工具;注意这些工具可以 import /pkg 与 /internal 中的代码 |
tools/README.md |
/examples |
为你的应用和/或公开库提供的示例 | examples/README.md |
/third_party |
外部辅助工具、fork 的代码与其他第三方工具(如 Swagger UI) | — |
/githooks |
Git hooks | — |
/assets |
随仓库走的资源(图片、logo 等) | — |
/website |
若不使用 GitHub Pages,这里放项目站点数据 | website/README.md |
各目录 README 中还给出了主流项目的同类实践(/docs:hugo、openshift/origin、dapr;/tools:istio、dapr;/examples:nats.go、docker-slim、packer;/website:vault、perkeep),可作对照参考。
八、不应存在的目录:/src
文档用一整节明确反对在 Go 项目根下建 src/:
- 出现
src/的 Go 项目通常来自 Java 开发者(src在 Java 生态中是常见模式),"你并不想让 Go 代码和 Go 项目看起来像 Java"; - 不要混淆两个
src:项目级的/src与 Go 为 workspace 使用的/src。$GOPATH环境变量指向当前 workspace(非 Windows 系统默认$HOME/go),workspace 顶层包含/pkg、/bin、/src三个目录,真正的项目会落在/src之下。因此若项目自带/src,实际路径会变成类似/some/path/to/workspace/src/your_project/src/your_code.go的双重嵌套; - Go 1.11 之后项目可以放在
GOPATH之外,但这不意味着采用src/布局是好主意。
九、质量信号:Badges
文档列出的徽章(徽章本身不在本文复现,仅说明用途与获取方式):
- Go Report Card:扫描
gofmt、go vet、gocyclo、golint、ineffassign、license 与拼写(misspell),把服务上的仓库引用替换为项目自己的引用即可使用; - GoDoc(已废弃):文档中已用删除线标注该徽章失效,作为历史说明保留;
- Pkg.go.dev:Go 生态新的"发现与文档"目的地,可用其 badge 生成工具创建徽章;
- Release:展示项目最新 release 版本号,把链接指向自己的仓库即可。
十、落地建议与总结
综合文档与仓库结构,把这套布局落到一个真实项目的步骤可以是:
- 克隆 project-layout 仓库(如需 git clone,可指向你的托管副本;本仓库的主 README 见 README.md,本文对应的波斯语译本见 README_fa.md);
- 保留所需目录、删除其余——尤其是 go.mod 中
module github.com/YOUR-USER-OR-ORG-NAME/YOUR-REPO-NAME这一占位 module 声明必须替换为自己的真实路径,go 1.19版本行按团队工具链调整; - 用
gofmt+staticcheck兜底命名与格式; - 遵循
/cmd(薄 main)、/internal(编译器强制私有)、/pkg(显式公开承诺)的意图表达,让目录名本身承担文档作用; - 按需启用
/api、/web、/configs、/deployments等服务型/交付型目录,并保持根 Makefile 极简、脚本下沉/scripts; - 不引入
src/;小项目宁缺勿滥,vendor仅在代理方案不满足约束时才需要。
文档末尾的 "Notes" 也交代了演进方向:一套"更有主见的(more opinionated)"、附带示例/可复用配置、脚本与代码的项目模板仍是进行中的工作(WIP)——即当前版本刻意只提供骨架,避免替项目做架构决策。这正与全文基调一致:目录结构是团队协作的公共语言,而不是架构本身。
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 StartedRust0623
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