(开篇配图:一个迷宫般的API文档截图,红色圈出混乱的命名和状态码)
第一坑:URL命名像写散文
先看最基础的URL设计。我记得第一个API的设计是这样的:
“`
/getUserInfo
/createNewOrder
/deleteAllOldRecords
当时觉得挺清晰的,结果被review时直接打回。RESTful API的核心是资源导向,不是动作导向。正确的写法应该是:
`javascript
// 错误示范:用动词描述操作
GET /getUserInfo/123
POST /createNewOrder
DELETE /deleteAllOldRecords
// 正确示范:用HTTP方法表达动作,资源名用名词
GET /users/123
POST /orders
DELE
TE /records
`
为什么要这么写?因为REST的核心是把所有东西都抽象成资源,HTTP方法(GET、POST、PUT、DELETE)就是你的动词。这样设计的好处是,前端和后端都能一眼看出这是对什么资源做什么操作。
另一个坑是复数还是单数。官方规范推荐用复数,但很多人会纠结。我的建议是统一用复数,因为操作通常是针对集合的:
``
/users // 用户集合
/users/123 // 单个用户
/orders // 订单集合
/orders/456 // 单个订单
第二坑:版本号放在哪
这个设计真的反人类。我见过版本号放在路径前缀里的:
``
/v1/users
/v2/users
也见过放在Header里的:
``
Accept: application/vnd.myapi.v1+json
URL路径方式直观、易于缓存和路由,适合公开API;Header方式更符合REST理念,适合内部API或需要精细内容协商的场景,但对调试不友好
(你是不是也在Postman里填过这玩意儿?)
我的选择是用Header方式,但配合一个简单的fallback,解析时用正则避免误判:
`javascriptv${match[1]}
// 后端示例:Express中间件处理版本号
const apiVersion = (req, res, next) => {
const accept = req.headers['accept'] || '';
// 用正则匹配标准格式,避免误匹配
const match = accept.match(/application\/vnd\.myapi\.v(\d+)\+json/);
const version = match ? : 'v1';
req.apiVersion = version;
next();
};
// 使用时
app.get('/users', apiVersion, (req, res) => {
if (req.apiVersion === 'v2') {
// v2的逻辑
} else {
// v1的逻辑
}
});
`
这样前端直接调用 Accept: application/vnd.myapi.v2+json,后端解析起来也清晰。如果前端忘了填,自动降级到v1,不会报错。
第三坑:状态码用得乱七八糟
这是我挨骂最多的部分。之前一个接口,成功返回200,没数据返回404,权限不足也返回200(只是body里写了"error": "no permission")。前端满屏的if else,简直想砍人。
标准的RESTful API状态码应该这样用:
`javascript参数错误: ${response.data.message}
// 完整的状态码处理示例
const handleAPIResponse = (response) => {
switch (response.status) {
case 200:
// 成功返回数据
return response.data;
case 201:
// 创建成功(POST)
return { message: '创建成功', id: response.data.id };
case 204:
// 删除成功,无返回内容
return null;
case 400:
// 请求参数错误
throw new Error();请 ${retryAfter} 秒后重试
case 401:
// 未认证
window.location.href = '/login';
break;
case 403:
// 权限不足
throw new Error('您没有权限执行此操作');
case 404:
// 资源不存在
throw new Error('请求的资源不存在');
case 429:
// 请求太频繁
const retryAfter = response.headers['retry-after'];
throw new Error();未知错误: ${response.status}
case 500:
// 服务器错误
throw new Error('服务器繁忙,请稍后重试');
default:
throw new Error();`
}
};
为什么别人说看你的API像看天书?就因为状态码不统一。记住:2xx永远成功,4xx永远用户问题,5xx永远服务器问题。前端看到4xx就知道去排查自己的参数,看到5xx就知道找后端。
(核心配图:一张状态码对照表,左边是HTTP状态码,右边是常见场景,比如201配创建成功、422配参数校验失败)
第四坑:HATEOAS不会用
这个术语看着高大上,其实是一个可选的高级特性,核心是通过超媒体动态发现可用操作,而不是仅仅在响应里塞几个链接。比如你返回一个用户列表,前端怎么知道怎么创建新用户?怎么修改单个用户?
规范的RESTful API可以在响应里包含相关操作链接,但要注意这只是一个可选特性,根据项目复杂度决定是否使用:
`json`
{
"data": [
{
"id": 123,
"name": "张三",
"email": "zhangsan@example.com",
"links": {
"self": "/users/123",
"orders": "/users/123/orders"
}
}
],
"links": {
"self": "/users?page=1",
"next": "/users?page=2",
"prev": null,
"create": {
"method": "POST",
"href": "/users"
}
}
}
这样前端拿到响应后,不需要写死URL拼接逻辑,直接读 data[0].links.orders 就能跳转到该用户的订单页。即使后端改了URL结构,前端也不用改代码。不过别被这个术语吓到,大多数RESTful API并不严格遵循HATEOAS,简单项目里直接写死URL也没毛病。
第五坑:分页和过滤参数不统一
我见过一个项目里,分页参数有的用 page 和 size,有的用 offset 和 limit,还有的直接用 start 和 end。前端写分页组件时,每个接口都要适配一套参数,代码量翻三倍。
统一规范应该是:
`javascript
// 分页请求
GET /users?page=1&size=20
// 响应
{
"data": [...],
"pagination": {
"page": 1,
"size": 20,
"total": 100,
"totalPages": 5
}
}
// 过滤请求
GET /users?name=张三&status=active&age_gt=18
// 复杂过滤建议用参数化方式,避免直接拼字符串
GET /users?filter=name:like:张*,status:eq:active,age:gte:18
`
⚠️ 安全警告:过滤参数必须经过严格校验和转义,避免注入攻击。不要直接把用户输入拼接到查询条件里,要用参数化查询或白名单验证。
还有个技巧:分页大小要有限制。有人传 size=100000,直接把服务器拉爆。后端要做校验:
`javascript`
const MAX_PAGE_SIZE = 100;
const rawSize = parseInt(req.query.size, 10);
const pageSize = Number.isNaN(rawSize) || rawSize <= 0 ? 20 : Math.min(rawSize, MAX_PAGE_SIZE);
第六坑:错误信息像迷语
最常见的错误返回是这样的:
`json`
{
"code": 10001,
"message": "Internal error"
}
前端看到 10001 只能找后端问。好的错误信息应该给足线索:
`json
// 参数校验失败
{
"error": {
"code": 422,
"message": "参数校验失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
},
{
"field": "age",
"message": "年龄必须在1-150之间"
}
]
}
}
// 资源冲突
{
"error": {
"code": 409,
"message": "用户邮箱已被注册",
"conflictField": "email",
"suggestion": "请使用其他邮箱或找回密码"
}
}
`
为什么这么做?因为前端需要精确知道哪个字段错了,才能给出针对性的UI提示。你只说"参数错误",用户都不知道改哪里。
注意:错误响应中不要暴露敏感字段的原始值(如密码、令牌),非敏感字段如邮箱、年龄可以保留,方便前端调试。敏感数据只在服务端日志中记录。
(总结前配图:一个对比图,左边是混乱的API设计(动词URL、404当空数据、错误码泛泛),右边是规范设计(名词URL、正确状态码、详细错误信息))
实战:一个完整的规范示例
说了这么多,直接看一个完整的API规范示例:
“javascript
// 用户资源API规范
// 1. 获取用户列表(带分页和过滤)
GET /users?page=1&size=20&status=active
Response 200:
{
“data”: [
{
“id”: 1,
“name”: “张三”,
“status”: “active”,
“createdAt”: “2024-01-01T00:00:00Z”,
“links”: {
“self”: “/users/1”,
“orders”: “/users/1/orders”
}
}
],
“pagination”: { “page”: 1, “size”: 20, “total”: 100, “to
本文仅供参考,不构成医疗建议。
本文由AI辅助创作,仅供参考。