Files
huang 409a68d60b
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m45s
feat: OpenAPI 契约对齐与框架优化
主要变更:
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 个主规范文件

破坏性变更:无
向后兼容:是
2026-01-30 11:40:36 +08:00

272 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 清单不一致
- 缺少部分 handlerPersonalCustomer、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 小时