文章目录
- 一、命令可以生成一个go的工程
-
- [1.1 go mod tidy 下载依赖](#1.1 go mod tidy 下载依赖)
- [1.2 运行命令](#1.2 运行命令)
- 二、包机制详解
- 三、根目录
- [四、 多 go.mod 工程示例](#四、 多 go.mod 工程示例)
-
- [4.1、为什么要多个 go.mod?](#4.1、为什么要多个 go.mod?)
- 4.2、典型结构示例
- [4.3 实战示例:](#4.3 实战示例:)
- [4.4 本地开发:go.work](#4.4 本地开发:go.work)
- [五、多 go.mod 工程示例2](#五、多 go.mod 工程示例2)
- 六、module/package语法
-
- [6.1 模块名包含域名的用法](#6.1 模块名包含域名的用法)
- [6.2 module/package/import语法](#6.2 module/package/import语法)
一、命令可以生成一个go的工程
初始化一个包含可执行 main 方法的 Go 工程,只需两条命令。下面我会分步说明,并解释每条命令的作用。
操作步骤
1、创建并进入项目目录
首先,为你的项目创建一个独立的文件夹,并进入该目录:
bash
mkdir myproject
cd myproject
2、初始化 Go 模块 (go mod init)
这是关键的一步,用于创建一个 Go 模块来管理你的项目依赖。运行以下命令:
bash
go mod init <模块名称>
-
模块名称通常是你的代码仓库的路径,例如 github.com/yourusername/myproject 。
-
如果是本地测试,也可以使用一个简单的名称,例如 myproject 。
-
执行后,会在当前目录生成一个
go.mod文件,这是项目依赖管理的核心文件
3、创建 main.go 文件
可以和go.mod文件平级
go
cat > main.go << EOF
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
EOF
-
package main 声明了这是一个可独立执行的程序 。
-
func main() 是程序的入口函数 。
-
fmt.Println 用于在控制台打印输出
如何运行
完成以上步骤,你就有两种方式来运行你的Go程序了:
| 命令 | 作用 | 适用场景 |
|---|---|---|
go run . |
直接编译并运行程序,不会在当前目录留下可执行文件。 | 开发调试阶段,频繁修改和测试时使用。 |
go build . |
仅编译,生成一个可执行的二进制文件(Windows下为.exe文件)。 | 编译完成后需要分发或部署程序时使用。 |
项目结构示意
完成后,你的项目目录结构看起来会像这样:
bash
myproject/
├── go.mod # 模块管理文件
└── main.go # 主程序源文件
1.1 go mod tidy 下载依赖
如果你的go文件中Import了第三方包,那么需要go mod tidy命令,自动下载依赖
1.2 运行命令
| 特性 | go run main.go |
go run . |
|---|---|---|
| 指定方式 | 显式指定要运行的文件 | 指定一个包(当前目录的包) |
| 处理文件范围 | 只编译和运行 main.go 这一个文件 |
编译并运行当前目录下整个包的所有 .go 文件 |
| 多文件项目 | ❌ 如果项目有其他文件(如 helper.go),会因为未定义而编译失败 |
✅ 自动包含包内所有文件,正常工作 |
假设你的项目结构如下:
go
myproject/
├── main.go
├── helper.go
└── utils/
└── utils.go
main.go:
go
package main
func main() {
printMessage() // 这个函数定义在 helper.go 中
}
helper.go:
go
package main
import "fmt"
func printMessage() {
fmt.Println("Hello from helper!")
}
运行 go run main.go:
go
# ❌ 会报错!
# ./main.go:4:2: undefined: printMessage
因为 go run main.go 只编译 main.go,编译器不知道 printMessage 函数存在。
运行 go run .
go
# ✅ 正常运行!
# Hello from helper!
因为 go run . 编译了当前包(myproject)下的所有 .go 文件(main.go 和 helper.go),所以能找到 printMessage 函数。
go run . 的工作方式是:
-
解析当前目录下的包(package main)
-
编译该包中的所有 .go 文件(包括 main.go、helper.go 等)
-
找到其中的
main() 函数作为入口点 -
运行生成的可执行文件
二、包机制详解
1、同一个目录下的所有 .go 文件必须属于同一个包名
错误示例
go
// 目录 /myapp/user/
// user.go
package user // ❌ 错误
// profile.go
package profile // ❌ 同一个目录下不同包名
// 编译错误:found packages user and profile in same directory
正确示例:
go
// 目录 /myapp/user/
// user.go
package user // ✅ 正确
// profile.go
package user // ✅ 同一个包名
这三个文件虽然在不同的文件中,但因为同一个目录且同一个包名!
三、根目录
Go 的"根目录"概念有两种,别搞混了:
1、GOROOT(Go 安装根目录)
Go 语言自己的家
查看 GOROOT这个环境变量即可
bash
# 你安装 Go 的地方
/usr/local/go/ # Linux/Mac 默认
C:\Go\ # Windows 默认
# 里面装着 Go 自己的东西
/usr/local/go/
/src/ # Go 标准库的源码(fmt, net, http等)
/bin/ # go 命令本身
/pkg/ # 编译好的库文件
这个你不用管,Go 自己会用。就像你不需要知道 Python 安装在哪里一样。
2、Module 根目录(现代 Go)✅ 重点
你项目的根目录,由 go.mod 文件标记
go
# 你的项目根目录
/myapp/ # ← 这就是项目根目录
go.mod # ← 有这个文件的就是根目录
main.go
/internal/ # 从这个目录开始算
/pkg/
/cmd/
怎么知道根目录在哪里?
bash
# 在项目任意目录执行
go list -m
# 输出:myapp (这就是模块名)
# 或者找 go.mod 文件
find . -name "go.mod"
# 输出:./go.mod (所在目录就是根目录)
四、 多 go.mod 工程示例
一个项目可以有多个 go.mod 文件,这叫 Multi-Module Repository。这在大型项目或微服务架构中很常见。
4.1、为什么要多个 go.mod?
-
独立版本管理:每个模块独立发版
-
依赖隔离:不同模块可以用不同版本的依赖
-
微服务架构:每个服务独立
-
大型 Monorepo:一个仓库包含多个项目
4.2、典型结构示例
bash
/mycompany/ # Git 仓库根目录
├── go.mod # 根模块(可选)
├── go.work # Workspace 文件(本地开发用)
│
├── /services/
│ ├── /user-service/
│ │ ├── go.mod # user-service 独立模块,模块根
│ │ ├── main.go
│ │ └── /internal/
│ │
│ ├── /order-service/
│ │ ├── go.mod # order-service 独立模块,模块根
│ │ ├── main.go
│ │ └── /internal/
│ │
│ └── /payment-service/
│ ├── go.mod
│ └── main.go
│
├── /libs/
│ ├── /common/
│ │ ├── go.mod # 公共库模块
│ │ └── /auth/
│ │
│ └── /database/
│ ├── go.mod
│ └── mysql.go
│
└── /tools/
└── /cli/
├── go.mod # 工具模块
└── main.go
4.3 实战示例:
项目结构:
go
ecommerce/ # Git 仓库根目录
├── go.work # Workspace(本地开发)
│
├── /services/
│ ├── /user-service/
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── user.go
│ │
│ ├── /order-service/
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── order.go
│ │
│ └── /api-gateway/
│ ├── go.mod
│ └── main.go
│
└── /pkg/
├── /common/
│ ├── go.mod
│ ├── /logger/
│ │ └── logger.go
│ └── /errors/
│ └── errors.go
│
└── /protos/
├── go.mod
└── user.pb.go
项目准备:
go
# 创建项目结构
mkdir -p ecommerce/{services/user-service,services/order-service,pkg/common}
cd ecommerce
# 创建各模块的 go.mod
cd services/user-service
go mod init ecommerce/services/user-service
cd ../order-service
go mod init ecommerce/services/order-service
cd ../../pkg/common
go mod init ecommerce/pkg/common
cd ../..
# 现在有了三个独立的模块
单模块构建构建命令:
bash
# 进入 user-service 模块
cd services/user-service
# 下载依赖
go mod download
# 整理依赖
go mod tidy
# 构建当前模块
go build ./...
# 构建并生成可执行文件
go build -o bin/user-service ./cmd/server
# 运行测试
go test ./...
# 运行当前模块
go run main.go
模块定义文件
services/user-service/go.mod:
go
module ecommerce/services/user-service # 模块名称,供其他的go.mod文件通过require配置依赖
go 1.21
require (
ecommerce/pkg/common v0.0.0 #通过共享库(明确依赖),声明依赖 common 模块
ecommerce/pkg/protos v0.0.0
github.com/gorilla/mux v1.8.0
)
// 本地开发时指向本地路径(生产环境会替换为真实版本)
replace (
ecommerce/pkg/common => ../../pkg/common
ecommerce/pkg/protos => ../../pkg/protos
)
services/order-service/go.mod:
go
module ecommerce/services/order-service # 模块名称,供其他的go.mod文件通过require配置依赖
go 1.21
require (
ecommerce/pkg/common v0.0.0 #通过共享库(明确依赖),声明依赖 common 模块
ecommerce/pkg/protos v0.0.0
github.com/go-redis/redis/v8 v8.11.5
)
replace (
ecommerce/pkg/common => ../../pkg/common
ecommerce/pkg/protos => ../../pkg/protos
)
pkg/common/go.mod:
go
module ecommerce/pkg/common # 模块名称,供其他的go.mod文件通过require配置依赖
go 1.21
require (
github.com/sirupsen/logrus v1.9.0
)
pkg/protos/go.mod:
go
module ecommerce/pkg/protos
go 1.21
require (
google.golang.org/grpc v1.58.0
google.golang.org/protobuf v1.31.0
)
代码示例
services/user-service/user.go
go
package main
import (
"ecommerce/pkg/common/logger" # 模块名+包名进行引用
"ecommerce/pkg/protos"
"github.com/gorilla/mux"
)
func main() {
// 使用公共模块
logger.Info("User service starting...")
r := mux.NewRouter()
// 使用 protos 定义的结构
user := &protos.User{Id: "123", Name: "Alice"}
// ...
}
services/order-service/order.go:
go
package main
import (
"ecommerce/pkg/common/logger" # 依赖 common 模块
"ecommerce/pkg/protos"
)
func main() {
// 同一个 common 包,但可以有不同的版本
logger.Info("Order service starting...")
order := &protos.Order{Id: "ORD-001"}
// ...
}
ecommerce/pkg/common/logger/logger.go:
go
// ecommerce/pkg/common/logger/logger.go
package logger // ← 这是包名,决定了外部如何使用
import (
"fmt"
"time"
)
// 公开的常量(大写开头)
const (
LevelDebug = "DEBUG"
LevelInfo = "INFO"
LevelError = "ERROR"
)
// 公开的变量(大写开头)
var DefaultLevel = LevelInfo
// 私有的变量(小写开头)
var currentTime = time.Now
// 公开的结构体(大写开头)
type Logger struct {
prefix string
level string
}
// 构造函数(公开)
func New(prefix string) *Logger {
return &Logger{
prefix: prefix,
level: DefaultLevel,
}
}
// 公开方法(大写开头)
func (l *Logger) Info(msg string) {
if l.level == LevelInfo || l.level == LevelDebug {
fmt.Printf("[%s] [INFO] %s: %s\n",
time.Now().Format("15:04:05"),
l.prefix,
msg)
}
}
func (l *Logger) Debug(msg string) {
if l.level == LevelDebug {
fmt.Printf("[%s] [DEBUG] %s: %s\n",
time.Now().Format("15:04:05"),
l.prefix,
msg)
}
}
func (l *Logger) Error(msg string) {
fmt.Printf("[%s] [ERROR] %s: %s\n",
time.Now().Format("15:04:05"),
l.prefix,
msg)
}
// 公开的函数(大写开头)
func LogInfo(prefix, msg string) {
fmt.Printf("[INFO] %s: %s\n", prefix, msg)
}
func LogError(prefix, msg string) {
fmt.Printf("[ERROR] %s: %s\n", prefix, msg)
}
// 私有的辅助函数(小写开头)
func formatMessage(level, prefix, msg string) string {
return fmt.Sprintf("[%s] %s: %s", level, prefix, msg)
}
4.4 本地开发:go.work
创建 workspace
bash
# 在项目根目录 ecommerce/
go work init ./services/user-service ./services/order-service ./pkg/common
# 查看 workspace 配置
cat go.work
# 输出:
# go 1.21
#
# use (
# ./services/user-service
# ./services/order-service
# ./pkg/common
# )
ecommerce/go.work(解决本地开发时的模块替换问题)
go
go 1.21
use (
./services/user-service
./services/order-service
./services/payment-service
./services/api-gateway
./pkg/common
./pkg/protos
)
使用 Workspace 的好处:
go
# 不需要在每个模块里写 replace 指令
cd services/user-service
go run main.go # 自动使用本地代码
# 修改 pkg/common 的代码
# user-service 会立即生效,无需重新发布
五、多 go.mod 工程示例2
多级嵌套情况
存在如下结构:
存在一个myrepo/go.mod 和myrepo/services//go.mod
go
myrepo/
├── go.mod # 模块根 A
├── dao/
│ ├── orderdao.go # 查找结果:模块 A
│ └── sub/
│ └── helper.go # 查找结果:模块 A
├── services/
│ ├── go.mod # 模块根 B
│ └── auth/
│ └── main.go # 查找结果:模块 B(被阻断后,不会往上查找)
└── tools/
└── gen/
└── main.go # 查找结果:向上到根?没有 go.mod?
# 一直找到根目录(模块 A) 为示例,给出不同构建命令的溯源过程
当进行编译时myrepo/services/auth,会寻找go.mod ,那么怎么查找的呢?
bash
cd myrepo/services/auth
go list -m
# 输出:myrepo/services (不是 myrepo)
go env GOMOD
# 输出:/path/to/myrepo/services/go.mod
查找过程:
bash
# 1. 从 auth/ 开始
# 2. 没找到 go.mod
# 3. 向上到 services/
# 4. 找到 go.mod(停止,不会继续向上)
# 结果:使用 services/go.mod,不是根目录的
当进行编译时 myrepo/dao,会寻找go.mod ,那么怎么查找的呢?
bash
cd myrepo/dao
go build
# 查找过程:
# 1. 从 /myrepo/dao/ 开始
# 2. 没有 go.mod
# 3. 向上到 /myrepo/
# 4. 找到 go.mod ✅(停止)
# 结果:使用 /myrepo/go.mod(模块根 A)
一个模块可以包含任意深度的子目录,只要这些子目录没有自己的 go.mod,构建时,会往上找:
- 查找从当前目录开始,向上逐级
- 找到第一个 go.mod 就停止
- 如果一直找到根目录都没有,报错
- 子目录没有 go.mod = 属于最近的上级模块
六、module/package语法
6.1 模块名包含域名的用法
这个"域名"其实不是真正的网络域名,而是模块的唯一标识符。让我用最简单的方式解释
go
// 这些"域名"的作用不是让 Go 去访问网站
module github.com/gin-gonic/gin // 只是一个名字
module ecommerce/services/user // 也只是一个名字
module mycompany.com/utils // 还是一个名字
// Go 并不检查这个域名是否真实存在
// 它的作用是:确保全世界不会有另一个模块叫这个名字
写法1:无域名(示例中的写法)
bash
module ecommerce/services/user-service
// 适用场景:
// ✅ 本地学习项目
// ✅ 公司内部项目(配合 replace 使用)
// ✅ 快速原型开发
写法2:有域名(生产推荐)
bash
module github.com/mycompany/ecommerce/services/user-service
// 适用场景:
// ✅ 开源项目
// ✅ 对外发布的库
// ✅ 需要被其他项目远程引用的模块
完整的对比示例:
bash
# 示例中的写法(本地开发)
ecommerce/
├── services/
│ └── user-service/
│ ├── go.mod # module ecommerce/services/user-service
│ └── main.go
└── pkg/
└── common/
├── go.mod # module ecommerce/pkg/common
└── common.go
# 生产环境的写法(对外发布)
ecommerce/
├── services/
│ └── user-service/
│ ├── go.mod # module github.com/mycompany/ecommerce/services/user-service
│ └── main.go
└── pkg/
└── common/
├── go.mod # module github.com/mycompany/ecommerce/pkg/common
└── common.go
6.2 module/package/import语法
-
module模块名 = 域名(可省略) + 模块根路径(模块的唯一标识,即go.mod文件所在的全路径)
-
package包名 = 当前.go文件声明的包名(与目录名无关,只是惯例一般把包名定位父目录,仅父目录一层!)
-
import = 被引用的模块名 + 从该模块根到目标包的相对路径(含所有层)
bash
# 文件系统
/home/user/projects/myapp/
├── go.mod # module example.com/myapp
├── main.go
└── services/
└── user/
└── user.go # package user
# 对应关系
文件系统路径 → /home/user/projects/myapp/services/user/user.go
模块名 → example.com/myapp
包路径(导入路径的后半部分)→ services/user
包名(package声明) → user
完整导入路径 → example.com/myapp/services/user