4.2 KiB
4.2 KiB
tags, aliases, date created, date modified
| tags | aliases | date created | date modified | |
|---|---|---|---|---|
|
星期三, 十二月 10日 2025, 11:23:15 上午 | 星期三, 十二月 10日 2025, 12:12:46 中午 |
Phase 1 统一响应结构定义 (The Contract)
📦 统一响应结构定义 (The Contract)
所有 HTTP 接口(无论成功与否)必须严格返回以下 JSON 结构:
{
"code": 20001, // 业务状态码 (0=成功, 非0=错误)
"msg": "用户已存在", // 用户可见的提示文案 (Safe Message)
"data": { ... }, // 业务数据 payload (成功时返回,失败时通常为 null)
"trace_id": "a1b2-c3d4" // 全链路追踪 ID (必填,用于 SRE 排查)
}
🎨 场景示例与设计理由
🟢 场景 A: 成功返回对象 (Single Object)
请求: GET /api/v1/users/1001
HTTP Status: 200 OK
{
"code": 0,
"msg": "OK",
"data": {
"user_id": 1001,
"nickname": "TechLead_01",
"avatar": "https://cdn.example.com/u/1001.jpg"
},
"trace_id": "0a1b2c3d-4e5f-6789-1234-567890abcdef"
}
📌 设计理由:
- Code 0: 符合业界惯例(如 Google/Tencent API),
0明确表示逻辑执行成功。 - Data 类型: 返回具体的 Object。
🟡 场景 B: 成功返回空列表 (Empty List)
请求: GET /api/v1/articles?category=golang (假设该分类下无文章)
HTTP Status: 200 OK
{
"code": 0,
"msg": "OK",
"data": {
"list": [],
"total": 0
},
"trace_id": "0a1b2c3d-4e5f-6789-1234-567890abcdef"
}
📌 设计理由:
- Data 不为
null: 对于列表型接口,data内部的list字段必须返回空数组[],而不是null。- 原因: 前端可以直接调用
.map()或.forEach()而无需判空,极大降低前端出现Cannot read property 'map' of null的崩溃风险。
- 原因: 前端可以直接调用
- 结构一致性: 即使是列表,建议包裹在 Object 中(如
{list: [], total: 0}),方便未来扩展分页字段。
🔴 场景 C: 业务/系统错误 (Error Handling)
这里我们需要区分 “预期内的业务错误” 和 “预期外的系统错误”,但在 JSON 表现上它们必须是一致的。
Case C-1: 预期内的业务错误
场景: 用户尝试注册已存在的邮箱。
Service 层返回: ecode.UserAlreadyExist (Code: 20001)
{
"code": 20001,
"msg": "用户已存在",
"data": null,
"trace_id": "0a1b2c3d-4e5f-6789-1234-567890abcdef"
}
Case C-2: 预期外的系统错误 (触发安全防御)
场景: 数据库突然断连,GORM 返回 dial tcp 127.0.0.1:5432: connect: connection refused。
Service 层返回: 原生 error 对象。
{
"code": 50000,
"msg": "Internal Server Error", // <--- 严禁显示 "dial tcp ..."
"data": null,
"trace_id": "0a1b2c3d-4e5f-6789-1234-567890abcdef"
}
📌 设计理由:
- Security (安全降级):
app.Error(err)的内部逻辑必须执行 Type Assertion (类型断言)。- 若
err是*ecode.Error,则透传其msg(Case C-1)。 - 若
err是普通error(Case C-2),视为系统级异常。必须将 JSON 中的msg强制重写为"Internal Server Error"或通用文案,防止数据库表结构、IP 地址等敏感信息泄露给攻击者。
- HTTP 200: 即使是 Code 50000,HTTP Status 依然保持 200。这确保了网关层(Nginx/Gateway)不会拦截 Body,前端始终能解析 JSON 拿到
code和trace_id用于展示和报错。
🛡️ 关键实现逻辑预告
为了实现上述契约,在接下来的 Step 5: internal/pkg/app/response.go 中,我们将实现如下核心逻辑:
- Trace ID 注入: 在
New(c)时执行r.traceID = c.GetString("trace_id")。 - 错误清洗:
// 伪代码逻辑预览
func (r *Response) Error(err error) {
if e, ok := err.(*ecode.Error); ok {
// 业务错误:直接使用
r.json(e.Code(), e.Msg())
} else {
// 系统错误:记录原始日志,但在 JSON 中降级
log.Error("System Error", zap.Error(err), zap.String("trace_id", r.traceID))
r.json(ecode.ServerErr, "Internal Server Error") // 🔒 安全替换
}
}
这套设计已经满足了 Phase 1 的所有契约要求。