接口规范:RESTful & RPC
接口是SDK的骨架。你写的接口好不好用,直接决定了对接方的「体感」。我这些年看了太多因为命名混乱、状态码写得随意,导致双方扯皮的案例。今天咱们就把这块掰开揉碎讲清楚。
接口命名规范
先说RESTful。我个人的习惯是:资源用名词复数,动作交给HTTP动词。比如获取用户列表,就是 GET /users;创建用户是 POST /users;更新某个用户是 PATCH /users/{id}。别搞出 /getUserList 这种RPC味十足的路径。
- 动词混在路径里:
/deleteUser?id=xxx→ 应该用DELETE /users/{id} - 路径层级混乱:
/users/info/orders→ 应该用/users/{userId}/orders - 单词单复数随意:一会
/order一会/orders→ 统一用复数
RPC风格的接口,我一般建议不要和RESTful混用。如果你项目里定了RPC协议,那就用 动词+名词 的驼峰命名,比如 createUser、queryOrderDetail。嗯,这里要注意:RPC接口的参数必须稳定,不要随意增删字段,否则老版本客户端会崩。
请求/响应结构
我见过最糟心的事——同一个SDK,三个接口返回的JSON结构都不一样。有的字段叫 code,有的叫 status,有的干脆不包装直接返回数组。你说对接方看了头不头痛?
我建议统一采用以下结构:
// 请求体示例(POST /users)
{
"username": "张三",
"email": "zhangsan@example.com",
"age": 28
}
// 响应体示例
{
"code": 0, // 业务状态码,0表示成功
"message": "success",
"requestId": "e3b0c442-...", // 追踪ID,方便排查
"data": { // 真正的业务数据
"userId": 1001,
"username": "张三"
}
}
为什么要在响应里加 requestId?我曾经被线上问题折腾过:客户端报错,但日志里找不到对应的请求。加上这个字段后,运维拿着ID去链路追踪系统一搜,问题秒现。你想想看,一个字段省了多少debug时间。
Content-Type: application/jsonAuthorization: Bearer <>(如有鉴权)X-Request-Id(客户端生成,幂等使用)Accept-Language(国际化场景)
状态码定义
HTTP状态码和业务状态码,两者要分清楚。HTTP状态码只管网络层面的成功失败,比如200、400、500。业务状态码放在响应体 code 字段里,告诉客户端这次请求的业务结果。
| HTTP状态码 | 含义 | 常见场景 |
|---|---|---|
| 200 | 成功 | 正常返回数据 |
| 400 | 请求参数错误 | 缺少必填字段、格式错误 |
| 401 | 未授权 | 过期或无效 |
| 403 | 无权限 | 资源访问被拒绝 |
| 404 | 资源不存在 | 查询的ID不存在 |
| 429 | 频率限制 | API调用超限 |
| 500 | 服务端内部错误 | 数据库异常、未捕获异常 |
| 503 | 服务暂不可用 | 维护中、过载 |
{"code":0,"data":"SQL Error: ..."},以为请求成功了……这种问题一定要在网关层统一拦截,业务异常返回对应的400或500,同时掩盖内部细节。
分页规范
列表接口必须做分页。不做分页?你想想看,用户表1000万条数据,一次查出来,服务端内存先爆掉。我建议统一采用 基于偏移量的分页(page + pageSize),如果数据量大且实时性高,可以用游标分页(cursor + limit)。
请求参数:
GET /users?page=1&pageSize=20
GET /users?cursor=abc123&limit=20
响应体:
{
"code": 0,
"message": "success",
"requestId": "...",
"data": {
"items": [...], // 当前页数据
"total": 1234, // 总记录数(偏移量分页需要)
"page": 1,
"pageSize": 20,
"hasMore": true // 游标分页用
}
}
注意 page 从1开始还是从0开始?我个人习惯从1开始,这样对接方比较好理解。但不管从几,必须在文档里明确写出来。还有pageSize要限制最大值,我一般设成100,超过就截断。
错误码设计
错误码是我最想吐槽的地方。很多团队设计错误码,一种是全用0和1,一种是弄几十个数字没有文档。其实错误码设计的核心是:可读、可分类、可扩展。
我常用 业务领域 + 错误类型 + 具体编号 的结构,比如:
// 格式:AABBBCCC
// AA = 业务领域(01用户,02订单,03支付...)
// BBB = 错误类型(001参数错误,002鉴权错误,003资源不存在...)
// CCC = 具体编号(001~999)
示例:
01001001 => 用户模块,参数错误,手机号格式不正确
02003001 => 订单模块,资源不存在,订单ID无效
03002001 => 支付模块,鉴权错误,余额不足
这样做的好处是什么?客户端可以根据错误码前缀就知道是哪个模块出的问题,而且新加错误码不会跟旧的冲突。
- 0 永远表示成功,非0表示失败
- 错误码不可变:一旦发布,不能修改含义,只能废弃或重编号
- 错误信息要友好,但不能暴露内部实现:比如「数据库连接池耗尽」改成「系统繁忙,请稍后重试」
- 错误信息里包含
requestId,方便运维查日志
我们来看一个完整的错误响应示例:
{
"code": 1002001,
"message": "用户不存在",
"requestId": "a1b2c3d4-...",
"data": null
}
说到这里,我想起之前有个合作方,他们的错误码全部用 1、2、3……一个数字对应一种错误,文档只写了「1:错误 2:错误」。呵呵,排查问题只能靠猜。后来我建议他们改成结构化编码,对接效率翻倍。
pageIndex 和 pageNo 混用,导致前端换了三个版本才统一。从那以后,我就在项目初期写死了这份规范,谁也别想擅自改。