Swagger 与 OpenAPI
Spring Boot 3.x 推荐使用 springdoc-openapi(兼容 OpenAPI 3.0)生成 API 文档,替代已停止维护的 Springfox。访问 http://localhost:8080/swagger-ui.html 可查看交互式文档。
依赖
<!-- Spring Boot 3.x 对应 springdoc 2.x -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>WebFlux 项目替换为 springdoc-openapi-starter-webflux-ui。
快速开始
依赖加入后无需任何配置,访问以下地址:
| 地址 | 说明 |
|---|---|
/swagger-ui.html | 交互式 UI(重定向到 /swagger-ui/index.html) |
/v3/api-docs | OpenAPI JSON 规范 |
/v3/api-docs.yaml | OpenAPI YAML 规范 |
全局 API 信息
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info()
.title("订单服务 API")
.description("订单管理系统 REST 接口文档")
.version("v1.0.0")
.contact(new Contact()
.name("后端团队")
.email("backend@example.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0"))
)
.servers(List.of(
new Server().url("https://api.example.com").description("生产环境"),
new Server().url("http://localhost:8080").description("本地开发")
));
}
}接口注解
@Tag — 接口分组
@RestController
@RequestMapping("/api/v1/orders")
@Tag(name = "订单管理", description = "创建、查询、更新、删除订单")
public class OrderController { ... }@Operation — 方法说明
@GetMapping("/{id}")
@Operation(
summary = "根据 ID 查询订单",
description = "返回单个订单的完整信息,包含商品明细",
operationId = "getOrderById"
)
public ResponseEntity<OrderDto> getOrder(@PathVariable Long id) { ... }@Parameter — 参数说明
@GetMapping
@Operation(summary = "分页查询订单列表")
public Page<OrderDto> listOrders(
@Parameter(description = "用户 ID", required = true, example = "1001")
@RequestParam Long userId,
@Parameter(description = "订单状态", schema = @Schema(
allowableValues = {"PENDING", "PAID", "SHIPPED", "DONE"}))
@RequestParam(required = false) String status,
@Parameter(hidden = true) // 隐藏 Pageable 内部参数
Pageable pageable) { ... }@ApiResponse — 响应说明
@PostMapping
@Operation(summary = "创建订单")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "创建成功",
content = @Content(schema = @Schema(implementation = OrderDto.class))),
@ApiResponse(responseCode = "400", description = "请求参数校验失败",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "401", description = "未认证",
content = @Content),
@ApiResponse(responseCode = "500", description = "服务器内部错误",
content = @Content)
})
public ResponseEntity<OrderDto> createOrder(
@Valid @RequestBody CreateOrderRequest request) { ... }@Schema — DTO 字段说明
@Schema(description = "创建订单请求")
public record CreateOrderRequest(
@Schema(description = "用户 ID", example = "1001", requiredMode = REQUIRED)
@NotNull Long userId,
@Schema(description = "收货地址", example = "北京市朝阳区XX路1号", requiredMode = REQUIRED)
@NotBlank String address,
@Schema(description = "商品列表,至少一项", requiredMode = REQUIRED)
@NotEmpty List<OrderItemRequest> items,
@Schema(description = "备注", example = "请尽快发货", requiredMode = NOT_REQUIRED)
String remark
) {}安全认证配置(Bearer Token)
@Bean
public OpenAPI openAPI() {
SecurityScheme bearerScheme = new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.name("bearerAuth");
SecurityRequirement securityRequirement =
new SecurityRequirement().addList("bearerAuth");
return new OpenAPI()
.info(new Info().title("API 文档").version("1.0"))
.addSecurityItem(securityRequirement) // 全局应用认证
.components(new Components()
.addSecuritySchemes("bearerAuth", bearerScheme));
}部分接口无需认证时,在方法上覆盖:
@PostMapping("/login")
@Operation(
summary = "用户登录",
security = {} // 空数组表示此接口不需要认证
)
public TokenResponse login(@RequestBody LoginRequest req) { ... }JWT 认证方案详见 OAuth2与JWT。
接口分组
将不同模块的接口分离到独立的文档组:
@Bean
public GroupedOpenApi orderApi() {
return GroupedOpenApi.builder()
.group("订单模块")
.pathsToMatch("/api/v1/orders/**")
.build();
}
@Bean
public GroupedOpenApi userApi() {
return GroupedOpenApi.builder()
.group("用户模块")
.pathsToMatch("/api/v1/users/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("管理后台")
.pathsToMatch("/admin/**")
.addOpenApiCustomizer(openApi ->
openApi.info(new Info().title("管理后台 API").version("1.0")))
.build();
}配置项
springdoc:
# Swagger UI 路径
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha # 按 tag 名字母排序
operations-sorter: method # 按 HTTP 方法排序
try-it-out-enabled: true # 默认展开"Try it out"
filter: true # 显示接口搜索框
# API 文档路径
api-docs:
path: /v3/api-docs
enabled: true
# 显示 Actuator 端点(默认不显示)
show-actuator: false
# 包扫描范围
packages-to-scan: com.example.controller
# 生产环境禁用
# api-docs.enabled: false生产环境关闭文档:
# application-prod.yml
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: false统一响应包装
与 统一响应格式 配合,让 Swagger 正确展示包装后的响应类型:
// 泛型包装类
@Schema(description = "统一响应")
public record Result<T>(
@Schema(description = "是否成功") boolean success,
@Schema(description = "响应数据") T data,
@Schema(description = "错误信息") String message
) {}
// 在 @ApiResponse 中指定包装类型
@ApiResponse(responseCode = "200",
content = @Content(schema = @Schema(implementation = OrderResultSchema.class)))
// 辅助 Schema 类(解决泛型擦除问题)
class OrderResultSchema extends Result<OrderDto> {}忽略特定接口
// 方法级:隐藏单个接口
@Hidden
@GetMapping("/internal/health")
public String health() { return "ok"; }
// 类级:隐藏整个 Controller
@Hidden
@RestController
public class InternalController { ... }导出为静态文档
CI 阶段可以将 /v3/api-docs 导出为 JSON 文件,再用 Redoc / Slate 渲染:
# 应用启动后导出
curl http://localhost:8080/v3/api-docs -o api-docs.json
# 用 redocly 生成静态 HTML
npx @redocly/cli build-docs api-docs.json -o api-docs.html相关链接
- MVC — Controller 注解与路由定义
- 统一响应格式 — 响应包装类与 Swagger 泛型方案
- 参数校验 —
@Valid校验与 400 错误响应 - 全局异常处理 — 统一错误响应结构
- OAuth2与JWT — Bearer Token 安全方案
- Actuator监控 — Actuator 端点在文档中的展示