260 lines
11 KiB
Markdown
260 lines
11 KiB
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 测试。
|