docs: 创建 API 接口设计规范
This commit is contained in:
@@ -0,0 +1,588 @@
|
|||||||
|
# 健身房管理系统 API 接口设计规范
|
||||||
|
|
||||||
|
> 文档编号:GYM-API-SPEC-001
|
||||||
|
> 版本:v1.0
|
||||||
|
> 创建日期:2026-03-08
|
||||||
|
> 最后更新日期:2026-03-08
|
||||||
|
> 作者:张翔
|
||||||
|
> 状态:正式发布
|
||||||
|
|
||||||
|
## 文档修订历史
|
||||||
|
|
||||||
|
| 版本 | 日期 | 作者 | 修订内容 |
|
||||||
|
| ---- | ---------- | ---- | -------- |
|
||||||
|
| v1.0 | 2026-03-08 | 张翔 | 创建 API 接口设计规范 |
|
||||||
|
|
||||||
|
## 参考文档
|
||||||
|
|
||||||
|
- RESTful API 最佳实践
|
||||||
|
- OpenAPI 3.0 规范
|
||||||
|
- Spring WebFlux 官方文档
|
||||||
|
- RSocket 规范
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、API 设计原则
|
||||||
|
|
||||||
|
### 1.1 RESTful 风格
|
||||||
|
|
||||||
|
**资源导向**:
|
||||||
|
- 使用名词表示资源,不使用动词
|
||||||
|
- 使用 HTTP 方法表示操作
|
||||||
|
- 使用复数名词表示资源集合
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```
|
||||||
|
✅ GET /api/v1/members # 获取会员列表
|
||||||
|
✅ POST /api/v1/members # 创建会员
|
||||||
|
✅ GET /api/v1/members/{id} # 获取单个会员
|
||||||
|
✅ PUT /api/v1/members/{id} # 更新会员
|
||||||
|
✅ DELETE /api/v1/members/{id} # 删除会员
|
||||||
|
❌ GET /api/v1/getMembers
|
||||||
|
❌ POST /api/v1/createMember
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 版本控制
|
||||||
|
|
||||||
|
**URL 路径版本化**:
|
||||||
|
```
|
||||||
|
/api/v1/members
|
||||||
|
/api/v2/members
|
||||||
|
```
|
||||||
|
|
||||||
|
**版本号规则**:
|
||||||
|
- 格式:`v{主版本号}`
|
||||||
|
- 主版本号递增:不兼容的 API 变更
|
||||||
|
- 向后兼容:在同一版本内添加字段
|
||||||
|
|
||||||
|
### 1.3 响应式 API 设计
|
||||||
|
|
||||||
|
**异步非阻塞**:
|
||||||
|
- 使用 Spring WebFlux 实现响应式 API
|
||||||
|
- 返回类型:`Mono<T>`(单个对象)、`Flux<T>`(集合)
|
||||||
|
- 支持 Server-Sent Events (SSE) 实时推送
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```java
|
||||||
|
// 单个资源
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Mono<Member> getMember(@PathVariable Long id) {
|
||||||
|
return memberService.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 资源集合
|
||||||
|
@GetMapping
|
||||||
|
public Flux<Member> listMembers() {
|
||||||
|
return memberService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSE 实时推送
|
||||||
|
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
|
public Flux<Member> streamMembers() {
|
||||||
|
return memberService.streamAll();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、API 响应格式
|
||||||
|
|
||||||
|
### 2.1 标准响应结构
|
||||||
|
|
||||||
|
**成功响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "张三",
|
||||||
|
"phone": "138****1234"
|
||||||
|
},
|
||||||
|
"timestamp": "2026-03-08T10:30:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**列表响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "张三"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "李四"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pagination": {
|
||||||
|
"page": 1,
|
||||||
|
"size": 20,
|
||||||
|
"total": 100,
|
||||||
|
"totalPages": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": "2026-03-08T10:30:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "参数验证失败",
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"field": "phone",
|
||||||
|
"message": "手机号格式不正确"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": "2026-03-08T10:30:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 HTTP 状态码
|
||||||
|
|
||||||
|
| 状态码 | 含义 | 使用场景 |
|
||||||
|
|--------|------|----------|
|
||||||
|
| 200 OK | 成功 | GET、PUT、PATCH 成功 |
|
||||||
|
| 201 Created | 已创建 | POST 成功创建资源 |
|
||||||
|
| 204 No Content | 无内容 | DELETE 成功 |
|
||||||
|
| 400 Bad Request | 请求错误 | 参数验证失败 |
|
||||||
|
| 401 Unauthorized | 未授权 | 未登录或 Token 过期 |
|
||||||
|
| 403 Forbidden | 禁止访问 | 权限不足 |
|
||||||
|
| 404 Not Found | 未找到 | 资源不存在 |
|
||||||
|
| 409 Conflict | 冲突 | 资源已存在 |
|
||||||
|
| 422 Unprocessable Entity | 不可处理 | 业务规则验证失败 |
|
||||||
|
| 429 Too Many Requests | 请求过多 | 触发限流 |
|
||||||
|
| 500 Internal Server Error | 服务器错误 | 系统异常 |
|
||||||
|
|
||||||
|
### 2.3 数据格式
|
||||||
|
|
||||||
|
**日期时间格式**:
|
||||||
|
```
|
||||||
|
ISO 8601: 2026-03-08T10:30:00Z
|
||||||
|
日期:2026-03-08
|
||||||
|
时间:10:30:00
|
||||||
|
```
|
||||||
|
|
||||||
|
**数字格式**:
|
||||||
|
```
|
||||||
|
金额:100.00 (DECIMAL)
|
||||||
|
数量:10 (INTEGER)
|
||||||
|
比例:0.15 (DECIMAL)
|
||||||
|
```
|
||||||
|
|
||||||
|
**布尔值**:
|
||||||
|
```
|
||||||
|
true/false (JSON 原生布尔值)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、API 接口分类
|
||||||
|
|
||||||
|
### 3.1 会员管理 API
|
||||||
|
|
||||||
|
#### 3.1.1 创建会员
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/members
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"phone": "13812341234",
|
||||||
|
"name": "张三",
|
||||||
|
"gender": 1,
|
||||||
|
"birthday": "1990-01-01",
|
||||||
|
"fitnessGoal": "增肌"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 201,
|
||||||
|
"message": "创建成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"phone": "138****1234",
|
||||||
|
"name": "张三"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.1.2 获取会员详情
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/members/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"phone": "138****1234",
|
||||||
|
"name": "张三",
|
||||||
|
"gender": 1,
|
||||||
|
"birthday": "1990-01-01",
|
||||||
|
"fitnessGoal": "增肌",
|
||||||
|
"status": 1,
|
||||||
|
"level": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.1.3 会员列表查询
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/members
|
||||||
|
```
|
||||||
|
|
||||||
|
**查询参数**:
|
||||||
|
```
|
||||||
|
?phone=13812341234&status=1&page=1&size=20&sort=createdAt,desc
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"items": [...],
|
||||||
|
"pagination": {
|
||||||
|
"page": 1,
|
||||||
|
"size": 20,
|
||||||
|
"total": 100,
|
||||||
|
"totalPages": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 预约管理 API
|
||||||
|
|
||||||
|
#### 3.2.1 创建预约
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/bookings
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"slotId": 1,
|
||||||
|
"memberId": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 201,
|
||||||
|
"message": "预约成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"bookingNo": "BK202603080001",
|
||||||
|
"status": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.2 取消预约
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /api/v1/bookings/{id}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 204,
|
||||||
|
"message": "取消成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 订阅管理 API
|
||||||
|
|
||||||
|
#### 3.3.1 开通订阅模块
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/subscriptions
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"moduleCode": "marketing",
|
||||||
|
"billingCycle": 1,
|
||||||
|
"startDate": "2026-03-08",
|
||||||
|
"endDate": "2026-04-07"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 201,
|
||||||
|
"message": "开通成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"subscriptionNo": "SUB202603080001",
|
||||||
|
"moduleCode": "marketing",
|
||||||
|
"status": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、错误处理
|
||||||
|
|
||||||
|
### 4.1 错误码规范
|
||||||
|
|
||||||
|
**错误码结构**:
|
||||||
|
```
|
||||||
|
业务码 (3 位) + 错误类型码 (2 位) + 具体错误码 (2 位)
|
||||||
|
```
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```
|
||||||
|
MEM0101 - 会员创建失败
|
||||||
|
MEM0102 - 会员手机号已存在
|
||||||
|
BOK0201 - 预约失败
|
||||||
|
BOK0202 - 预约时段已满
|
||||||
|
SUB0301 - 订阅开通失败
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 全局异常处理
|
||||||
|
|
||||||
|
**控制器建议**:
|
||||||
|
```java
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(ResourceNotFoundException.class)
|
||||||
|
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||||
|
public Mono<ApiResponse<Void>> handleResourceNotFound(
|
||||||
|
ResourceNotFoundException ex) {
|
||||||
|
return Mono.just(ApiResponse.error(404, ex.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ValidationException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public Mono<ApiResponse<Void>> handleValidationException(
|
||||||
|
ValidationException ex) {
|
||||||
|
return Mono.just(ApiResponse.error(400, ex.getMessage(), ex.getErrors()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public Mono<ApiResponse<Void>> handleException(Exception ex) {
|
||||||
|
log.error("系统异常", ex);
|
||||||
|
return Mono.just(ApiResponse.error(500, "系统繁忙,请稍后重试"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 参数验证
|
||||||
|
|
||||||
|
**请求体验证**:
|
||||||
|
```java
|
||||||
|
public class CreateMemberRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "手机号不能为空")
|
||||||
|
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@NotBlank(message = "姓名不能为空")
|
||||||
|
@Size(min = 1, max = 50, message = "姓名长度不能超过 50 个字符")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@NotNull(message = "性别不能为空")
|
||||||
|
@Min(value = 0, message = "性别值无效")
|
||||||
|
@Max(value = 2, message = "性别值无效")
|
||||||
|
private Integer gender;
|
||||||
|
|
||||||
|
// getters and setters
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、安全设计
|
||||||
|
|
||||||
|
### 5.1 认证机制
|
||||||
|
|
||||||
|
**JWT Token 认证**:
|
||||||
|
```
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Token 结构**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sub": "user123",
|
||||||
|
"tenantId": 1,
|
||||||
|
"storeId": 1,
|
||||||
|
"roles": ["ADMIN"],
|
||||||
|
"iat": 1709870400,
|
||||||
|
"exp": 1709956800
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 权限控制
|
||||||
|
|
||||||
|
**基于角色的访问控制 (RBAC)**:
|
||||||
|
```java
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@PostMapping
|
||||||
|
public Mono<Member> createMember(@RequestBody CreateMemberRequest request) {
|
||||||
|
return memberService.create(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasAnyRole('ADMIN', 'COACH')")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Mono<Member> getMember(@PathVariable Long id) {
|
||||||
|
return memberService.findById(id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 限流
|
||||||
|
|
||||||
|
**令牌桶限流**:
|
||||||
|
```java
|
||||||
|
@RateLimiter(name = "apiRateLimiter")
|
||||||
|
@GetMapping
|
||||||
|
public Flux<Member> listMembers() {
|
||||||
|
return memberService.findAll();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**配置**:
|
||||||
|
```yaml
|
||||||
|
resilience4j:
|
||||||
|
ratelimiter:
|
||||||
|
instances:
|
||||||
|
apiRateLimiter:
|
||||||
|
limit-for-period: 100 # 每次允许 100 个请求
|
||||||
|
limit-refresh-period: 1s # 每秒刷新
|
||||||
|
timeout-duration: 0 # 不等待
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、API 文档
|
||||||
|
|
||||||
|
### 6.1 OpenAPI 规范
|
||||||
|
|
||||||
|
**使用 Springdoc OpenAPI**:
|
||||||
|
```java
|
||||||
|
@Bean
|
||||||
|
public OpenAPI customOpenAPI() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.info(new Info()
|
||||||
|
.title("健身房管理系统 API")
|
||||||
|
.version("v1")
|
||||||
|
.description("健身房管理系统 RESTful API 文档"))
|
||||||
|
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
|
||||||
|
.components(new Components()
|
||||||
|
.addSecuritySchemes("bearerAuth",
|
||||||
|
new SecurityScheme()
|
||||||
|
.type(SecurityScheme.Type.HTTP)
|
||||||
|
.scheme("bearer")
|
||||||
|
.bearerFormat("JWT")));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 API 文档访问
|
||||||
|
|
||||||
|
**Swagger UI**:
|
||||||
|
```
|
||||||
|
http://localhost:8080/swagger-ui.html
|
||||||
|
```
|
||||||
|
|
||||||
|
**OpenAPI JSON**:
|
||||||
|
```
|
||||||
|
http://localhost:8080/v3/api-docs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、API 版本迁移
|
||||||
|
|
||||||
|
### 7.1 版本兼容策略
|
||||||
|
|
||||||
|
**向后兼容**:
|
||||||
|
- 添加新字段:不影响旧版本
|
||||||
|
- 添加新接口:不影响旧版本
|
||||||
|
- 扩展枚举值:不影响旧版本
|
||||||
|
|
||||||
|
**不兼容变更**:
|
||||||
|
- 删除字段:需要升级版本
|
||||||
|
- 修改字段类型:需要升级版本
|
||||||
|
- 修改业务逻辑:需要升级版本
|
||||||
|
|
||||||
|
### 7.2 版本废弃流程
|
||||||
|
|
||||||
|
1. **标记废弃**:在旧版本 API 添加 `@Deprecated` 注解
|
||||||
|
2. **通知用户**:通过文档、邮件通知升级
|
||||||
|
3. **过渡期**:至少保留 3 个月
|
||||||
|
4. **正式下线**:移除旧版本 API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、性能优化
|
||||||
|
|
||||||
|
### 8.1 分页优化
|
||||||
|
|
||||||
|
**游标分页**:
|
||||||
|
```
|
||||||
|
GET /api/v1/members?cursor=eyJpZCI6MTAwfQ==&size=20
|
||||||
|
```
|
||||||
|
|
||||||
|
**优势**:
|
||||||
|
- 避免深度分页性能问题
|
||||||
|
- 适合大数据量场景
|
||||||
|
|
||||||
|
### 8.2 字段过滤
|
||||||
|
|
||||||
|
**按需返回字段**:
|
||||||
|
```
|
||||||
|
GET /api/v1/members?fields=id,name,phone
|
||||||
|
```
|
||||||
|
|
||||||
|
**优势**:
|
||||||
|
- 减少网络传输
|
||||||
|
- 提升响应速度
|
||||||
|
|
||||||
|
### 8.3 缓存策略
|
||||||
|
|
||||||
|
**HTTP 缓存头**:
|
||||||
|
```
|
||||||
|
Cache-Control: max-age=3600, public
|
||||||
|
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
||||||
|
Last-Modified: Wed, 08 Mar 2026 10:30:00 GMT
|
||||||
|
```
|
||||||
|
|
||||||
|
**Redis 缓存**:
|
||||||
|
```java
|
||||||
|
@Cacheable(value = "members", key = "#id")
|
||||||
|
public Mono<Member> findById(Long id) {
|
||||||
|
return memberRepository.findById(id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档结束**
|
||||||
Reference in New Issue
Block a user