All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m45s
主要变更: 1. OpenAPI 文档契约对齐 - 统一错误响应字段名为 msg(非 message) - 规范 envelope 响应结构(code, msg, data, timestamp) - 个人客户路由纳入文档体系(使用 Register 机制) - 新增 BuildDocHandlers() 统一管理 handler 构造 - 确保文档生成的幂等性 2. Service 层错误处理统一 - 全面替换 fmt.Errorf 为 errors.New/Wrap - 统一错误码使用规范 - Handler 层参数校验不泄露底层细节 - 新增错误码验证集成测试 3. 代码质量提升 - 删除未使用的 Task handler 和路由 - 新增代码规范检查脚本(check-service-errors.sh) - 新增注释路径一致性检查(check-comment-paths.sh) - 更新 API 文档生成指南 4. OpenSpec 归档 - 归档 openapi-contract-alignment 变更(63 tasks) - 归档 service-error-unify-core 变更 - 归档 service-error-unify-support 变更 - 归档 code-cleanup-docs-update 变更 - 归档 handler-validation-security 变更 - 同步 delta specs 到主规范文件 影响范围: - pkg/openapi: 新增 handlers.go,优化 generator.go - internal/service/*: 48 个 service 文件错误处理统一 - internal/handler/admin: 优化参数校验错误提示 - internal/routes: 个人客户路由改造,删除 task 路由 - scripts: 新增 3 个代码检查脚本 - docs: 更新 OpenAPI 文档(15750+ 行) - openspec/specs: 同步 3 个主规范文件 破坏性变更:无 向后兼容:是
272 lines
6.5 KiB
Markdown
272 lines
6.5 KiB
Markdown
# 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 小时
|