在现代前后端分离的开发模式下,清晰、准确、实时同步的 API 文档是团队协作的基础。SpringDoc OpenAPI 作为 Spring Boot 生态中 Swagger 的继任者,提供了更简洁的注解体系和更好的 Spring Boot 3 兼容性。
一、SpringDoc vs SpringFox
| 特性 | SpringDoc OpenAPI | SpringFox Swagger |
|---|---|---|
| Spring Boot 3 / Jakarta EE | 原生支持 | 不支持(已停止维护) |
| 注解数量 | 精简(复用 JSR-303) | 较多 |
| OpenAPI 规范 | 3.0 / 3.1 | 2.0 |
| 维护状态 | 活跃更新 | 已停止维护 |
| 方案推荐 | 新项目首选 | 建议迁移 |
二、基础集成
2.1 Maven 依赖
<dependencies>
<!-- SpringDoc OpenAPI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
2.2 全局配置
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("电商订单服务 API")
.version("v2.1.0")
.description("提供订单创建、查询、支付等核心能力")
.contact(new Contact()
.name("技术团队")
.email("tech@example.com")
.url("https://docs.example.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0")))
.externalDocs(new ExternalDocumentation()
.description("详细设计文档")
.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")));
}
}
2.3 分组配置
@Configuration
public class OpenApiGroupConfig {
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("public")
.pathsToMatch("/api/public/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/api/admin/**")
.addOpenApiMethodFilter(method -> method.isAnnotationPresent(AdminAuth.class))
.build();
}
@Bean
public GroupedOpenApi internalApi() {
return GroupedOpenApi.builder()
.group("internal")
.pathsToMatch("/api/internal/**")
.addOpenApiCustomiser(openApi ->
openApi.info(new Info().title("内部服务 API").version("1.0")))
.build();
}
}
三、注解详解
3.1 接口层注解
@Tag(name = "订单管理", description = "订单创建、查询、取消等操作")
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Operation(
summary = "创建订单",
description = "根据购物车商品创建订单,支持优惠券和积分抵扣",
tags = {"订单管理"}
)
@ApiResponses({
@ApiResponse(responseCode = "201", description = "创建成功",
content = @Content(schema = @Schema(implementation = OrderResponse.class))),
@ApiResponse(responseCode = "400", description = "参数校验失败",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "409", description = "库存不足",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@PostMapping
public ResponseEntity<OrderResponse> createOrder(
@Valid @RequestBody
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "订单创建请求",
required = true,
content = @Content(schema = @Schema(implementation = OrderCreateRequest.class))
)
OrderCreateRequest request
) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(orderService.create(request));
}
@Operation(summary = "查询订单详情")
@Parameter(name = "orderId", description = "订单编号", required = true, example = "ORD-20240814-001")
@GetMapping("/{orderId}")
public OrderDetail getOrder(@PathVariable String orderId) {
return orderService.findDetail(orderId);
}
@Operation(summary = "分页查询订单列表")
@GetMapping
public PageResult<OrderSummary> listOrders(
@Parameter(description = "页码", example = "0") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "每页大小", example = "20") @RequestParam(defaultValue = "20") int size,
@Parameter(description = "状态筛选") @RequestParam(required = false) OrderStatus status
) {
return orderService.findPage(page, size, status);
}
}
3.2 模型注解
@Schema(description = "订单创建请求")
public class OrderCreateRequest {
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10086")
@NotNull
private Long userId;
@Schema(description = "收货地址ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private Long addressId;
@Schema(description = "商品列表", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty
@Valid
private List<OrderItemRequest> items;
@Schema(description = "优惠券码", example = "SUMMER2024")
private String couponCode;
@Schema(description = "使用积分", minimum = "0", example = "500")
@Min(0)
private Integer usePoints;
@Schema(description = "订单备注", maxLength = 500, example = "请发顺丰")
@Size(max = 500)
private String remark;
@Schema(description = "订单来源", allowableValues = {"APP", "WEB", "MINI_PROGRAM"})
private OrderSource source;
// getters/setters
}
@Schema(description = "订单创建响应")
public class OrderResponse {
@Schema(description = "订单编号", example = "ORD-20240814-001")
private String orderId;
@Schema(description = "订单状态", example = "PENDING_PAYMENT")
private OrderStatus status;
@Schema(description = "应付金额", example = "299.99")
private BigDecimal amount;
@Schema(description = "支付超时时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime expireTime;
@Schema(description = "支付链接")
private String paymentUrl;
}
@Schema(description = "统一错误响应")
public class ErrorResponse {
@Schema(description = "错误码", example = "ORDER_STOCK_INSUFFICIENT")
private String code;
@Schema(description = "错误信息", example = "商品库存不足")
private String message;
@Schema(description = "错误详情")
private List<FieldError> errors;
@Schema(description = "时间戳")
private Instant timestamp;
}
3.3 枚举映射
@Schema(description = "订单状态")
public enum OrderStatus {
@Schema(description = "待付款")
PENDING_PAYMENT,
@Schema(description = "已付款")
PAID,
@Schema(description = "已发货")
SHIPPED,
@Schema(description = "已完成")
COMPLETED,
@Schema(description = "已取消")
CANCELLED;
}
四、高级功能
4.1 离线文档导出
<build>
<plugins>
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>integration-test</id>
<goals><goal>generate</goal></goals>
</execution>
</executions>
<configuration>
<apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl>
<outputFileName>openapi.json</outputFileName>
<outputDir>${project.build.directory}</outputDir>
</configuration>
</plugin>
</plugins>
</build>
4.2 运行时隐藏字段
public class User {
@Schema(description = "用户ID")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码", accessMode = Schema.AccessMode.WRITE_ONLY)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
@Schema(description = "内部状态", hidden = true)
private Integer internalStatus;
}
4.3 自定义扩展属性
@Schema(description = "API 版本信息",
extensions = {
@Extension(name = "x-internal", properties = @ExtensionProperty(name = "team", value = "order")),
@Extension(name = "x-deprecated-since", properties = @ExtensionProperty(name = "version", value = "2.0"))
})
public class ApiVersionInfo {
// ...
}
五、测试集成
5.1 Mock Mvc 测试
@WebMvcTest(OrderController.class)
@AutoConfigureRestDocs
class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void createOrder() throws Exception {
OrderCreateRequest request = new OrderCreateRequest();
request.setUserId(10086L);
// ... set other fields
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtils.toJson(request)))
.andExpect(status().isCreated())
.andDo(document("order-create",
requestFields(
fieldWithPath("userId").description("用户ID"),
fieldWithPath("items").description("商品列表")
),
responseFields(
fieldWithPath("orderId").description("订单编号"),
fieldWithPath("status").description("订单状态")
)));
}
}
5.2 契约测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class ContractTest {
@Test
void apiContractCompliance() {
// 验证响应是否符合 OpenAPI 契约
String openApiJson = restTemplate.getForObject("/v3/api-docs", String.class);
OpenAPI openAPI = new OpenAPIParser().readContents(openApiJson, null, null).getOpenAPI();
// 使用 swagger-request-validator 验证
OpenApiValidationFilter validationFilter = new OpenApiValidationFilter(openAPI);
given()
.filter(validationFilter)
.when()
.get("/api/orders/ORD-001")
.then()
.statusCode(200);
}
}
六、前端 Mock 服务
# docker-compose.yml,一键启动 Mock 服务
version: '3'
services:
prism:
image: stoplight/prism:4
command: >
mock -h 0.0.0.0 /api/openapi.json
--dynamic
volumes:
- ./target/openapi.json:/api/openapi.json:ro
ports:
- "4010:4010"
七、最佳实践
7.1 注解层级策略
推荐层级:
├── Controller 层 → @Tag, @Operation, @ApiResponse
├── DTO/Request → @Schema(description, required, example)
├── DTO/Response → @Schema + @JsonFormat
├── 枚举 → @Schema(description) on each enum value
└── 通用字段 → 抽象基类复用
7.2 文档即契约
- 接口变更时同步更新注解
- 将 OpenAPI JSON 纳入版本控制
- CI 中增加契约兼容性检查
- 前端基于 OpenAPI 生成 TypeScript 类型
7.3 安全考虑
springdoc:
show-actuator: false # 不暴露 Actuator 端点
show-login-endpoint: false
api-docs:
enabled: true
swagger-ui:
enabled: ${SWAGGER_ENABLED:true} # 生产环境可关闭
oauth:
client-id: swagger-ui
八、SpringDoc 3.x 新特性与迁移
8.1 Spring Boot 3 / Jakarta EE 支持
SpringDoc OpenAPI 2.x 与 Spring Boot 3 原生兼容:
<!-- Spring Boot 3 使用这个依赖 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<!-- 若仍需 WebFlux(响应式栈) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.3.0</version>
</dependency>
迁移检查清单:
- ✅ 包名从
javax.*迁移到jakarta.* - ✅
@Schema替代@ApiModel(Swagger 2 注解) - ✅
@Operation替代@ApiOperation - ✅ 不再需要
springfox的@EnableSwagger2
8.2 响应式 API 文档
WebFlux 项目中的 OpenAPI 文档生成,与 WebMVC 几乎一致:
@Configuration
public class ReactiveOpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info().title("响应式订单服务").version("v1"))
.addServersItem(new Server().url("https://api.example.com").description("生产环境"))
.addServersItem(new Server().url("http://localhost:8080").description("本地开发"));
}
}
@RestController
public class ReactiveOrderController {
@Operation(summary = "创建订单(响应式)")
@PostMapping("/api/orders")
public Mono<ResponseEntity<OrderResponse>> createOrder(
@Valid @RequestBody OrderCreateRequest request
) {
return orderService.create(request)
.map(order -> ResponseEntity.status(HttpStatus.CREATED).body(order));
}
@Operation(summary = "订单状态流")
@GetMapping(value = "/api/orders/{id}/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<OrderEvent> orderEvents(@PathVariable String id) {
return orderService.streamEvents(id);
}
}
8.3 认证方式扩展
SpringDoc 支持多种认证模型:
@Bean
public OpenAPI securityOpenAPI() {
return new OpenAPI()
// OAuth2
.addSecurityItem(new SecurityRequirement().addList("oauth2"))
.components(new Components()
.addSecuritySchemes("oauth2", new SecurityScheme()
.type(SecurityScheme.Type.OAUTH2)
.flows(new OAuthFlows()
.password(new OAuthFlow()
.tokenUrl("/oauth/token")
.scopes(new Scopes().addString("read", "读取权限"))
)
)
)
// API Key
.addSecuritySchemes("apiKey", new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-API-Key")
)
// JWT (已在基础配置中展示)
.addSecuritySchemes("bearerAuth", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
)
);
}
九、OpenAPI Generator 代码生成
将 OpenAPI 契约作为单一数据源,生成多语言客户端和服务端代码:
# 生成 TypeScript 前端客户端
openapi-generator-cli generate \
-i openapi.json \
-g typescript-fetch \
-o ./src/api-client \
--additional-properties=supportsES6=true,npmName=game-api-client
# 生成 Go 服务端骨架
gen openapi-generator-cli generate \
-i openapi.json \
-g go-server \
-o ./go-server \
--additional-properties=router=chi,featureCORS=true
# 生成 Python 测试脚本
gen openapi-generator-cli generate \
-i openapi.json \
-g python \
-o ./python-client
# 生成 Markdown API 参考文档
openapi-generator-cli generate \
-i openapi.json \
-g markdown \
-o ./docs/api
CI 集成代码生成
# .github/workflows/api-gen.yml
name: API Code Generation
on:
push:
paths:
- 'openapi.json'
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate TypeScript Client
uses: openapi-generators/openapitools-generator-action@v1
with:
generator: typescript-fetch
config-file: openapi-config.json
command-args: -o ./frontend/src/api
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
title: "chore: regenerate API client from OpenAPI"
branch: "auto/api-gen"
commit-message: "chore: regenerate API client"
十、最佳实践总结
注解层级策略
推荐层级:
├── Controller 层 → @Tag, @Operation, @ApiResponse
├── DTO/Request → @Schema(description, required, example)
├── DTO/Response → @Schema + @JsonFormat
├── 枚举 → @Schema(description) on each enum value
└── 通用字段 → 抽象基类复用
文档即契约开发流程
设计阶段 ──► API 设计评审 ──► 编写 OpenAPI YAML(设计优先)
│
开发阶段 ◄─── 前后端并行开发 ◄───┘
│ │
▼ ▼
后端注解驱动 前端 Mock + 类型生成
生成文档 开发联调
│ │
└─────┬────────┘
▼
CI 契约校验(兼容检测)
│
▼
发布 OpenAPI JSON 到 Registry
│
▼
生成多语言 SDK
安全与生产配置
springdoc:
show-actuator: false # 不暴露 Actuator 端点
show-login-endpoint: false # 隐藏登录接口
api-docs:
enabled: true
path: /v3/api-docs # 自定义 API 文档路径
swagger-ui:
enabled: ${SWAGGER_ENABLED:true}
path: /swagger-ui.html
operationsSorter: method # 按 HTTP 方法排序
tagsSorter: alpha # 按字母排序标签
oauth:
client-id: swagger-ui
cache:
disabled: false # 生产环境启用缓存
十一、总结
| 能力 | 实现方式 | 价值 |
|---|---|---|
| 自动文档 | 注解驱动 | 减少文档维护成本 |
| 接口契约 | OpenAPI 3.0 规范 | 前后端并行开发 |
| 离线导出 | Maven 插件 | 交付与归档 |
| Mock 测试 | Prism | 前端独立开发 |
| 代码生成 | OpenAPI Generator | 类型安全 |
| 安全认证 | JWT/OAuth2/API Key | 全链路安全 |
| 响应式文档 | WebFlux 原生支持 | 异步 API 可视化 |
| 多环境切换 | Server 配置 | 直观看不同环境差异 |
SpringDoc OpenAPI 不仅是文档工具,更是API 契约管理的基础设施。将文档嵌入代码、通过 CI 验证契约、利用代码生成保持前后端类型一致,让文档随版本演进,是现代 API 开发的核心实践。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。