开门见山地说,API设计这事儿,真是谁干谁知道。前几天我review同事代码,看到一个接口叫getUserInfoByIdWithDetailInformation,我当时就想问:这是在写API还是在背单词?
今天不整虚的,直接聊聊我踩过的那些坑,以及怎么优雅地爬出来。
1. URL设计:别把接口写成谜语
先来看几个反例:
/getUser- 请问是获取还是别的啥?/userinfo?id=1- 用名词很难吗?/queryUserList_20240301- 这是接口还是版本号?
正确的姿势:
- 用名词,不要用动词:
/users而不是/getUsers - 资源层级要清晰:
/users/123/orders表示用户123的订单列表 - 复数形式走天下:
/users、/posts、/comments
记住一个原则:你的URL应该像一句话一样自然。比如/users/123/orders/456,读出来就是"获取用户123的订单456",一目了然。
2. HTTP方法:GET是GET,POST是POST
这是我见过最乱的领域。有人GET获取数据,有人POST获取数据,POST创建数据,PUT也创建数据——反正就是一团浆糊。
约定俗成的规矩:
- GET - 读取资源,安全且幂等
- POST - 创建资源
- PUT - 完整更新(幂等)
- PATCH - 部分更新(非幂等)
- DELETE - 删除资源
举几个例子:
GET /users- 获取用户列表POST /users- 创建新用户PUT /users/123- 完整更新用户123PATCH /users/123- 只更新用户123的某个字段DELETE /users/123- 删除用户123
为什么要区分PUT和PATCH?因为语义不同。PUT表示"把这整个东西替换成新的",PATCH表示"稍微改改这个"。别再PUT只传一个字段了,看着膈应。
3. 状态码:别什么都返回200
有些人特别喜欢200,甭管成功失败都200 + error message。请问这和耍流氓有什么区别?
常用状态码速查:
- 2xx - 成功系列
- 200 - OK,正常返回
- 201 - Created,创建成功
- 204 - No Content,删除成功或无返回内容
- 4xx - 客户端错误
- 400 - Bad Request,参数错误
- 401 - Unauthorized,没登录
- 403 - Forbidden,没权限
- 404 - Not Found,找不到资源
- 422 - Unprocessable Entity,参数校验失败
- 429 - Too Many Requests,请求太频繁了
- 5xx - 服务器错误
- 500 - Internal Server Error 服务器抽风
- 502 - Bad Gateway 上游挂了
- 503 - Service Unavailable 服务不可用
我的建议:把状态码当语言用。200表示"事儿办成了",400表示"你的请求有问题",500表示"我这边出事了"。别委屈了状态码,它们也很难。
4. 错误响应:把人类当人看
见过最离谱的错误返回是这样的:
{"code": -1, "msg": "error"}
我请问呢?什么error?哪里error了?你倒是说啊!
合格的错误响应应该长这样:
{ "error": { "code": "USER_NOT_FOUND", "message": "用户不存在", "details": { "user_id": 123 } }}
或者更详细点:
{ "error": { "code": "VALIDATION_FAILED", "message": "参数校验失败", "fields": [ { "field": "email", "message": "邮箱格式不正确" }, { "field": "password", "message": "密码长度必须大于8位" } ] }}
记住:你的API是给人用的,不是给机器用的。开发者看到这种错误,应该知道错在哪、怎么改。
5. 版本控制:别让旧接口坑死人
接口上线了,过几天要改数据结构。怎么办?直接改?然后所有调用方都炸了?
版本控制方案:
- URL路径版本:
/api/v1/users、/api/v2/users - Header版本:
Accept: application/vnd.myapp.v1+json
我的建议:URL路径最直观也最安全。v1稳定后就别动了,有问题加v2。让旧版本苟着,别急着下架,你永远不知道哪个客户还在用。
版本演进策略:
- 新增字段 - 没问题,向前兼容
- 废弃字段 - 打上deprecated标记,别直接删
- 删除接口 - 先warn,再deprecated,最后再下
6. 认证授权:别让人家猜密码
见过太多奇奇怪怪的认证方式了:
- URL里带密码:
/api/users?password=123456- 这是生怕别人看不见? - Basic Auth不验证 - 要你何用?
- Token放URL里 - 会被记录到日志的!
正确姿势:
- 登录接口返回Token,后续请求用
Authorization: Bearer {token} - 敏感操作二次验证 - 比如支付、修改密码
- Token有过期时间 - 安全第一
还有,授权和认证是两码事。认证是"你是谁",授权是"你能干嘛"。别搞混了。
7. 分页:别一次性返回全部数据
有人问我:为什么我的API这么慢?我一看,好家伙,GET /posts返回了十年间的所有文章——几万条记录,一次性全给你。
分页参数建议:
page+per_page- 页码和每页数量- 或
cursor- 游标分页,适合大数据量
返回记得带元信息:
{ "data": [...], "pagination": { "page": 1, "per_page": 20, "total": 1000, "total_pages": 50 }}
8. 文档:没有文档的API等于没有API
最后说点虚的但特别重要的:文档!文档!文档!
你的API再优雅,没人看得懂也是白搭。文档应该包括:
- 每个接口的用途
- 请求参数说明(类型、必填/可选、默认值)
- 响应格式示例
- 错误码列表
- 认证方式
推荐用Swagger/OpenAPI或者Postman生成文档,省时省力。
写在最后
API设计这件事,说难不难,说简单也不简单。核心就一句话:把别人当人,也把自己当人。
你的API是给别人用的,设身处地想想开发者拿到这个接口时的心情:是"这写的什么玩意儿"还是"哇塞太贴心了"?
代码写得好,API设计妙,bug少来找我,大家都好。
祝你的接口,永远不会被人挂在知乎上吐槽。🛠️