第 219 章:API 设计规范
学习目标
- 掌握 RESTful API 设计规范
- 统一响应格式
- 学会错误码体系
- 理解 GraphQL 与 gRPC 选型
一、RESTful 设计规范
1.1 资源建模
把业务抽象为资源(名词),操作对应 HTTP 方法。
❌ /createOrder
❌ /getUser
✅ /orders POST 创建
✅ /users/{id} GET 查询1.2 HTTP 方法语义
| 方法 | 幂等 | 语义 |
|---|---|---|
| GET | ✅ | 获取资源 |
| POST | ❌ | 新建资源 |
| PUT | ✅ | 全量替换 |
| PATCH | ❌ | 部分更新 |
| DELETE | ✅ | 删除资源 |
1.3 URI 设计
✅ 推荐:
GET /users # 列表
GET /users/123 # 详情
POST /users # 创建
PUT /users/123 # 替换
PATCH /users/123 # 部分更新
DELETE /users/123 # 删除
# 子资源
GET /users/123/orders # 用户的订单
POST /users/123/orders # 给用户下单
# 过滤 / 排序 / 分页
GET /users?status=active&sort=-created_at&page=1&size=20
# 操作(动词)
POST /users/123/avatar # 上传头像
POST /orders/123/cancel # 取消订单
❌ 不要在 URI 中用动词:
GET /getUsers
POST /createOrder1.4 复数 vs 单数
✅ 推荐复数: /users /orders /products
理由: URI 表"资源集合",概念更清晰1.5 路径 vs Header
| 用途 | 推荐位置 |
|---|---|
| 业务参数 | 路径 / 查询参数 |
| 鉴权 | Header (Authorization) |
| 幂等 | Header (Idempotency-Key) |
| 版本 | URL 或 Header |
| 业务上下文 | Header (X-User-Id, X-Tenant-Id) |
1.6 HATEOAS
返回资源时携带相关链接,降低耦合。
json
{
"id": 123,
"name": "Tom",
"status": "active",
"_links": {
"self": { "href": "/users/123" },
"orders": { "href": "/users/123/orders" },
"avatar": { "href": "/users/123/avatar" }
}
}二、统一响应格式
2.1 通用 Result
java
@Data
public class Result<T> {
private int code;
private String message;
private T data;
private String traceId;
public static <T> Result<T> ok() {
return build(200, "success", null);
}
public static <T> Result<T> ok(T data) {
return build(200, "success", data);
}
public static <T> Result<T> error(int code, String message) {
return build(code, message, null);
}
public boolean isSuccess() {
return code == 200;
}
}使用:
java
@GetMapping("/users/{id}")
public Result<UserDTO> get(@PathVariable Long id) {
return Result.ok(userService.getById(id));
}2.2 分页响应
java
@Data
public class PageResult<T> {
private List<T> items;
private long total;
private int page;
private int size;
public static <T> PageResult<T> of(List<T> items, long total, int page, int size) {
PageResult<T> r = new PageResult<>();
r.items = items;
r.total = total;
r.page = page;
r.size = size;
return r;
}
}2.3 响应示例
成功:
json
{
"code": 200,
"message": "success",
"data": {
"id": 123,
"name": "Tom"
},
"traceId": "a1b2c3"
}失败:
json
{
"code": 40001,
"message": "参数错误:用户ID不能为空",
"data": null,
"traceId": "a1b2c3"
}三、错误码体系
3.1 设计原则
- 全数字:便于跨语言传递
- 分层结构:业务码 + 子码
- 易于定位:从错误码可看出模块
3.2 结构
python
5位数字: AABCC
AA = 服务编码(2位)
B = 错误级别(1位)
CC = 具体错误(2位)
例:
10001 = 用户服务-参数错误
10101 = 用户服务-未找到
10201 = 用户服务-已存在
20001 = 订单服务-参数错误
20002 = 订单服务-余额不足
20003 = 订单服务-库存不足3.3 错误级别
| 范围 | 含义 |
|---|---|
| 0xx | 系统级错误 |
| 1xx | 用户服务 |
| 2xx | 订单服务 |
| 4xx | 参数校验失败 |
| 5xx | 权限 / 鉴权 |
3.4 HTTP 状态码 vs 业务码
java
@RestController
public class UserController {
@GetMapping("/users/{id}")
public Result<User> get(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
// HTTP 状态 404 + 业务码 10101
return ResponseEntity.status(404).body(
Result.error(10101, "用户不存在")
);
}
return ResponseEntity.ok(Result.ok(user));
}
}原则:业务码用于定位,HTTP 状态用于协议识别。
| 场景 | HTTP | 业务码 |
|---|---|---|
| 成功 | 200/201 | 200 |
| 客户端错误 | 400 | 4xxxx |
| 未授权 | 401 | 401xx |
| 禁止访问 | 403 | 403xx |
| 未找到 | 404 | 404xx |
| 服务器错误 | 500 | 5xxxx |
四、字段命名规范
4.1 命名风格
java
// JSON 用 snake_case 还是 camelCase?
// 主流: camelCase (Java/JS)
{
"userId": 1,
"userName": "Tom"
}
// 一些场景用 snake_case (Python/Ruby)
{
"user_id": 1,
"user_name": "Tom"
}建议:Java 栈用 camelCase,跨语言用 snake_case。
4.2 时间字段
json
// 字符串(推荐,跨时区安全)
"createdAt": "2026-08-13T10:30:00+08:00"
"createdAt": "2026-08-13 10:30:00"
// 时间戳(便于计算)
"createTime": 16918962000004.3 金额
json
// 分(整数,避免浮点精度)
"amount": 1999 // ¥19.99
// Decimal 字符串
"amount": "19.99"4.4 空值与默认值
java
@Data
public class UserDTO {
private String name;
private Integer age; // null 表示未知,不是 0
private BigDecimal balance;
@JsonInclude(JsonInclude.Include.NON_NULL) // null 不序列化
private String phone;
}五、版本管理
5.1 URL 版本(常用)
/api/v1/users
/api/v2/users适用:版本差异大、独立维护
5.2 Header 版本
GET /api/users
Accept: application/vnd.myapp.v2+json适用:同接口多版本
5.3 查询参数版本
GET /api/users?version=2适用:不推荐用于生产
5.4 兼容性原则
- ✅ 添加新字段(消费者忽略未知字段)
- ✅ 添加新端点
- ❌ 修改字段类型
- ❌ 重命名字段
- ❌ 删除字段(标记 deprecated,过两个版本再删)
六、GraphQL
6.1 解决什么问题
传统 REST:前端要拼接多次请求
GraphQL:一次请求获取所有数据
graphql
query {
user(id: 123) {
name
avatar
orders(first: 10) {
id
amount
status
}
}
}6.2 Spring Boot + GraphQL
java
@Controller
public class UserController {
@QueryMapping
public UserDTO userById(@Argument Long id) {
return userService.findById(id);
}
@SchemaMapping(typeName = "User", field = "orders")
public List<OrderDTO> orders(UserDTO user) {
return orderService.findByUser(user.getId());
}
}graphql
# schema.graphqls
type User {
id: ID!
name: String
avatar: String
orders(first: Int = 10): [Order!]!
}6.3 何时用 GraphQL
| 适合 | 不适合 |
|---|---|
| 多端(移动/Web)字段差异大 | 简单 CRUD |
| 数据图(实体关系复杂) | 性能极敏感场景(N+1) |
| 前端迭代快 | 小团队 / 简单业务 |
七、gRPC
7.1 特点
- HTTP/2 多路复用
- Protobuf 二进制(快 5-10 倍)
- 强类型契约
- 支持 4 种通信方式
7.2 通信模式
protobuf
// 1. Unary
rpc GetUser(UserRequest) returns (UserResponse);
// 2. Server Streaming
rpc ListUsers(ListRequest) returns (stream User);
// 3. Client Streaming
rpc Upload(stream Chunk) returns (UploadResult);
// 4. Bidirectional
rpc Chat(stream Message) returns (stream Message);7.3 何时用 gRPC
| 适合 | 不适合 |
|---|---|
| 服务间高频低延迟调用 | 外部 API(浏览器难调) |
| 强契约(团队间) | 需要流式上传/可读文本 |
| 多语言服务 | 调试时只看 log |
八、API 文档
8.1 OpenAPI 3.0(Swagger)
yaml
openapi: 3.0.0
info:
title: 用户服务 API
version: 1.0.0
paths:
/users/{id}:
get:
summary: 查询用户
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/User'8.2 springdoc-openapi(推荐)
java
@Operation(summary = "查询用户")
@GetMapping("/users/{id}")
public UserDTO get(
@Parameter(description = "用户ID") @PathVariable Long id
) {
return userService.get(id);
}访问: http://localhost:8080/swagger-ui.html
九、安全规范
9.1 鉴权
yaml
# API Gateway 统一鉴权
spring:
cloud:
gateway:
routes:
- id: protected-route
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- name: AuthFilter # 校验 JWT9.2 HTTPS
所有外部 API 必须 HTTPS
内部服务可 mTLS9.3 限流
java
// 网关侧限流
@SentinelResource("createOrder")
@PostMapping("/orders")
public Result<Order> create(...) { ... }9.4 防重放
- 时间戳 + 签名
- 一次性 token
- 幂等键
十、API 网关聚合
聚合多个服务调用,减少客户端请求,降低复杂度。
十一、本章小结
| 主题 | 要点 |
|---|---|
| RESTful | 资源命名 + HTTP 方法语义 |
| 响应 | 统一封装 + 分页 |
| 错误码 | 分层设计 + HTTP 状态 |
| 版本 | URL 路径 + 兼容原则 |
| GraphQL | 多端聚合场景 |
| gRPC | 内部高频调用 |
| 文档 | OpenAPI / springdoc |
| 安全 | 鉴权 + HTTPS + 限流 |
动手练习
- 为订单服务设计完整 RESTful API(列表、详情、创建、状态流转、取消)
- 实现通用 Result 封装,含分页能力
- 定义错误码体系(订单 + 商品 + 库存三服务)
- 用 springdoc-openapi 生成接口文档
推荐阅读
下一章:第 220 章:事件驱动架构