# Go API 开发规范

## 1. 概述

### 1.1 目的
本文档旨在为 `bindbox-game` 项目制定一套统一、清晰的 Go API 开发规范。通过标准化代码结构、命名约定、错误处理和文档注释，我们致力于提高代码的可读性、可维护性和团队协作效率，并从根本上避免之前开发中遇到的问题（如包导入不一致、特殊字符使用不当等）。

### 1.2 适用范围
本规范主要适用于项目 `internal/api/` 目录下的所有 API 层的开发工作。所有新的 API 模块以及对现有模块的修改，都必须严格遵守本规范。

### 1.3 核心原则
*   **一致性**：所有代码都应遵循相同的模式和风格。
*   **清晰性**：代码应易于阅读和理解，避免复杂的或晦涩的实现。
*   **可维护性**：代码结构应清晰，便于未来扩展和重构。
*   **api/service**：代码结构应清晰，api 和 service 应该保持一致的命名规范。必须: 一个 api 对应一个 service。一个函数功能对应一个 service 方法。单独一个文件
*   **接口文档注释**：所有 API 端点都必须包含详细的 Swagger 注释，描述请求参数、响应体、可能的错误码等。
*   **测试功能**：所有 API 端点都必须包含详细的测试用例，确保功能的正确性和稳定性。(cmd/testchain/ 中写)

---

## 2. 目录与文件结构

规范的目录结构是项目可维护性的基础。

*   **模块化目录**：每一个独立的业务功能模块都应在 `internal/api/` 下创建一个专属目录。
    ```
    internal/api/
    ├── admin/
    ├── activity/
    └── ... (其他模块)
    ```

*   **Handler 结构体定义**：在每个模块目录下，应有一个与模块同名的 `go` 文件（如 `admin.go`），用于定义该模块的 `handler` 结构体和 `New` 初始化函数（注：Handler 不直接进行数据库读写）。
    ```go
    // internal/api/admin/admin.go
    package admin

    type handler struct {
        logger  logger.CustomLogger
        writeDB *dao.Query   // 仅用于注入（避免在 Handler 直接使用）
        readDB  *dao.Query   // 仅用于注入（避免在 Handler 直接使用）
        svc     adminsvc.Service
    }

    func New(logger logger.CustomLogger, db mysql.Repo) *handler {
        return &handler{
            logger:  logger,
            writeDB: dao.Use(db.GetDbW()),
            readDB:  dao.Use(db.GetDbR()),
            svc:     adminsvc.New(logger, db),
        }
    }
    ```

*   **端点文件分离**：每个 API 端点（Endpoint）的实现应放在一个独立的 `go` 文件中，文件名应清晰描述其功能，示例对齐当前 `admin` 模块：
    ```
    internal/api/admin/
    ├── admin.go           // Handler 定义
    ├── login.go           // 管理员登录
    ├── admin_create.go    // 新增客服
    ├── admin_modify.go    // 编辑客服（含路径参数）
    ├── admin_delete.go    // 删除客服（批量）
    └── admin_list.go      // 客服列表（分页查询）
    ```

---

## 3. 命名规范

统一的命名规范能极大提升代码的可读性。

*   **Go 文件**：使用小写蛇形命名法 (`snake_case`)，如 `admin_create.go`。
*   **Handler 函数**：使用大驼峰命名法 (`PascalCase`)，且函数名应清晰表达其动作，如 `CreateAdmin()`。
*   **请求/响应结构体**：使用小驼峰命名法 (`camelCase`)，并以 `Request`/`Response` 作为后缀，如 `createAdminRequest`, `createAdminResponse`。

---

## 4. API Handler 规范

API Handler 是业务逻辑的入口，其规范性至关重要。

### 4.1 函数签名
所有 API Handler 函数都必须返回 `core.HandlerFunc` 类型。这是框架的核心设计，用于统一处理请求上下文。

```go
// CreateAdmin 新增客服
func (h *handler) CreateAdmin() core.HandlerFunc {
    return func(ctx core.Context) {
        // ... 实现
    }
}
```

### 4.2 参数绑定与校验
*   **绑定（JSON）**：POST/PUT 等带请求体的接口使用 `ctx.ShouldBindJSON(req)` 绑定到预定义的 `Request` 结构体。
*   **绑定（Query）**：GET 查询接口使用 `ctx.ShouldBindForm(req)` 绑定到预定义的 `Request` 结构体（字段使用 `form:"..."` tag）。
*   **路径参数**：通过 `ctx.Param("id")` 等读取路径参数并进行类型转换与校验。
*   **校验**：绑定方法会自动执行 `binding` tag 定义的校验规则。如果校验失败，必须立即中断请求并返回错误。

```go
req := new(createAdminRequest)
res := new(createAdminResponse)
if err := ctx.ShouldBindJSON(req); err != nil {
    ctx.AbortWithError(core.Error(
        http.StatusBadRequest,
        code.ParamBindError,
        validation.Error(err)),
    )
    return
}
```

```go
// GET 查询绑定示例（与 admin_list 对齐）
req := new(listRequest)
res := new(listResponse)
if err := ctx.ShouldBindForm(req); err != nil {
    ctx.AbortWithError(core.Error(
        http.StatusBadRequest,
        code.ParamBindError,
        validation.Error(err)),
    )
    return
}
```

```go
// 路径参数读取示例（与 admin_modify 对齐）
id, err := strconv.Atoi(ctx.Param("id"))
if err != nil {
    ctx.AbortWithError(core.Error(
        http.StatusBadRequest,
        code.ParamBindError,
        "未传递编号ID"),
    )
    return
}
```

### 4.3 响应处理
*   **成功响应**：当业务逻辑成功执行后，必须使用 `ctx.Payload(res)` 方法来返回标准的成功响应。`res` 是预定义的 `Response` 结构体实例。
    ```go
    res.Message = "操作成功"
    ctx.Payload(res)
    ```
*   **错误响应**：见第 6 章“错误处理规范”。

---

## 5. 数据传输对象 (DTO) 规范

DTO (Data Transfer Object) 是 API 契约的直接体现。

*   **结构体定义**：每个 API 都应定义清晰的 `Request` 和 `Response` 结构体。
*   **字段注释**：所有结构体字段都必须有清晰的中文注释，解释其含义和用途。
*   **JSON Tag**：所有字段都必须包含 `json:"..."` tag，以确保与前端交互的 JSON 字段名一致。
*   **Validation Tag**：对于 `Request` 结构体中需要校验的字段，必须添加 `binding:"..."` tag 来定义校验规则（如 `required`）。

```go
// internal/api/admin/admin_create.go

type createAdminRequest struct {
	UserName string `json:"username" binding:"required"` // 用户名
	NickName string `json:"nickname" binding:"required"` // 昵称
	Password string `json:"password" binding:"required"` // 密码
    Mobile   string `json:"mobile"`                      // 手机号 (非必填)
}

type createAdminResponse struct {
    Message string `json:"message"` // 提示信息
}
```

```go
// internal/api/admin/admin_list.go
// GET 查询示例（字段使用 form 标签）
type listRequest struct {
    Username string `form:"username"`  // 用户名
    Nickname string `form:"nickname"`  // 昵称
    Page     int    `form:"page"`      // 当前页码，默认 1
    PageSize int    `form:"page_size"` // 每页返回的数据量，最多 100 条
}

type listData struct {
    ID        int32  `json:"id"`
    UserName  string `json:"username"`
    NickName  string `json:"nickname"`
    Mobile    string `json:"mobile"`
    Avatar    string `json:"avatar"`
    CreatedAt string `json:"created_at"`
    UpdatedAt string `json:"updated_at"`
}

type listResponse struct {
    Page     int        `json:"page"`
    PageSize int        `json:"page_size"`
    Total    int64      `json:"total"`
    List     []listData `json:"list"`
}
```

---

## 6. 错误处理规范

统一的错误处理是保证 API 健壮性和可预测性的关键。

*   **统一入口**：所有在 Handler 中捕获的错误（参数校验失败、数据库操作失败、权限不足等）都必须通过 `ctx.AbortWithError()` 函数来处理。
*   **`core.Error` 结构**：`ctx.AbortWithError` 接收一个 `core.Error` 对象，该对象封装了错误的三个核心要素：
    1.  **HTTP 状态码**：如 `http.StatusBadRequest`。
    2.  **业务错误码**：在 `internal/code/` 中预定义的 `code.Code`，如 `code.CreateAdminError`。
    3.  **详细错误信息**：一个描述错误具体原因的字符串。

*   **未找到记录**：当数据库查询返回 `gorm.ErrRecordNotFound` 时，需要按具体业务错误码返回明确的提示信息（如“账号不存在”）。
*   **权限校验失败**：当 `ctx.SessionUserInfo().IsSuper != 1` 时，禁止操作，返回对应业务错误码和提示“禁止操作”。

```go
// 示例：用户已存在
if info != nil {
    ctx.AbortWithError(core.Error(
        http.StatusBadRequest,
        code.CreateAdminError,
        fmt.Sprintf("%s: %s", code.Text(code.CreateAdminError), "该账号已存在")),
    )
    return
}
```

```go
// 示例：未找到记录（与 admin_modify/login 对齐）
if err == gorm.ErrRecordNotFound {
    ctx.AbortWithError(core.Error(
        http.StatusBadRequest,
        code.AdminLoginError,
        fmt.Sprintf("%s: %s", code.Text(code.AdminLoginError), "账号不存在，请联系管理员。")),
    )
    return
}
```

```go
// 示例：权限校验失败（与 create/modify/delete/list 对齐）
if ctx.SessionUserInfo().IsSuper != 1 {
    ctx.AbortWithError(core.Error(
        http.StatusBadRequest,
        code.CreateAdminError,
        fmt.Sprintf("%s: %s", code.Text(code.CreateAdminError), "禁止操作")),
    )
    return
}
```

---

### 6.1 数据库上下文与读写分离（Service 层）
*   **上下文传递**：所有数据库操作必须调用 `WithContext(ctx)`（Service 层接收 `context.Context`）。
*   **读写分离**：读取使用 `s.readDB`，写入使用 `s.writeDB`。
*   **分页查询会话**：分页列表与计数需要分别创建独立会话，避免互相影响。

```go
query := s.readDB.Admin.WithContext(ctx)
listQueryDB := query.Session(&gorm.Session{})
countQueryDB := query.Session(&gorm.Session{})

resultData, err := listQueryDB.Order(s.readDB.Admin.ID.Desc()).Limit(in.PageSize).Offset((in.Page-1)*in.PageSize).Find()
if err != nil { /* 错误处理 */ }

count, err := countQueryDB.Count()
if err != nil { /* 错误处理 */ }
```

---

## 7. 日志记录规范

虽然在 `admin` 模块的示例中未显式大量使用，但规范的日志记录对于问题排查至关重要。

*   **使用注入的 Logger**：应通过 `handler` 结构体中注入的 `h.logger` 实例来记录日志。
*   **记录关键信息**：在关键业务节点或错误发生时，记录必要的上下文信息，如请求参数、用户 ID 等。

```go
// 推荐实践 (示例)
h.logger.Error("创建管理员失败", zap.Error(err), zap.String("username", req.UserName))
```

---

## 8. 注释与文档 (Swagger)

API 的可发现性和易用性依赖于完善的文档。

*   **强制 Swagger 注解**：每个 API Handler 函数上方都必须添加完整的 Swagger 注解块。
*   **注解内容**：必须包含以下核心注解：
    *   `@Summary`：一句话功能简介。
    *   `@Description`：更详细的功能描述。
    *   `@Tags`：API 分组标签，便于在 Swagger UI 中分类。
    *   `@Accept` / `@Produce`：通常为 `json`。
    *   `@Param`：描述请求参数，包括参数位置（`body`）、类型、是否必需和说明。
    *   `@Success`：描述成功响应的结构。
    *   `@Failure`：描述可能发生的错误响应。
    *   `@Router`：API 的路由路径和 HTTP 方法。
    *   `@Security`：如果接口需要认证，需添加此注解。

```go
// internal/api/admin/admin_create.go

// CreateAdmin 新增客服
// @Summary 新增客服
// @Description 新增客服
// @Tags 管理端.客服管理
// @Accept json
// @Produce json
// @Param RequestBody body createAdminRequest true "请求参数"
// @Success 200 {object} createAdminResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/create [post]
// @Security LoginVerifyToken
func (h *handler) CreateAdmin() core.HandlerFunc { /* ... */ }

// internal/api/admin/admin_list.go（GET 查询示例）
// @Param username query string false "用户名"
// @Param nickname query string false "昵称"
// @Param page query int true "当前页码" default(1)
// @Param page_size query int true "每页返回的数据量，最多 100 条" default(20)
// @Router /api/admin/list [get]
// @Security LoginVerifyToken

// internal/api/admin/admin_modify.go（路径参数示例）
// @Param id path string true "编号ID"
// @Router /api/admin/{id} [put]
// @Security LoginVerifyToken

// internal/api/admin/login.go（登录接口不加 Security）
// @Router /api/admin/login [post]
```

---

## 9. 路由与中间件规范

路由是 API 对外的入口，必须与模块职责、认证策略保持一致，并在 `internal/router/router.go` 统一注册。

### 9.1 分组与路径前缀
- **管理端**：统一前缀 `"/api/admin"`
  - 非认证组：用于登录、系统状态等不需要鉴权的接口。
  - 认证组：使用鉴权中间件保护，适用于敏感操作（新增、编辑、删除、列表）。
- **用户端（APP）**：统一前缀 `"/api/app"`，用于对外业务接口（如活动相关）。

### 9.2 鉴权中间件
- **封装方式**：通过 `core.WrapAuthHandler(interceptor.AdminTokenAuthVerify)` 将会话信息写入上下文，认证失败统一返回业务错误。
- **接口签名**：`internal/router/interceptor/interceptor.go:12-18` 定义了鉴权接口：
  `AdminTokenAuthVerify(ctx core.Context) (sessionUserInfo proposal.SessionUserInfo, err core.BusinessError)`。
- **实现参考**：管理端鉴权实现见 `internal/router/interceptor/admin_auth.go:1-80`。

### 9.3 路由注册示例（与当前 admin 模块对齐）
```go
// internal/router/router.go

// 管理端非认证接口路由组（登录）
adminNonAuthApiRouter := mux.Group("/api/admin")
{
    adminNonAuthApiRouter.GET("/license/status", func(ctx core.Context) { /* ... */ })
    adminNonAuthApiRouter.POST("/login", adminHandler.Login())
}

// 管理端认证接口路由组（需要 Authorization）
adminAuthApiRouter := mux.Group("/api/admin", core.WrapAuthHandler(intc.AdminTokenAuthVerify))
{
    adminAuthApiRouter.POST("/create", adminHandler.CreateAdmin())
    adminAuthApiRouter.PUT("/:id", adminHandler.ModifyAdmin())
    adminAuthApiRouter.POST("/delete", adminHandler.DeleteAdmin())
    adminAuthApiRouter.GET("/list", adminHandler.PageList())
}

// APP 端接口路由组
appApiRouter := mux.Group("/api/app")
{
    appHandler := app.New(logger, db)
    appApiRouter.GET("/activities", appHandler.ListActivities())
    // ... 其他 APP 端接口
}
```

### 9.4 Swagger 安全定义与使用
- **安全定义位置**：`main.go:23-26` 定义认证方案 `LoginVerifyToken`，从请求头读取 `Authorization`。
- **注解使用规范**：
  - 管理端认证组接口均需添加 `@Security LoginVerifyToken`。
  - 登录接口不需要添加 `@Security` 注解。

### 9.5 路径命名与语义
- **资源语义**：
  - `POST /api/admin/create`：创建资源（支持复杂校验与默认值设置）。
  - `PUT /api/admin/:id`：更新单个资源，路径参数 `id` 必须有效。
  - `POST /api/admin/delete`：批量删除，参数 `ids` 以逗号分隔。
  - `GET /api/admin/list`：分页检索，`page`/`page_size` 合理限制（最大 100）。
- **用户端**：保持 RESTful 风格，如 `GET /api/app/activities`、`GET /api/app/activities/:activity_id`。

### 9.6 指标与别名（可选）
- 为避免路径参数导致指标维度爆炸，可在 Handler 前添加别名记录指标：
```go
// 例如：为 /api/app/activities/:activity_id 设置指标记录别名
appApiRouter.GET("/activities/:activity_id",
    core.AliasForRecordMetrics("/api/app/activities/:activity_id"),
    appHandler.GetActivityDetail(),
)
```

---

## 10. 服务层分离规范

为避免 Handler 同时承担传输与业务职责，必须在 `internal/service/{module}` 引入服务层，统一承载业务规则、数据协作与事务处理。

### 10.1 目录与命名
- 目录：`internal/service/admin`、`internal/service/activity` 等按模块划分。
- 包名：与模块名一致，Handler 导入时使用别名避免冲突（如 `svc "bindbox-game/internal/service/admin"`）。

### 10.1.1 文件拆分（与 API 端点保持一致）
- 每个端点在服务层对应一个实现文件，避免所有方法堆叠在单文件：
  ```
  internal/service/admin/
  ├── service.go   // 接口、构造
  ├── login.go     // 登录
  ├── create.go    // 新增客服
  ├── modify.go    // 编辑客服
  ├── delete.go    // 删除客服
  └── list.go      // 客服列表
  ```
  - 规则：每个端点的输入/输出类型在对应文件中定义（例如 `LoginInput`/`LoginResult` 在 `login.go`）。

---

## 11. 流程规范（Router → API → Service）

### 11.1 职责边界
- Router：负责分组、认证拦截与端点注册；不承载业务逻辑。
  - 管理端非认证路由：`internal/router/router.go:44-51`
  - 管理端认证路由（鉴权中间件）：`internal/router/router.go:53-60`
  - 用户端 APP 路由：`internal/router/router.go:62-72`
- API（Handler）：负责参数绑定、权限校验、错误码映射与响应输出；不直接操作数据库。
  - 绑定参数并校验：如登录 `internal/api/admin/login.go:35-45`、列表 `internal/api/admin/admin_list.go:57-64`
  - 权限校验：如列表 `internal/api/admin/admin_list.go:83-90`、创建 `internal/api/admin/admin_create.go:49-56`
  - 错误码映射与响应：如登录 `internal/api/admin/login.go:53-59,61-64`
- Service：承载领域逻辑、数据访问与事务；输入/输出类型在对应端点文件中定义。
  - 登录实现：`internal/service/admin/login.go:16-52`
  - 创建实现：`internal/service/admin/admin_create.go:14-42`
  - 编辑实现：`internal/service/admin/admin_modify.go:13-50`
  - 删除实现：`internal/service/admin/admin_delete.go:9-15`
  - 列表实现：`internal/service/admin/admin_list.go:12-38`

### 11.2 端到端流程（以管理端为例）
- Router 接收请求并路由到对应分组：非认证或认证（携带 `Authorization`）。
- 若为认证接口，鉴权中间件注入会话信息：`internal/router/interceptor/admin_auth.go:1-80`。
- Handler：
  - `ShouldBindJSON` 或 `ShouldBindForm` 解析请求（含路径参数 `ctx.Param`）。
  - 权限检查（如 `ctx.SessionUserInfo().IsSuper`）。
  - 将 DTO 转换为 Service 输入，调用 `svc.*(ctx.RequestContext(), input)`。
  - 根据 Service 返回的错误映射业务错误码，或输出成功响应 `ctx.Payload(res)`。
- Service：
  - 使用 `dao.Query.WithContext` 与读写分离实现查询或写入。
  - 必要场景内使用事务保障原子性（参考 `dao.gen.go` 的 `Query.Transaction`）。

### 11.3 错误处理策略
- Service 返回语义化错误信息（`error`）；不直接决定 HTTP 或业务码。
- Handler 统一映射到 `core.Error(httpCode, code.* , message)` 并 `AbortWithError`。
  - 登录错误映射：`internal/api/admin/login.go:53-59`
  - 创建错误映射：`internal/api/admin/admin_create.go:66-71`
  - 编辑错误映射：`internal/api/admin/admin_modify.go:87-93`
  - 删除错误映射：`internal/api/admin/admin_delete.go:95-102`
  - 列表错误映射：`internal/api/admin/admin_list.go:98-105`

### 11.4 数据绑定与类型转换
- 登录：`loginRequest` → `LoginInput` 映射：`internal/api/admin/login.go:47-51`
- 创建：`createAdminRequest` → `CreateInput` 映射：`internal/api/admin/admin_create.go:58-65`
- 编辑：`modifyAdminRequest` → `ModifyInput` 映射：`internal/api/admin/admin_modify.go:79-86`
- 列表：`listRequest(form)` → `ListInput` 映射：`internal/api/admin/admin_list.go:92-97`
- 删除：解析 `ids` 列表后直接传入 `svc.Delete`：`internal/api/admin/admin_delete.go:95`

### 11.5 事务与读写分离
- 读操作统一使用 `readDB`，写操作统一使用 `writeDB`。
- 所有 DAO 调用必须附带上下文：`WithContext(ctx)`。
- 多步骤写操作建议在 Service 层使用事务（`Query.Transaction`）：`internal/repository/mysql/dao/gen.go:211-213`。

### 11.6 鉴权与会话
- 路由层通过 `core.WrapAuthHandler(intc.AdminTokenAuthVerify)` 进行会话注入：`internal/router/router.go:53-60`。
- 鉴权逻辑参考：`internal/router/interceptor/admin_auth.go:1-80`。
- Handler 使用 `ctx.SessionUserInfo()` 获取操作者信息（如 `CreatedBy`、`UpdatedBy`）。

### 11.7 命名与拆分
- API 与 Service 均按端点拆分文件，命名对齐，便于定位与维护。
- Service 的输入/输出类型在对应端点文件定义（如 `login.go` 定义 `LoginInput/LoginResult`）。

---

## 12. Repository 规范

### 12.1 层职责
- 负责数据访问（CRUD、分页、事务），不包含业务规则与 HTTP 细节。
- 仅依赖底层 DAO/Model，向 Service 层提供稳定的数据操作能力。

### 12.2 目录与组件
- 目录：`internal/repository/mysql`
  - `dao/`：由 `gorm/gen` 生成的查询对象与读写分离封装，参考：
    - 读写分离方法：`internal/repository/mysql/dao/gen.go:135-143`
    - 事务封装：`internal/repository/mysql/dao/gen.go:211-213`
    - 上下文绑定：`internal/repository/mysql/dao/gen.go:188-209`
  - `model/`：数据库对应的实体结构体（字段、标签）。

### 12.3 使用规范（在 Service 层）
- 初始化查询对象：
  - 读库：`readDB := dao.Use(db.GetDbR())`
  - 写库：`writeDB := dao.Use(db.GetDbW())`
- 所有操作必须绑定上下文：`WithContext(ctx)`，示例（查询管理员）：
```go
info, err := s.readDB.Admin.WithContext(ctx).
    Where(s.readDB.Admin.Username.Eq(in.Username)).
    First()
```
- 分页与计数使用独立会话，避免互相影响：
```go
listQueryDB := query.Session(&gorm.Session{})
countQueryDB := query.Session(&gorm.Session{})
items, _ := listQueryDB.Order(...).Limit(...).Offset(...).Find()
total, _ := countQueryDB.Count()
```
- 多步骤写操作建议在事务中完成：
```go
_ = s.writeDB.Transaction(func(tx *dao.Query) error {
    // 在 tx 上执行写操作
    return nil
})
```

### 12.4 分层建议
- 如需进一步解耦 DAO，可在 `internal/repository/{module}` 定义模块级仓库接口（如 `adminrepo`），内部封装 DAO 细节；Service 只依赖仓库接口，便于单元测试与替换数据源。
- 目前项目直接在 Service 层使用 DAO，符合轻量项目场景；当跨表协作复杂时可引入仓库接口以降低耦合。


 
