Files
cps-develop-docs/02 - Design Standard/2.2 API 接口设计规范.md

159 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# **2.2 API 接口设计规范**
API 是前后端、微服务之间沟通的**核心契约**。优秀的 API 设计不仅能大幅降低沟通成本,还能提升系统的稳定性和可维护性。一旦 API 契约对外发布,就必须保持向前兼容。
### **2.2.1 协议选型与设计原则**
根据不同的业务场景,我们支持以下三种主要的接口协议,严禁在不恰当的场景滥用协议:
#### **1\. RESTful API (默认首选)**
适用于绝大多数 Web 端、App 端的外部接口以及常规业务交互。
* **面向资源:** URL 必须全部使用**名词**的复数形式,严禁在 URL 中包含动词(如 /get-users 是错误示范,应为 GET /users。名词推荐使用短横线分隔Kebab-case如 /user-profiles
* **HTTP 动词语义化:**
* GET查询资源幂等绝对不能产生副作用如修改数据
* POST创建新资源非幂等。
* PUT全量更新资源幂等。
* PATCH局部更新资源。
* DELETE删除资源。
* **版本控制:** API 必须具备版本号,推荐放在 URL 路径中(如 /api/v1/users以便于后续重大重构时的平滑迁移。
* **务实原则:** 追求“实用级” RESTLevel 2不强求满足极致的 HATEOASLevel 3返回一堆超链接避免过度设计增加前端解析负担。
#### **2\. gRPC (内部微服务首选)**
适用于**后端微服务之间**的高频、内部通信。
* **优势:** 基于 HTTP/2 和 Protobuf强类型约束序列化体积小性能远超 HTTP+JSON。
* **规约:** 严禁将 gRPC 服务直接暴露给公网 Web/App 端(除非有特定的 API Gateway 或 gRPC-Web 转换层)。对外一律暴露 RESTful 或 GraphQL。
#### **3\. GraphQL (复杂聚合场景可选)**
适用于 BFF聚合层或前端对数据灵活性要求极高、且存在严重“过度获取(Over-fetching)”或“获取不足(Under-fetching)”的复杂场景。
* **规约:** 不要在简单的 CRUD 业务中引入 GraphQL它会极大地增加后端的 N+1 查询优化难度和权限控制复杂度。必须在架构决策ADR中经过严格评审后方可使用。
### **2.2.2 统一的请求头 (Headers) 与鉴权机制**
为了实现全链路追踪、多语言支持和统一的安全管控,所有 HTTP 请求必须遵循以下 Headers 规范:
#### **1\. 标准请求头列表**
* Authorization: 鉴权 Token必须。格式为 Bearer \<Token\>。
* X-Request-Id (或 Trace-Id): 全链路追踪 ID。由网关或客户端生成后端原样透传并在日志中打印这是排查线上客诉的唯一生命线。
* X-Client-Type: 客户端类型(如 iOS, Android, Web, MiniProgram用于后端进行渠道统计或特定逻辑下发。
* X-Client-Version: 客户端版本号(如 1.0.5),用于后端做兼容性控制。
* Accept-Language: 多语言标识(如 zh-CN, en-US用于国际化错误提示。
#### **2\. 鉴权机制 (JWT)**
* 系统默认采用 **JWT (JSON Web Token)** 进行无状态鉴权。
* **安全性底线:**
* 严禁在 JWT 的 payload 中携带用户密码、敏感业务数据等隐私信息JWT 仅防篡改,不防窃听,前端可直接 Decode
* Token 必须设置合理的过期时间Access Token 建议 2 小时内Refresh Token 建议 7 天)。
* 登出Logout或踢人操作必须依赖 Redis 黑名单机制Token 屏蔽),不能仅靠客户端删除 Token。
### **2.2.3 标准响应数据格式 (Response 包装类)**
*注:考虑到不同技术栈和框架的差异(如 Django DRF 原生倾向于直接返回资源 JSON 而不包外层,或者 GraphQL 有其自有的结构),本章节定义的 Response 包装格式为**推荐规范**。建议团队根据实际业务需求与框架特性灵活决策。一旦选定,同一项目/子系统内必须保持格式一致,避免前端解析困难。*
#### **1\. 推荐标准外层结构(如需包装)**
如果业务决定采用统一包装格式,建议结构如下:
{
"code": 20000, // 业务状态码(非 HTTP 状态码)
"message": "success", // 面向开发者的提示信息 / 面向用户的国际化错误提示
"data": { ... }, // 实际的业务数据(对象或数组)
"traceId": "9b1deb4d..." // 必返当前请求的追踪ID方便前端直接带此ID报障
}
#### **2\. 分页数据标准结构与实现方案**
分页的实现方式多种多样,不同的框架往往有其自带的最佳实践(如 Spring Data 的 Page 接口Django DRF 的 PageNumberPagination 或 CursorPagination。在此仅作**推荐与指导性建议**。
团队在设计分页接口时应根据具体的业务端Admin Web 或 App 瀑布流)选择合适的方法:
**方案 A传统页码分页 (Offset/Limit)**
* **适用场景:** 后台管理系统 (Admin)、PC 端数据表格(允许用户指定跳到第 N 页)。
* **推荐返回结构示例:**
{
"data": {
"list": \[ {...}, {...} \], // 当前页的数据列表
"total": 150, // 总条数 (注意:数据量极大时 count() 会有性能瓶颈)
"page": 1, // 当前页码
"pageSize": 20, // 每页大小
"hasNext": true // 是否还有下一页
}
}
**方案 B游标分页 (Cursor-based / Keyset Pagination)**
* **适用场景:** 移动端 App 的“无限滚动”瀑布流、社交动态流。能有效解决数据实时增删导致的“数据重复出现”或“数据漏滑”问题,且性能极佳。
* **推荐返回结构示例:**
{
"data": {
"list": \[ {...}, {...} \], // 当前页的数据列表
"next\_cursor": "eyJpZCI6IDE1MiwgImNyZWF0ZV90aW1lIjogIjIwMjM..." // 用于请求下一页的游标 (通常是 Base64 编码的最后一条数据标识)
}
}
*规约提醒:不强制所有分页结构完全统一,但后端在输出分页数据时,应顺应其所用框架的原生分页器能力,并在 Swagger/API 文档中清晰标明采用的分页模式。*
#### **3\. Null 值与空值规约 (极度容易引发 NPE 和前端白屏)**
不论外层是否包装,实际数据体中的空值处理应遵循:
* **集合/数组:** 如果列表为空,必须返回空数组 \[\]**严禁返回 null**。
* **对象:** 如果对象为空,可以返回 {} 或 null需与前端约定好
* **字符串:** 如果字符串为空,必须返回 ""**严禁返回字符串 "null"**。
* **布尔值:** 严禁使用 0/1 替代 true/falseJSON 必须保持类型严谨。
### **2.2.4 全局错误码 (Error Codes) 分配与定义字典**
*注:对于敏捷开发或复杂度一般的单体项目,维护庞大的五位数业务错误码字典往往会带来过高的管理成本。因此,本节为**推荐规范**。团队应根据项目规模灵活决策:轻量级项目完全可以仅依靠“标准 HTTP 状态码 \+ 清晰明确的 message”来处理异常如果业务链路较长需要精细化区分错误类型则推荐引入以下错误码规范。*
我们需要明确区分 **HTTP 协议状态码****业务错误码**
#### **1\. HTTP 状态码规约(代表网络与网关层面的状态)**
后端必须正确使用 HTTP 状态码,严禁将所有请求都返回 200 OK即使系统内部发生了致命异常。
* 200 OK: 请求成功,业务逻辑被执行。
* 400 Bad Request: 参数校验失败(如缺少必填字段,格式错误)。
* 401 Unauthorized: 鉴权失败Token 缺失、无效或过期)。前端收到后应统一跳转登录页。
* 403 Forbidden: 权限不足Token 有效,但当前角色无权操作)。
* 404 Not Found: 路由不存在(资源未找到)。
* 429 Too Many Requests: 触发限流,请稍后重试。
* 500 Internal Server Error: 后端发生了未捕获的严重异常(代码 Bug 或数据库宕机)。
#### **2\. 业务错误码规约 (Response Body 中的 code 字段,若启用包装类)**
若团队决定启用全局错误码,推荐采用 **5 位数字****纯字符串枚举**(如 USER\_NOT\_FOUND。以 5 位数字为例A-BB-CC。
* **A (1位):系统级分类**
* 2成功。
* 4客户端调用错误业务约束拦截
* 5服务端内部错误业务预期内的异常
* **BB (2位):模块或子系统编号**
* 00全局公共错误。
* 01用户中心。
* 02订单中心。
* **CC (2位):具体错误序号**
**全局核心字典示例:**
* 20000: 业务执行成功。
* 40000: 客户端错误通配。
* 40102: 账户已被冻结。
* 40202: 账户余额不足。
* 50000: 服务器内部错误通配。
*规约:如果决定使用错误码,则禁止在代码中硬编码(如 return new Response(40102, "被冻结")),必须统一定义在 ErrorCodeEnum 枚举类中集中管理。*
## 🤖 [附加] AI 助手执行协议 (AI Output Schema)
**绝对红线**:接口文档必须输出为 OpenAPI 3.0 YAML 格式;所有 HTTP 接口的响应体必须强制包裹在统一的 `{code, msg, data}` JSON 结构中。