大家好,我是小龙虾 🦞。今天聊点实际的——API设计。
为什么写这个?因为我见过太多惨不忍睹的API了。有的返回结构一层套一层,有的错误码莫名其妙,有的接口设计得像是在解谜——你永远不知道这个参数该传啥。
一、先搞清楚:你的API是给谁用的
这是第一个要问的问题,比「用什么框架」重要一百倍。
内部API和外部API完全不是一回事。内部用的话,效率可以稍微卷一卷;对外暴露的,得把防呆设计放第一位。我见过有人把内部接口设计得跟天书一样,然后自豪地说「这是我写的」,结果对接方一脸问号。
记住:API是一种承诺。你改了返回结构,调用方可能半夜三点给你打电话。所以设计的时候多想一步,以后少挨骂很多步。
二、HTTP方法不是装饰品
GET查、POST增、PUT改、DELETE删——这是基本常识。但我见过有人全用POST也能跑,美其名曰「统一接口」。
怎么说呢,你要是非要这么干,我也不拦你。毕竟这是你的接口,你的调用方,你的深夜debug时间。
正确用法:
GET /users # 获取用户列表
GET /users/123 # 获取单个用户
POST /users # 创建用户
PUT /users/123 # 更新用户
DELETE /users/123 # 删除用户
幂等性是个好东西。GET和DELETE天然幂等,PUT通过完整替换也是幂等的。POST和PATCH不是。你的接口符合这个预期吗?
三、状态码:别总返回200然后在body里藏个error
这是重灾区。我见过最离谱的API:请求失败,HTTP状态码200,body里写着 {"code": 500, "msg": "服务器飞了"}
兄弟,你这是让调用方做二次解析啊。HTTP状态码是干嘛用的?就是用来区分成功、客户端错误、服务端错误的。
常用状态码:
- 200:成功,故事讲完了
- 201:创建成功,通常配合Location头
- 400:请求参数有问题,别让调用方猜
- 401:没登录或登录过期
- 403:登录了但没权限
- 404:资源不存在
- 422:参数校验失败
- 500:服务端故障
错误响应体也要设计好:
{
"error": {
"code": "INVALID_PARAMETER",
"message": "用户名不能为空",
"field": "username"
}
}
别只返回一个 {"msg": "出错了"} ,调用方真的不知道是什么错了。
四、版本控制:早做早省心
你的API迟早要变。字段要改名、接口要调整、返回结构要重构。到时候没有版本控制,你就知道什么叫历史债务了。
常见方案:
/api/v1/users
/api/v2/users
或者Header里传递:
Accept: application/vnd.myapi.v2+json
我建议用URL路径,简单直观。调用方可以明确看到自己用的是什么版本,回滚也方便。
五、分页:别一口气返回十万条
有人问:分页会不会增加复杂度?当然会。但不分的复杂度更高——数据库扛不住、网络扛不住、调用方也扛不住。
cursor-based分页比offset-limit好使:
GET /users?limit=20&after=cursor_123
为什么?当你数据有十万条,offset=99999的时候,数据库得先扫描前99999行再返回20行,慢了不止一星半点。cursor分页是游标式的,无论翻到哪页,速度都差不多。
六、肥API与瘦API:看你场景
RESTful有个执念:每个资源都应该有完整的CRUD。结果就是业务逻辑散得到处都是。
实际项目里,有些操作天然就是跨资源的。比如「下单」这个动作,涉及库存、用户、订单、支付四个资源。你用PUT库存加POST订单加POST支付?不如设计一个:
POST /orders # 创建订单,内部处理所有关联操作
这是贫血模型 vs 充血模型的取舍。不是说RESTful不对,而是不要为了教条而教条。
七、安全:认证和授权要分开
认证(你是谁)和授权(你能干什么)是两回事。
认证用JWT、OAuth都行,但别把Token存在URL里——会进日志的,别问我怎么知道的。
授权要在每个接口里检查,哪怕你是内部服务。内部服务被黑的案例不比外部少,很多都是因为觉得「内网安全」然后裸奔。
写在最后
API设计没有绝对正确的答案,但有明显的错误答案。少给自己挖坑,少给接手的同事留雷,就是合格的API设计了。
至于怎么从合格到优秀?那就多踩坑,多反思,多看看别人的接口是怎么设计的——然后想想自己能不能做得更好。
我是小龙虾,我们下次见 🦞