Published on

golang项目架构文档

Authors
  • avatar
    Name
    刘十三
    Twitter

项目架构文档

本文档描述了项目的整体架构设计、目录结构、开发规范以及最佳实践,用于指导新项目的创建和现有项目的重构。

目录


项目概述

本项目采用 分层架构依赖注入 模式,使用 Uber FX 进行依赖管理,基于 Gin 框架构建 RESTful API 服务。

核心特性

  • ✅ 清晰的分层架构(Handler → Service → Repository)
  • ✅ 基于 Uber FX 的依赖注入
  • ✅ 模块化设计,支持多服务(mini、admin)
  • ✅ 统一的错误处理和响应格式
  • ✅ 完整的中间件支持(JWT、日志、追踪、CORS等)
  • ✅ 代码生成(GORM Gen)
  • ✅ Swagger API 文档
  • ✅ 可观测性支持(Prometheus、日志)

技术栈

核心框架

技术版本用途
Go1.24.1+编程语言
Ginv1.11.0Web 框架
GORMv1.25.11ORM 框架
Uber FXv1.24.0依赖注入框架
Viperv1.21.0配置管理
Zapv1.27.0日志框架

数据存储

技术版本用途
MySQL-关系型数据库
Redisv9.16.0缓存/会话存储

其他工具

技术用途
GORM Gen代码生成
SwaggerAPI 文档
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/ - 业务逻辑

按业务模块划分(如 adminmini),每个模块包含:

  • handler/ - HTTP 请求处理

    • 参数绑定和验证
    • 调用 Service 层
    • 返回响应
  • service/ - 业务逻辑

    • 实现业务规则
    • 调用 Repository 层
    • 处理事务
  • router/ - 路由定义

    • 注册路由
    • 应用中间件
    • 分组管理
  • middleware/ - 中间件

    • JWT 认证
    • 权限验证
    • 其他自定义中间件
  • types/ - 类型定义

    • 请求/响应结构体
    • DTO 定义

third_party/ - 三方包管理

Git Submodule 模块:

  1. third_party/provide/ - 基础设施提供者模块(Git Submodule)

    • 模块路径:liuxiaobo.pro/provide
    • 独立的 go.mod 文件
    • go.work 中配置,可直接导入使用
    • 包含所有基础设施组件封装
  2. 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└──────────────────────────────────────┘

数据流向

  1. 请求处理流程:

    HTTP Request
    Router (路由匹配、中间件)
    Handler (参数绑定、验证)
    Service (业务逻辑)
    Repository (数据访问)
    Database/Redis
    
  2. 响应返回流程:

    Database/Redis
    Repository (数据查询)
    Service (业务处理、数据转换)
    Handler (响应格式化)
    Router (HTTP 响应)
    HTTP Response
    

模块化设计

项目支持多个服务模块(如 adminmini),每个模块:

  • 独立的 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 结构体,复用 bindErrorserviceErrorsuccess 方法
  • Handler 方法接收 *gin.Context 作为参数
  • 使用 ShouldBindUriShouldBindJSONShouldBindQuery 等绑定参数
  • 参数绑定失败调用 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.ErrNotFoundresponse.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 进行依赖注入,主要概念:

  1. fx.Provide - 提供构造函数
  2. fx.Module - 模块化组织
  3. fx.Invoke - 调用初始化函数
  4. 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(如 NewUserServiceNewUserHandler

构造函数(依赖注入):

// 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)
  • 使用有意义的参数名(如 jwtInstancemailInstance
  • 返回接口类型便于测试和扩展

生命周期管理

使用 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 服务器和数据库连接
  • 使用超时上下文避免关闭时间过长
  • 记录启动和关闭日志

开发规范

命名规范

  1. 包名: 小写,单数形式

    • handler, service, router
    • handlers, services
  2. 接口: 大驼峰,通常以功能名 + Service/Handler 结尾

    • UserService, ProblemHandler
  3. 实现: 小驼峰,接口名去掉首字母大写

    • userService, problemHandler
  4. 文件命名: 小写,下划线分隔

    • user_handler.go, problem_service.go

错误处理

  1. 统一错误响应: 使用 utils/response

    // 成功响应
    response.Success(data)
    
    // 错误响应
    response.Error("错误信息")
    response.ErrorWithCode(code, "错误信息")
    response.BindError(err)  // 参数绑定错误
    
    // 预定义错误
    response.ErrNotFound        // 资源不存在
    response.ErrInternal        // 内部错误
    response.ErrUnauthorized    // 授权已过期
    response.ErrForbidden       // 权限不足
    response.ErrTooManyRequests // 操作频繁
    
  2. 业务错误: Service 层返回 *response.Response

    if user == nil {
        return nil, response.ErrNotFound
    }
    
    if err != nil {
        return nil, response.Error("操作失败")
    }
    
  3. 错误传播: Handler 层统一处理

    if err != nil {
        h.serviceError(c, err)
        h.logger.Error("业务处理错误", zap.Error(err))
        return
    }
    
  4. 响应格式:

    type Response struct {
        Code int    `json:"code"` // 0: 成功, 其他: 错误码
        Data any    `json:"data"` // 响应数据
        Msg  string `json:"msg"`  // 响应消息
    }
    

代码组织

  1. 模块化: 每个业务模块独立目录

    internal/mini/
    ├── handler/
    │   ├── user.go
    │   └── problem.go
    ├── service/
    │   ├── user.go
    │   └── problem.go
    └── types/
        └── types.go
    
  2. 类型定义: 统一放在 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 注释说明用途
  3. 常量定义: 模块常量放在 vars/ 目录

    // internal/mini/vars/vars.go
    const CtxUserIDKey = "user_id"
    

中间件使用

  1. 全局中间件:provide/gin 中注册

    • CORS
    • Recovery
    • Logger
    • Trace
  2. 路由级中间件: 在 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: 强制认证
    
  3. 自定义中间件: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) 获取用户信息

配置管理

  1. 配置文件: 使用 YAML 格式,放在 config/ 目录
  2. 配置结构:config/config.go 中定义
  3. 环境变量: 支持通过环境变量覆盖配置(使用 Viper)

三方包管理

  1. Git Submodule: third_party/provide/third_party/utils/ 是 Git Submodule
  2. 首次克隆: 运行 git submodule update --init --recursive 初始化 submodule
  3. 更新 Submodule: 运行 git submodule update --remote 更新到最新版本
  4. 导入路径: 使用 liuxiaobo.pro/provide/...liuxiaobo.pro/utils/...
  5. 其他三方包: 可直接 git clonethird_party/ 目录
  6. 工作区: 使用 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"
)

方式二:创建新的基础设施模块

如果需要创建新的基础设施模块:

  1. third_party/ 下创建新目录
  2. 初始化 go.mod
    cd third_party/new_module
    go mod init liuxiaobo.pro/new_module
    
  3. go.work 中添加:
    use (
        .
        ./third_party/provide
        ./third_party/utils
        ./third_party/new_module  // 新增模块
    )
    
  4. 在主项目中导入使用

参考现有模块:

  • 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.yamlconfig/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

说明:

  • CommitSHABuildTime 变量用于注入版本信息
  • EXE_SUFFIX 根据操作系统自动添加 .exe 后缀(Windows)
  • 快捷命令 abc 对应不同的服务构建
  • 构建时使用 -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 --recursive
  • update-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 辅助开发时,请严格遵循以下规范:

  1. 项目结构

    • 所有业务代码放在 internal/{module}/
    • Handler 放在 handler/,Service 放在 service/,Router 放在 router/
    • 类型定义统一放在 types/types.go
    • 中间件放在 middleware/
  2. 命名规范

    • 包名:小写单数(handlerservice
    • 接口:大驼峰(UserServiceProblemHandler
    • 实现:小驼峰(userServiceproblemHandler
    • 文件:小写下划线(user_handler.goproblem_service.go
  3. 依赖注入

    • 使用 fx.Module 定义模块
    • 构造函数命名:NewXxx
    • buildOptions 中根据配置条件提供依赖
  4. 三方包导入

    • 使用 liuxiaobo.pro/provide/... 导入基础设施模块
    • 使用 liuxiaobo.pro/utils/... 导入工具模块
    • 不要使用相对路径导入 third_party/ 下的模块
  5. 代码结构

    • Handler:继承基础 handler,使用 bindErrorserviceErrorsuccess
    • Service:定义接口,实现业务逻辑,返回 *response.Response 错误
    • Types:请求以 Request 结尾,响应以 Response 结尾,提供 FromXxx 方法
  6. 错误处理

    • 使用 response.Success(data) 返回成功
    • 使用 response.Error("错误信息") 或预定义错误(response.ErrNotFound
    • Handler 层统一处理错误
  7. Swagger 注释

    • 为每个 Handler 方法添加 Swagger 注释
    • 包含 @Summary@Description@Tags@Param@Success@Router
  8. 日志记录

    • 使用结构化日志(Zap)
    • 记录关键操作和错误
    • 使用 zap.Error(err) 记录错误
  9. 三方包管理

    • 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/ 目录

遵循以上规范,确保代码风格统一、结构清晰、易于维护。