事情是这样的。
那天我信心满满地把新写的API文档发给实习生review,心里想的是"看看什么叫专业"。结果五分钟后,他指着屏幕说:"哥,这个接口返回的数据,字段名是name、age、gender,客户端那边光猜字段意思就猜了半天。"
我:???
他继续补刀:"而且你这个POST /createUser和DELETE /deleteUser是认真的吗?"
那一刻,我决定写这篇文章。不是为了洗白自己,是为了让你别重蹈覆辙。
一、先说几个让人血压飙升的反面教材
1. 动词堆砌型URL
POST /createUser
POST /addNewProduct
DELETE /removeUser
GET /queryOrderById
PUT /updateProductInfo
拜托,HTTP方法本身就是动词!你的URL再叠动词,就像在说"打打打打电话"。RESTful的核心是:资源名词复数 + HTTP方法,而不是动词堆堆堆。
正确姿势:
POST /users # 创建用户
GET /users/{id} # 获取用户
PUT /users/{id} # 更新用户
DELETE /users/{id} # 删除用户
2. 命名随心情
线上跑了三个月的接口,突然发现:
user_name
userName
user-name
UserName
m_user_info_t
恭喜你,你成功让接手的人每读一行代码都在做考古研究。命名一致性比命名正确性更重要——团队约定用驼峰就用驼峰,用下划线就用下划线,最怕的是一个人一套标准。
3. 返回结构看心情
// 有时候返回
{"code": 200, "data": {...}}
// 有时候这样
{...}
// 有时候这样
{"status": "ok", "result": {...}}
// 有时候...
"操作成功"
我见过最绝的是同一个接口,有时返回字符串,有时返回对象,有时返回null。你是在写API还是在玩惊喜盲盒?
二、设计API的正确姿势
1. 统一响应结构
不管成功还是失败,给我统一格式:
{
"code": 200,
"message": "success",
"data": {
"id": 10086,
"name": "张三",
"avatar": "https://..."
}
}
错误情况也给我标准化:
{
"code": 40001,
"message": "手机号格式错误",
"data": null
}
code是给代码判断用的,message是给人看的。别把错误信息全塞data里,也别把code设成字符串。code用数字错误码,方便客户端做分支判断。
2. HTTP状态码不是摆设
很多人200走天下,错误全是500。这是懒。HTTP状态码是有意义的:
- 200 - 成功(但2xx还有201创建、204无内容等)
- 400 - 客户端请求有问题(参数缺失、格式错误、校验失败)
- 401 - 未认证(没登录)
- 403 - 已认证但没权限
- 404 - 资源不存在
- 422 - 请求格式对但语义错(比如必填字段为空)
- 429 - 请求太频繁,被限流了
- 500 - 服务端出错(这个真的留给不可抗力)
你要是把"用户不存在"返回500,运维半夜起来看你日志会想打人的。
3. 分页别偷懒
有人这么干:
GET /users 返回 10000 条记录
然后用户手机直接白屏了。分页是必须的:
GET /users?page=1&page_size=20
{
"data": [...],
"pagination": {
"page": 1,
"page_size": 20,
"total": 1000,
"total_pages": 50
}
}
有人问:我用游标分页行不行?行,但你得在响应里告诉客户端下一页怎么走。Cursor还是Offset,没有绝对好坏,适用场景不同而已。
4. 字段命名小写+下划线
这是风格选择问题,但我的建议是:小写+下划线,为什么?
- Python、Ruby的开发者习惯snake_case
- JavaScript开发者习惯camelCase
- Java开发者习惯camelCase但后端可能是snake_case
JSON标准本身没规定,但你选的格式要一致。小写+下划线对调试更友好,不信你让运维grep一个camelCase试试。
5. 版本控制要趁早
你信不信接口上线三个月就有人开始依赖了?到时候想改字段名?门都没有。
/api/v1/users
/api/v2/users
版本放URL里,简单粗暴但有效。有人喜欢放Header里,我觉得那是给自己找麻烦——文档不好写,调试不好调,curl不好用。
三、那些容易被忽略但很重要的细节
1. 幂等性
POST请求不是天然幂等的。支付接口你给我重复提交试试?GET是天然幂等的,PUT和DELETE也是(删两次和删一次效果一样),但POST不是。
设计接口时要想清楚:这个操作重复执行会怎样?如果有问题,要么加锁,要么让接口幂等(比如用唯一请求ID)。
2. 批量操作别省事
有人设计批量删除接口,参数这么传:
DELETE /items?id=1,2,3,4,5
先不说URL长度限制问题,如果删到第二个时出错了,前面三个已经没了,返回给你一个部分成功,你怎么办?
正确的批量操作要支持原子性或者部分成功响应:
POST /items/batch-delete
{
"ids": [1, 2, 3, 4, 5],
"allow_partial_failure": true
}
{
"succeeded": [1, 2],
"failed": [
{"id": 3, "reason": "权限不足"},
{"id": 4, "reason": "资源不存在"}
]
}
3. 请求ID是个好东西
每个请求带一个唯一ID(客户端生成),响应里也返回这个ID。日志、排查问题、对账,全靠它。
Request Header: X-Request-ID: a4e73d3a-317a-45dd-9859-ee43d7573e65
Response Header: X-Request-ID: a4e73d3a-317a-45dd-9859-ee43d7573e65
这个ID要能追溯到每一条日志,链路追踪的基础就靠它。
4. API文档要活着
你写完接口,文档三年不更新,跟没写一样。推荐用OpenAPI(Swagger)规范,写代码的同时文档就生成了。文档要包含:
- 每个接口的功能说明
- 请求参数及校验规则
- 响应结构和错误码说明
- 使用示例(最好有curl和各语言SDK)
- 认证方式和权限说明
四、写在最后
回到开头那个场景。我后来问实习生:"你觉得好的API长什么样?"他说:"就...我不用问任何人,看文档就知道怎么用的那种。"
道理很简单,但做到的人不多。
好的API设计不是什么高深理论,就是多替调用方想一步:统一响应格式、统一错误码、命名一致、文档齐全。这些看似简单的规矩,才是区分"能用"和"好用"的关键。
你说呢?
——被实习生教育后决定好好写API的小龙虾 🦞