前言

最近在重构用户中心项目的API接口。刚开始写API时,完全不知道有什么规范,就是随便写。后来看了一些资料,才知道还有RESTful这种设计风格。这篇文章记录一下我学到的一些RESTful API设计最佳(bushi)实践 。

1. URL设计

使用名词,不使用动词

1
2
3
4
5
6
7
8
9
# 不好
GET /getUsers
POST /createUser
DELETE /deleteUser

# 好
GET /users
POST /users
DELETE /users/:id

使用复数形式

1
2
3
4
# 统一使用复数
GET /users
GET /orders
GET /products

层级关系

1
2
3
4
5
# 获取用户的订单
GET /users/:userId/orders

# 获取订单的商品
GET /orders/:orderId/products

避免过深的层级

1
2
3
4
5
# 不好(层级过深)
GET /users/:userId/orders/:orderId/products/:productId/reviews/:reviewId

# 好(扁平化)
GET /reviews?productId=:productId

2. HTTP方法

方法 用途 是否幂等 是否有请求体
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
27
28
29
# 获取用户列表
GET /users?page=1&size=10

# 获取单个用户
GET /users/123

# 创建用户
POST /users
Content-Type: application/json
{
"name": "张三",
"email": "zhangsan@example.com"
}

# 更新用户(全量)
PUT /users/123
{
"name": "李四",
"email": "lisi@example.com"
}

# 更新用户(部分)
PATCH /users/123
{
"email": "newemail@example.com"
}

# 删除用户
DELETE /users/123

3. 状态码

正确使用HTTP状态码:

状态码 含义 使用场景
200 OK 请求成功
201 Created 资源创建成功
204 No Content 删除成功,无返回内容
400 Bad Request 请求参数错误
401 Unauthorized 未认证
403 Forbidden 无权限
404 Not Found 资源不存在
409 Conflict 资源冲突(如用户名已存在)
422 Unprocessable Entity 请求格式正确,但语义错误
500 Internal Server Error 服务器内部错误

统一响应格式

1
2
3
4
5
6
7
8
9
{
"code": 200,
"message": "success",
"data": {
"id": 123,
"name": "张三"
},
"timestamp": 1640995200000
}

错误响应:

1
2
3
4
5
6
7
8
9
{
"code": 400,
"message": "参数错误",
"error": {
"field": "email",
"message": "邮箱格式不正确"
},
"timestamp": 1640995200000
}

4. 分页

使用query参数

1
GET /users?page=1&size=10&sort=createTime,desc

响应格式

1
2
3
4
5
6
7
8
9
10
11
12
{
"code": 200,
"data": {
"content": [...],
"page": {
"number": 1,
"size": 10,
"totalElements": 100,
"totalPages": 10
}
}
}

5. 过滤和搜索

1
2
3
4
5
6
7
8
9
10
11
# 过滤
GET /users?status=active&role=admin

# 搜索
GET /users?keyword=张三

# 范围查询
GET /orders?startDate=2025-01-01&endDate=2025-12-31

# 排序
GET /users?sort=createTime,desc&sort=name,asc

6. 版本控制

URL版本控制(推荐)

1
2
GET /v1/users
GET /v2/users

Header版本控制

1
2
GET /users
Accept: application/vnd.api+json;version=1

7. 安全性

使用HTTPS

生产环境必须使用HTTPS。

认证和授权

1
2
3
# 使用Bearer Token
GET /users
Authorization: Bearer <token>

防止SQL注入

使用参数化查询,不要拼接SQL。

防止XSS

对用户输入进行转义。

限流

1
2
3
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200

8. 文档

使用Swagger/OpenAPI生成API文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/api/v1/users")
@Api(tags = "用户管理")
public class UserController {

@GetMapping("/{id}")
@ApiOperation("获取用户信息")
@ApiResponses({
@ApiResponse(code = 200, message = "成功"),
@ApiResponse(code = 404, message = "用户不存在")
})
public ResponseEntity<User> getUser(
@ApiParam("用户ID") @PathVariable Long id
) {
// ...
}
}

9. 错误处理

统一异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(
ResourceNotFoundException e
) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(404, e.getMessage()));
}

@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidation(
ValidationException e
) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(400, e.getMessage()));
}
}

10. 性能优化

字段过滤

允许客户端指定需要的字段:

1
GET /users?fields=id,name,email

压缩响应

1
2
GET /users
Accept-Encoding: gzip

缓存

1
2
3
GET /users/123
Cache-Control: public, max-age=3600
ETag: "abc123"

11. 用户中心项目实战示例

之前在《用户中心项目总结:从 0 到 1 的完整实践》里提到,我刚开始设计接口时存在“路径混乱、方法不规范”的问题。所以在这里大概说一下我在这个项目里重构后的接口是怎么样的:

11.1 资源与 URL 重新规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 认证相关(无需版本,放在 /auth 下)
POST /auth/register # 注册
POST /auth/login # 登录
POST /auth/logout # 退出

# 用户资源(版本化)
GET /api/v1/users/me # 获取当前用户
PATCH /api/v1/users/me # 更新当前用户

# 管理员视角的用户资源
GET /api/v1/users # 分页查询用户列表
GET /api/v1/users/{userId} # 查看单个用户
PUT /api/v1/users/{userId} # 全量更新
PATCH /api/v1/users/{userId} # 局部更新
DELETE /api/v1/users/{userId} # 删除用户(标记 isDelete)

我把资源拆成三类:登录注册走 /auth,普通用户只动 /users/me,管理员才操作 /api/v1/users/{userId}。统一用名词 + HTTP 动词,别人看接口就能猜出意图。

11.2 请求 & 响应示例

以管理员修改用户状态为例,整个请求只关心三件事:命中正确的 URL、带上 token、告诉后端我要改哪些字段。

1
2
3
4
5
6
7
8
PATCH /api/v1/users/1024
Authorization: Bearer <token>
Content-Type: application/json

{
"status": "active",
"userRole": "admin"
}

响应统一套壳,code/message/data 避免每次猜字段,requestId 和 timestamp 帮我在日志里快速定位一次请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"code": 200,
"message": "success",
"data": {
"userId": 1024,
"username": "ahz",
"status": "active",
"userRole": "admin",
"updatedAt": "2024-11-29T15:40:00+08:00"
},
"requestId": "7f1d6c5b",
"timestamp": 1733371200000
}

如果发生业务冲突(比如用户名重复),我返回 409,并在 error.field/reason 写明原因。表单校验不过则用 422,这样前端能给用户更准确的提示。

1
2
3
4
5
6
7
8
9
{
"code": 409,
"message": "用户名已存在",
"error": {
"field": "username",
"reason": "DUPLICATE"
},
"timestamp": 1733371200000
}

11.3 过滤、分页、排序一次到位

1
2
3
GET /api/v1/users?page=1&size=20&role=admin&status=active&sort=createdAt,desc
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 92

列表类接口一次把分页、过滤、排序写清楚,数据库侧补上 idx_email、idx_phone 等索引。响应中再带 pagination 元数据和限流剩余额度,前端就不用多发请求来确认。

11.4 版本、认证与权限

  • 版本:只要有不兼容的改动,就复制一份到 /api/v2,老版本至少多留一两个迭代,让客户端慢慢升级。
  • 认证:所有受保护的接口都用 Authorization: Bearer <token>,网关校验通过后把用户放进 UserContext,业务代码不用重复写校验。
  • 权限:普通用户只能访问 GET/PATCH /api/v1/users/me,管理员才有 users/{userId} 的权限,角色就是简单的 user/admin 枚举。

总结

设计RESTful API时要注意:

  1. 语义清晰:URL和方法要能表达意图
  2. 状态码正确:使用合适的HTTP状态码
  3. 格式统一:响应格式要统一
  4. 文档完善:提供清晰的API文档
  5. 安全可靠:考虑认证、授权、限流等
  6. 性能优化:支持分页、过滤、缓存等

写在最后

好的API设计不仅要功能正确,还要易用、安全、高效。这需要我们在设计时多思考,多参考最佳实践。

刚开始写API的时候,完全不知道有什么规范,就是随便写(现在回头看,真的不忍直视 😅)。现在虽然还算不上很会,但至少我知道每次新开接口要先定义资源,再考虑版本与鉴权,最后配好监控和限流,整套流程像打补丁一样补上了。