From 062b314844b6286fee048b74a1db89e055dd289e Mon Sep 17 00:00:00 2001 From: liuyun Date: Sun, 11 Oct 2026 22:42:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(error):=20=E4=B8=9A=E5=8A=A1=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=A0=81=E6=B3=A8=E5=86=8C=E8=A1=A8=EF=BC=88=E5=AD=90?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=20+=20openspec=20change=20+=20guide=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/guide.md | 2 +- .../error-code-registry/.openspec.yaml | 2 + .../changes/error-code-registry/design.md | 40 +++++++++++++++++++ .../changes/error-code-registry/proposal.md | 21 ++++++++++ .../specs/shared/api-contract/spec.md | 16 ++++++++ openspec/changes/error-code-registry/tasks.md | 18 +++++++++ sources/sysmgr-nest | 2 +- 7 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 openspec/changes/error-code-registry/.openspec.yaml create mode 100644 openspec/changes/error-code-registry/design.md create mode 100644 openspec/changes/error-code-registry/proposal.md create mode 100644 openspec/changes/error-code-registry/specs/shared/api-contract/spec.md create mode 100644 openspec/changes/error-code-registry/tasks.md diff --git a/docs/guide.md b/docs/guide.md index 71f0789..73f287f 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -473,7 +473,7 @@ useDict(code) // → { items, tagMap, options },均响应式 { "code": 0, "message": "ok", "data": { } } ``` -- `code=0` 成功;失败 `code = HTTP状态码 * 100`(401→40100,403→40300,400→40000,500→50000)。 +- `code=0` 成功;失败 `code` 为业务码(5 位 `[域码 2 位][序 3 位]`,单一注册表见 [`src/common/error-codes.ts`](../sources/sysmgr-nest/src/common/error-codes.ts),如 `10001` 账号或密码错误、`11002` 用户名已存在);未登记业务码的异常回退 `HTTP状态码 × 100`(40000/40100/40300/40400/40900/50000)。 - 前端 `http` 已自动拆包:成功拿 `data`,失败抛 `ApiError(code, message)`。 ### 4.2 分页 diff --git a/openspec/changes/error-code-registry/.openspec.yaml b/openspec/changes/error-code-registry/.openspec.yaml new file mode 100644 index 0000000..a369219 --- /dev/null +++ b/openspec/changes/error-code-registry/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-11 diff --git a/openspec/changes/error-code-registry/design.md b/openspec/changes/error-code-registry/design.md new file mode 100644 index 0000000..dacc913 --- /dev/null +++ b/openspec/changes/error-code-registry/design.md @@ -0,0 +1,40 @@ +## Context + +`AllExceptionsFilter` 目前把 `HttpException` 统一映射为 `code = HTTP 状态 ×100`;业务 throw 都是 `BadRequestException/ConflictException/NotFoundException('字符串')`,无 code。前端 `ApiError(code, message)` 已读 code,但无稳定业务码可分支。动机见 proposal.md - Why。 + +## Goals / Non-Goals + +**Goals:** + +- 建立单一错误码注册表与 `BusinessException`,迁移关键业务错误。 + +**Non-Goals:** + +- 不一次性迁移全部 throw(逐步);不引入错误码国际化/多语言。 + +## Decisions + +### D1 — code 方案:`[域码 2 位][序 3 位]` +5 位数字。域码固定:00 通用、10 认证、11 用户、12 角色、13 资源、14 组织、15 租户、16 字典、17 配置、18 审计、19 登录、20 会话、21 通知。HTTP 状态由 `BusinessException` 单独携带,`code` 与 HTTP 解耦。 + +### D2 — `BusinessException` + 过滤器识别 +`src/common/business-exception.ts`:`class BusinessException extends HttpException { constructor(code, message, status = 400) }`。`AllExceptionsFilter`:若为 `BusinessException` 用其 `code`,否则回退 `status×100`。 + +### D3 — 注册表为单一事实源 +`src/common/error-codes.ts` 集中登记:通用码 + 各域码,注释标明 HTTP 状态。新增业务错误先在注册表加码,再 `throw new BusinessException(ErrorCodes.XXX, '...')`。 + +### D4 — 迁移关键错误(示例集) +迁移:登录(账号或密码错误/锁定/停用)、待改密门禁、用户名冲突、原密码错误、用户/租户/资源不存在、越权等高频错误;其余保留字符串(可后续迁移)。 + +## Risks / Trade-offs + +- **[code 与 HTTP 解耦后前端需注意]** → 前端 `ApiError.code` 现为业务码;HTTP 状态仍用于 401 自动刷新(http.ts 按 status 判断,不受影响)。 +- **[迁移不完整]** → 未迁移的仍回退 `status×100`,与注册码并存,逐步收敛。 + +## Migration Plan + +无数据迁移;纯后端代码 + 文档。回滚即撤销。 + +## Open Questions + +无。 diff --git a/openspec/changes/error-code-registry/proposal.md b/openspec/changes/error-code-registry/proposal.md new file mode 100644 index 0000000..a4e3fc4 --- /dev/null +++ b/openspec/changes/error-code-registry/proposal.md @@ -0,0 +1,21 @@ +## Why + +当前业务错误都是字符串消息(`throw new BadRequestException('用户名已存在')`),无统一错误码,前端无法按 code 分支处理,fork 后各模块各自为政。需要一套「业务域 + 错误序」的错误码注册表作为单一事实源。 + +## What Changes + +- 新增 `src/common/error-codes.ts` 错误码注册表(5 位数字 `[域码 2 位][序 3 位]`,域码集中登记)。 +- 新增 `BusinessException(code, message, status)`;`AllExceptionsFilter` 识别业务码,非业务异常仍回退 `HTTP×100`。 +- 迁移关键业务错误(登录/锁定/待改密/用户名冲突/租户不存在等)到注册码。 + +## Capabilities + +### Modified Capabilities + +- `shared/api-contract`: 「错误码分段」改为「业务域 + 错误序」细分段。 + +## Impact + +- **后端**:`error-codes.ts` + `business-exception.ts`(common)、`AllExceptionsFilter` 识别业务码、auth/users/tenants 等关键 throw 迁移。 +- **文档**:guide.md 补错误码注册表说明。 +- 无数据/前端变更(前端仍读 `code` + `message`)。 diff --git a/openspec/changes/error-code-registry/specs/shared/api-contract/spec.md b/openspec/changes/error-code-registry/specs/shared/api-contract/spec.md new file mode 100644 index 0000000..c5d30b8 --- /dev/null +++ b/openspec/changes/error-code-registry/specs/shared/api-contract/spec.md @@ -0,0 +1,16 @@ +## MODIFIED Requirements + +### Requirement: 错误码分段 +错误码 SHALL 采用「业务域 + 错误序」分段:5 位数字 `[域码 2 位][序 3 位]`,域码集中登记于 `error-codes` 注册表(00 通用、10 认证、11 用户、12 角色、13 资源、14 组织、15 租户、16 字典、17 配置、20 会话、21 通知…);HTTP 状态仍按语义返回(400/401/403/404/409/500),`code` 为业务码。 + +#### Scenario: 未登录 +- **WHEN** 未携带有效 token 访问受保护接口 +- **THEN** HTTP 401,code 为认证域业务码(如 10002),无业务码时回退 40100 + +#### Scenario: 业务错误码 +- **WHEN** 业务处理失败(如用户名已存在) +- **THEN** 返回对应 HTTP 状态 + 注册表内的业务码与可读 message + +#### Scenario: 系统错误兜底 +- **WHEN** 非业务异常(框架/未知错误) +- **THEN** code 回退为 HTTP 状态 ×100 diff --git a/openspec/changes/error-code-registry/tasks.md b/openspec/changes/error-code-registry/tasks.md new file mode 100644 index 0000000..4092a5f --- /dev/null +++ b/openspec/changes/error-code-registry/tasks.md @@ -0,0 +1,18 @@ +## 1. 基础设施 + +- [x] 1.1 新增 `src/common/error-codes.ts`(域码 + 通用码 + 各域码集中登记) +- [x] 1.2 新增 `src/common/business-exception.ts`(`BusinessException(code, message, status)`) +- [x] 1.3 `AllExceptionsFilter` 识别 `BusinessException`,否则回退 `status×100` + +## 2. 迁移关键错误 + +- [x] 2.1 `auth.service`:登录失败/锁定/停用/待改密/原密码错误 +- [x] 2.2 `users.service`:用户名冲突、用户不存在 +- [x] 2.3 `tenants.service`:租户不存在/已存在 +- [x] 2.4 `sessions.service`:会话不存在 + +## 3. 文档与验证 + +- [x] 3.1 guide.md 补错误码注册表说明 +- [x] 3.2 后端 build + 单测 + e2e 通过 +- [x] 3.3 冒烟:触发一个业务错误,返回注册业务码而非 status×100 diff --git a/sources/sysmgr-nest b/sources/sysmgr-nest index f747ad5..8eefa7f 160000 --- a/sources/sysmgr-nest +++ b/sources/sysmgr-nest @@ -1 +1 @@ -Subproject commit f747ad5442e314f41d9319b61941da5f7af69096 +Subproject commit 8eefa7fa4499ed9b3c733211794360fad26061b4