# 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 小时