包管理
Go的包管理让代码组织变得简单清晰,Go Modules是现代Go项目的标准。我刚开始学Go的时候,还是 GOPATH 时代,那种目录结构的限制让我很不适应。后来 Go Modules 出来了,真的是解放了生产力!现在管理依赖跟 npm 一样方便。
包的基础
什么是包?
包(Package)是Go代码组织的基本单位,一个目录下的所有.go文件属于同一个包。
myproject/
├── main.go // package main
├── utils/
│ ├── string.go // package utils
│ └── math.go // package utils
└── models/
└── user.go // package models
包声明
// 每个.go文件第一行
package main // 可执行程序
package utils // 库包
导入包
import "fmt" // 单个包
import "github.com/gin-gonic/gin" // 第三方包
import ( // 多个包
"fmt"
"strings"
"myproject/utils" // 本地包
)
import (
"fmt"
str "strings" // 别名
. "math" // 点导入(不推荐)
_ "database/sql" // 空白导入(只执行init)
)
Go Modules
初始化模块
# 创建新模块
mkdir myproject && cd myproject
go mod init github.com/username/myproject
# 或者简单名称
go mod init myproject
生成 go.mod 文件:
module myproject
go 1.21
添加依赖
# 自动下载依赖
go get github.com/gin-gonic/gin
# 指定版本
go get github.com/gin-gonic/gin@v1.9.0
# 更新依赖
go get -u github.com/gin-gonic/gin
# 清理未使用的依赖
go mod tidy
go.mod 文件
module myproject
go 1.21
require (
github.com/gin-gonic/gin v1.9.0
github.com/go-sql-driver/mysql v1.7.0
)
require (
// 间接依赖
github.com/bytedance/sonic v1.8.0 // indirect
)
go.sum 文件
记录依赖的校验和,保证可重复构建:
github.com/gin-gonic/gin v1.9.0 h1:...
github.com/gin-gonic/gin v1.9.0/go.mod h1:...
项目结构
标准项目布局
myproject/
├── cmd/ # 可执行程序入口
│ └── myapp/
│ └── main.go
├── internal/ # 私有代码(不能被其他项目导入)
│ ├── config/
│ │ └── config.go
│ └── service/
│ └── user.go
├── pkg/ # 可被外部导入的库
│ └── utils/
│ └── string.go
├── api/ # API定义(如proto文件)
├── web/ # 静态资源
├── configs/ # 配置文件
├── scripts/ # 脚本
├── go.mod
├── go.sum
└── README.md
简单项目
myapp/
├── main.go
├── handler/
│ └── user.go
├── model/
│ └── user.go
├── service/
│ └── user.go
├── go.mod
└── go.sum
可见性规则
Go使用首字母大小写控制可见性:
package user
// 大写:公开(可被其他包访问)
type User struct {
ID int // 公开
Name string // 公开
age int // 私有(小写)
}
func NewUser() *User { } // 公开
func validateUser() bool { } // 私有
// 公开常量
const MaxAge = 150
// 私有变量
var defaultAge = 18
创建和使用自己的包
项目结构
myproject/
├── main.go
├── mathutil/
│ └── math.go
└── go.mod
mathutil/math.go
package mathutil
// Add 两数相加(公开函数)
func Add(a, b int) int {
return a + b
}
// Multiply 两数相乘
func Multiply(a, b int) int {
return a * b
}
// max 返回较大值(私有函数)
func max(a, b int) int {
if a > b {
return a
}
return b
}
// Max 公开的最大值函数
func Max(a, b int) int {
return max(a, b)
}
main.go
package main
import (
"fmt"
"myproject/mathutil"
)
func main() {
sum := mathutil.Add(1, 2)
product := mathutil.Multiply(3, 4)
fmt.Println("Sum:", sum) // 3
fmt.Println("Product:", product) // 12
}
init 函数
每个包可以有多个init函数,在main之前自动执行:
package config
import "fmt"
var AppName string
func init() {
fmt.Println("config包初始化")
AppName = "MyApp"
}
// 可以有多个init
func init() {
fmt.Println("config包第二个init")
}
执行顺序:
- 导入的包的init
- 当前包的包级变量
- 当前包的init函数
- main函数
常用命令
# 初始化模块
go mod init <module-name>
# 添加依赖
go get <package>
# 下载依赖
go mod download
# 整理依赖
go mod tidy
# 查看依赖
go list -m all
# 查看可用版本
go list -m -versions github.com/gin-gonic/gin
# 为什么需要这个依赖
go mod why github.com/some/package
# 生成vendor目录
go mod vendor
# 验证依赖
go mod verify
私有仓库
配置GOPRIVATE
# 设置私有仓库
go env -w GOPRIVATE=github.com/mycompany/*,gitlab.mycompany.com/*
# 或在环境变量中
export GOPRIVATE=github.com/mycompany/*
使用SSH
# 配置git使用SSH
git config --global url."git@github.com:".insteadOf "https://github.com/"
实战案例:日志库
创建一个简单的日志库:
logger/
├── go.mod
├── logger.go
├── formatter.go
└── level.go
go.mod
module github.com/myuser/logger
go 1.21
level.go
package logger
// 日志级别
type Level int
const (
DEBUG Level = iota
INFO
WARN
ERROR
FATAL
)
func (l Level) String() string {
switch l {
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARN:
return "WARN"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
default:
return "UNKNOWN"
}
}
formatter.go
package logger
import (
"fmt"
"time"
)
// Formatter 日志格式化器接口
type Formatter interface {
Format(level Level, msg string) string
}
// TextFormatter 文本格式化器
type TextFormatter struct{}
func (f *TextFormatter) Format(level Level, msg string) string {
return fmt.Sprintf("[%s] %s - %s\n",
time.Now().Format("2006-01-02 15:04:05"),
level.String(),
msg)
}
// JSONFormatter JSON格式化器
type JSONFormatter struct{}
func (f *JSONFormatter) Format(level Level, msg string) string {
return fmt.Sprintf(`{"time":"%s","level":"%s","msg":"%s"}`+"\n",
time.Now().Format(time.RFC3339),
level.String(),
msg)
}
logger.go
package logger
import (
"io"
"os"
"sync"
)
// Logger 日志记录器
type Logger struct {
mu sync.Mutex
level Level
output io.Writer
formatter Formatter
}
// 默认logger
var defaultLogger = New()
// New 创建新的Logger
func New() *Logger {
return &Logger{
level: INFO,
output: os.Stdout,
formatter: &TextFormatter{},
}
}
// SetLevel 设置日志级别
func (l *Logger) SetLevel(level Level) {
l.mu.Lock()
defer l.mu.Unlock()
l.level = level
}
// SetOutput 设置输出目标
func (l *Logger) SetOutput(w io.Writer) {
l.mu.Lock()
defer l.mu.Unlock()
l.output = w
}
// SetFormatter 设置格式化器
func (l *Logger) SetFormatter(f Formatter) {
l.mu.Lock()
defer l.mu.Unlock()
l.formatter = f
}
// log 内部日志方法
func (l *Logger) log(level Level, msg string) {
if level < l.level {
return
}
l.mu.Lock()
defer l.mu.Unlock()
formatted := l.formatter.Format(level, msg)
l.output.Write([]byte(formatted))
}
// Debug 调试日志
func (l *Logger) Debug(msg string) {
l.log(DEBUG, msg)
}
// Info 信息日志
func (l *Logger) Info(msg string) {
l.log(INFO, msg)
}
// Warn 警告日志
func (l *Logger) Warn(msg string) {
l.log(WARN, msg)
}
// Error 错误日志
func (l *Logger) Error(msg string) {
l.log(ERROR, msg)
}
// Fatal 致命错误日志
func (l *Logger) Fatal(msg string) {
l.log(FATAL, msg)
os.Exit(1)
}
// 包级别函数,使用默认logger
func Debug(msg string) { defaultLogger.Debug(msg) }
func Info(msg string) { defaultLogger.Info(msg) }
func Warn(msg string) { defaultLogger.Warn(msg) }
func Error(msg string) { defaultLogger.Error(msg) }
func Fatal(msg string) { defaultLogger.Fatal(msg) }
func SetLevel(level Level) { defaultLogger.SetLevel(level) }
func SetOutput(w io.Writer) { defaultLogger.SetOutput(w) }
func SetFormatter(f Formatter) { defaultLogger.SetFormatter(f) }
使用日志库
package main
import (
"github.com/myuser/logger"
)
func main() {
// 使用默认logger
logger.Info("应用启动")
logger.Debug("这条不会显示,级别不够")
// 设置级别
logger.SetLevel(logger.DEBUG)
logger.Debug("现在可以显示了")
// 使用JSON格式
logger.SetFormatter(&logger.JSONFormatter{})
logger.Info("JSON格式日志")
// 创建新的logger
myLogger := logger.New()
myLogger.SetLevel(logger.WARN)
myLogger.Warn("警告信息")
}
版本管理
语义化版本
v1.2.3
│ │ │
│ │ └─ 补丁版本(bug修复)
│ └─── 次版本(新功能,向后兼容)
└───── 主版本(不兼容的变更)
发布版本
# 打标签
git tag v1.0.0
git push origin v1.0.0
# v2及以上需要修改go.mod
module github.com/myuser/mylib/v2
引入v2
import "github.com/myuser/mylib/v2"
练习
- 创建一个包含多个包的项目
- 实现一个简单的配置读取包
- 使用go mod管理第三方依赖
参考答案
// 项目结构
// myapp/
// ├── main.go
// ├── config/
// │ └── config.go
// └── go.mod
// go.mod
module myapp
go 1.21
// config/config.go
package config
import (
"encoding/json"
"os"
)
type Config struct {
AppName string `json:"app_name"`
Port int `json:"port"`
Debug bool `json:"debug"`
Database struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
DBName string `json:"dbname"`
} `json:"database"`
}
func Load(filename string) (*Config, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
config := &Config{}
decoder := json.NewDecoder(file)
if err := decoder.Decode(config); err != nil {
return nil, err
}
return config, nil
}
// main.go
package main
import (
"fmt"
"myapp/config"
)
func main() {
cfg, err := config.Load("config.json")
if err != nil {
fmt.Println("加载配置失败:", err)
return
}
fmt.Printf("应用: %s\n", cfg.AppName)
fmt.Printf("端口: %d\n", cfg.Port)
fmt.Printf("数据库: %s:%d\n", cfg.Database.Host, cfg.Database.Port)
}
包管理让项目井井有条,下一节学习单元测试!
