- 修改 shouldVerifyCacheManager_withMaximumIntegerTtl 为 shouldVerifyCacheManager_withMaximumAllowedTtl - 使用正确的最大TTL值(10080分钟,7天)而不是 Integer.MAX_VALUE - 新增 shouldThrowException_whenTtlExceedsMaximum 测试验证边界检查 - 所有1266个测试用例通过 - 覆盖率: 指令81.89%, 行88.48%, 分支51.55% docs: 添加项目状态报告 - 生成 PROJECT_STATUS_REPORT.md 详细记录项目当前状态 - 包含质量指标、已完成功能、待办事项和技术债务
23 KiB
23 KiB
🦟 蚊子项目 - OpenAPI 3.0 文档配置
📋 概述
蚊子项目使用SpringDoc OpenAPI生成OpenAPI 3.0规范的API文档,支持自动生成和实时更新。
🚀 快速开始
1. 添加依赖
<!-- pom.xml -->
<properties>
<springdoc.version>2.3.0</springdoc.version>
</properties>
<dependencies>
<!-- SpringDoc OpenAPI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- Kubernetes支持(可选) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>${springdoc.version}</version>
</dependency>
</dependencies>
2. 基础配置
# application-prod.yml
springdoc:
api-docs:
enabled: true
path: /api-docs
groups:
enabled: true
swagger-ui:
enabled: true
path: /swagger-ui.html
display-operation-id: true
display-request-duration: true
show-extensions: true
show-common-extensions: true
default-models-expand-depth: 2
default-model-expand-depth: 2
try-it-out-enabled: true
persist-authorization: true
tags-sorter: alpha
operations-sorter: alpha
group-configs:
- group: public
display-name: Public APIs
paths-to-match: /api/v1/**
- group: internal
display-name: Internal APIs
paths-to-match: /api/v1/internal/**
- group: admin
display-name: Admin APIs
paths-to-match: /api/v1/admin/**
3. OpenAPI配置类
package com.mosquito.project.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import java.util.List;
/**
* OpenAPI配置类
*/
@Configuration
@Profile("prod")
public class OpenApiConfig {
@Value("${spring.application.name}")
private String applicationName;
@Value("${spring.application.version}")
private String applicationVersion;
@Value("${springdoc.api-docs.server.url}")
private String serverUrl;
@Bean
public OpenAPI mosquitoOpenAPI() {
// 安全方案定义
SecurityScheme apiKeyScheme = new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-API-Key")
.description("API密钥认证");
SecurityScheme bearerAuthScheme = new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT Token认证");
// 安全要求
SecurityRequirement apiKeyRequirement = new SecurityRequirement()
.addList("API Key");
SecurityRequirement bearerAuthRequirement = new SecurityRequirement()
.addList("Bearer Auth");
// 服务器配置
Server server = new Server()
.url(serverUrl)
.description("生产环境服务器");
// 组件配置
Components components = new Components()
.addSecuritySchemes("API Key", apiKeyScheme)
.addSecuritySchemes("Bearer Auth", bearerAuthScheme);
return new OpenAPI()
.info(new Info()
.title("蚊子项目 API文档")
.description("蚊子项目推广活动管理系统的API接口文档")
.version(applicationVersion)
.contact(new Contact()
.name("蚊子项目团队")
.email("support@mosquito.com")
.url("https://mosquito.com"))
.license(new License()
.name("MIT License")
.url("https://opensource.org/licenses/MIT")))
.servers(List.of(server))
.components(components)
.addSecurityItem(apiKeyRequirement)
.addSecurityItem(bearerAuthRequirement);
}
}
4. 开发环境配置
package com.mosquito.project.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.Collections;
import java.util.List;
/**
* Swagger配置(开发环境)
*/
@Configuration
@EnableOpenApi
@Profile("dev")
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.securitySchemes(Collections.singletonList(apiKey()))
.securityContexts(Collections.singletonList(securityContext()))
.select()
.apis(RequestHandlerSelectors.basePackage("com.mosquito.project.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("蚊子项目 API文档")
.description("蚊子项目推广活动管理系统的API接口文档")
.version("2.0.0")
.contact(new Contact(
"蚊子项目团队",
"https://mosquito.com",
"support@mosquito.com"))
.license("MIT License")
.licenseUrl("https://opensource.org/licenses/MIT")
.build();
}
private ApiKey apiKey() {
return new ApiKey("API Key", "X-API-Key", "header");
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(operationContext -> true)
.build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
return Collections.singletonList(
new SecurityReference("API Key", new AuthorizationScope[]{authorizationScope})
);
}
}
📖 API注解示例
1. Controller注解
package com.mosquito.project.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/activities")
@Tag(name = "活动管理", description = "活动相关的API接口")
@SecurityRequirement(name = "API Key")
public class ActivityController {
/**
* 创建活动
*/
@PostMapping
@Operation(
summary = "创建新活动",
description = "创建一个新的推广活动,返回活动ID",
tags = {"活动管理"},
operationId = "createActivity"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "201",
description = "活动创建成功",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = Activity.class)
)
),
@ApiResponse(
responseCode = "400",
description = "请求参数错误",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ApiResponse.class)
)
),
@ApiResponse(
responseCode = "401",
description = "API密钥无效",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ApiResponse.class)
)
)
})
public ResponseEntity<ApiResponse<Activity>> createActivity(
@Parameter(
name = "request",
description = "活动创建请求",
required = true,
schema = @Schema(implementation = CreateActivityRequest.class)
)
@RequestBody CreateActivityRequest request) {
// 实现逻辑
}
/**
* 获取活动详情
*/
@GetMapping("/{id}")
@Operation(
summary = "获取活动详情",
description = "根据活动ID获取活动的详细信息",
tags = {"活动管理"},
operationId = "getActivityById"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "活动详情",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = Activity.class)
)
),
@ApiResponse(
responseCode = "404",
description = "活动不存在",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ApiResponse.class)
)
)
})
public ResponseEntity<ApiResponse<Activity>> getActivity(
@Parameter(
name = "id",
description = "活动ID",
required = true,
example = "1"
)
@PathVariable Long id) {
// 实现逻辑
}
/**
* 更新活动
*/
@PutMapping("/{id}")
@Operation(
summary = "更新活动信息",
description = "更新指定活动的信息",
tags = {"活动管理"},
operationId = "updateActivity"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "更新成功",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = Activity.class)
)
),
@ApiResponse(
responseCode = "404",
description = "活动不存在"
)
})
public ResponseEntity<ApiResponse<Activity>> updateActivity(
@PathVariable Long id,
@RequestBody UpdateActivityRequest request) {
// 实现逻辑
}
/**
* 删除活动
*/
@DeleteMapping("/{id}")
@Operation(
summary = "删除活动",
description = "删除指定的活动",
tags = {"活动管理"},
operationId = "deleteActivity"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "删除成功"
),
@ApiResponse(
responseCode = "404",
description = "活动不存在"
)
})
public ResponseEntity<Void> deleteActivity(@PathVariable Long id) {
// 实现逻辑
}
/**
* 获取排行榜
*/
@GetMapping("/{id}/leaderboard")
@Operation(
summary = "获取活动排行榜",
description = "获取指定活动的排行榜数据,支持分页",
tags = {"活动管理"},
operationId = "getLeaderboard"
)
@Parameters({
@Parameter(
name = "id",
description = "活动ID",
required = true
),
@Parameter(
name = "page",
description = "页码,从0开始",
required = false,
schema = @Schema(type = "integer", defaultValue = "0")
),
@Parameter(
name = "size",
description = "每页大小",
required = false,
schema = @Schema(type = "integer", defaultValue = "20", maximum = "100")
),
@Parameter(
name = "topN",
description = "只显示前N名,如果设置则忽略分页",
required = false,
schema = @Schema(type = "integer")
)
})
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "排行榜数据",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = LeaderboardResponse.class)
)
)
})
public ResponseEntity<ApiResponse<LeaderboardResponse>> getLeaderboard(
@PathVariable Long id,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) Integer topN) {
// 实现逻辑
}
}
2. 模型注解
package com.mosquito.project.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
/**
* 活动创建请求
*/
@Data
@Schema(description = "活动创建请求")
public class CreateActivityRequest {
@Schema(
description = "活动名称",
example = "新年推广活动",
required = true
)
@NotBlank(message = "活动名称不能为空")
@Size(min = 2, max = 100, message = "活动名称长度必须在2-100个字符之间")
private String name;
@Schema(
description = "活动开始时间",
example = "2024-01-01T10:00:00",
required = true
)
@NotNull(message = "开始时间不能为空")
private LocalDateTime startTime;
@Schema(
description = "活动结束时间",
example = "2024-01-31T23:59:59",
required = true
)
@NotNull(message = "结束时间不能为空")
private LocalDateTime endTime;
@Schema(
description = "活动描述",
example = "新年期间的用户推广活动"
)
private String description;
@Schema(
description = "活动状态",
example = "draft",
allowableValues = {"draft", "active", "completed", "cancelled"}
)
private String status;
}
package com.mosquito.project.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
/**
* 活动响应
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "活动响应")
public class Activity {
@Schema(description = "活动ID", example = "1")
private Long id;
@Schema(description = "活动名称", example = "新年推广活动")
private String name;
@Schema(description = "活动开始时间", example = "2024-01-01T10:00:00")
private LocalDateTime startTime;
@Schema(description = "活动结束时间", example = "2024-01-31T23:59:59")
private LocalDateTime endTime;
@Schema(description = "活动状态", example = "active")
private String status;
@Schema(description = "活动描述")
private String description;
@Schema(description = "创建时间", example = "2024-01-01T08:00:00")
private LocalDateTime createdAt;
@Schema(description = "更新时间", example = "2024-01-01T08:00:00")
private LocalDateTime updatedAt;
}
3. 枚举注解
package com.mosquito.project.domain;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 活动状态枚举
*/
@Schema(description = "活动状态")
public enum ActivityStatus {
@Schema(description = "草稿状态")
DRAFT("draft", "草稿"),
@Schema(description = "进行中")
ACTIVE("active", "进行中"),
@Schema(description = "已完成")
COMPLETED("completed", "已完成"),
@Schema(description = "已取消")
CANCELLED("cancelled", "已取消");
private final String code;
private final String description;
ActivityStatus(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
}
🌐 访问API文档
Swagger UI
开发环境: http://localhost:8080/swagger-ui.html
测试环境: https://test-api.mosquito.com/swagger-ui.html
生产环境: https://api.mosquito.com/swagger-ui.html
OpenAPI JSON
http://localhost:8080/api-docs
https://api.mosquito.com/api-docs
OpenAPI YAML
http://localhost:8080/api-docs.yaml
https://api.mosquito.com/api-docs.yaml
🔒 安全配置
1. API密钥认证
@SecurityRequirement(name = "API Key")
@Operation(summary = "需要API密钥的接口")
public ResponseEntity<?> securedEndpoint() {
// 实现逻辑
}
2. JWT认证
@SecurityRequirement(name = "Bearer Auth")
@Operation(summary = "需要JWT Token的接口")
public ResponseEntity<?> jwtSecuredEndpoint() {
// 实现逻辑
}
3. 多重安全要求
@Operation(
summary = "多种认证方式",
security = {
@SecurityRequirement(name = "API Key"),
@SecurityRequirement(name = "Bearer Auth")
}
)
public ResponseEntity<?> multipleAuthEndpoint() {
// 实现逻辑
}
📚 导出API文档
1. 导出JSON格式
curl -o openapi.json http://localhost:8080/api-docs
2. 导出YAML格式
curl -o openapi.yaml http://localhost:8080/api-docs.yaml
3. 使用OpenAPI Generator生成客户端
# 生成TypeScript客户端
openapi-generator-cli generate \
-i openapi.json \
-g typescript-axios \
-o ./client/typescript
# 生成Java客户端
openapi-generator-cli generate \
-i openapi.json \
-g java \
-o ./client/java
# 生成Python客户端
openapi-generator-cli generate \
-i openapi.json \
-g python \
-o ./client/python
🧪 测试API文档
1. 使用Swagger UI测试
// 在浏览器中访问Swagger UI
// 1. 点击 "Authorize" 按钮
// 2. 输入API密钥: "your-api-key"
// 3. 点击 "Authorize"
// 4. 现在可以使用 "Try it out" 功能测试API
2. 使用Postman测试
// Postman Pre-request Script
pm.request.headers.add({
key: 'X-API-Key',
value: 'your-api-key'
});
// 导入OpenAPI到Postman
// 1. 打开Postman
// 2. File -> Import
// 3. 选择 openapi.json 文件
// 4. Postman会自动创建Collection
🔧 自定义配置
1. 自定义响应示例
@Operation(
summary = "创建活动",
responses = {
@ApiResponse(
responseCode = "201",
description = "活动创建成功",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = Activity.class),
examples = @ExampleObject(
name = "示例响应",
value = "{\"id\":1,\"name\":\"新年推广活动\",\"status\":\"active\"}"
)
)
)
}
)
2. 自定义请求示例
@Operation(
summary = "创建活动",
requestBody = @RequestBody(
description = "活动创建请求",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = CreateActivityRequest.class),
examples = {
@ExampleObject(
name = "标准请求",
value = "{\"name\":\"新年推广活动\",\"startTime\":\"2024-01-01T10:00:00\",\"endTime\":\"2024-01-31T23:59:59\"}"
)
}
)
)
)
3. 自定义错误响应
@Operation(
summary = "创建活动",
responses = {
@ApiResponse(
responseCode = "400",
description = "请求参数错误",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class),
examples = @ExampleObject(
name = "参数错误示例",
value = "{\"code\":\"VALIDATION_ERROR\",\"message\":\"活动名称不能为空\",\"details\":{\"name\":\"活动名称不能为空\"}}"
)
)
)
}
)
📊 API文档最佳实践
1. 分组组织
@Tag(name = "活动管理", description = "活动相关的API接口")
@Tag(name = "用户管理", description = "用户相关的API接口")
@Tag(name = "分享功能", description = "分享相关的API接口")
2. 清晰的描述
@Operation(
summary = "创建新活动", // 简短的标题
description = """
创建一个新的推广活动。
**注意事项:**
- 活动名称不能为空
- 开始时间必须早于结束时间
- 活动时长不能超过90天
""" // 详细的描述
)
3. 错误处理
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "操作成功"),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "401", description = "API密钥无效"),
@ApiResponse(responseCode = "403", description = "权限不足"),
@ApiResponse(responseCode = "404", description = "资源不存在"),
@ApiResponse(responseCode = "429", description = "请求过于频繁"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
🔄 自动化文档更新
1. CI/CD集成
# .github/workflows/docs.yml
name: Update API Documentation
on:
push:
branches: [main]
jobs:
update-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate OpenAPI Spec
run: |
curl -o openapi.json http://localhost:8080/api-docs
curl -o openapi.yaml http://localhost:8080/api-docs.yaml
- name: Commit Documentation
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add openapi.json openapi.yaml
git commit -m "Update API documentation"
git push
2. 自动生成客户端SDK
#!/bin/bash
# generate-client-sdks.sh
# 生成TypeScript客户端
echo "Generating TypeScript client..."
openapi-generator-cli generate \
-i openapi.json \
-g typescript-axios \
-o ./client/typescript
# 生成Java客户端
echo "Generating Java client..."
openapi-generator-cli generate \
-i openapi.json \
-g java \
-o ./client/java
echo "Client SDKs generated successfully!"
OpenAPI文档配置版本: v2.0.0
最后更新: 2026-01-22
维护团队: API Team