API 接口设计规范——RESTful 最佳实践

API 接口设计规范——RESTful 最佳实践

作者: CaoZH
日期: 2025-06-15
本文为原创教程


当你和团队一起开发时,API 接口设计是否规范直接影响前后端的协作效率。一份好的 API 规范,能让前端开发者不用问”这个接口返回什么字段”就能直接开发。

本文总结了一套经过多个项目验证的 RESTful API 设计规范,涵盖命名、状态码、分页、错误处理等关键方面。

一、基础规范

URL 命名

1
2
3
4
5
6
7
8
9
10
11
12
13
## 核心原则
- 使用名词复数:/users 而非 /user
- 使用小写 + 连字符:/order-items 而非 /orderItems
- 使用版本号:/api/v1/users
- 层级关系用路径表示:/users/123/orders

## 示例
GET /api/v1/users # 用户列表
GET /api/v1/users/123 # 单个用户
POST /api/v1/users # 创建用户
PUT /api/v1/users/123 # 更新用户
DELETE /api/v1/users/123 # 删除用户
GET /api/v1/users/123/orders # 用户的订单列表

HTTP 方法语义

1
2
3
4
5
6
7
| 方法 | 语义 | 是否幂等 | 请求体 | 响应体 |
|------|------|---------|--------|--------|
| GET | 查询 | ✅ | 无 | 数据 |
| POST | 创建 | ❌ | 有 | 创建成功的资源 |
| PUT | 全量更新 | ✅ | 有 | 更新后的资源 |
| PATCH | 部分更新 | ✅ | 有 | 更新后的资源 |
| DELETE | 删除 | ✅ | 无 | 空/删除状态 |

二、统一响应格式

成功响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 单条数据
{
"code": 200,
"message": "操作成功",
"data": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"createdAt": "2025-06-15T10:00:00Z"
}
}

// 列表数据
{
"code": 200,
"message": "查询成功",
"data": {
"list": [
{ "id": 1, "username": "admin" },
{ "id": 2, "username": "test" }
],
"total": 100,
"page": 1,
"pageSize": 20
}
}

错误响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 参数错误
{
"code": 400,
"message": "参数校验失败",
"errors": [
{ "field": "email", "message": "邮箱格式不正确" },
{ "field": "age", "message": "年龄必须大于0" }
]
}

// 未授权
{
"code": 401,
"message": "未登录或 Token 已过期"
}

// 无权限
{
"code": 403,
"message": "权限不足"
}

// 资源不存在
{
"code": 404,
"message": "用户不存在"
}

// 服务端错误
{
"code": 500,
"message": "服务器内部错误,请稍后重试"
}

三、状态码使用指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
## 2xx — 成功
| 状态码 | 含义 | 使用场景 |
|--------|------|---------|
| 200 | OK | GET 查询成功,PUT/PATCH 更新成功 |
| 201 | Created | POST 创建成功 |
| 204 | No Content | DELETE 删除成功 |

## 3xx — 重定向
| 状态码 | 含义 | 使用场景 |
|--------|------|---------|
| 301 | Moved Permanently | 资源永久迁移 |
| 302 | Found | 临时重定向 |
| 304 | Not Modified | 缓存有效 |

## 4xx — 客户端错误
| 状态码 | 含义 | 使用场景 |
|--------|------|---------|
| 400 | Bad Request | 参数错误、格式错误 |
| 401 | Unauthorized | 未登录、Token 失效 |
| 403 | Forbidden | 已登录但无权限 |
| 404 | Not Found | 资源不存在 |
| 405 | Method Not Allowed | 接口不支持该 HTTP 方法 |
| 409 | Conflict | 资源冲突(用户名重复等) |
| 422 | Unprocessable Entity | 业务校验不通过 |
| 429 | Too Many Requests | 请求频率过高 |

## 5xx — 服务端错误
| 状态码 | 含义 | 使用场景 |
|--------|------|---------|
| 500 | Internal Server Error | 服务器内部错误 |
| 502 | Bad Gateway | 上游服务不可用 |
| 503 | Service Unavailable | 服务暂时不可用 |
| 504 | Gateway Timeout | 上游服务超时 |

四、分页规范

请求

1
GET /api/v1/users?page=1&pageSize=20&sort=-createdAt&status=active
参数 类型 默认值 说明
page int 1 页码
pageSize int 20 每页条数(最大 100)
sort string -createdAt 排序字段,- 前缀表示降序
其他 - - 查询过滤条件

响应

1
2
3
4
5
6
7
8
9
10
11
{
"code": 200,
"message": "查询成功",
"data": {
"list": [...],
"total": 1000,
"page": 1,
"pageSize": 20,
"totalPages": 50
}
}

五、认证与鉴权

1
2
# 请求头中携带 Token
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Spring Boot 过滤器示例
@Component
public class JwtAuthFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {

String token = extractToken(request);
if (token != null) {
try {
Claims claims = JwtUtil.parseToken(token);
request.setAttribute("userId", claims.get("userId"));
request.setAttribute("username", claims.get("sub"));
} catch (Exception e) {
response.setStatus(401);
return;
}
}
chain.doFilter(request, response);
}

private String extractToken(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
return header.substring(7);
}
return null;
}
}

六、接口文档

Swagger / Knife4j 配置

1
2
3
4
5
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
@EnableSwagger2
public class SwaggerConfig {

@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("用户管理系统 API")
.version("v1")
.build())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.controller"))
.paths(PathSelectors.any())
.build();
}
}

七、最佳实践清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## 设计阶段
□ 使用名词复数命名资源
□ 版本号放在 URL 中
□ 设计好后再开发
□ 前后端先约定接口格式

## 开发阶段
□ 统一响应格式 { code, message, data }
□ 正确使用 HTTP 状态码
□ 所有接口都要有认证
□ 参数校验并返回清晰的错误信息
□ 分页接口统一参数名
□ 敏感字段不返回到前端(密码等)

## 运维阶段
□ 接口添加请求频率限制
□ 记录关键接口的访问日志
□ 提供 API 文档(Swagger)
□ 添加健康检查接口 /api/health

八、总结

好的 API 设计应该让调用者不用看文档也能猜到接口怎么用

1
2
3
4
5
6
7
## 核心原则

1. 一致性 — 命名、格式、错误处理保持统一
2. 简单性 — URL 路径不超过三级
3. 安全性 — 每个接口都需要鉴权
4. 可扩展性 — 预留版本号,响应格式可扩展
5. 自文档化 — 符合 REST 语义,一看就懂

首发于 CaoZH 的笔记