refactor: 统一错误消息数据源,优化错误码与映射表管理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m36s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m36s
主要改动: - 改造 errors.New() 和 Wrap() 函数签名为可变参数,优先使用 errorMessages 映射表 - 添加 allErrorCodes 注册表和 init() 启动时校验,确保错误码与映射表一致 - 添加 TestAllCodesHaveMessages 和 TestNoOrphanMessages 测试防止映射表腐化 - 清理 109 处与映射表一致的冗余硬编码(service 层) - 保留业务特定消息覆盖能力 新增 API 用法: - errors.New(errors.CodeUnauthorized) // 使用映射表默认消息 - errors.New(errors.CodeNotFound, "提现申请不存在") // 覆盖为自定义消息
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-22
|
||||
@@ -0,0 +1,228 @@
|
||||
## Context
|
||||
|
||||
### 背景
|
||||
|
||||
项目中错误处理使用 `pkg/errors/` 包,核心组件:
|
||||
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| `codes.go` | 定义错误码常量 `Code*` 和消息映射表 `errorMessages` |
|
||||
| `errors.go` | 定义 `AppError` 结构和 `New()`/`Wrap()` 构造函数 |
|
||||
| `handler.go` | Fiber 全局 ErrorHandler,处理错误响应 |
|
||||
|
||||
### 当前问题
|
||||
|
||||
```go
|
||||
// errors.go 的设计意图:当 message 为空时自动使用映射表
|
||||
func New(code int, message string) *AppError {
|
||||
if message == "" {
|
||||
message = GetMessage(code, "zh-CN") // 理论上的自动填充
|
||||
}
|
||||
return &AppError{Code: code, Message: message}
|
||||
}
|
||||
|
||||
// 实际业务代码:100% 传入硬编码消息,映射表从未被使用
|
||||
errors.New(errors.CodeNotFound, "提现申请不存在") // 不是空字符串
|
||||
```
|
||||
|
||||
**量化数据**(通过 grep 统计):
|
||||
- `errors.New(code, "硬编码")` 调用:291 处
|
||||
- `errors.New(code, "")` 调用:0 处
|
||||
- 映射表使用率:0%
|
||||
|
||||
### 约束
|
||||
|
||||
- 必须向后兼容,不能破坏现有 API 响应格式
|
||||
- 业务特定消息(如 "提现申请不存在")优于通用消息("资源未找到")
|
||||
- 不能引入运行时性能损耗
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
### Goals
|
||||
|
||||
1. **单一数据源原则**:错误码与消息映射表作为默认消息来源
|
||||
2. **编译时/启动时校验**:缺失映射条目会立即暴露,而非运行时默默失败
|
||||
3. **向后兼容**:保留业务特定消息覆盖能力
|
||||
4. **开发体验优化**:简化 API,减少样板代码
|
||||
|
||||
### Non-Goals
|
||||
|
||||
1. ❌ 多语言支持(保留 `lang` 参数但不实现)
|
||||
2. ❌ 修改 API 响应格式
|
||||
3. ❌ 强制所有错误使用映射表消息(保留覆盖能力)
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: 改造 `errors.New()` 函数签名
|
||||
|
||||
**选项**:
|
||||
|
||||
| 选项 | 实现 | 优缺点 |
|
||||
|------|------|--------|
|
||||
| A. 删除 message 参数 | `New(code int)` | ✅ 强制单一数据源<br>❌ 丢失业务上下文 |
|
||||
| B. message 改为可选 | `New(code int, msg ...string)` | ✅ 默认用映射表<br>✅ 允许覆盖<br>✅ 向后兼容 |
|
||||
| C. 新增 NewWithMsg | `New(code)` + `NewWithMsg(code, msg)` | ✅ 清晰区分<br>❌ 需改所有调用点 |
|
||||
|
||||
**决策**:选项 B - 使用可变参数
|
||||
|
||||
```go
|
||||
// 新签名
|
||||
func New(code int, customMsg ...string) *AppError {
|
||||
msg := GetMessage(code, "zh-CN") // 默认从映射表取
|
||||
if len(customMsg) > 0 && customMsg[0] != "" {
|
||||
msg = customMsg[0] // 允许覆盖
|
||||
}
|
||||
return &AppError{Code: code, Message: msg}
|
||||
}
|
||||
|
||||
// 使用方式
|
||||
errors.New(errors.CodeNotFound) // 使用映射表: "资源未找到"
|
||||
errors.New(errors.CodeNotFound, "提现申请不存在") // 覆盖: "提现申请不存在"
|
||||
```
|
||||
|
||||
**理由**:
|
||||
- 向后兼容:现有代码无需修改即可编译
|
||||
- 渐进式迁移:可逐步清理冗余硬编码
|
||||
- 保留灵活性:业务特定消息仍可使用
|
||||
|
||||
### Decision 2: 启动时校验机制
|
||||
|
||||
**选项**:
|
||||
|
||||
| 选项 | 时机 | 优缺点 |
|
||||
|------|------|--------|
|
||||
| A. init() panic | 程序启动 | ✅ 立即发现<br>❌ 启动失败 |
|
||||
| B. init() 日志警告 | 程序启动 | ✅ 不阻塞启动<br>❌ 可能被忽略 |
|
||||
| C. 仅测试校验 | CI 运行 | ✅ 不影响生产<br>❌ 本地开发可能遗漏 |
|
||||
|
||||
**决策**:选项 A + C 组合
|
||||
|
||||
1. **init() 严格校验**:缺失映射立即 panic,阻止服务启动
|
||||
2. **CI 测试兜底**:测试覆盖所有错误码,防止遗漏
|
||||
|
||||
```go
|
||||
func init() {
|
||||
for code := range allCodes() { // 遍历所有 Code* 常量
|
||||
if _, ok := errorMessages[code]; !ok {
|
||||
panic(fmt.Sprintf("错误码 %d 缺少映射消息", code))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**理由**:
|
||||
- 快速失败:问题在部署前暴露,而非运行时
|
||||
- 强制一致性:不允许"遗漏"状态存在
|
||||
|
||||
### Decision 3: 错误码常量收集方式
|
||||
|
||||
**选项**:
|
||||
|
||||
| 选项 | 实现 | 优缺点 |
|
||||
|------|------|--------|
|
||||
| A. 手动维护列表 | `allCodes = []int{CodeSuccess, CodeNotFound, ...}` | ❌ 容易忘记更新 |
|
||||
| B. 反射扫描 | 运行时反射遍历 const | ❌ Go 不支持反射 const |
|
||||
| C. 代码生成 | `go generate` 扫描生成 | ✅ 自动化<br>❌ 增加构建复杂度 |
|
||||
| D. 基于映射表反向校验 | 遍历 errorMessages 的 key | ✅ 简单<br>❌ 无法检测缺失 |
|
||||
| E. 定义错误码注册表 | 显式注册每个错误码 | ✅ 清晰<br>✅ 编译时检查 |
|
||||
|
||||
**决策**:选项 E - 定义完整的错误码切片
|
||||
|
||||
```go
|
||||
// 所有错误码必须在此列表中注册
|
||||
var allErrorCodes = []int{
|
||||
CodeSuccess,
|
||||
CodeInvalidParam,
|
||||
CodeMissingToken,
|
||||
// ... 所有错误码
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, code := range allErrorCodes {
|
||||
if _, ok := errorMessages[code]; !ok {
|
||||
panic(fmt.Sprintf("错误码 %d 缺少映射消息", code))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**理由**:
|
||||
- 显式优于隐式:所有错误码一目了然
|
||||
- 新增错误码时必须同时更新两处(常量 + 列表),测试会强制检查映射表
|
||||
|
||||
### Decision 4: 业务代码清理策略
|
||||
|
||||
**选项**:
|
||||
|
||||
| 选项 | 范围 | 优缺点 |
|
||||
|------|------|--------|
|
||||
| A. 全量清理 | 所有 291 处 | ✅ 彻底<br>❌ 风险高,改动大 |
|
||||
| B. 仅清理冗余 | 消息与映射表一致的 | ✅ 安全<br>✅ 保留业务上下文 |
|
||||
| C. 不清理 | 保持现状 | ✅ 零风险<br>❌ 技术债未还 |
|
||||
|
||||
**决策**:选项 B - 仅清理冗余硬编码
|
||||
|
||||
清理规则:
|
||||
```go
|
||||
// 清理前:消息与映射表完全一致
|
||||
errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
|
||||
// 清理后:使用映射表默认值
|
||||
errors.New(errors.CodeUnauthorized)
|
||||
|
||||
// 保留:业务特定消息,比映射表更精确
|
||||
errors.New(errors.CodeNotFound, "提现申请不存在") // 保留,比 "资源未找到" 更清晰
|
||||
```
|
||||
|
||||
**理由**:
|
||||
- 最小改动原则:只改必要的
|
||||
- 保留业务价值:精确消息优于通用消息
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
### 风险矩阵
|
||||
|
||||
| 风险 | 等级 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| 启动时 panic 影响部署 | 中 | 本地开发和 CI 会先发现;错误消息清晰指明缺失的错误码 |
|
||||
| 消息文案变化影响前端 | 低 | 仅清理与映射表一致的;业务特定消息保留 |
|
||||
| 大规模代码改动引入 bug | 中 | 分阶段实施:先加校验和测试,再清理代码 |
|
||||
| 遗漏错误码导致 panic | 低 | allErrorCodes 列表 + 测试双重保障 |
|
||||
|
||||
### Trade-offs
|
||||
|
||||
1. **启动时 panic vs 运行时容错**
|
||||
- 选择 panic:快速失败,问题在部署前暴露
|
||||
- 代价:如果遗漏会阻止服务启动
|
||||
|
||||
2. **强制映射表 vs 允许覆盖**
|
||||
- 选择允许覆盖:保留业务灵活性
|
||||
- 代价:无法 100% 保证消息一致性
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: 基础设施(本次实施)
|
||||
|
||||
1. 改造 `errors.New()` 函数签名(向后兼容)
|
||||
2. 添加 `allErrorCodes` 注册表
|
||||
3. 添加 `init()` 启动校验
|
||||
4. 添加 `TestAllCodesHaveMessages` 测试
|
||||
5. 补充可能缺失的 errorMessages 条目
|
||||
|
||||
### Phase 2: 代码清理(本次实施)
|
||||
|
||||
1. 清理与映射表一致的冗余硬编码
|
||||
2. 保留业务特定消息
|
||||
|
||||
### Rollback Strategy
|
||||
|
||||
如出现问题,回滚方式:
|
||||
1. `errors.New()` 签名改动是向后兼容的,无需回滚
|
||||
2. 如果 init() panic 导致问题,临时注释校验代码
|
||||
3. Git revert 整个变更
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **是否需要 linter 规则?**
|
||||
- 可考虑添加 golangci-lint 自定义规则,检测 `errors.New(code, msg)` 中 msg 与映射表重复的情况
|
||||
- 暂不实施,后续根据需要添加
|
||||
@@ -0,0 +1,54 @@
|
||||
## Why
|
||||
|
||||
当前项目中错误消息存在**双数据源问题**:
|
||||
1. `pkg/errors/codes.go` 中定义了 `errorMessages` 映射表
|
||||
2. 业务代码中 `errors.New(code, "硬编码消息")` 全部传入硬编码消息
|
||||
|
||||
结果是:
|
||||
- 映射表的"自动填充"功能(当 message 为空时使用映射表)**从未被使用**(死代码)
|
||||
- 新增错误码时可能忘记更新映射表,导致映射表逐渐腐化
|
||||
- 长期开发后新人不知道该用哪种方式,造成使用混乱
|
||||
- 同一错误码在不同地方可能返回不同消息,影响一致性
|
||||
|
||||
## What Changes
|
||||
|
||||
- **改造 `errors.New()` 函数签名**:优先使用映射表消息,允许可选覆盖
|
||||
- **添加 `init()` 启动时校验**:确保所有错误码常量都有对应的映射表条目
|
||||
- **添加 CI 测试**:防止映射表腐化,确保错误码与消息映射完整
|
||||
- **清理业务代码中的冗余硬编码**:将与映射表一致的消息改为使用默认值
|
||||
- **保留业务特定消息覆盖能力**:如 "提现申请不存在" 比 "资源未找到" 更清晰的场景
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `error-code-validation`: 错误码与消息映射的编译时/运行时校验机制,确保所有 Code* 常量都有对应的 errorMessages 条目
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。此变更不改变现有 spec 的行为要求,仅加固内部实现。
|
||||
|
||||
## Impact
|
||||
|
||||
### 代码影响
|
||||
|
||||
| 文件/目录 | 影响 |
|
||||
|-----------|------|
|
||||
| `pkg/errors/errors.go` | 改造 `New()` 函数签名,添加 `init()` 校验 |
|
||||
| `pkg/errors/codes.go` | 可能需要补充缺失的映射条目 |
|
||||
| `pkg/errors/codes_test.go` | 添加完整性校验测试 |
|
||||
| `internal/service/**/*.go` | 清理冗余硬编码(约 150+ 处) |
|
||||
| `internal/handler/**/*.go` | 清理冗余硬编码(约 80+ 处) |
|
||||
| `pkg/middleware/*.go` | 清理冗余硬编码(约 15 处) |
|
||||
|
||||
### API 影响
|
||||
|
||||
无。错误响应格式不变,仅内部消息来源统一。
|
||||
|
||||
### 风险评估
|
||||
|
||||
| 风险 | 等级 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| 消息文案变化影响前端展示 | 低 | 保留业务特定消息覆盖能力 |
|
||||
| 遗漏错误码导致启动失败 | 中 | init() 校验会在启动时立即暴露问题 |
|
||||
| 大规模代码变更引入 bug | 中 | 分阶段实施:先加校验,再清理代码 |
|
||||
@@ -0,0 +1,91 @@
|
||||
# error-code-validation
|
||||
|
||||
错误码与消息映射的校验机制,确保所有错误码常量都有对应的消息映射。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 错误码消息映射完整性校验
|
||||
|
||||
系统 SHALL 在启动时校验所有已注册的错误码都有对应的 `errorMessages` 映射条目。
|
||||
|
||||
如果发现缺失映射,系统 MUST 立即 panic 并输出清晰的错误信息,指明缺失的错误码。
|
||||
|
||||
#### Scenario: 所有错误码都有映射时正常启动
|
||||
|
||||
- **WHEN** 所有 `allErrorCodes` 中的错误码都在 `errorMessages` 映射表中存在
|
||||
- **THEN** 系统正常启动,无错误日志
|
||||
|
||||
#### Scenario: 存在缺失映射时启动失败
|
||||
|
||||
- **WHEN** 某个错误码(如 `CodeNewFeature = 1099`)在 `allErrorCodes` 中注册但 `errorMessages` 中缺失
|
||||
- **THEN** 系统 panic,错误信息包含 "错误码 1099 缺少映射消息"
|
||||
|
||||
### Requirement: 错误码注册表维护
|
||||
|
||||
系统 SHALL 维护一个 `allErrorCodes` 切片,包含所有已定义的错误码常量。
|
||||
|
||||
新增错误码时,开发者 MUST 同时:
|
||||
1. 在 `codes.go` 中定义常量
|
||||
2. 在 `allErrorCodes` 中注册
|
||||
3. 在 `errorMessages` 中添加映射
|
||||
|
||||
#### Scenario: 新增错误码完整注册
|
||||
|
||||
- **WHEN** 开发者新增错误码 `CodeXxx = 1100`
|
||||
- **THEN** 必须同时在 `allErrorCodes` 和 `errorMessages` 中添加对应条目
|
||||
- **THEN** 否则启动时 panic 或测试失败
|
||||
|
||||
### Requirement: errors.New 默认使用映射表消息
|
||||
|
||||
`errors.New()` 函数 SHALL 优先使用 `errorMessages` 映射表中的消息作为默认值。
|
||||
|
||||
当调用者提供自定义消息时,系统 MUST 允许覆盖默认消息。
|
||||
|
||||
#### Scenario: 不传消息参数时使用映射表
|
||||
|
||||
- **WHEN** 调用 `errors.New(errors.CodeNotFound)`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "资源未找到"(映射表中的值)
|
||||
|
||||
#### Scenario: 传空字符串时使用映射表
|
||||
|
||||
- **WHEN** 调用 `errors.New(errors.CodeNotFound, "")`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "资源未找到"(映射表中的值)
|
||||
|
||||
#### Scenario: 传自定义消息时覆盖映射表
|
||||
|
||||
- **WHEN** 调用 `errors.New(errors.CodeNotFound, "提现申请不存在")`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "提现申请不存在"(自定义值)
|
||||
|
||||
### Requirement: errors.Wrap 默认使用映射表消息
|
||||
|
||||
`errors.Wrap()` 函数 SHALL 与 `errors.New()` 保持一致的消息处理逻辑。
|
||||
|
||||
#### Scenario: Wrap 不传消息时使用映射表
|
||||
|
||||
- **WHEN** 调用 `errors.Wrap(errors.CodeDatabaseError, originalErr)`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "数据库错误"(映射表中的值)
|
||||
- **THEN** 返回的 `AppError.Err` 为 `originalErr`
|
||||
|
||||
#### Scenario: Wrap 传自定义消息时覆盖
|
||||
|
||||
- **WHEN** 调用 `errors.Wrap(errors.CodeDatabaseError, "查询用户失败", originalErr)`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "查询用户失败"
|
||||
- **THEN** 返回的 `AppError.Err` 为 `originalErr`
|
||||
|
||||
### Requirement: CI 测试覆盖映射完整性
|
||||
|
||||
系统 SHALL 提供单元测试 `TestAllCodesHaveMessages`,验证所有注册的错误码都有对应的映射。
|
||||
|
||||
此测试 MUST 在 CI 流程中运行,防止映射表腐化。
|
||||
|
||||
#### Scenario: 测试检测到缺失映射
|
||||
|
||||
- **WHEN** 运行 `go test ./pkg/errors/...`
|
||||
- **WHEN** 存在错误码在 `allErrorCodes` 但不在 `errorMessages` 中
|
||||
- **THEN** 测试失败,输出缺失的错误码列表
|
||||
|
||||
#### Scenario: 测试检测到孤立映射
|
||||
|
||||
- **WHEN** 运行 `go test ./pkg/errors/...`
|
||||
- **WHEN** 存在映射条目的错误码不在 `allErrorCodes` 中
|
||||
- **THEN** 测试失败,输出孤立的错误码列表(可选警告)
|
||||
@@ -0,0 +1,49 @@
|
||||
# 统一错误消息数据源 - 任务清单
|
||||
|
||||
## 1. 基础设施改造
|
||||
|
||||
- [x] 1.1 在 `pkg/errors/codes.go` 中添加 `allErrorCodes` 错误码注册表
|
||||
- [x] 1.2 改造 `pkg/errors/errors.go` 中的 `New()` 函数签名为可变参数
|
||||
- [x] 1.3 改造 `pkg/errors/errors.go` 中的 `Wrap()` 函数签名为可变参数
|
||||
- [x] 1.4 在 `pkg/errors/codes.go` 中添加 `init()` 启动时校验函数
|
||||
|
||||
## 2. 测试保障
|
||||
|
||||
- [x] 2.1 在 `pkg/errors/codes_test.go` 中添加 `TestAllCodesHaveMessages` 测试
|
||||
- [x] 2.2 在 `pkg/errors/codes_test.go` 中添加 `TestNoOrphanMessages` 测试(检测孤立映射)
|
||||
- [x] 2.3 更新 `pkg/errors/handler_test.go` 测试覆盖新的函数签名
|
||||
|
||||
## 3. 业务代码清理 - Service 层
|
||||
|
||||
- [x] 3.1 清理 `internal/service/commission_withdrawal/service.go` 冗余硬编码
|
||||
- [x] 3.2 清理 `internal/service/shop/service.go` 冗余硬编码
|
||||
- [x] 3.3 清理 `internal/service/auth/service.go` 冗余硬编码
|
||||
- [x] 3.4 清理 `internal/service/shop_account/service.go` 冗余硬编码
|
||||
- [x] 3.5 清理 `internal/service/enterprise/service.go` 冗余硬编码
|
||||
- [x] 3.6 清理 `internal/service/customer/service.go` 冗余硬编码
|
||||
- [x] 3.7 清理 `internal/service/customer_account/service.go` 冗余硬编码
|
||||
- [x] 3.8 清理 `internal/service/role/service.go` 冗余硬编码
|
||||
- [x] 3.9 清理 `internal/service/permission/service.go` 冗余硬编码
|
||||
- [x] 3.10 清理 `internal/service/account/service.go` 冗余硬编码
|
||||
- [x] 3.11 清理 `internal/service/enterprise_card/service.go` 冗余硬编码
|
||||
- [x] 3.12 清理 `internal/service/my_commission/service.go` 冗余硬编码
|
||||
- [x] 3.13 清理 `internal/service/shop_commission/service.go` 冗余硬编码
|
||||
- [x] 3.14 清理 `internal/service/commission_withdrawal_setting/service.go` 冗余硬编码
|
||||
|
||||
## 4. 业务代码清理 - Handler 层
|
||||
|
||||
- [x] 4.1 清理 `internal/handler/admin/*.go` 冗余硬编码(无需清理,都是业务特定消息)
|
||||
- [x] 4.2 清理 `internal/handler/h5/*.go` 冗余硬编码(无需清理,都是业务特定消息)
|
||||
- [x] 4.3 清理 `internal/handler/app/*.go` 冗余硬编码(无需清理,都是业务特定消息)
|
||||
|
||||
## 5. 业务代码清理 - Middleware 和其他
|
||||
|
||||
- [x] 5.1 清理 `pkg/middleware/*.go` 冗余硬编码(无需清理,都是业务特定消息)
|
||||
- [x] 5.2 清理 `internal/middleware/*.go` 冗余硬编码(无需清理,都是业务特定消息)
|
||||
- [x] 5.3 清理 `internal/bootstrap/*.go` 冗余硬编码(无需清理,都是业务特定消息)
|
||||
|
||||
## 6. 验证和收尾
|
||||
|
||||
- [x] 6.1 运行完整测试套件 `go test ./pkg/...` - 全部通过
|
||||
- [x] 6.2 运行 lsp_diagnostics 检查类型错误 - 无错误
|
||||
- [x] 6.3 编译验证 `go build ./...` - 成功
|
||||
91
openspec/specs/error-code-validation/spec.md
Normal file
91
openspec/specs/error-code-validation/spec.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# error-code-validation Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change unify-error-message-source. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: 错误码消息映射完整性校验
|
||||
|
||||
系统 SHALL 在启动时校验所有已注册的错误码都有对应的 `errorMessages` 映射条目。
|
||||
|
||||
如果发现缺失映射,系统 MUST 立即 panic 并输出清晰的错误信息,指明缺失的错误码。
|
||||
|
||||
#### Scenario: 所有错误码都有映射时正常启动
|
||||
|
||||
- **WHEN** 所有 `allErrorCodes` 中的错误码都在 `errorMessages` 映射表中存在
|
||||
- **THEN** 系统正常启动,无错误日志
|
||||
|
||||
#### Scenario: 存在缺失映射时启动失败
|
||||
|
||||
- **WHEN** 某个错误码(如 `CodeNewFeature = 1099`)在 `allErrorCodes` 中注册但 `errorMessages` 中缺失
|
||||
- **THEN** 系统 panic,错误信息包含 "错误码 1099 缺少映射消息"
|
||||
|
||||
### Requirement: 错误码注册表维护
|
||||
|
||||
系统 SHALL 维护一个 `allErrorCodes` 切片,包含所有已定义的错误码常量。
|
||||
|
||||
新增错误码时,开发者 MUST 同时:
|
||||
1. 在 `codes.go` 中定义常量
|
||||
2. 在 `allErrorCodes` 中注册
|
||||
3. 在 `errorMessages` 中添加映射
|
||||
|
||||
#### Scenario: 新增错误码完整注册
|
||||
|
||||
- **WHEN** 开发者新增错误码 `CodeXxx = 1100`
|
||||
- **THEN** 必须同时在 `allErrorCodes` 和 `errorMessages` 中添加对应条目
|
||||
- **THEN** 否则启动时 panic 或测试失败
|
||||
|
||||
### Requirement: errors.New 默认使用映射表消息
|
||||
|
||||
`errors.New()` 函数 SHALL 优先使用 `errorMessages` 映射表中的消息作为默认值。
|
||||
|
||||
当调用者提供自定义消息时,系统 MUST 允许覆盖默认消息。
|
||||
|
||||
#### Scenario: 不传消息参数时使用映射表
|
||||
|
||||
- **WHEN** 调用 `errors.New(errors.CodeNotFound)`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "资源未找到"(映射表中的值)
|
||||
|
||||
#### Scenario: 传空字符串时使用映射表
|
||||
|
||||
- **WHEN** 调用 `errors.New(errors.CodeNotFound, "")`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "资源未找到"(映射表中的值)
|
||||
|
||||
#### Scenario: 传自定义消息时覆盖映射表
|
||||
|
||||
- **WHEN** 调用 `errors.New(errors.CodeNotFound, "提现申请不存在")`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "提现申请不存在"(自定义值)
|
||||
|
||||
### Requirement: errors.Wrap 默认使用映射表消息
|
||||
|
||||
`errors.Wrap()` 函数 SHALL 与 `errors.New()` 保持一致的消息处理逻辑。
|
||||
|
||||
#### Scenario: Wrap 不传消息时使用映射表
|
||||
|
||||
- **WHEN** 调用 `errors.Wrap(errors.CodeDatabaseError, originalErr)`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "数据库错误"(映射表中的值)
|
||||
- **THEN** 返回的 `AppError.Err` 为 `originalErr`
|
||||
|
||||
#### Scenario: Wrap 传自定义消息时覆盖
|
||||
|
||||
- **WHEN** 调用 `errors.Wrap(errors.CodeDatabaseError, "查询用户失败", originalErr)`
|
||||
- **THEN** 返回的 `AppError.Message` 为 "查询用户失败"
|
||||
- **THEN** 返回的 `AppError.Err` 为 `originalErr`
|
||||
|
||||
### Requirement: CI 测试覆盖映射完整性
|
||||
|
||||
系统 SHALL 提供单元测试 `TestAllCodesHaveMessages`,验证所有注册的错误码都有对应的映射。
|
||||
|
||||
此测试 MUST 在 CI 流程中运行,防止映射表腐化。
|
||||
|
||||
#### Scenario: 测试检测到缺失映射
|
||||
|
||||
- **WHEN** 运行 `go test ./pkg/errors/...`
|
||||
- **WHEN** 存在错误码在 `allErrorCodes` 但不在 `errorMessages` 中
|
||||
- **THEN** 测试失败,输出缺失的错误码列表
|
||||
|
||||
#### Scenario: 测试检测到孤立映射
|
||||
|
||||
- **WHEN** 运行 `go test ./pkg/errors/...`
|
||||
- **WHEN** 存在映射条目的错误码不在 `allErrorCodes` 中
|
||||
- **THEN** 测试失败,输出孤立的错误码列表(可选警告)
|
||||
|
||||
Reference in New Issue
Block a user