当前位置: 首页 > news >正文

RESTFul API接口设计指南_V2

API接口设计指南_V0.1.0

前言

接口是系统与外界交互的窗口,其他系统通过接口可以知道你管理着哪些资源,他能对这些资源干些什么。

当然我们不遵守规范或建议也可以满足上面的目标,既然如此我们为什么还要按RESTful的规范来设计我们的接口呢?

这样的灵魂拷问很现实也很真实,有小朋友会讲:

:::
我写接口都是一把梭,快得很,

什么RESTful?什么规范?

不存在的!

规范只会减慢我打字的速度。
:::

1. 核心理念:API即产品 (API as a Product)

在开始设计任何端点之前,我们必须树立一个核心理念:API是提供给其他开发者(无论是前端、移动端还是其他微服务)使用的产品。这意味着API的设计必须优先考虑:

  • 开发者体验 (Developer Experience, DX): API是否易于理解、学习和使用?错误信息是否清晰明确?

  • 健壮性 (Robustness): API在面对错误输入、依赖服务故障时,行为是否可预测?

  • 可演化性 (Evolvability): API能否在不破坏现有客户端的情况下,平滑地进行功能迭代和升级?

  • 安全性 (Security): API是否充分保护了数据和系统资源?

这篇文档中的所有规范,都源于以上核心理念。

2. RESTful 基础与命名规范

我们采用RESTful作为API设计的主要风格,它利用HTTP协议的语义来表达操作。

2.1. 资源路径 (URI)

URI代表“资源”,应全部使用名词复数,并采用**小写字母和连字符(kebab-case)**的组合。

  • 【正例】

    • 获取所有用户: GET /users

    • 获取ID为123的用户: GET /users/123

    • 获取用户123的所有订单: GET /users/123/orders

    • 获取用户123的最新订单: GET /users/123/latest-order (latest-order作为单一资源)

  • 【反例】

    • GET /getAllUsers (动词出现在路径中)

    • POST /user/create (动词出现在路径中)

    • GET /users_and_orders (资源边界不清晰)

    • GET /users/123/getOrder (动词出现在路径中)

  • 讲解:

    • 优势: 遵循HTTP语义,路径清晰地描述了资源层次,而非操作。kebab-case增强了URL的可读性,并避免了大小写敏感性问题。

    • 劣势: 对于非CRUD的复杂操作(如批量审核),可能需要引入/actions子资源或使用不同的设计模式,如RPC风格的端点。

2.2. HTTP 方法 (Verbs)

使用HTTP方法来描述对资源的操作。

  • GET: 安全且幂等。用于读取资源,不应产生副作用。

  • POST: 非幂等。用于创建子资源。

  • PUT: 幂等。用于完整替换一个已存在的资源。

  • PATCH: 幂等。用于部分更新一个已存在的资源。

  • DELETE: 幂等。用于删除一个资源。

  • 幂等性解释: 多次执行相同的操作,其结果与执行一次完全相同。这对于构建可靠的、可重试的客户端至关重要。

  • 评审要点:

    • GET请求是否被用于修改数据?(严重错误)

    • 更新操作是使用PUT还是PATCH?如果客户端只发送了部分字段,使用PUT可能会意外地将其他字段置为null

3. 数据传输结构 (DTO - Data Transfer Object)

一致、可预测的数据结构是提升开发者体验的关键。

3.1. 标准响应体封装

所有API响应都应包裹在一个标准结构中,方便客户端进行统一处理。

  • 【正例】
// 标准响应结构
export interface ApiResponse<T> {success: boolean;       // 操作是否成功code: number;           // 业务状态码 (非HTTP状态码)message: string | null; // 提示信息data: T | null;         // 成功时的数据error?: ApiError;       // 失败时的详细错误信息
}// 详细错误对象
export interface ApiError {type: string;           // 错误类型 (e.g., VALIDATION_ERROR, AUTHENTICATION_ERROR)details?: Record<string, string>; // 详细信息,如字段校验失败
}
  • 【反例】

    • 成功时直接返回数据: [{ "id": 1, "name": "..." }]

    • 失败时返回不同的结构: { "error": "Invalid ID", "reason": "..." }

  • 讲解:

    • 优势:
  1. 客户端统一处理: Vue客户端可以创建一个统一的拦截器来处理所有success: false的情况,无需在每个业务组件中重复编写错误处理逻辑。

    1. 元数据承载: 可以在响应中携带分页、追踪ID等元数据。

    2. 业务码分离: HTTP状态码表达协议层面的状态(如401未授权),而业务码code可以表达更精细的业务逻辑状态(如20001-库存不足)。

  2. 劣势: 增加了少量的网络负载。

3.2. 分页 (Pagination)

对于返回集合资源的接口,必须实现分页,以防止数据量过大拖垮服务器和客户端。

  • 【正例】data字段中包含分页信息。
// 在 ApiResponse.data 中
{"page": 1,          // 当前页码"size": 20,         // 每页数量"totalElements": 153, // 总条目数"totalPages": 8,    // 总页数"content": [/* 列表数据 */]
}

请求参数: GET /users?page=1&size=20

  • 【反例】

    • GET /users 返回所有用户数据。

    • 分页信息放在HTTP Headers中,对客户端不友好。

  • 讲解: 将分页信息和数据内容一起返回,是最直观、对前端最友好的方式。

3.3. 日期和时间

所有日期和时间字段都应使用 UTC 时间,并遵循 ISO 8601 格式。

  • 【正例】: "2025-09-08T15:30:00.123Z"

  • 【反例】: "2025/09/08 15:30", 1725780600 (Unix时间戳,可读性差)

  • 讲解: ISO 8601是全球标准,几乎所有语言都有内置的库来解析它,避免了时区转换的混乱。

3.4. 空值处理

  • 不存在的字段: 不返回该字段。

  • 值为**null**的字段: 明确返回 null

  • 空集合: 返回空数组 [],而不是 null

4. API版本管理 (Versioning)

  • 推荐方式: 在URL中加入版本号,如 /v1/users

  • 讲解:

    • 优势: 最直观,对开发者和浏览器都非常友好。可以方便地对不同版本的API进行路由和负载均衡。

    • 劣势: 路径中包含了版本信息,不够“纯粹”。(但工程上,清晰度远比纯粹性重要)

    • 时机: 只有在发生**破坏性变更(Breaking Change)**时(如删除字段、修改字段类型)才需要升级版本号。新增字段属于非破坏性变更。

5. 高级设计模式

5.1. 过滤、排序和字段选择

允许客户端按需索取数据,是提升性能和灵活性的关键。

  • 过滤: GET /orders?status=shipped&customerId=123

  • 排序: GET /users?sort=createdAt,desc (按创建时间降序)

  • 字段选择 (稀疏字段集): GET /users/123?fields=id,name,email

  • 讲解:

    • 优势: 极大地减少了网络负载,对于移动端或低带宽环境尤其重要。后端也可以根据fields参数优化数据库查询,避免查询不必要的列。

    • 实现: 后端可以使用规范解析库(如 RSQL)来安全地将查询参数转换为数据库查询。

5.2. 实时通信与服务器推送事件 (SSE - Server-Sent Events)

对于需要服务器向客户端单向推送实时数据的场景(如站内信、状态更新、日志流),SSE是比WebSocket更轻量、更简单的选择。

  • 场景: 后端任务执行进度通知。

  • 讲解:

    • 优势:
  1. 基于HTTP: 无需新的协议或端口,易于与现有基础设施(如Nginx)集成。

    1. 简单: 客户端API非常简单 (EventSource API),且支持自动重连。

    2. 轻量: 相比WebSocket,协议开销更小。

  2. 劣势: 只能实现服务器到客户端的单向通信。

6. 安全 (Security)

  • 认证 (Authentication): 推荐使用基于 OAuth2/OIDCToken 认证(如 JWT)。Token通过Authorization: Bearer <token> HTTP头传递。

  • 授权 (Authorization): 在微服务网关或服务内部,根据用户的角色和权限(Scopes)对API访问进行控制。

  • HTTPS: 所有API通信必须使用HTTPS加密。

7. 实现样例 (Vue + TypeScript + Spring Cloud)

场景:获取用户列表并实时接收用户创建通知

7.1. 后端实现 (Spring Cloud)

1. 标准响应与错误处理

// ApiResponse.java (使用泛型)
@Data
public class ApiResponse<T> {private boolean success;private int code;private String message;private T data;// 省略构造函数...
}// GlobalExceptionHandler.java (统一异常处理)
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public ApiResponse<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {// ...构造详细的ApiError对象return new ApiResponse<>(false, 40001, "Validation Failed", null);}
}

2. 用户Controller (REST API)

@RestController
@RequestMapping("/v1/users")
public class UserController {@Autowiredprivate UserService userService;// 【正例】使用DTO,实现了分页、排序@GetMappingpublic ApiResponse<Page<UserDTO>> getUsers(@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "20") int size,@RequestParam(required = false) String sort) {Pageable pageable = PageRequest.of(page, size, Sort.by(sort)); // 简化版排序Page<UserDTO> userPage = userService.findAll(pageable);return new ApiResponse<>(true, 200, "Success", userPage);}
}

3. SSE Controller

@RestController
@RequestMapping("/v1/events")
public class EventController {private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();// 【正例】客户端订阅SSE事件@GetMapping(path = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter subscribe(String clientId) {SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);emitters.put(clientId, emitter);emitter.onCompletion(() -> emitters.remove(clientId));emitter.onTimeout(() -> emitters.remove(clientId));return emitter;}// 当有新用户创建时,调用此方法推送事件public void sendUserCreatedEvent(UserDTO newUser) {emitters.forEach((clientId, emitter) -> {try {emitter.send(SseEmitter.event().name("user-created") // 事件类型.data(newUser)); // 事件数据} catch (IOException e) {emitter.completeWithError(e);}});}
}
7.2. 前端实现 (Vue 3 + TypeScript)

1. API客户端 (封装axios)

// api/client.ts
import axios from 'axios';
import type { ApiResponse } from './types'; // 引入类型定义const apiClient = axios.create({ baseURL: '/api' });apiClient.interceptors.response.use((response) => {// 【正例】统一处理ApiResponse结构const apiResponse: ApiResponse<any> = response.data;if (apiResponse.success) {return apiResponse.data; // 直接返回data字段给业务逻辑} else {// 统一处理业务错误,如弹窗提示showToast(apiResponse.message || '操作失败');return Promise.reject(apiResponse);}},(error) => {// 处理HTTP层面的错误showToast('网络请求失败');return Promise.reject(error);}
);export default apiClient;

2. Vue组件中使用

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import apiClient from '@/api/client';
import type { UserDTO, Page } from '@/api/types';const users = ref<UserDTO[]>([]);
const eventSource = ref<EventSource | null>(null);// 【正例】调用分页接口
const fetchUsers = async () => {try {const userPage: Page<UserDTO> = await apiClient.get('/v1/users?page=0&size=10');users.value = userPage.content;} catch (error) {console.error("Failed to fetch users:", error);}
};// 【正例】监听SSE事件
const setupSseListener = () => {const clientId = 'unique-client-id'; // 应由认证系统生成eventSource.value = new EventSource(`/api/v1/events/subscribe?clientId=${clientId}`);eventSource.value.addEventListener('user-created', (event) => {const newUser = JSON.parse(event.data) as UserDTO;users.value.unshift(newUser); // 在列表顶部添加新用户showNotification(`新用户创建: ${newUser.name}`);});eventSource.value.onerror = () => {console.error("SSE connection error.");// EventSource会在此处自动尝试重连};
};onMounted(() => {fetchUsers();setupSseListener();
});onUnmounted(() => {// 组件销毁时关闭连接eventSource.value?.close();
});
</script>

【反例】前端代码

// 在组件中直接使用axios,没有统一封装和错误处理
axios.get('/api/v1/users').then(response => {// 每次都要判断response.data.successif(response.data.success) {users.value = response.data.data.content;} else {alert(response.data.message);}
}).catch(err => {alert('网络错误');
});
  • 劣势: 模板代码重复,错误处理分散,难以维护。

8. 云原生友好性

为了让服务在Kubernetes等云原生环境中更好地被管理(MCP - Managed Control Plane),API应提供:

  • 健康检查端点:

    • GET /actuator/health/liveness: 服务是否存活。

    • GET /actuator/health/readiness: 服务是否准备好接收流量。

  • 结构化日志: 所有API请求和响应的关键信息应以JSON格式输出到日志,方便日志系统(如ELK, Loki)收集和分析。


文章转载自:

http://sadEx8xr.jgnjL.cn
http://tozmt1iq.jgnjL.cn
http://Rt3wCNIw.jgnjL.cn
http://MOYv9730.jgnjL.cn
http://8B4Qt9Og.jgnjL.cn
http://p78qjmFU.jgnjL.cn
http://dtlIHYuN.jgnjL.cn
http://RMwjoF1V.jgnjL.cn
http://wyA7XXTR.jgnjL.cn
http://2B7X3qbY.jgnjL.cn
http://5ZKOZMf4.jgnjL.cn
http://SByDBi6G.jgnjL.cn
http://lov77CAp.jgnjL.cn
http://RYO6OF9X.jgnjL.cn
http://LLSuYB0Q.jgnjL.cn
http://mX0wiW6f.jgnjL.cn
http://UYNJPOSP.jgnjL.cn
http://SCLth7iy.jgnjL.cn
http://jZnoTmjX.jgnjL.cn
http://CjlHAzkW.jgnjL.cn
http://1GfJYqmp.jgnjL.cn
http://yb87V8yt.jgnjL.cn
http://1msUVyPW.jgnjL.cn
http://CgosSvQ3.jgnjL.cn
http://j0FGOCCA.jgnjL.cn
http://n41McfeF.jgnjL.cn
http://8SoVigoA.jgnjL.cn
http://NJd81v3D.jgnjL.cn
http://IwaB6MmH.jgnjL.cn
http://PUWUpeWN.jgnjL.cn
http://www.dtcms.com/a/384591.html

相关文章:

  • Linux第十七讲:应用层自定义协议与序列化
  • ESLint 自定义规则开发
  • 三维地震数据体:形态、处理流程与勘探应用笔记
  • HTTP标头全解析:保护你的Web应用!
  • 机器人控制器开发(定位——cartographer ros2 使用2)
  • 元学习原理与实验实战:让机器学会快速学习
  • [Cesium] 基于Cesium的二次开发的库
  • 红外IR的运用
  • 基于51单片机可燃气体报警、风扇、继电器断闸
  • Ubuntu下搭建vllm+modelscope+deepseek qwen3
  • 【 SQLMap】GET型注入
  • Actix-webRust Web框架入门教程
  • Docker Grafana 忘了密码修改方法
  • 移动端触摸事件与鼠标事件的触发机制详解
  • Go语言深度解析:从入门到精通的完整指南
  • CKS-CN 考试知识点分享(6) 日志审计
  • CentOS 7 环境下 PHP 7.3 与 PHP-FPM 完整安装指南(外网 yum / 内网源码双方案)
  • ubuntu24.04下让终端显示当前git分支的最简单的方法
  • 快速安装WIN10
  • 【bert微调+微博数据集】-实现微博热点话题预测与文本的情感分析
  • Java 黑马程序员学习笔记(进阶篇9)
  • 认知语义学中的隐喻理论对人工智能自然语言处理深层语义分析的启示与影响研究
  • 03-htmlcss
  • 【PSINS工具箱下的例程】用于生成平面上8字型飞行轨迹,高度和飞行速度等值可自定义|包括AVP(姿态、速度、位置)和IMU数据(加速度计与陀螺仪)
  • SSB-Based Signal Processing for Passive Radar Using a 5G Network
  • SQLAlchemy使用笔记(一)
  • 【C#】.net core 8.0 MVC在一次偶然间发现控制器方法整个Model实体类对象值为null,猛然发现原来是
  • 【小白笔记】 Linux 命令及其含义
  • vue ElementUI textarea在光标位置插入指定变量及校验
  • 边缘人工智能计算机