11 KiB
11 KiB
tags, aliases, date created, date modified
| tags | aliases | date created | date modified | |||
|---|---|---|---|---|---|---|
|
星期三, 十二月 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包装的底层原因)。
- User Msg (Safe): JSON Body 中的
- 动态文案支持:
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 数据。
- 所有 业务逻辑错误 (Code
- HTTP 500 Internal Server Error:
- 所有 基础设施错误 (Code
1xxxx),包括 Panic、数据库断连、Redis 超时。 - 理由: 触发云厂商负载均衡器 (LB) 的熔断机制,将流量切出故障节点。
- 所有 基础设施错误 (Code
- HTTP 401/403:
- 仅用于网关层面的拦截(如 JWT 格式错误),业务层鉴权失败建议走 HTTP 200 + Code
20101。
- 仅用于网关层面的拦截(如 JWT 格式错误),业务层鉴权失败建议走 HTTP 200 + Code
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)
-
包引用原则:
ecode包必须保持零依赖(只依赖标准库)。app包依赖ecode。
-
Service 层纯净性:
internal/domain/service代码中严禁出现import "github.com/gin-gonic/gin"。internal/domain/service代码中严禁出现import "enterprise-cms-core/internal/pkg/app"。- 只允许引入
internal/pkg/ecode。
-
错误包装与响应清洗:
- Log:
app.Error(err)内部必须将err的完整堆栈打印到 Zap 日志中。 - Response:
- 若
err可被断言为*ecode.Error,则取其Msg字段返回。 - 若
err仅为普通error(如 DB error),严禁直接将其内容返回给前端,必须统一兜底返回ecode.ServerError的文案("Internal Server Error")。
- 若
- Log:
-
全局兜底机制 (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):
-
命名规范:
- 缩写: 使用全大写缩写 (如
ServeHTTP,ID,URL),严禁Url,Id。 - 局部变量: 保持短小 (如
ctx,err,req),避免 Java 式的长命名 (如requestContext,errorObject)。 - 工厂方法:
ecode包内使用New(),app包内使用NewResponse()。
- 缩写: 使用全大写缩写 (如
-
代码组织:
- 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)
为了应对未来需求变更,本模块需采用以下模式:
-
Functional Options (针对
app包):- 构造
Response对象时,应支持 Option 模式,以便未来无需破坏函数签名即可添加新字段(如 TraceID, DebugInfo)。 - 定义:
type Option func(*Response) - 签名:
func New(c *gin.Context, opts …Option) *Response
- 构造
-
Interface Segregation (接口隔离):
- 虽然
ecode是基础值对象,但app层若涉及复杂逻辑,应定义Responder接口,方便 Mock 测试。
- 虽然