SpringDoc OpenAPI:自动化接口文档与契约开发

使用 SpringDoc OpenAPI 自动生成 RESTful API 文档,掌握注解驱动、分组配置、离线导出与 Mock 服务的完整实践

在现代前后端分离的开发模式下,清晰、准确、实时同步的 API 文档是团队协作的基础。SpringDoc OpenAPI 作为 Spring Boot 生态中 Swagger 的继任者,提供了更简洁的注解体系和更好的 Spring Boot 3 兼容性。

一、SpringDoc vs SpringFox

特性SpringDoc OpenAPISpringFox Swagger
Spring Boot 3 / Jakarta EE原生支持不支持(已停止维护)
注解数量精简(复用 JSR-303)较多
OpenAPI 规范3.0 / 3.12.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 开发的核心实践。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java-enterprise」更多文章

  1. JPMS 模块化:module-info 与 JLink 精简运行时
  2. CDC 数据同步:Debezium 与 Kafka 架构实战
  3. 可观测性工程:Micrometer 指标模型与 OTLP 导出