Files
Inbox/Go项目实战/03_基础设施/01_错误处理/01_基础设施详细设计规格说明书.md
2025-12-11 07:24:36 +08:00

11 KiB
Raw Blame History

tags, aliases, date created, date modified
tags aliases date created date modified
🏗️ Infrastructure Design Specifications (v1.2)
🏗️ Infrastructure Design Specifications (v1.1)
🏗️ Infrastructure Design Specifications (v1.0)
星期三, 十二月 10日 2025, 9:41:53 上午 星期三, 十二月 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 必须符合以下结构:

{
  "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 测试。