关于 springdoc-openapi
面向 Spring Boot Java 项目的技能指南,介绍使用 springdoc-openapi 为 API 生成并配置 OpenAPI 文档与 Swagger UI。涵盖端点、模型、安全要求和 API 分组等文档实践。
主要功能
- 01配置 API 文档与 Swagger UI
- 02用注解描述端点和模型
- 03记录安全方案与响应
- 04按标签或路径分组 API
- 05隐藏内部端点
- 06排查常见配置问题
适用场景
- 01为 Spring Boot API 添加 OpenAPI 文档
- 02补充 DTO 字段和枚举说明
- 03整理公开与内部端点
- 04检查 Swagger UI 或模式生成问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill springdoc-openapi安装后在 Claude Code 中输入/springdoc-openapi确认已加载。
详细介绍
Springdoc OpenAPI(Swagger)
深入了解:使用技术为
springdoc-openapi的mcp__documentation__fetch_docs获取全面文档。
Maven 配置
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
应用配置
springdoc:
api-docs:
path: /v3/api-docs
enabled: true
swagger-ui:
path: /swagger-ui.html
enabled: true
operationsSorter: method
tagsSorter: alpha
displayRequestDuration: true
filter: true
packages-to-scan: com.example.controller
paths-to-match: /api/**
OpenAPI 配置
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("My API")
.version("1.0.0")
.description("REST API Documentation")
.contact(new Contact()
.name("API Support")
.email("[email protected]"))
.license(new License()
.name("MIT")
.url("https://opensource.org/licenses/MIT")))
.externalDocs(new ExternalDocumentation()
.description("Wiki Documentation")
.url("https://wiki.example.com"))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
.components(new Components()
.addSecuritySchemes("bearerAuth",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.description("JWT token authentication")));
}
}
控制器文档
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Tag(name = "Users", description = "User management endpoints")
public class UserController {
private final UserService userService;
@Operation(
summary = "Get all users",
description = "Returns a paginated list of all users"
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Success",
content = @Content(schema = @Schema(implementation = PageUserResponse.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@GetMapping
public ResponseEntity<Page<UserResponse>> findAll(
@Parameter(description = "Page number (0-indexed)")
@RequestParam(defaultValue = "0") int page,
@Parameter(description = "Page size")
@RequestParam(defaultValue = "10") int size) {
return ResponseEntity.ok(userService.findAll(page, size));
}
@Operation(summary = "Get user by ID")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "User found"),
@ApiResponse(responseCode = "404", description = "User not found")
})
@GetMapping("/{id}")
public ResponseEntity<UserResponse> findById(
@Parameter(description = "User ID", required = true, example = "1")
@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
@Operation(summary = "Create new user")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "User created"),
@ApiResponse(responseCode = "400", description = "Invalid input")
})
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<UserResponse> create(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "User creation data",
required = true,
content = @Content(schema = @Schema(implementation = CreateUserRequest.class)))
@Valid @RequestBody CreateUserRequest dto) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(userService.create(dto));
}
}
Schema 文档
@Data
@Schema(description = "User creation request")
public class CreateUserRequest {
@Schema(
description = "User's full name",
example = "John Doe",
minLength = 2,
maxLength = 100,
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank
@Size(min = 2, max = 100)
private String name;
@Schema(
description = "User's email address",
example = "[email protected]",
format = "email",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotBlank
@Email
private String email;
@Schema(
description = "User's password",
example = "SecurePass123!",
minLength = 8,
requiredMode = Schema.RequiredMode.REQUIRED,
accessMode = Schema.AccessMode.WRITE_ONLY
)
@NotBlank
@Size(min = 8)
private String password;
@Schema(
description = "User's role",
example = "USER",
defaultValue = "USER",
allowableValues = {"ADMIN", "MANAGER", "USER"}
)
private UserRole role;
}
@Data
@Builder
@Schema(description = "User response")
public class UserResponse {
@Schema(description = "User ID", example = "1")
private Long id;
@Schema(description = "User's name", example = "John Doe")
private String name;
@Schema(description = "User's email", example = "[email protected]")
private String email;
@Schema(description = "User's role", example = "USER")
private UserRole role;
@Schema(description = "Account status", example = "ACTIVE")
private UserStatus status;
@Schema(description = "Creation timestamp", example = "2024-01-15T10:30:00")
private LocalDateTime createdAt;
}
枚举文档
@Schema(description = "User roles")
public enum UserRole {
@Schema(description = "System administrator with full access")
ADMIN,
@Schema(description = "Department manager")
MANAGER,
@Schema(description = "Regular user")
USER
}
安全文档
// Mark endpoint as public (no auth required)
@Operation(summary = "Login", security = {})
@PostMapping("/auth/login")
public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
return ResponseEntity.ok(authService.login(request));
}
// Require specific roles in documentation
@Operation(
summary = "Delete user",
security = @SecurityRequirement(name = "bearerAuth")
)
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
按标签对 API 分组
@Configuration
public class OpenApiConfig {
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("public")
.pathsToMatch("/api/v1/public/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/api/v1/admin/**")
.build();
}
}
隐藏端点
@Hidden // Hide entire controller
@RestController
public class InternalController { }
// Hide specific endpoint
@Operation(hidden = true)
@GetMapping("/internal")
public void internal() { }
关键注解
| 注解 | 用途 |
|---|---|
@Tag | 对端点分组 |
@Operation | 描述端点 |
@Parameter | 记录路径/查询参数 |
@Schema | 记录模型/字段 |
@ApiResponse | 记录响应 |
@Hidden | 在文档中隐藏 |
@SecurityRequirement | 安全方案 |
不适合使用此技能的情况
- 编写通用 OpenAPI 规范(使用
openapi技能) - 编写 GraphQL API 文档(使用
graphql技能) - 非 Spring Boot Java 项目
- 生成前端 OpenAPI 客户端(使用
openapi-codegen技能) - Node.js/TypeScript API(使用
openapi或特定框架的技能)
反模式
| 反模式 | 不良影响 | 解决方案 |
|---|---|---|
| 端点缺少 @Operation | 文档质量差,缺少描述 | 为所有端点添加 @Operation |
| @Schema 中没有示例 | API 难以理解 | 为所有 Schema 添加示例值 |
| DTO 未使用 @Schema | 缺少字段说明 | 使用 @Schema 记录所有 DTO 字段 |
| 未隐藏内部端点 | 暴露实现细节 | 使用 @Hidden 隐藏内部端点 |
| 缺少安全要求 | 身份验证要求不明确 | 在需要时添加 @SecurityRequirement |
| 缺少错误响应文档 | API 合约不完整 | 记录所有 4xx/5xx 响应 |
| 将实体类用作 API 模型 | 泄露数据库结构 | 创建独立的 DTO 类 |
| 在代码中硬编码 API 信息 | 配置未外置 | 使用 application.yml 配置 API 元数据 |
| @Parameter 缺少说明 | API 可用性差 | 记录所有路径/查询参数 |
快速故障排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| Swagger UI 未加载 | springdoc 配置不正确 | 检查 application.yml 中的 springdoc.swagger-ui.path |
| 未显示端点 | 控制器不在扫描路径中 | 检查 springdoc.packages-to-scan |
| Schema 缺少字段 | 字段为 private 且没有 getter | 添加 getter,或在字段上使用 @Schema |
| UI 中的安全功能不起作用 | 未配置安全方案 | 在配置中添加 @SecurityScheme |
| UI 中的基础路径错误 | 未配置服务器 URL | 在 OpenAPI 配置中设置 servers |
| 生成的 Schema 中存在类型错误 | 不支持 DTO 字段类型 | 使用 @Schema 指定格式 |
| 循环引用错误 | DTO 自引用 | 使用 @Schema(ref = "#/components/schemas/X") |
| 缺少枚举值 | 未记录枚举 | 在枚举字段上添加 @Schema |
| /v3/api-docs 返回 404 | 未启用 springdoc | 添加 springdoc 依赖,并检查 api-docs.enabled |
参考文档
评论
支持 Markdown