# Change: OpenAPI 文档契约对齐

## Why

确保 OpenAPI 文档描述的响应结构与真实运行时一致，避免 SDK 生成和接口对接问题。

**当前问题**：

1. **响应字段名不一致**：
   - OpenAPI 错误响应定义为 `message` 字段
   - 真实运行时返回为 `msg` 字段

2. **成功响应缺少 envelope**：
   - OpenAPI 文档直接返回 DTO schema
   - 真实运行时包裹在 `{code, data, msg, timestamp}` 中

3. **handlers 清单不完整**：
   - `cmd/api/docs.go` 和 `cmd/gendocs/main.go` 清单不一致
   - 缺少部分 handler（PersonalCustomer、ShopPackageBatchAllocation、ShopPackageBatchPricing）

4. **个人客户路由未纳入文档**：
   - `/api/c/v1` 路由未使用 `Register(...)` 机制
   - 不在 OpenAPI 文档体系中

## What Changes

### 4.1 响应字段名对齐

修改 OpenAPI 错误响应 schema：

```yaml
# ❌ 当前
components:
  schemas:
    ErrorResponse:
      properties:
        code: { type: integer }
        message: { type: string }  # 错误：应为 msg
        data: { type: object }
        timestamp: { type: string }

# ✅ 修复后
components:
  schemas:
    ErrorResponse:
      properties:
        code: { type: integer, example: 0 }
        msg: { type: string, example: "success" }  # 对齐真实字段名
        data: { type: object }
        timestamp: { type: string, format: date-time }
```

### 4.2 成功响应体现 envelope

修改成功响应格式，包裹 DTO：

```yaml
# ❌ 当前（直接返回 DTO）
/api/admin/users:
  get:
    responses:
      200:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserDTO'

# ✅ 修复后（包裹 envelope）
/api/admin/users:
  get:
    responses:
      200:
        content:
          application/json:
            schema:
              type: object
              properties:
                code: { type: integer, example: 0 }
                msg: { type: string, example: "success" }
                data:
                  $ref: '#/components/schemas/UserDTO'
                timestamp: { type: string, format: date-time }
```

### 4.3 补齐 handlers 清单

在文档生成器中补充缺失的 handler：

```go
// cmd/api/docs.go 和 cmd/gendocs/main.go

handlers := &bootstrap.Handlers{
    // ... 现有 handlers
    
    // 补充缺失的 handlers
    PersonalCustomer:             personal.NewPersonalCustomerHandler(nil),
    ShopPackageBatchAllocation:   admin.NewShopPackageBatchAllocationHandler(nil),
    ShopPackageBatchPricing:      admin.NewShopPackageBatchPricingHandler(nil),
}
```

### 4.4 个人客户路由纳入文档

改造 `internal/routes/personal.go` 使用 `Register(...)` 机制：

```go
// ❌ 当前
func RegisterPersonalRoutes(app *fiber.App, handlers *bootstrap.Handlers) {
    api := app.Group("/api/c/v1")
    api.Get("/cards/:iccid", handlers.PersonalCustomer.GetCard)
    // ...
}

// ✅ 修复后
func RegisterPersonalRoutes(doc *openapi.Generator, basePath string, handlers *bootstrap.Handlers) {
    doc.Register(openapi.RouteSpec{
        Method:  "GET",
        Path:    "/api/c/v1/cards/:iccid",
        Handler: handlers.PersonalCustomer.GetCard,
        Summary: "获取个人客户卡详情",
        Tags:    []string{"个人客户"},
        Auth:    true,
        Input:   nil,  // 路径参数
        Output:  &dto.CardDetailResponse{},
    })
    // ...
}
```

## Decisions

### OpenAPI 生成策略

1. **统一 envelope 包裹**：所有成功响应使用 `{code, data, msg, timestamp}`
2. **字段名一致**：错误响应使用 `msg` 而非 `message`
3. **DTO 保持具体类型**：`data` 字段保留具体的 DTO schema
4. **自动化 handlers 构造**：文档生成时 handlers 可以传入 `nil` 依赖

### 文档生成复用

抽取公共函数避免重复：

```go
// pkg/openapi/handlers.go (新建)
func BuildDocHandlers() *bootstrap.Handlers {
    // 文档生成用，所有依赖传 nil
    return &bootstrap.Handlers{
        Account:    admin.NewAccountHandler(nil, nil),
        Shop:       admin.NewShopHandler(nil, nil),
        // ... 所有 handlers
    }
}
```

在 `cmd/api/docs.go` 和 `cmd/gendocs/main.go` 中复用：

```go
handlers := openapi.BuildDocHandlers()
```

## Impact

### Breaking Changes

- OpenAPI 文档结构变化（响应格式）
- 需要通知 SDK 使用方重新生成 SDK
- 前端可能需要调整响应解析逻辑（如果直接使用 OpenAPI 生成的类型）

### Documentation Updates

- 更新 `docs/api-documentation-guide.md` 补充 envelope 说明
- 补充个人客户 API 路由注册示例
- 在 API 文档中说明 envelope 格式

### Testing Requirements

生成文档后对比验证：

```bash
# 1. 重新生成文档
go run cmd/gendocs/main.go

# 2. 对比差异
diff logs/openapi.yaml logs/openapi.yaml.old

# 3. 验证关键点
# - 检查响应字段名是否为 msg（非 message）
# - 检查成功响应是否包含 envelope
# - 检查 /api/c/v1 路由是否出现
# - 检查接口数量是否完整
```

## Affected Specs

- **UPDATE**: `openspec/specs/openapi-generation/spec.md`
  - 补充 envelope 包裹要求
  - 更新字段名规范

- **UPDATE**: `openspec/specs/personal-customer/spec.md`
  - 个人客户 API 进入文档体系

## Verification Checklist

### 编译检查
```bash
go build -o /tmp/test_gendocs ./cmd/gendocs
```

### 文档生成
```bash
go run cmd/gendocs/main.go
```

### 文档验证

检查生成的 `logs/openapi.yaml`：

- [ ] 错误响应字段名为 `msg`（非 `message`）
- [ ] 成功响应包含 envelope：
  ```yaml
  200:
    content:
      application/json:
        schema:
          type: object
          properties:
            code: { type: integer }
            msg: { type: string }
            data: { ... }
            timestamp: { type: string }
  ```
- [ ] `/api/c/v1` 路由出现在文档中
- [ ] 接口数量完整（与已注册路由一致）

### 示例响应验证

对比文档示例与真实响应：

**文档示例**：
```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "id": 1,
    "username": "admin"
  },
  "timestamp": "2026-01-29T10:00:00Z"
}
```

**真实响应**（curl 测试）：
```bash
curl -X GET http://localhost:8080/api/admin/users/1 \
  -H "Authorization: Bearer $TOKEN"
```

确认字段名和结构一致。

## Estimated Effort

| 任务 | 预估时间 |
|-----|---------|
| 4.1 响应字段名对齐 | 0.5h |
| 4.2 成功响应 envelope | 1h |
| 4.3 补齐 handlers 清单 | 0.5h |
| 4.4 个人客户路由纳入 | 1h |
| 文档验证 | 0.5h |
| 文档更新 | 0.5h |

**总计**：约 4 小时
