- Published on
golang项目架构文档
- Authors

- Name
- 刘十三
项目架构文档
本文档描述了项目的整体架构设计、目录结构、开发规范以及最佳实践,用于指导新项目的创建和现有项目的重构。
目录
项目概述
本项目采用 分层架构 和 依赖注入 模式,使用 Uber FX 进行依赖管理,基于 Gin 框架构建 RESTful API 服务。
核心特性
- ✅ 清晰的分层架构(Handler → Service → Repository)
- ✅ 基于 Uber FX 的依赖注入
- ✅ 模块化设计,支持多服务(mini、admin)
- ✅ 统一的错误处理和响应格式
- ✅ 完整的中间件支持(JWT、日志、追踪、CORS等)
- ✅ 代码生成(GORM Gen)
- ✅ Swagger API 文档
- ✅ 可观测性支持(Prometheus、日志)
技术栈
核心框架
| 技术 | 版本 | 用途 |
|---|---|---|
| Go | 1.24.1+ | 编程语言 |
| Gin | v1.11.0 | Web 框架 |
| GORM | v1.25.11 | ORM 框架 |
| Uber FX | v1.24.0 | 依赖注入框架 |
| Viper | v1.21.0 | 配置管理 |
| Zap | v1.27.0 | 日志框架 |
数据存储
| 技术 | 版本 | 用途 |
|---|---|---|
| MySQL | - | 关系型数据库 |
| Redis | v9.16.0 | 缓存/会话存储 |
其他工具
| 技术 | 用途 |
|---|---|
| GORM Gen | 代码生成 |
| Swagger | API 文档 |
| Prometheus | 指标监控 |
| Kafka | 消息队列(可选) |
| WebSocket | 实时通信 |
项目结构
your_project/
├── cmd/ # 应用入口点
│ ├── admin/ # 管理后台服务入口
│ ├── mini/ # 客户端服务入口
│ ├── migration/ # 数据库迁移工具
│ └── gorm_gen/ # GORM 代码生成工具
│
├── internal/ # 内部业务逻辑(不对外暴露)
│ ├── admin/ # 管理后台模块
│ │ ├── handler/ # HTTP 处理器层
│ │ ├── service/ # 业务逻辑层
│ │ ├── router/ # 路由定义
│ │ ├── middleware/ # 中间件
│ │ ├── types/ # 类型定义
│ │ └── vars/ # 模块常量
│ │
│ └── mini/ # 客户端模块
│ ├── handler/ # HTTP 处理器层
│ ├── service/ # 业务逻辑层
│ ├── router/ # 路由定义
│ ├── middleware/ # 中间件
│ ├── types/ # 类型定义
│ ├── vars/ # 模块常量
│ └── websocket/ # WebSocket 处理
│
├── third_party/ # 三方包目录
│ ├── provide/ # 基础设施提供者模块(独立模块,在 go.work 中使用)
│ │ ├── gin/ # Gin 引擎封装
│ │ ├── gorm/ # GORM 数据库封装
│ │ ├── redis/ # Redis 客户端封装
│ │ ├── jwt/ # JWT 工具
│ │ ├── logger/ # 日志封装
│ │ ├── mail/ # 邮件服务
│ │ ├── prometheus/ # Prometheus 指标
│ │ ├── kafka/ # Kafka 客户端
│ │ ├── websocket/ # WebSocket 管理
│ │ ├── vars/ # 全局常量
│ │ ├── es/ # Elasticsearch 客户端
│ │ ├── etcd/ # Etcd 客户端
│ │ ├── casbin/ # Casbin 权限管理
│ │ ├── mcp/ # MCP 协议
│ │ ├── eino/ # Eino AI 框架
│ │ ├── ali/ # 阿里云服务
│ │ ├── grafana/ # Grafana 集成
│ │ ├── blackchain/ # 区块链相关
│ │ ├── go.mod # 模块定义(liuxiaobo.pro/provide)
│ │ └── go.sum # 依赖校验
│ │
│ ├── utils/ # 工具函数模块(独立模块,在 go.work 中使用)
│ │ ├── response/ # 统一响应格式
│ │ ├── crypto/ # 加密工具
│ │ ├── timee/ # 时间工具
│ │ ├── apple/ # Apple 相关工具
│ │ ├── iap/ # 内购验证
│ │ ├── expo/ # Expo 推送
│ │ ├── slicee/ # 切片工具
│ │ ├── stringss/ # 字符串工具
│ │ ├── go.mod # 模块定义(liuxiaobo.pro/utils)
│ │ └── go.sum # 依赖校验
│ │
│ └── ... # 其他三方包(可直接 git clone 子包到此目录)
│
├── repo/ # 数据访问层
│ ├── query/ # GORM Gen 生成的查询代码
│ ├── redis/ # Redis 数据访问
│ └── schemas/ # 数据模型定义
│
├── config/ # 配置文件
│ ├── config.go # 配置结构定义
│ ├── dev.yaml # 开发环境配置
│ └── prod.yaml # 生产环境配置
│
├── docs/ # API 文档(Swagger)
│ ├── admin/ # 管理后台 API 文档
│ └── mini/ # 客户端 API 文档
│
├── deploy/ # 部署相关文件
│ ├── docker-compose.yaml
│ ├── nginx/
│ ├── mysql/
│ └── ...
│
├── static/ # 静态文件
├── go.mod # Go 模块定义
├── go.sum # 依赖校验
├── go.work # Go 工作区定义(包含主项目和第三方模块)
└── Makefile # 构建脚本
目录说明
cmd/ - 应用入口
每个服务有独立的入口文件,负责:
- 解析命令行参数(配置文件路径)
- 初始化依赖注入容器(Uber FX)
- 注册生命周期钩子(启动/关闭)
- 启动 HTTP 服务器
示例结构:
func main() {
configFile := flag.String("f", "", "配置文件路径")
flag.Parse()
app := fx.New(
fx.Supply(*configFile),
fx.Provide(...), // 提供依赖
fx.Module(...), // 注册模块
fx.Invoke(...), // 调用初始化
)
app.Run()
}
internal/ - 业务逻辑
按业务模块划分(如 admin、mini),每个模块包含:
handler/ - HTTP 请求处理
- 参数绑定和验证
- 调用 Service 层
- 返回响应
service/ - 业务逻辑
- 实现业务规则
- 调用 Repository 层
- 处理事务
router/ - 路由定义
- 注册路由
- 应用中间件
- 分组管理
middleware/ - 中间件
- JWT 认证
- 权限验证
- 其他自定义中间件
types/ - 类型定义
- 请求/响应结构体
- DTO 定义
third_party/ - 三方包管理
Git Submodule 模块:
third_party/provide/- 基础设施提供者模块(Git Submodule)- 模块路径:
liuxiaobo.pro/provide - 独立的
go.mod文件 - 在
go.work中配置,可直接导入使用 - 包含所有基础设施组件封装
- 模块路径:
third_party/utils/- 工具函数模块(Git Submodule)- 模块路径:
liuxiaobo.pro/utils - 独立的
go.mod文件 - 在
go.work中配置,可直接导入使用 - 包含通用工具函数
- 模块路径:
首次克隆项目后初始化 Submodule:
# 初始化并克隆所有 submodule
git submodule update --init --recursive
# 或者克隆项目时同时克隆 submodule
git clone --recurse-submodules <repository-url>
更新 Submodule:
# 更新所有 submodule 到最新提交
git submodule update --remote
# 更新特定 submodule
git submodule update --remote third_party/provide
git submodule update --remote third_party/utils
# 进入 submodule 目录手动更新
cd third_party/provide
git pull origin master
cd ../utils
git pull origin master
导入方式:
// 导入 provide 模块
import (
"liuxiaobo.pro/provide/gin"
"liuxiaobo.pro/provide/gorm"
"liuxiaobo.pro/provide/logger"
"liuxiaobo.pro/provide/jwt"
)
// 导入 utils 模块
import (
"liuxiaobo.pro/utils/response"
"liuxiaobo.pro/utils/crypto"
"liuxiaobo.pro/utils/timee"
)
其他三方包:
- 可以直接
git clone子包到third_party/目录 - 在主项目的
go.mod中使用replace指令引用 - 或使用标准的 Go 模块导入方式
go.work 配置:
go 1.24.10
use (
. // 主项目
./third_party/provide // provide 模块
./third_party/utils // utils 模块
)
规范:
third_party/provide/和third_party/utils/是 Git Submodule,有自己的go.mod和独立仓库- 这两个模块在
go.work中配置,实现本地开发 - 首次克隆项目后需要初始化 submodule
- 更新代码时记得同步更新 submodule
- 使用
liuxiaobo.pro/provide/...和liuxiaobo.pro/utils/...导入路径
repo/ - 数据访问层
- query/ - GORM Gen 生成的查询代码
- redis/ - Redis 数据访问封装
- schemas/ - 数据模型定义(对应数据库表)
架构设计
分层架构
┌─────────────────────────────────────┐
│ HTTP Request │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Router Layer │ 路由定义、中间件
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Handler Layer │ 参数绑定、响应格式化
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Service Layer │ 业务逻辑、事务处理
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Repository Layer │ 数据访问(GORM/Redis)
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Database/Redis │
└──────────────────────────────────────┘
数据流向
请求处理流程:
HTTP Request → Router (路由匹配、中间件) → Handler (参数绑定、验证) → Service (业务逻辑) → Repository (数据访问) → Database/Redis响应返回流程:
Database/Redis → Repository (数据查询) → Service (业务处理、数据转换) → Handler (响应格式化) → Router (HTTP 响应) → HTTP Response
模块化设计
项目支持多个服务模块(如 admin、mini),每个模块:
- 独立的 Handler、Service、Router
- 共享基础设施(provide)
- 共享数据访问层(repo)
- 可独立部署
各层职责
Handler 层
职责:
- 接收 HTTP 请求
- 参数绑定和验证(使用 Gin 的
ShouldBind) - 调用 Service 层
- 处理错误并返回统一响应格式
代码示例:
// handler/handler.go - 基础 Handler
type handler struct{}
func NewHandler() *handler {
return &handler{}
}
func (handler) bindError(c *gin.Context, err error) {
c.JSON(http.StatusBadRequest, response.BindError(err))
}
func (handler) serviceError(c *gin.Context, err error) {
if _, ok := err.(*response.Response); !ok {
c.JSON(http.StatusBadRequest, response.Error(err.Error()))
return
}
c.JSON(http.StatusBadRequest, err)
}
func (handler) success(c *gin.Context, data any) {
c.JSON(http.StatusOK, response.Success(data))
}
// handler/user.go - 具体 Handler
type UserHandler struct {
handler
logger logger.Logger
userService service.UserService
}
func NewUserHandler(handler *handler, log logger.Logger, userService service.UserService) *UserHandler {
return &UserHandler{
handler: *handler,
logger: log,
userService: userService,
}
}
// Detail 获取用户详情
// @Summary 获取用户详情
// @Description 根据用户ID获取用户详细信息
// @Tags 用户
// @Accept json
// @Produce json
// @Param id path string true "用户ID"
// @Success 200 {object} response.Response{data=types.UserDetailResponse} "用户信息"
// @Router /user/{id} [get]
func (h *UserHandler) Detail(c *gin.Context) {
var req types.UserDetailRequest
if err := c.ShouldBindUri(&req); err != nil {
h.bindError(c, err)
h.logger.Error("请求参数错误", zap.Error(err))
return
}
data, err := h.userService.Detail(c, &req)
if err != nil {
h.serviceError(c, err)
h.logger.Error("业务处理错误", zap.Error(err))
return
}
h.success(c, data)
}
规范:
- Handler 继承基础
handler结构体,复用bindError、serviceError、success方法 - Handler 方法接收
*gin.Context作为参数 - 使用
ShouldBindUri、ShouldBindJSON、ShouldBindQuery等绑定参数 - 参数绑定失败调用
bindError,业务错误调用serviceError,成功调用success - 记录关键错误日志
- 添加 Swagger 注释(
@Summary、@Description、@Tags、@Param、@Success、@Router) - 不包含业务逻辑,只做参数处理和响应格式化
Service 层
职责:
- 实现业务逻辑
- 调用 Repository 层进行数据操作
- 处理事务
- 数据转换和验证
代码示例:
// service/service.go - 基础 Service
type service struct {
config *config.Config
}
func NewService(config *config.Config) *service {
return &service{
config: config,
}
}
func (s *service) currentUserID(ctx context.Context) (uint, error) {
userID, ok := ctx.Value(vars.CtxUserIDKey).(uint)
if !ok {
return 0, response.ErrUnauthorized
}
return userID, nil
}
// service/user.go - 具体 Service
type UserService interface {
Detail(ctx context.Context, req *types.UserDetailRequest) (*types.UserDetailResponse, error)
IOSLogin(ctx context.Context, req *types.IOSLoginRequest) (*types.IOSLoginResponse, error)
Update(ctx context.Context, req *types.UserUpdateRequest) (*types.UserUpdateResponse, error)
}
type userService struct {
query *query.Query
logger logger.Logger
config *config.Config
jwt *jwt.JWT
mail *provideMail.Mail
redisRepo *redisrepo.RedisRepo
}
func NewUserService(
query *query.Query,
logger logger.Logger,
config *config.Config,
jwtInstance *jwt.JWT,
redisRepo *redisrepo.RedisRepo,
mailInstance *provideMail.Mail,
) UserService {
return &userService{
query: query,
logger: logger,
config: config,
jwt: jwtInstance,
mail: mailInstance,
redisRepo: redisRepo,
}
}
func (s *userService) Detail(ctx context.Context, req *types.UserDetailRequest) (*types.UserDetailResponse, error) {
user, err := s.query.User.WithContext(ctx).Where(s.query.User.ID.Eq(req.ID)).First()
if err != nil {
s.logger.Error("查询用户失败", zap.Error(err))
return nil, err
}
return new(types.UserDetailResponse).FromUser(user), nil
}
规范:
- 定义接口(Interface)便于测试和扩展
- 方法接收
context.Context作为第一个参数 - 返回业务错误使用
*response.Response(如response.ErrNotFound、response.Error("错误信息")) - 使用 GORM Gen 生成的查询代码进行数据库操作
- 使用
FromXxx方法进行数据模型到响应类型的转换 - 记录关键错误日志
- 不直接操作 HTTP 相关对象
Repository 层
职责:
- 数据访问(数据库、Redis)
- 使用 GORM Gen 生成的查询代码
- 封装复杂查询逻辑
代码示例:
// 使用 GORM Gen 生成的查询
user, err := q.User.WithContext(ctx).
Where(q.User.ID.Eq(userID)).
First()
// Redis 操作
err := r.redisRepo.Set(ctx, key, value, time.Hour)
规范:
- 使用 GORM Gen 生成的代码进行数据库操作
- Redis 操作封装在
repo/redis中 - 不包含业务逻辑
Router 层
职责:
- 定义路由规则
- 应用中间件
- 路由分组(认证/非认证)
代码示例:
func (r *Router) RegisterRoutes(engine *provideGin.Engine) {
// 注册 Prometheus metrics 路由(如果启用)
if r.cfg.Prometheus.Enabled && r.prometheusRegistry != nil {
engine.GET(r.cfg.Prometheus.Path, gin.WrapH(r.prometheusRegistry.Handler()))
engine.Use(r.prometheusMiddleware())
}
// 无需认证的路由
noauth := engine.Group(miniPrefix)
noauth.Use(provideGin.RequestLogMiddleware(r.query))
noauth.Use(provideGin.TraceMiddleware())
noauth.Use(provideGin.LoggerMiddleware(r.logger))
noauth.Use(middleware.JwtMiddleware(r.jwt, true))
{
noauth.Group("/swagger").GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
noauth.Static("/s", r.cfg.ServerMini.StaticPath)
noauth.POST("/user/ios/login", r.userHandler.IOSLogin)
noauth.POST("/user/email/login", r.userHandler.EmailLogin)
noauth.GET("/user/:id", r.userHandler.Detail)
}
// 需要认证的路由
auth := engine.Group(miniPrefix)
auth.Use(provideGin.RequestLogMiddleware(r.query))
auth.Use(provideGin.TraceMiddleware())
auth.Use(provideGin.LoggerMiddleware(r.logger))
auth.Use(middleware.JwtMiddleware(r.jwt, false))
{
auth.PUT("/user", r.userHandler.Update)
auth.GET("/user/:id", r.userHandler.Detail)
}
}
规范:
- 根据配置条件注册路由(如 Prometheus metrics)
- 路由分组:
noauth(无需认证)、auth(需要认证) - 统一应用中间件:请求日志、追踪、日志记录、JWT 认证
- 使用路由前缀常量(如
miniPrefix = "/mini") - 静态文件和 Swagger 文档放在无需认证组
依赖注入
Uber FX 使用
项目使用 Uber FX 进行依赖注入,主要概念:
- fx.Provide - 提供构造函数
- fx.Module - 模块化组织
- fx.Invoke - 调用初始化函数
- fx.Lifecycle - 生命周期管理
依赖注入示例
入口文件(cmd/mini/mini.go):
func main() {
configFile := flag.String("f", "", "配置文件路径")
flag.Parse()
if *configFile == "" {
printBuildInfo()
os.Exit(0)
}
opts, err := buildOptions(*configFile)
if err != nil {
fmt.Printf("构建选项失败: %v\n", err)
os.Exit(1)
}
app := fx.New(opts...)
app.Run()
}
func buildOptions(configFile string) ([]fx.Option, error) {
cfg, err := config.NewConfig(configFile)
if err != nil {
return nil, err
}
opts := []fx.Option{
fx.Supply(configFile),
fx.Provide(
config.NewViper,
config.NewConfig,
provideGin.NewEngine,
gorm.NewDB,
query.NewQuery,
provideredis.NewRedis,
redisrepo.NewRedisRepo,
provideMail.NewMail,
jwt.NewJWT,
providePrometheus.New,
provideOSS.NewOSS,
),
}
// 根据配置条件提供依赖
if cfg.Prometheus.Enabled {
opts = append(opts, fx.Provide(
kafka.New,
logger.NewLoggerWithKafka,
))
} else {
opts = append(opts, fx.Provide(
logger.NewLogger,
))
}
opts = append(opts,
websocket.Module,
service.Module,
handler.Module,
router.Module,
fx.Invoke(registerLifecycle),
)
return opts, nil
}
说明:
- 使用
buildOptions函数根据配置动态构建依赖 - 支持条件依赖注入(如根据
Prometheus.Enabled决定使用哪个 logger) - 先读取配置,再构建依赖选项
模块定义(internal/mini/service/service.go):
var Module = fx.Module("service",
fx.Provide(
NewService,
NewUserService,
NewPaymentService,
NewProblemTopicService,
NewProblemService,
// ... 其他 Service
),
)
模块定义(internal/mini/handler/handler.go):
var Module = fx.Module("handler",
fx.Provide(
NewHandler,
NewUserHandler,
NewPaymentHandler,
NewProblemHandler,
// ... 其他 Handler
),
)
规范:
- 每个模块定义
Module变量,使用fx.Module包装 - 模块名使用小写(如
"service"、"handler") - 在
fx.Provide中注册所有构造函数 - 构造函数命名:
NewXxx(如NewUserService、NewUserHandler)
构造函数(依赖注入):
// Service 构造函数
func NewUserService(
query *query.Query,
logger logger.Logger,
config *config.Config,
jwtInstance *jwt.JWT,
redisRepo *redisrepo.RedisRepo,
mailInstance *provideMail.Mail,
) UserService {
return &userService{
query: query,
logger: logger,
config: config,
jwt: jwtInstance,
redisRepo: redisRepo,
mail: mailInstance,
}
}
// Handler 构造函数
func NewUserHandler(
handler *handler,
log logger.Logger,
userService service.UserService,
) *UserHandler {
return &UserHandler{
handler: *handler,
logger: log,
userService: userService,
}
}
// Router 构造函数
func NewRouter(
cfg *config.Config,
jwtInstance *jwt.JWT,
prometheusRegistry *providePrometheus.Registry,
logger logger.Logger,
query *query.Query,
userHandler *handler.UserHandler,
// ... 其他 Handler
) *Router {
return &Router{
cfg: cfg,
jwt: jwtInstance,
prometheusRegistry: prometheusRegistry,
logger: logger,
query: query,
userHandler: userHandler,
// ...
}
}
规范:
- 构造函数接收依赖作为参数,返回接口或具体类型
- 参数顺序:基础设施(query、logger、config)→ 业务依赖(service、handler)
- 使用有意义的参数名(如
jwtInstance、mailInstance) - 返回接口类型便于测试和扩展
生命周期管理
使用 fx.Lifecycle 管理服务启动和关闭:
func registerLifecycle(
lc fx.Lifecycle,
cfg *config.Config,
log logger.Logger,
engine *provideGin.Engine,
db *gorm.DB,
router *router.Router,
) {
addr := fmt.Sprintf("%s:%d", cfg.ServerMini.Host, cfg.ServerMini.Port)
srv := &http.Server{
Addr: addr,
Handler: engine.Engine,
ReadTimeout: cfg.ServerMini.ReadTimeout,
WriteTimeout: cfg.ServerMini.WriteTimeout,
MaxHeaderBytes: cfg.ServerMini.MaxHeaderBytes,
}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
log.Info("HTTP 服务器启动", zap.String("addr", addr))
router.RegisterRoutes(engine)
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("HTTP 服务器启动失败", zap.Error(err))
}
}()
go printBuildInfo()
return nil
},
OnStop: func(ctx context.Context) error {
log.Info("正在关闭 HTTP 服务器...")
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Error("HTTP 服务器关闭失败", zap.Error(err))
return err
}
log.Info("HTTP 服务器已关闭")
log.Info("正在关闭数据库连接...")
if err := db.Close(); err != nil {
log.Error("数据库连接关闭失败", zap.Error(err))
return err
}
log.Info("数据库连接已关闭")
return nil
},
})
}
规范:
OnStart中注册路由,启动 HTTP 服务器(使用 goroutine)OnStop中优雅关闭 HTTP 服务器和数据库连接- 使用超时上下文避免关闭时间过长
- 记录启动和关闭日志
开发规范
命名规范
包名: 小写,单数形式
- ✅
handler,service,router - ❌
handlers,services
- ✅
接口: 大驼峰,通常以功能名 + Service/Handler 结尾
- ✅
UserService,ProblemHandler
- ✅
实现: 小驼峰,接口名去掉首字母大写
- ✅
userService,problemHandler
- ✅
文件命名: 小写,下划线分隔
- ✅
user_handler.go,problem_service.go
- ✅
错误处理
统一错误响应: 使用
utils/response包// 成功响应 response.Success(data) // 错误响应 response.Error("错误信息") response.ErrorWithCode(code, "错误信息") response.BindError(err) // 参数绑定错误 // 预定义错误 response.ErrNotFound // 资源不存在 response.ErrInternal // 内部错误 response.ErrUnauthorized // 授权已过期 response.ErrForbidden // 权限不足 response.ErrTooManyRequests // 操作频繁业务错误: Service 层返回
*response.Responseif user == nil { return nil, response.ErrNotFound } if err != nil { return nil, response.Error("操作失败") }错误传播: Handler 层统一处理
if err != nil { h.serviceError(c, err) h.logger.Error("业务处理错误", zap.Error(err)) return }响应格式:
type Response struct { Code int `json:"code"` // 0: 成功, 其他: 错误码 Data any `json:"data"` // 响应数据 Msg string `json:"msg"` // 响应消息 }
代码组织
模块化: 每个业务模块独立目录
internal/mini/ ├── handler/ │ ├── user.go │ └── problem.go ├── service/ │ ├── user.go │ └── problem.go └── types/ └── types.go类型定义: 统一放在
types/目录// types/types.go // 请求类型:以 Request 结尾 type UserDetailRequest struct { ID uint `uri:"id" binding:"required"` // 用户ID } type UserUpdateRequest struct { Nickname *string `json:"nickname"` // 昵称(可选) Email *string `json:"email"` // 邮箱(可选) } // 响应类型:以 Response 结尾 type UserDetailResponse struct { ID uint `json:"id"` // 用户ID Nickname string `json:"nickname"` // 昵称 Avatar string `json:"avatar"` // 头像 Phone string `json:"phone"` // 手机号 OnlineTime int64 `json:"online_time"` // 在线时长(秒) } // 数据转换方法:FromXxx func (u *UserDetailResponse) FromUser(user *schemas.User) *UserDetailResponse { return &UserDetailResponse{ ID: user.ID, Nickname: user.Nickname, Avatar: user.Avatar, Phone: user.Phone, OnlineTime: user.OnlineTime, } }规范:
- 请求类型以
Request结尾,响应类型以Response结尾 - 使用 Gin 标签:
uri(路径参数)、json(JSON 体)、form(查询参数) - 使用
binding:"required"标记必填字段 - 可选字段使用指针类型(
*string、*int) - 提供
FromXxx方法将数据库模型转换为响应类型 - 字段添加 JSON 注释说明用途
- 请求类型以
常量定义: 模块常量放在
vars/目录// internal/mini/vars/vars.go const CtxUserIDKey = "user_id"
中间件使用
全局中间件: 在
provide/gin中注册- CORS
- Recovery
- Logger
- Trace
路由级中间件: 在 Router 中注册
// 请求日志中间件 noauth.Use(provideGin.RequestLogMiddleware(r.query)) // 追踪中间件(生成 TraceID) noauth.Use(provideGin.TraceMiddleware()) // 日志中间件 noauth.Use(provideGin.LoggerMiddleware(r.logger)) // JWT 认证中间件 noauth.Use(middleware.JwtMiddleware(r.jwt, true)) // true: 不强制认证 auth.Use(middleware.JwtMiddleware(r.jwt, false)) // false: 强制认证自定义中间件: 在
internal/{module}/middleware/中定义// middleware/jwt_middleware.go func JwtMiddleware(jwtInstance *jwt.JWT, force bool) gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") token = strings.TrimPrefix(token, "Bearer ") if token == "" { if force { c.Next() return } c.AbortWithStatusJSON(http.StatusUnauthorized, response.ErrUnauthorized) return } claims, err := jwtInstance.ParseToken(token) if err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, response.ErrUnauthorized) return } c.Set(vars.CtxUserIDKey, claims.UserID) c.Next() } }规范:
- 中间件函数返回
gin.HandlerFunc - 认证失败使用
c.AbortWithStatusJSON终止请求 - 将用户信息存储到 Context:
c.Set(vars.CtxUserIDKey, userID) - Service 层通过
ctx.Value(vars.CtxUserIDKey)获取用户信息
- 中间件函数返回
配置管理
- 配置文件: 使用 YAML 格式,放在
config/目录 - 配置结构: 在
config/config.go中定义 - 环境变量: 支持通过环境变量覆盖配置(使用 Viper)
三方包管理
- Git Submodule:
third_party/provide/和third_party/utils/是 Git Submodule - 首次克隆: 运行
git submodule update --init --recursive初始化 submodule - 更新 Submodule: 运行
git submodule update --remote更新到最新版本 - 导入路径: 使用
liuxiaobo.pro/provide/...和liuxiaobo.pro/utils/... - 其他三方包: 可直接
git clone到third_party/目录 - 工作区: 使用
go.work管理多模块工作区
快速开始
首次克隆项目
# 方式一:克隆时同时初始化 submodule(推荐)
git clone --recurse-submodules <repository-url>
cd your_project
# 方式二:先克隆,再初始化 submodule
git clone <repository-url>
cd your_project
git submodule update --init --recursive
更新项目代码
# 更新主项目代码
git pull
# 更新所有 submodule 到最新提交
git submodule update --remote
# 或者更新特定 submodule
git submodule update --remote third_party/provide
git submodule update --remote third_party/utils
运行项目
# 运行 mini 服务
go run cmd/mini/mini.go -f config/dev.yaml
# 运行 admin 服务
go run cmd/admin/admin.go -f config/dev.yaml
新建项目指南
1. 克隆项目
# 克隆项目(包含 submodule)
git clone --recurse-submodules <repository-url>
cd your_project
# 或者先克隆,再初始化 submodule
git clone <repository-url>
cd your_project
git submodule update --init --recursive
2. 初始化项目结构(新建项目时)
mkdir new_project
cd new_project
go mod init your_module_name
3. 创建目录结构
your_project/
├── cmd/
│ └── server/
│ └── server.go
├── internal/
│ └── api/
│ ├── handler/
│ ├── service/
│ ├── router/
│ ├── middleware/
│ └── types/
├── provide/
│ ├── gin/
│ ├── gorm/
│ ├── logger/
│ └── vars/
├── repo/
│ ├── query/
│ └── schemas/
├── utils/
│ └── response/
├── config/
└── go.mod
4. 安装核心依赖
go get github.com/gin-gonic/gin
go get go.uber.org/fx
go get go.uber.org/zap
go get github.com/spf13/viper
go get gorm.io/gorm
go get gorm.io/driver/mysql
go get github.com/redis/go-redis/v9
5. 创建入口文件
参考 cmd/mini/mini.go,创建:
- 命令行参数解析
- FX 依赖注入配置
- 生命周期管理
6. 创建基础设施
方式一:使用现有模块(推荐)
项目已提供 third_party/provide/ 和 third_party/utils/ 模块,直接导入使用:
import (
"liuxiaobo.pro/provide/gin"
"liuxiaobo.pro/provide/gorm"
"liuxiaobo.pro/provide/logger"
"liuxiaobo.pro/utils/response"
)
方式二:创建新的基础设施模块
如果需要创建新的基础设施模块:
- 在
third_party/下创建新目录 - 初始化
go.mod:cd third_party/new_module go mod init liuxiaobo.pro/new_module - 在
go.work中添加:use ( . ./third_party/provide ./third_party/utils ./third_party/new_module // 新增模块 ) - 在主项目中导入使用
参考现有模块:
third_party/provide/gin/- Gin 引擎封装third_party/provide/gorm/- 数据库连接third_party/provide/logger/- 日志封装third_party/provide/vars/- 常量定义third_party/utils/response/- 统一响应格式
7. 创建业务模块
参考 internal/mini/,创建:
handler/- HTTP 处理器service/- 业务逻辑router/- 路由定义types/- 类型定义
8. 配置 GORM Gen
参考 cmd/gorm_gen/gorm_gen.go,配置代码生成:
- 定义数据模型(
repo/schemas/) - 生成查询代码(
repo/query/)
9. 配置文件
创建 config/dev.yaml 和 config/prod.yaml,定义:
- 服务器配置
- 数据库配置
- Redis 配置
- 其他配置项
10. 添加 Swagger 文档
go install github.com/swaggo/swag/cmd/swag@latest
swag init -g cmd/server/server.go
11. 测试运行
go run cmd/server/server.go -f config/dev.yaml
Makefile 使用指南
项目提供了 Makefile 来简化常用操作,包括构建、格式化、文档生成等。
Makefile 示例
以下是项目使用的 Makefile 示例:
CommitSHA ?= $(shell git rev-parse --short HEAD)
BuildTime ?= $(shell powershell -Command "[TimeZoneInfo]::ConvertTimeBySystemTimeZoneId([DateTime]::Now, 'China Standard Time').ToString('yyyy-MM-dd HH:mm:sszzz')" 2>/dev/null || TZ=Asia/Shanghai date '+%Y-%m-%d %H:%M:%S%z')
EXE_SUFFIX := $(if $(filter Windows_NT,$(OS)),.exe,)
a: build-mini
b: build-admin
c: build-mcp
tidy:
@echo "tidy dependencies..."
@go mod tidy
nil:
@echo "check nilpointer..."
nilaway ./...
fmt:
@echo "format code..."
@swag fmt -d .
@gofumpt -l -w .
@go fmt ./...
swag-mini:
@echo "generate swagger docs..."
@swag fmt -d .
@swag init -parseDependency --parseInternal -g ../../cmd/mini/mini.go -d internal/mini -o docs/mini
swag-admin:
@echo "generate swagger docs..."
@swag fmt -d .
@swag init -parseDependency --parseInternal -g ../../cmd/admin/admin.go -d internal/admin -o docs/admin
build-mini: tidy swag-mini
@echo "building mini server..."
@go build -ldflags "-s -w -X 'main.CommitSHA=$(CommitSHA)' -X 'main.BuildTime=$(BuildTime)'" -o bin/mini$(EXE_SUFFIX) cmd/mini/mini.go
@echo "mini server built successfully"
build-admin: tidy swag-admin
@echo "building admin server..."
@go build -ldflags "-s -w -X 'main.CommitSHA=$(CommitSHA)' -X 'main.BuildTime=$(BuildTime)'" -o bin/admin$(EXE_SUFFIX) cmd/admin/admin.go
@echo "admin server built successfully"
build-mcp: tidy
@echo "building mcp server..."
@go build -ldflags "-s -w -X 'main.CommitSHA=$(CommitSHA)' -X 'main.BuildTime=$(BuildTime)'" -o bin/mcp$(EXE_SUFFIX) cmd/mcp/mcp.go
@echo "mcp server built successfully"
init-submodule:
@echo "init submodule..."
@git submodule update --init --recursive
update-submodule:
@echo "update submodule..."
@git submodule update --remote
说明:
CommitSHA和BuildTime变量用于注入版本信息EXE_SUFFIX根据操作系统自动添加.exe后缀(Windows)- 快捷命令
a、b、c对应不同的服务构建 - 构建时使用
-ldflags注入版本信息到二进制文件
构建命令
# 构建服务(xxx 为 cmd 目录下的服务名称,如 mini、admin、mcp)
# 包含依赖整理和 Swagger 文档生成(如果支持)
make build-xxx
# 或使用快捷命令(a、b、c 等)
make a
说明:
- 构建时会自动整理依赖(
go mod tidy) - 支持 Swagger 的服务构建前会自动生成 Swagger 文档
- 构建产物输出到
bin/目录 - 构建时会注入版本信息(CommitSHA、BuildTime)
代码格式化
# 格式化代码(包括 Swagger 注释和 Go 代码)
make fmt
说明:
- 格式化 Swagger 注释(
swag fmt) - 使用
gofumpt格式化 Go 代码 - 使用
go fmt格式化所有 Go 文件
Swagger 文档生成
# 生成服务的 Swagger 文档(xxx 为 cmd 目录下的服务名称)
make swag-xxx
说明:
- 文档输出到
docs/xxx/目录 - 会自动解析依赖和内部包
依赖管理
# 整理 Go 模块依赖
make tidy
说明:
- 运行
go mod tidy整理依赖 - 移除未使用的依赖,添加缺失的依赖
代码检查
# 检查 nil 指针问题(需要安装 nilaway)
make nil
说明:
- 使用
nilaway工具检查潜在的 nil 指针问题 - 需要先安装:
go install go.uber.org/nilaway/cmd/nilaway@latest
Submodule 管理
# 初始化 submodule(首次克隆项目后使用)
make init-submodule
# 更新 submodule 到最新版本
make update-submodule
说明:
init-submodule等同于git submodule update --init --recursiveupdate-submodule等同于git submodule update --remote
完整命令列表
| 命令 | 说明 |
|---|---|
make a/b/c | 构建服务(快捷命令,对应不同服务) |
make build-xxx | 构建服务(xxx 为 cmd 目录下的服务名称) |
make fmt | 格式化代码 |
make swag-xxx | 生成服务的 Swagger 文档 |
make tidy | 整理依赖 |
make nil | 检查 nil 指针问题 |
make init-submodule | 初始化 submodule |
make update-submodule | 更新 submodule |
使用示例
首次克隆项目后的完整流程:
# 1. 初始化 submodule
make init-submodule
# 2. 整理依赖
make tidy
# 3. 构建服务(xxx 为服务名称)
make build-xxx
# 4. 运行服务
./bin/xxx -f config/dev.yaml
日常开发流程:
# 1. 更新代码和 submodule
git pull
make update-submodule
# 2. 格式化代码
make fmt
# 3. 生成 Swagger 文档(如果修改了 API)
make swag-xxx
# 4. 构建并测试
make build-xxx
./bin/xxx -f config/dev.yaml
最佳实践
1. 依赖注入
- ✅ 使用 FX 进行依赖注入,避免全局变量
- ✅ 构造函数接收依赖作为参数
- ✅ 使用接口定义 Service,便于测试
2. 错误处理
- ✅ 统一错误响应格式
- ✅ Service 层返回业务错误
- ✅ Handler 层统一处理错误
3. 日志记录
- ✅ 使用结构化日志(Zap)
- ✅ 记录关键操作和错误
- ✅ 使用 Context 传递 TraceID
4. 配置管理
- ✅ 使用 Viper 管理配置
- ✅ 支持环境变量覆盖
- ✅ 配置验证和默认值
5. 代码生成
- ✅ 使用 GORM Gen 生成数据库查询代码
- ✅ 定期更新生成的代码
- ✅ 不要手动修改生成的代码
6. 测试
- ✅ 为 Service 层编写单元测试
- ✅ 使用 Mock 进行依赖隔离
- ✅ 集成测试覆盖关键流程
7. 代码格式
- ✅ 使用
gofmt格式化代码 - ✅ 遵循 Go 官方代码规范
- ✅ 使用有意义的变量名和函数名
- ✅ 添加必要的注释(特别是公共 API)
8. 类型转换
- ✅ 在
types/中定义响应类型 - ✅ 提供
FromXxx方法将数据库模型转换为响应类型 - ✅ 避免在 Handler 或 Service 中直接使用数据库模型
示例:
// types/types.go
func (u *UserDetailResponse) FromUser(user *schemas.User) *UserDetailResponse {
return &UserDetailResponse{
ID: user.ID,
Nickname: user.Nickname,
Avatar: user.Avatar,
Phone: user.Phone,
OnlineTime: user.OnlineTime,
}
}
// service/user.go
func (s *userService) Detail(ctx context.Context, req *types.UserDetailRequest) (*types.UserDetailResponse, error) {
user, err := s.query.User.WithContext(ctx).Where(s.query.User.ID.Eq(req.ID)).First()
if err != nil {
return nil, err
}
return new(types.UserDetailResponse).FromUser(user), nil
}
总结
本架构文档提供了项目的完整设计指南,包括:
- 📁 清晰的项目结构 - 分层明确,职责单一
- 🔧 依赖注入模式 - 使用 Uber FX 管理依赖
- 🏗️ 模块化设计 - 支持多服务、易扩展
- 📝 开发规范 - 统一的代码风格和最佳实践
- 🚀 快速启动 - 新建项目指南
遵循本架构设计,可以确保项目的一致性、可维护性和可扩展性。
文档版本: v1.1
最后更新: 2024
AI 开发指南
代码生成规范
当使用 AI 辅助开发时,请严格遵循以下规范:
项目结构
- 所有业务代码放在
internal/{module}/下 - Handler 放在
handler/,Service 放在service/,Router 放在router/ - 类型定义统一放在
types/types.go - 中间件放在
middleware/
- 所有业务代码放在
命名规范
- 包名:小写单数(
handler、service) - 接口:大驼峰(
UserService、ProblemHandler) - 实现:小驼峰(
userService、problemHandler) - 文件:小写下划线(
user_handler.go、problem_service.go)
- 包名:小写单数(
依赖注入
- 使用
fx.Module定义模块 - 构造函数命名:
NewXxx - 在
buildOptions中根据配置条件提供依赖
- 使用
三方包导入
- 使用
liuxiaobo.pro/provide/...导入基础设施模块 - 使用
liuxiaobo.pro/utils/...导入工具模块 - 不要使用相对路径导入
third_party/下的模块
- 使用
代码结构
- Handler:继承基础
handler,使用bindError、serviceError、success - Service:定义接口,实现业务逻辑,返回
*response.Response错误 - Types:请求以
Request结尾,响应以Response结尾,提供FromXxx方法
- Handler:继承基础
错误处理
- 使用
response.Success(data)返回成功 - 使用
response.Error("错误信息")或预定义错误(response.ErrNotFound) - Handler 层统一处理错误
- 使用
Swagger 注释
- 为每个 Handler 方法添加 Swagger 注释
- 包含
@Summary、@Description、@Tags、@Param、@Success、@Router
日志记录
- 使用结构化日志(Zap)
- 记录关键操作和错误
- 使用
zap.Error(err)记录错误
三方包管理
third_party/provide/和third_party/utils/是 Git Submodule- 首次克隆后运行
git submodule update --init --recursive - 更新时运行
git submodule update --remote - 使用
liuxiaobo.pro/provide/...和liuxiaobo.pro/utils/...导入 - 其他三方包可直接添加到
third_party/目录
遵循以上规范,确保代码风格统一、结构清晰、易于维护。