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

21 KiB
Raw Permalink Blame History

tags, aliases, date created, date modified
tags aliases date created date modified
Project Context Aggregation
星期三, 十二月 10日 2025, 11:10:48 上午 星期三, 十二月 10日 2025, 11:12:52 上午

Project Context Aggregation

Source Items: 3

==== AI 辅助基础设施构建 SOP (v2.1) - 错误处理与响应篇.md ====

# 🏗️ 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]:

在开始写代码前,请先为 `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 结构 (复制使用)

你现在是我的 **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: 伪代码验证 (复制使用)

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]:

我们现在执行 **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]:

已确认 `{文件名}` 代码无误。
请将该代码存入你的**短期记忆**,作为后续步骤的上下文依赖。
**不要重复输出它**。我们准备进入下一步。

(重复 A -> B直到所有源码文件生成完毕)


Phase 4: 极限防御测试 (Extreme Defensive Testing)

目的: 模拟“最糟糕”的业务代码调用,确保基础设施不崩。

🤖 Prompt 4: 生成红队测试用例

所有核心代码已生成。现在请为 `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: 找茬模式 (复制使用)

切换角色为 **SRE (站点可靠性工程师)**。
请审查上述所有代码ecode + app**风险排查:**
1.  **Panic 风险:** 是否有未捕获的 Panic 点?
2.  **监控盲区:** 当前的 Error Log 是否包含了足够的上下文(如 StackTrace供排查
3.  **状态码混淆:** 我们采用了“HTTP 200 + 业务码”模式,请确认这是否会影响网关层的 5xx 告警配置?

请简要列出 1-2 个优化建议。

==== 基础设施详细设计规格说明书.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)

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)

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" (副作用/注意事项),而非翻译代码逻辑。
  • 示例:

    // 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 测试。

==== 错误处理模块文件夹骨架.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 处理