Files
Inbox/Go项目实战/03_基础设施/01_错误处理/99_错误处理上下文.md
2025-12-11 07:24:36 +08:00

549 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

---
tags: []
aliases:
- Project Context Aggregation
date created: 星期三, 十二月 10日 2025, 11:10:48 上午
date modified: 星期三, 十二月 10日 2025, 11:12:52 上午
---
# Project Context Aggregation
> Source Items: 3
==== AI 辅助基础设施构建 SOP (v2.1) - 错误处理与响应篇.md ====
```markdown
# 🏗️ AI 辅助基础设施构建 SOP (v2.1) - [错误处理与响应篇]
**核心理念:**
1. **Contract First (契约优先):** 永远先定义对外暴露的 JSON 结构,再写内部 Go 结构体。
2. **DX Driven (体验驱动):** 在实现逻辑前,先写“伪代码”验证调用是否顺手。
3. **Atomic Delivery (原子交付):** 单次交互只生成一个文件,利用“上下文锚点”串联上下文。
---
## 📋 准备工作:变量与架构确认
在使用以下 Prompt 前,请确认上下文:
- `{语言/框架}`: Go 1.24+ / Gin
- `{模块路径}`:
- `internal/pkg/ecode` (Level 0: 错误码 + 错误实体 + 映射逻辑)
- `internal/pkg/app` (Level 1: HTTP 响应封装,依赖 `ecode`)
- `{架构约束}`: `ecode` 包零依赖;`app` 包依赖 `ecode`
---
## Phase 0: 原子化任务拆解 (The MECE Protocol)
**目的:** 将大需求拆解为一组符合 MECE 原则的微任务清单。
### 🤖 拆解者 Prompt (复制使用)
```Markdown
你现在是我的 **Tech Lead (技术负责人)**。
我们要实现 `{模块名称}` 模块。为了防止代码生成中断和逻辑混乱,请不要直接开始写代码。
请先执行 **“MECE 任务拆解”**
**1. 架构约束分析:**
- 本模块遵循 Modular Clean Architecture。
- `internal/pkg/ecode`: 包含错误码常量、错误实体结构体、错误文案映射。**严禁依赖上层包**。
- `internal/pkg/app`: 包含 Gin 的 Response 封装。依赖 `ecode`。
**2. 原子化切分:**
请将开发工作拆解为 3-5 个“原子任务步”。
- 每个步骤必须针对**单个物理文件**。
- 步骤必须遵循依赖顺序(底层先于上层)。
**3. 输出格式:**
请输出一个 **Markdown Checklist (执行清单)**
格式示例:
- [ ] **Step 1: {文件名}** - {核心职责} (依赖: 无)
- [ ] **Step 2: {文件名}** - {核心职责} (依赖: Step 1)
**模块需求:**
我们需要一套统一的 HTTP 错误处理机制,支持自定义业务错误码,统一返回 JSON 格式。
```
---
## Phase 0.5: API 签名锁定 (API Surface Lock)
**目的:** 在实现具体逻辑前,强制锁定所有 Public 方法的签名,防止实现阶段出现参数不一致。
### 🤖 Prompt 0.5: 生成接口定义
**[发送给 AI]:**
````markdown
在开始写代码前,请先为 `internal/pkg/app` 包定义 **Public API 签名 (Exported Functions)**。
请直接提供 `Responder` 接口定义或核心函数的函数头(无需函数体)。
**要求:**
1. **一致性:** 确认 `context` 参数的位置(建议统一作为第一个参数)。
2. **完整性:** 必须包含 `New`, `Success`, `Error` 以及我们刚才讨论的 `ErrorCtx` (处理 trace_id)。
3. **Go Doc:** 为每个方法写出符合 Go 标准的注释。
**期望输出示例:**
```go
// Response wraps the gin.Context for unified JSON response.
type Response struct { … }
// New creates a new Response wrapper.
func New(c *gin.Context) *Response { … }
// Success sends a successful response with data.
func (r *Response) Success(data any) { … }
```
````
---
## Phase 1: 契约定义 (Contract Definition)
**目的:** 确立“对外口径”。
### 🤖 Prompt 1: 定义 JSON 结构 (复制使用)
```Markdown
你现在是我的 **API 治理专家**。
请设计一套统一的 **HTTP 响应结构 (JSON Envelope)**。
**设计原则:**
1. **统一性:** 无论成功还是失败Body 结构一致。
2. **字段要求:** 必须包含 `code` (int), `msg` (string), `data` (any), `trace_id` (string)。
**任务:**
请给出以下 3 种场景的 JSON 响应示例,并解释设计理由:
- 场景 A: 成功返回对象。
- 场景 B: 成功返回空列表 (明确 `data` 是 `null` 还是 `[]`)。
- 场景 C: 业务错误 (如 Code 20001)。
**[关键补充约束]**
1. **安全性优先:** `app.Error(err)` 处理逻辑中,必须区分**用户可见文案**和**底层调试信息**。若 `err` 包含底层堆栈(如 SQL 错误JSON 中的 `msg` 必须降级显示为 `ecode` 定义的通用文案(如 "Internal Error"),严禁透传底层 Error String。
2. **HTTP 状态码:** 本项目强制执行 **"HTTP 200 OK + Business Code"** 策略。除非 Gin 框架层崩溃,否则 HTTP Status 永远为 200。
3. **Trace ID:** 假设 `c.GetString("trace_id")` 可以获取 ID请在 `app.New(c)` 时将其注入 Response 结构体。
```
---
## Phase 2: 体验验证 (DX Verification)
**目的:** 模拟业务层调用,防止基础设施“反人类”。
### 🤖 Prompt 2: 伪代码验证 (复制使用)
```Markdown
JSON 结构已确认。
假设我们已经有了 `internal/pkg/ecode` 和 `internal/pkg/app`。
请写一段 Gin Handler 的 **伪代码 (Pseudo-code)**,展示开发者该如何使用它们。
**验证重点:**
1. **业务错误:** 如何返回 `ecode.New(20001, "…")`
2. **响应封装:** 如何调用 `app.New(c).Success(data)`
3. **代码简洁性:** 避免大量的 `if err != nil` 重复代码。
请展示最优雅的写法。
```
---
## Phase 3: 迭代式核心实现 (Iterative Implementation)
**核心机制:** 这是一个**循环步骤**。请查看 Phase 0 生成的 Checklist**逐个文件**执行。
### 🔄 循环动作 A: 生成代码
**[用户动作]:** 复制 Checklist 中当前未完成的步骤(例如 "Step 1: 生成 ecode/code.go")。
**[发送 Prompt]:**
```Markdown
我们现在执行 **Step {N}**。
**任务目标:**
{粘贴 Phase 0 Checklist 中的当前步骤描述}
**上下文约束 (严禁修改):**
1. **JSON 契约:** `{粘贴 Phase 1 确认的 JSON}`
2. **DX 规范:** `{粘贴 Phase 2 确认的伪代码}`
3. **依赖控制:** 如果是 `ecode` 包,严禁引用 `app` 或 `gin`。
**输出要求:**
请仅生成该步骤对应的 `{文件名}` 源代码。不要生成测试代码。
**通用代码质量约束 (Linter Rules):**
1. **注释规范:** 所有 Exported (首字母大写) 的结构体、函数、常量必须包含符合 Go Doc 规范的注释。
2. **复杂度控制:** 确保 `gocyclo` (圈复杂度) 低于 10。如果逻辑复杂请拆分为私有函数。
3. **错误检查:** 严禁忽略 error 返回值(如 `json.Marshal`),必须处理或 Log。
4. **Lint 检查:** 生成的代码必须能通过 `errcheck` 和 `staticcheck`。
```
### 🔄 循环动作 B: 上下文锚点 (Context Anchoring)
**[用户动作]:** 代码生成并确认无误后,发送此 Prompt 以建立记忆锚点。
**[发送 Prompt]:**
```Markdown
已确认 `{文件名}` 代码无误。
请将该代码存入你的**短期记忆**,作为后续步骤的上下文依赖。
**不要重复输出它**。我们准备进入下一步。
```
_(重复 A -> B直到所有源码文件生成完毕)_
---
## Phase 4: 极限防御测试 (Extreme Defensive Testing)
**目的:** 模拟“最糟糕”的业务代码调用,确保基础设施不崩。
### 🤖 Prompt 4: 生成红队测试用例
```markdown
所有核心代码已生成。现在请为 `internal/pkg/app/response.go` 编写单元测试 `response_test.go`。
**请覆盖以下 4 个极端场景 (Test Cases):**
1. **Raw Error 降级:**
- **场景:** 传入 `errors.New("db connection broken")` (非 ecode 类型)。
- **断言:** HTTP 状态码为 500 (或 200+Code 50000)Msg 为 "Internal Server Error" (严禁泄漏原始错误信息)。
2. **Double Response 防护:**
- **场景:** 在同一个 Handler 中连续调用 `app.Success()` 两次。
- **断言:** 第二次调用应被忽略或记录 Warning 日志,且不应导致 Panic。
3. **Nil Data 安全:**
- **场景:** 调用 `app.Success(nil)`。
- **断言:** JSON 中的 `data` 字段应为 `null` (或 `{}`,取决于契约),不应 Panic。
4. **并发 Map 读写:**
- **场景:** 启动 100 个 Goroutine 并发调用 `ecode.GetMsg(code)`。
- **断言:** `test -race` 必须通过,无数据竞争。
请输出完整的 Test 代码。
```
---
## Phase 5: 最终验收 (SRE Review)
**目的:** 模拟运维视角审查。
### 🤖 Prompt 5: 找茬模式 (复制使用)
```Markdown
切换角色为 **SRE (站点可靠性工程师)**。
请审查上述所有代码ecode + app
**风险排查:**
1. **Panic 风险:** 是否有未捕获的 Panic 点?
2. **监控盲区:** 当前的 Error Log 是否包含了足够的上下文(如 StackTrace供排查
3. **状态码混淆:** 我们采用了“HTTP 200 + 业务码”模式,请确认这是否会影响网关层的 5xx 告警配置?
请简要列出 1-2 个优化建议。
```
```bash
==== 基础设施详细设计规格说明书.md ====
```markdown
---
tags: []
aliases:
- 🏗️ Infrastructure Design Specifications (v1.2)
- 🏗️ Infrastructure Design Specifications (v1.1)
- 🏗️ Infrastructure Design Specifications (v1.0)
date created: 星期三, 十二月 10日 2025, 9:41:53 上午
date modified: 星期三, 十二月 10日 2025, 10:37:49 上午
---
# 🏗️ Infrastructure Design Specifications (v1.2)
项目名称: Enterprise-CMS-Core
模块: Infrastructure (Error Handling & Response)
版本: 1.2.0 (Refined)
状态: [✅ 已锁定]
---
## 1. 设计目标 (Design Objectives)
本模块旨在构建一套**统一的、类型安全的、框架解耦**的 HTTP 响应与错误处理机制。
- **统一性:** 无论成功与否API 必须返回结构一致的 JSON Envelope。
- **可观测性:** 错误必须携带业务语义ErrorCode而非仅返回 HTTP 500。
- **解耦性:** 业务逻辑层 (Service) 不感知 HTTP 框架 (Gin),仅通过 Go 原生 `error` 接口交互。
- **高内聚:** 错误码定义与错误实体封装在同一包内,减少调用摩擦。
---
## 2. 技术选型基线 (Tech Stack Baseline)
|**组件**|**选型**|**约束说明**|
|---|---|---|
|**HTTP Context**|`github.com/gin-gonic/gin`|仅在 `internal/pkg/app` (Level 1) 和 `handler` 层使用。**严禁**在 `service` 层引入。|
|**Error Handling**|Go Standard Library|使用 Go 1.13+ `errors` (`Is`, `As`, `New`) 和 `fmt.Errorf`。**严禁**引入第三方 error 库 (如 `pkg/errors`)。|
|**Serialization**|`encoding/json`|使用标准库。MVP 阶段暂不引入 `json-iterator`。|
|**Concurrency**|`sync.RWMutex`|用于保护错误码 Map 的并发读取(读多写少场景)。|
| **Tracing** | Gin Keys | Trace ID 必须由前置中间件或网关注入。Gin Context Key 约定为 `"X-Trace-ID"`。 |
---
## 3. 核心设计模式 (Design Patterns)
### 3.1 响应封装Context Object & Factory
采用 **“上下文对象”** 模式对 `gin.Context` 进行封装,提供链式调用的体验。
- **模式:** `app.New(c).Success(data)`
- **优势:** 屏蔽底层框架差异,统一入口。
### 3.2 错误处理:安全与动态机制 (Security & Dynamics)
- **双层信息架构:**
- **User Msg (Safe):** JSON Body 中的 `msg` 字段。**仅**允许返回 `ecode` 中定义的静态文案,或经过白名单过滤的动态参数(如参数名)。
- **Log Detail (Unsafe):** 服务端日志。必须记录完整的 `err.Error()`包含堆栈、SQL 错误、`fmt.Errorf` 包装的底层原因)。
- **动态文案支持:**
- `ecode` 包需提供 `WithMsg(msg string)` 或 `WithDetails(args …any)` 方法,用于**安全地**覆盖默认文案。
- **示例:** `return ecode.InvalidParams.WithMsg("Email 格式错误")`。
### 3.3 状态码管理Centralized Registry
采用 **“集中式注册表”** 模式。
- **约束:** 所有业务错误码 (Business Code) 必须在 `internal/pkg/ecode` 包中定义为 `const`。
- **禁止:** 严禁在业务代码中硬编码数字Magic Number
### 3.4 错误码号段分配Error Code Allocation
结构定义:
错误码采用 5 位数字结构A BB NN
- **A (万位):** 模块/领域 (1=Infra, 2=User, 3=Content…)
- **BB (千百位):** 组件/子模块分类
- **NN (十个位):** 具体错误流水号
#### 1. 基础设施层 (System / Infra) - `10000 - 19999`
针对基础设施,**必须**严格遵守以下二级分类,严禁混用:
|**二级区间 (Sub-Range)**|**组件归属 (Component)**|**典型示例 (Examples)**|
|---|---|---|
|**10000 - 10099**|**Server General**|`10000` (Success), `10001` (Unknown Error), `10002` (Panic Recovered)|
|**10100 - 10199**|**Database (Internal)**|`10100` (DB Connection Lost), `10101` (SQL Syntax Error) - _注意业务查空属业务码不在此列_|
|**10200 - 10299**|**Cache (Redis)**|`10200` (Redis Timeout), `10201` (Key Evicted Unexpectedly)|
|**10300 - 10399**|**Serialization**|`10300` (JSON Marshal Failed), `10301` (Invalid Request Body)|
|**10400 - 10499**|**Middleware/Gateway**|`10400` (Too Many Requests/Rate Limit), `10401` (Route Not Found)|
|**10500 - 10599**|**3rd Party API**|`10500` (External Service Unavailable), `10501` (SMS Send Failed)|
#### 2. 业务模块层 (Business Modules) - `20000+`
业务模块建议参考同等逻辑进行二级划分(由各模块负责人定义,但建议遵循以下范式):
|**一级区间**|**模块**|**二级区间示例**|
|---|---|---|
|**20000 - 29999**|**User / Auth**|`200xx` (基础账户), `201xx` (登录/Token), `202xx` (RBAC 权限), `203xx` (KYC 认证)|
|**30000 - 39999**|**Content (CMS)**|`300xx` (文章), `301xx` (分类/标签), `302xx` (评论), `303xx` (审核流)|
---
## 4. 交互协议与数据流 (Interaction Protocol)
### 4.1 JSON 响应契约 (The Contract)
所有 HTTP 接口返回的 Body 必须符合以下结构:
```JSON
{
"code": 20001, // 业务状态码 (0=成功, 非0=错误)
"msg": "用户已存在", // 开发者提示/用户提示
"data": { … }, // 业务数据 (成功时为 Object/Array, 失败时为 null)
"trace_id": "abc-123" // 必填。取值优先级: c.GetHeader("X-Trace-ID") -> c.GetString("X-Trace-ID") -> UUID生成
}
```
### 4.2 HTTP 状态码策略 (Status Code Policy)
本项目采用 **"Hybrid 策略 "**
- **HTTP 200 OK:**
- 所有 **业务逻辑错误** (Code `2xxxx` - `4xxxx`)。
- 前端通过 Body 中的 `code != 0` 判断业务异常。
- _理由:_ 避免网关(如 Nginx拦截 4xx 响应并替换为默认错误页,导致前端拿不到 JSON 数据。
- **HTTP 500 Internal Server Error:**
- 所有 **基础设施错误** (Code `1xxxx`),包括 Panic、数据库断连、Redis 超时。
- _理由:_ 触发云厂商负载均衡器 (LB) 的熔断机制,将流量切出故障节点。
- **HTTP 401/403:**
- 仅用于网关层面的拦截(如 JWT 格式错误),业务层鉴权失败建议走 HTTP 200 + Code `20101`。
### 4.3 跨层交互时序 (Cross-Layer Flow)
```mermaid
sequenceDiagram
participant C as Controller (Handler)
participant S as Service (Domain)
participant I as Infra (pkg/app)
participant E as Ecode (pkg/ecode)
C->>I: app.New(c) 初始化
C->>S: Call Business Logic
alt 成功
S-->>C: return (data, nil)
C->>I: app.Success(data)
I-->>Client: JSON {code:0, data:…}
else 失败 (业务错误)
S-->>C: return (nil, ecode.New(20001))
C->>I: app.Error(err)
I->>I: errors.As(err) -> 提取 Code 20001
I-->>Client: JSON {code:20001, msg:"…"}
else 失败 (系统错误)
S-->>C: return (nil, errors.New("DB error"))
C->>I: app.Error(err)
I->>I: errors.As(err) -> 失败 (Fallback)
I-->>Client: JSON {code:50000, msg:"Internal Error"}
end
```
---
## 5. 目录结构与职责 (Directory & Responsibilities)
```Plaintext
internal/
├── middleware/ # [New] 全局中间件
│ ├── recovery.go # Panic 捕获 -> 转换为 ecode.ServerError (50000)
│ └── not_found.go # 404 捕获 -> 转换为 ecode.NotFound (40400)
└── pkg/
├── ecode/ # [Level 0] 错误核心包 (无内部依赖)
│ ├── code.go # const 常量定义 (UserNotFound = 20001)
│ ├── msg.go # 错误码文案映射 (Map & GetMsg)
│ └── error.go # Error 结构体定义 (New, Parse 方法)
└── app/ # [Level 1] HTTP 响应封装 (依赖 gin, ecode)
└── response.go # NewResponse, Success, Error 方法
```
---
## 6. 开发规范与 Linter 规则 (Linting Rules)
1. **包引用原则:**
- `ecode` 包必须保持零依赖(只依赖标准库)。
- `app` 包依赖 `ecode`。
2. **Service 层纯净性:**
- `internal/domain/service` 代码中**严禁出现** `import "github.com/gin-gonic/gin"`。
- `internal/domain/service` 代码中**严禁出现** `import "enterprise-cms-core/internal/pkg/app"`。
- 只允许引入 `internal/pkg/ecode`。
3. **错误包装与响应清洗:**
- **Log:** `app.Error(err)` 内部必须将 `err` 的完整堆栈打印到 Zap 日志中。
- **Response:**
- 若 `err` 可被断言为 `*ecode.Error`,则取其 `Msg` 字段返回。
- 若 `err` 仅为普通 `error` (如 DB error)**严禁**直接将其内容返回给前端,必须统一兜底返回 `ecode.ServerError` 的文案("Internal Server Error")。
4. **全局兜底机制 (Global Safety Net):**
- 项目必须在 `internal/middleware` 中实现 `Recovery` 中间件。
- **严禁**让 Gin 默认的 Panic 堆栈直接输出到 HTTP Body。
- **必须**捕获所有 Panic并调用 `app.Error(ecode.ServerError)` 统一输出为符合 JSON 契约的格式 (`{"code": 50000, "msg": "Internal Server Error", …}`)。
---
## 7. 工程化实施标准 (Engineering Standards)
### 7.1 代码风格契约 (Code Style Contract)
为确保代码长期可维护,生成的代码必须严格遵守以下 Go 惯用语 (Idioms)
1. **命名规范:**
- **缩写:** 使用全大写缩写 (如 `ServeHTTP`, `ID`, `URL`),严禁 `Url`, `Id`。
- **局部变量:** 保持短小 (如 `ctx`, `err`, `req`),避免 Java 式的长命名 (如 `requestContext`, `errorObject`)。
- **工厂方法:** `ecode` 包内使用 `New()`, `app` 包内使用 `NewResponse()`。
2. **代码组织:**
- **Import 分组:** 标准库 -> 第三方库 -> 内部库 (enterprise-cms-core/…)。
- **Guard Clauses:** 优先使用“卫语句”提前返回,减少 `else` 嵌套层级。
### 7.2 注释与文档 (Documentation)
为了提升团队协作效率,所有 Exported (首字母大写) 的类型、函数、常量必须包含符合 GoDoc 规范的**中文注释**。
- **格式规范:** `// FunctionName 中文描述…`
- **关键:** 注释**必须**以函数/变量名开头,且与中文描述之间**保留一个空格**。这是 Go 官方工具链解析文档的标准要求。
- **内容重心:**
- **摘要:** 第一行简明扼要地说明“它是做什么的”。
- **详情 (可选):** 解释 **"Why" (设计意图)** 和 **"Caveats" (副作用/注意事项)**,而非翻译代码逻辑。
- **示例:**
```Go
// Success 向客户端写入标准的 JSON 成功响应。
//
// 注意:
// 1. 无论业务逻辑如何,此方法会将 HTTP 状态码强制设置为 200。
// 2. data 字段若为 nil将序列化为 JSON 的 null。
func (r *Response) Success(data any) { … }
// UserNotFound 表示用户不存在的业务错误码 (20001)。
const UserNotFound = 20001
```
### 7.3 可扩展性设计 (Extensibility Patterns)
为了应对未来需求变更,本模块需采用以下模式:
1. **Functional Options (针对 `app` 包):**
- 构造 `Response` 对象时,应支持 Option 模式,以便未来无需破坏函数签名即可添加新字段(如 TraceID, DebugInfo
- *定义:* `type Option func(*Response)`
- *签名:* `func New(c *gin.Context, opts …Option) *Response`
2. **Interface Segregation (接口隔离):**
- 虽然 `ecode` 是基础值对象,但 `app` 层若涉及复杂逻辑,应定义 `Responder` 接口,方便 Mock 测试。
```bash
==== 错误处理模块文件夹骨架.md ====
```markdown
---
tags: []
date created: 星期三, 十二月 10日 2025, 11:00:25 上午
date modified: 星期三, 十二月 10日 2025, 11:04:26 上午
---
```plaintext
enterprise-cms-core/
├── internal/
│ ├── pkg/
│ │ ├── ecode/ # [Level 0] 基础领域层
│ │ │ ├── code.go # [Const] 纯常量定义 (ErrorCode Registry)
│ │ │ ├── error.go # [Type] 核心结构体定义 (struct Error)
│ │ │ ├── msg.go # [Data] 错误码文案映射 (var msg map[int]string)
│ │ │ └── ecode_test.go # [Test] 单元测试
│ │ │
│ │ └── app/ # [Level 1] 应用工具层
│ │ ├── responder.go # [Interface] 👈 修正点: 定义 type Responder interface
│ │ ├── response.go # [Impl] 定义 type Response struct (实现逻辑)
│ │ ├── options.go # [Pattern] 定义 Functional Options (配置扩展)
│ │ └── response_test.go # [Test] 单元测试
│ │
│ └── middleware/ # [Global]
│ ├── recovery.go # Panic 捕获
│ └── not_found.go # 404 处理
```
```bash