diff --git a/docs/design/technical/API-接口设计规范.md b/docs/design/technical/API-接口设计规范.md new file mode 100644 index 0000000..cc2106a --- /dev/null +++ b/docs/design/technical/API-接口设计规范.md @@ -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`(单个对象)、`Flux`(集合) +- 支持 Server-Sent Events (SSE) 实时推送 + +**示例**: +```java +// 单个资源 +@GetMapping("/{id}") +public Mono getMember(@PathVariable Long id) { + return memberService.findById(id); +} + +// 资源集合 +@GetMapping +public Flux listMembers() { + return memberService.findAll(); +} + +// SSE 实时推送 +@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) +public Flux 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> handleResourceNotFound( + ResourceNotFoundException ex) { + return Mono.just(ApiResponse.error(404, ex.getMessage())); + } + + @ExceptionHandler(ValidationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Mono> handleValidationException( + ValidationException ex) { + return Mono.just(ApiResponse.error(400, ex.getMessage(), ex.getErrors())); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Mono> 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 createMember(@RequestBody CreateMemberRequest request) { + return memberService.create(request); +} + +@PreAuthorize("hasAnyRole('ADMIN', 'COACH')") +@GetMapping("/{id}") +public Mono getMember(@PathVariable Long id) { + return memberService.findById(id); +} +``` + +### 5.3 限流 + +**令牌桶限流**: +```java +@RateLimiter(name = "apiRateLimiter") +@GetMapping +public Flux 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 findById(Long id) { + return memberRepository.findById(id); +} +``` + +--- + +**文档结束**