在现代前后端分离的开发模式下,清晰、准确、实时同步的 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
八、总结
| 能力 | 实现方式 | 价值 |
|---|---|---|
| 自动文档 | 注解驱动 | 减少文档维护成本 |
| 接口契约 | OpenAPI 3.0 规范 | 前后端并行开发 |
| 离线导出 | Maven 插件 | 交付与归档 |
| Mock 测试 | Prism | 前端独立开发 |
| 代码生成 | OpenAPI Generator | 类型安全 |
SpringDoc OpenAPI 不仅是文档工具,更是API 契约管理的基础设施。将文档嵌入代码、通过 CI 验证契约、让文档随版本演进,是现代 API 开发的核心实践。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。