<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>项目结构 on JasperSao的博客</title><link>https://jasperstonnne.github.io/MyBlog/tags/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/</link><description>Recent content in 项目结构 on JasperSao的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sun, 30 Aug 2026 08:12:59 +0000</lastBuildDate><atom:link href="https://jasperstonnne.github.io/MyBlog/tags/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/index.xml" rel="self" type="application/rss+xml"/><item><title>Go 语言工程化：模块管理与项目结构</title><link>https://jasperstonnne.github.io/MyBlog/p/go-engineering-modules-project-structure/</link><pubDate>Mon, 13 Jul 2026 15:42:45 +0800</pubDate><guid>https://jasperstonnne.github.io/MyBlog/p/go-engineering-modules-project-structure/</guid><description>&lt;img src="https://jasperstonnne.github.io/MyBlog/p/go-engineering-modules-project-structure/cover.svg" alt="Featured image of post Go 语言工程化：模块管理与项目结构" /&gt;&lt;p&gt;承接 &lt;a class="link" href="https://jasperstonnne.github.io/MyBlog/p/go-basics-variables-to-pointers/" &gt;Go 语言基础：从变量到指针&lt;/a&gt; 和 &lt;a class="link" href="https://jasperstonnne.github.io/MyBlog/p/go-methods-interfaces-errors/" &gt;Go 语言进阶：方法、接口与错误处理&lt;/a&gt;，这一篇覆盖 Go 项目的工程化核心：模块管理、包的组织、目录结构。&lt;/p&gt;&#10;&lt;h2 id="go-modules"&gt;Go Modules&#10;&lt;/h2&gt;&lt;p&gt;之前写的代码都在单个 &lt;code&gt;.go&lt;/code&gt; 文件里，使用 &lt;code&gt;package main&lt;/code&gt;，直接 &lt;code&gt;go run xxx.go&lt;/code&gt; 就能跑。但真实项目通常会有几十个 &lt;code&gt;.go&lt;/code&gt; 文件、多个包，还会用到第三方库。这些东西怎么管理？答案就是 Go Modules。&lt;/p&gt;&#10;&lt;h3 id="gomod-是什么"&gt;go.mod 是什么？&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;go.mod&lt;/code&gt; 就像项目的“身份证”，它主要记录三类信息：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;模块路径是什么：&lt;code&gt;module example.com/demo&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;项目使用哪个 Go 版本：&lt;code&gt;go 1.26.3&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;项目依赖哪些第三方模块：由 &lt;code&gt;require&lt;/code&gt; 指令记录&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="为什么要用-go-modules"&gt;为什么要用 Go Modules？&#10;&lt;/h3&gt;&lt;p&gt;假设你写了一个项目，用了第三方库。朋友拿到代码后，怎么知道需要哪些依赖和版本？&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;没有 &lt;code&gt;go.mod&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;朋友：你用了哪些库？&lt;/p&gt;&#10;&lt;p&gt;你：好像有 xxx、yyy，版本忘了……&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;有 &lt;code&gt;go.mod&lt;/code&gt;：&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;朋友：看 &lt;code&gt;go.mod&lt;/code&gt; 就知道了，执行 &lt;code&gt;go mod download&lt;/code&gt; 或 &lt;code&gt;go mod tidy&lt;/code&gt; 就能准备好依赖。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h3 id="常用命令"&gt;常用命令&#10;&lt;/h3&gt;&lt;h4 id="go-mod-init-模块路径"&gt;&lt;code&gt;go mod init 模块路径&lt;/code&gt;&#10;&lt;/h4&gt;&lt;p&gt;在项目开始时创建 &lt;code&gt;go.mod&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go mod init example.com/demo&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;生成的文件类似这样：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go.mod" data-lang="go.mod"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;example.com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="mf"&gt;1.26&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;练习项目可以使用 &lt;code&gt;demo&lt;/code&gt; 这类简单名称；准备发布的模块通常使用代码仓库地址，例如 &lt;code&gt;github.com/用户名/项目名&lt;/code&gt;，这样别人才能通过稳定的路径导入它。&lt;/p&gt;&#10;&lt;h4 id="go-mod-tidy"&gt;&lt;code&gt;go mod tidy&lt;/code&gt;&#10;&lt;/h4&gt;&lt;p&gt;&lt;code&gt;go mod tidy&lt;/code&gt; 会根据源码中的 &lt;code&gt;import&lt;/code&gt; 整理依赖：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;添加代码需要、但 &lt;code&gt;go.mod&lt;/code&gt; 还没有记录的模块&lt;/li&gt;&#10;&lt;li&gt;删除代码已经不再需要的模块&lt;/li&gt;&#10;&lt;li&gt;补充或清理 &lt;code&gt;go.sum&lt;/code&gt; 中的校验信息&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;简单说，它会让模块文件与当前代码保持一致。改完依赖后跑一次，通常是个好习惯。&lt;/p&gt;&#10;&lt;h4 id="go-get-模块路径"&gt;&lt;code&gt;go get 模块路径&lt;/code&gt;&#10;&lt;/h4&gt;&lt;p&gt;给当前模块添加或升级依赖：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go get github.com/gin-gonic/gin&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它会解析并下载依赖，同时更新 &lt;code&gt;go.mod&lt;/code&gt; 和 &lt;code&gt;go.sum&lt;/code&gt;。如果你要安装一个命令行工具，而不是给当前项目添加依赖，应使用带版本号的 &lt;code&gt;go install&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go install example.com/tool@latest&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="gosum-是什么"&gt;go.sum 是什么？&#10;&lt;/h3&gt;&lt;p&gt;执行 &lt;code&gt;go mod tidy&lt;/code&gt;、&lt;code&gt;go get&lt;/code&gt; 或构建项目后，通常会出现 &lt;code&gt;go.sum&lt;/code&gt;。它记录依赖模块内容的加密校验值：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;项目使用某个模块的特定版本&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;go.sum&lt;/code&gt; 保存这个版本及其 &lt;code&gt;go.mod&lt;/code&gt; 文件的校验值&lt;/li&gt;&#10;&lt;li&gt;再次下载时，Go 会验证拿到的内容是否一致&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;code&gt;go.sum&lt;/code&gt; 不是用来锁死所有依赖版本的锁文件；真正决定依赖版本的是 &lt;code&gt;go.mod&lt;/code&gt; 和 Go 的版本选择规则。&lt;code&gt;go.mod&lt;/code&gt; 与 &lt;code&gt;go.sum&lt;/code&gt; 都应该提交到 Git，通常不需要手动编辑 &lt;code&gt;go.sum&lt;/code&gt;。&lt;/p&gt;&#10;&lt;h2 id="package-拆分"&gt;package 拆分&#10;&lt;/h2&gt;&lt;h3 id="为什么要拆-package"&gt;为什么要拆 package？&#10;&lt;/h3&gt;&lt;p&gt;如果所有代码都堆在 &lt;code&gt;main.go&lt;/code&gt; 里，用户管理、订单管理和数据库连接会逐渐混在一起。按职责拆包之后，结构会清晰很多：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;demo/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; go.mod&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; main.go # 程序入口&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; user/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; user.go # 用户相关功能&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; order/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; order.go # 订单相关功能&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; db/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; db.go # 数据库相关功能&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="怎么拆"&gt;怎么拆？&#10;&lt;/h3&gt;&lt;p&gt;先创建 &lt;code&gt;user/user.go&lt;/code&gt;，声明它属于 &lt;code&gt;user&lt;/code&gt; 包：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Age&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%s, %d\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;再在项目根目录的 &lt;code&gt;main.go&lt;/code&gt; 中导入它：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;example.com/demo/user&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Jasper&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里的导入路径由两部分组成：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;example.com/demo/user&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── 模块路径 ──┘ └包目录&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也就是：&lt;strong&gt;导入路径 = &lt;code&gt;go.mod&lt;/code&gt; 中的模块路径 + 包所在的相对目录&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;h3 id="package-与可见性"&gt;package 与可见性&#10;&lt;/h3&gt;&lt;p&gt;同一目录下的普通 &lt;code&gt;.go&lt;/code&gt; 文件必须属于同一个包。包名通常与目录名最后一段一致，这是一条强烈推荐的惯例，但不是“包名必须等于目录名”的语法规则。&lt;/p&gt;&#10;&lt;p&gt;标识符是否能被其他包使用，则由首字母大小写决定：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;写法&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;可见范围&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;User&lt;/code&gt;、&lt;code&gt;Print&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;首字母大写，可以被其他包访问&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;user&lt;/code&gt;、&lt;code&gt;print&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;首字母小写，只能在当前包内访问&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="internal-访问控制"&gt;internal 访问控制&#10;&lt;/h2&gt;&lt;h3 id="internal-是什么"&gt;internal 是什么？&#10;&lt;/h3&gt;&lt;p&gt;一句话：&lt;code&gt;internal&lt;/code&gt; 是 Go 编译器真正认识的特殊目录，用来限制包的导入范围。&lt;/p&gt;&#10;&lt;p&gt;假设项目结构如下：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;myapp/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; go.mod # module example.com/myapp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmd/api/main.go&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; internal/db/db.go&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;example.com/myapp/internal/db&lt;/code&gt; 只能被 &lt;code&gt;example.com/myapp&lt;/code&gt; 目录树中的代码导入：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// myapp/cmd/api/main.go：可以导入&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;example.com/myapp/internal/db&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;另一个项目则不能导入它：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// otherapp/main.go：编译报错&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;example.com/myapp/internal/db&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;更准确地说，规则不是“同一个模块才能用”，而是：&lt;strong&gt;只有位于 &lt;code&gt;internal&lt;/code&gt; 父目录树中的代码才能导入它&lt;/strong&gt;。大多数项目把 &lt;code&gt;internal&lt;/code&gt; 放在模块根目录，所以看起来就像“只有本模块能用”。&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;目录&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;编译器是否特殊处理&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;用途&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;internal/&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;是，限制外部导入&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;不希望暴露给外部项目的实现&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;pkg/&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;否，只是社区惯例&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;明确准备对外复用的包，可选&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="常见项目结构"&gt;常见项目结构&#10;&lt;/h2&gt;&lt;p&gt;Go 官方没有规定一套所有项目都必须照搬的“标准目录结构”。下面是一种常见布局，适合已经出现多个入口和多个业务模块的项目：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;myproject/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmd/ # 程序入口&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; api/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; main.go # 启动 HTTP 服务&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; worker/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; main.go # 启动后台任务&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; internal/ # 不对模块外部开放的代码&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; config/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; config.go # 配置读取&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; db/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; db.go # 数据库连接&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; user/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; user.go # 用户相关逻辑&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; handler/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; handler.go # HTTP 处理函数&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pkg/ # 确实需要对外复用时再添加&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; utils/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; utils.go&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; go.mod&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; go.sum&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;三个常见目录的职责：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;cmd/&lt;/code&gt;：每个子目录对应一个可执行程序，包名为 &lt;code&gt;main&lt;/code&gt;；通常只负责依赖组装、配置和启动&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;internal/&lt;/code&gt;：放不希望被外部项目导入的实现代码，不代表所有业务逻辑都必须塞在这里&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;pkg/&lt;/code&gt;：表达“这些包准备公开复用”的意图，没有编译器层面的特殊含义；小项目完全可以没有&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;目录不是越多越工程化。只有一个可执行程序的小项目，把 &lt;code&gt;main.go&lt;/code&gt; 放在根目录也很正常；随着入口和职责增多，再引入 &lt;code&gt;cmd/&lt;/code&gt;、&lt;code&gt;internal/&lt;/code&gt; 即可。&lt;/p&gt;&#10;&lt;h3 id="为什么要多一层-cmd"&gt;为什么要多一层 cmd/？&#10;&lt;/h3&gt;&lt;p&gt;一个项目可能提供多个启动方式：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmd/api/main.go # 启动 HTTP 服务&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmd/worker/main.go # 启动后台任务&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它们是两个入口，但可以共享 &lt;code&gt;internal/&lt;/code&gt; 里的业务代码。&lt;code&gt;cmd/&lt;/code&gt; 下的程序应尽量轻量：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// cmd/api/main.go&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;example.com/myproject/internal/server&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;cmd/api&lt;/code&gt; 中不一定只能有一个 &lt;code&gt;main.go&lt;/code&gt;，也可以有多个属于 &lt;code&gt;package main&lt;/code&gt; 的文件；重要的是入口层保持轻薄，不把大量业务逻辑堆在这里。&lt;/p&gt;&#10;&lt;h2 id="引入第三方包"&gt;引入第三方包&#10;&lt;/h2&gt;&lt;p&gt;标准库是 Go 自带的，例如 &lt;code&gt;fmt&lt;/code&gt;、&lt;code&gt;net/http&lt;/code&gt;。标准库之外，由其他项目提供的包就是第三方包，例如：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;github.com/gin-gonic/gin&lt;/code&gt;：Web 框架&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;github.com/jackc/pgx/v5&lt;/code&gt;：PostgreSQL 驱动与工具包&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;github.com/spf13/cobra&lt;/code&gt;：命令行应用框架&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;以 UUID 包为例，先添加依赖：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go get github.com/google/uuid&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;再在代码里导入并使用：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/google/uuid&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;此时 &lt;code&gt;go.mod&lt;/code&gt; 可能包含：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go.mod" data-lang="go.mod"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;example.com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="mf"&gt;1.26&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="n"&gt;github.com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="n"&gt;v1.6.0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;改动依赖后执行：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go mod tidy&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; ./...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第一条命令整理模块文件，第二条命令确认整个模块仍能编译并通过测试。到这里，一个 Go 项目从单文件走向模块化工程的基本骨架就搭起来了。&lt;/p&gt;&#10;</description></item></channel></rss>