【go】建工程、初始化、module/package/import语法

文章目录

  • 一、命令可以生成一个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
相关推荐
XMYX-05 小时前
32 - Go 正则表达式:从匹配字符串到理解 RE2 引擎
golang·正则表达式
存在morning13 小时前
【GO语言开发实践】二 GO 并发快速上手
大数据·开发语言·golang
geovindu1 天前
go: Read-Write Lock Pattern
开发语言·后端·设计模式·golang·读写锁模式
知彼解己1 天前
Go 开发环境 安装
后端·golang
会编程的土豆1 天前
Go 连接 Redis 代码详细解析
开发语言·redis·golang
XMYX-01 天前
31 - Go url 解析:从字符串到结构化请求的完整路径
开发语言·golang
lolo大魔王1 天前
Go 语言数据库操作|GORM 实现 CRUD 超详细实战
数据库·golang
喵了几个咪1 天前
单体项目如何“无感”演进微服务?GoWind的Core+BFF分层实践
微服务·架构·golang·gowind·bff
139的世界真奇妙1 天前
生产问题排查记录
golang·bug·学习方法