1. 接口设计基础:RESTful 架构、HTTP 方法、状态码设计
接口设计,说白了就是让不同的系统能好好说话。在我十多年的从业经历里,见过太多因为接口设计不规范引发的线上事故。合规治理的第一步,就是把接口基础打扎实。
1.1 RESTful 架构:不只是 URL 风格
很多人觉得 RESTful 就是“把 URL 写成名词,用动词指代操作”。这个理解有点浅了。我个人习惯把 REST 理解成一种设计哲学,它要求我们以资源为中心来思考问题。
RESTful 六大核心原则:
- 客户端-服务器分离 —— 职责解耦,前端只管展示,后端只管数据
- 无状态 —— 每次请求都携带全部上下文,服务端不保留会话状态
- 可缓存 —— 响应中明确标记资源能否缓存、缓存多久
- 统一接口 —— 用固定的 HTTP 方法操作资源,用标准状态码反馈结果
- 分层系统 —— 客户端不知道它直接连的是源服务器还是中间层
- 按需代码(可选) —— 服务器可以传一段脚本让客户端执行
为什么一定要强调无状态?我年轻的时候在一个电商平台做过一个购物车接口,把用户数据保存在服务端 Session 里。结果每次部署新版本,所有用户的购物车都丢了。从那时起,我就强迫自己把状态全部推到客户端,服务端只管处理请求。
我的一个经验:
如果你在设计接口时还在纠结“这个操作该用 PUT 还是 PATCH”,那你已经走在正确的路上了。怕的是直接把 POST 一把梭,所有操作都往里塞。
如果你在设计接口时还在纠结“这个操作该用 PUT 还是 PATCH”,那你已经走在正确的路上了。怕的是直接把 POST 一把梭,所有操作都往里塞。
1.2 HTTP 方法:动词的正确用法
HTTP 协议定义了若干方法,但我们最常打交道的只有五个。你别看它们少,用错了照样出事。
| 方法 | 用途 | 幂等 | 安全性 |
|---|---|---|---|
GET |
获取资源 | 是 | 是(不改变服务器状态) |
POST |
创建资源 | 否 | 否 |
PUT |
完全替换资源 | 是 | 否 |
PATCH |
部分更新资源 | 否(通常不幂等) | 否 |
DELETE |
删除资源 | 是 | 否 |
关于幂等这件事,我补充两句。一个请求发出去,如果网络超时了,客户端重试以后后端应该能保证结果一致。GET、PUT、DELETE 天生支持幂等,但 POST 不行。如果你用 POST 去创建订单,你的客户端必须做好去重防护。
举个具体例子:
# 好的设计
GET /api/v1/users/{id} # 获取用户信息
POST /api/v1/users # 创建新用户
PUT /api/v1/users/{id} # 全量更新用户(传完整对象)
PATCH /api/v1/users/{id} # 部分更新(只传要改的字段)
DELETE /api/v1/users/{id} # 删除用户
# 不好的设计(我对这种深恶痛绝)
GET /api/v1/getUser?id=123
POST /api/v1/createUser
POST /api/v1/updateUser
POST /api/v1/deleteUser
我曾经踩过的一个坑:
有一个微服务为了图省事,把所有操作都用了 POST。后来审计时发现,根本无法判断某个接口到底做了什么事。想查日志都查不清,因为所有请求方法都是 POST。合规治理来了,第一件事就是要求把方法改对。
有一个微服务为了图省事,把所有操作都用了 POST。后来审计时发现,根本无法判断某个接口到底做了什么事。想查日志都查不清,因为所有请求方法都是 POST。合规治理来了,第一件事就是要求把方法改对。
1.3 状态码设计:别只回 200 和 500
你想想看,状态码就是接口的方言。方言太乱,别人就听不懂你在说什么。很多团队只用了 200(成功)和 500(服务器异常),这等于没用心设计。
我建议把状态码分为三大类:
| 类别 | 常见状态码 | 使用场景 |
|---|---|---|
| 2xx 成功 | 200 201 204 |
200 表示正常返回数据;201 表示创建成功(配合 Location 头);204 表示删除成功,响应体为空 |
| 4xx 客户端错误 | 400 401 403 404 409 422 |
400 参数错误;401 未认证;403 无权限;404 资源不存在;409 资源冲突;422 请求体格式错误 |
| 5xx 服务端错误 | 500 502 503 |
500 内部错误;502 网关超时;503 服务不可用 |
还记得我之前说的购物车教训吗?
后来我做了一个规则:状态码必须大于等于 400 时,返回体中必须附带 error 字段,写上人能读懂的问题描述。 你不能只扔一个 400 就让调用方自己猜。
后来我做了一个规则:状态码必须大于等于 400 时,返回体中必须附带 error 字段,写上人能读懂的问题描述。 你不能只扔一个 400 就让调用方自己猜。
你以为光有状态码就够了吗?不够。我曾在一个金融项目中见过,4xx 写了 "参数错误" 但关键字段是空的——结果排障耗了三个小时。那段代码是我同事写的,但这次事件以后,我带头立了一个规范:所有 4xx 响应都要详细标注具体哪个字段错了、期望什么格式。
// 一个规范的错误响应体
{
"error": {
"code": "VALIDATION_FAILED",
"message": "请求参数校验不通过",
"details": [
{
"field": "email",
"reason": "格式无效,期望:user@example.com"
}
]
}
}
1.4 一张图看懂接口设计基础
状态码设计的一个小建议:
别把业务错误码和 HTTP 状态码混用。曾经有个团队,用 200 表示 "业务成功",用 200 加一个 error 字段表示 "业务失败"。结果网关层记录的都是 200,全线告警全废。后来我要求他们 所有业务逻辑层面的错误——包括校验、权限、冲突——都必须使用 4xx 状态码,网关才能正确统计失败率。
别把业务错误码和 HTTP 状态码混用。曾经有个团队,用 200 表示 "业务成功",用 200 加一个 error 字段表示 "业务失败"。结果网关层记录的都是 200,全线告警全废。后来我要求他们 所有业务逻辑层面的错误——包括校验、权限、冲突——都必须使用 4xx 状态码,网关才能正确统计失败率。