155 lines
8.9 KiB
Markdown
155 lines
8.9 KiB
Markdown
# **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),以便于后续重大重构时的平滑迁移。
|
||
* **务实原则:** 追求“实用级” REST(Level 2),不强求满足极致的 HATEOAS(Level 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/false,JSON 必须保持类型严谨。
|
||
|
||
### **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 枚举类中集中管理。* |