前言
最近在重构用户中心项目的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
|
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时要注意:
- 语义清晰:URL和方法要能表达意图
- 状态码正确:使用合适的HTTP状态码
- 格式统一:响应格式要统一
- 文档完善:提供清晰的API文档
- 安全可靠:考虑认证、授权、限流等
- 性能优化:支持分页、过滤、缓存等
写在最后
好的API设计不仅要功能正确,还要易用、安全、高效。这需要我们在设计时多思考,多参考最佳实践。
刚开始写API的时候,完全不知道有什么规范,就是随便写(现在回头看,真的不忍直视 😅)。现在虽然还算不上很会,但至少我知道每次新开接口要先定义资源,再考虑版本与鉴权,最后配好监控和限流,整套流程像打补丁一样补上了。