Spring Boot 4.0 OpenAPI 文档与客户端代码生成
前言
手写接口文档的问题不是“麻烦”,而是它很容易和代码分离。Controller 已经加了必填参数,文档还写着可选;错误码已经改了,客户端仍然依赖旧说明。
OpenAPI 的解决方式是让接口描述成为机器可读契约。本章会:
- 用 Springdoc 从 Controller 生成 OpenAPI 文档。
- 在 Swagger UI 中直接调用接口。
- 导出版本化的
order-api.yaml。 - 用 OpenAPI Generator 生成 TypeScript 客户端。
- 编译生成客户端,确认契约可以落到代码。
配套源码:dvsusan/spring-boot4-demo。
最终结果
启动项目后可以打开:
http://localhost:8080/swagger-ui.html也可以获取原始契约:
curl "http://localhost:8080/v3/api-docs.yaml"生成 TypeScript 客户端:
./scripts/generate-order-client.sh生成目录:
target/generated-order-client/
├── src/apis/APIV1Api.ts
├── src/models/CreateOrderRequest.ts
├── src/models/OrderView.ts
├── package.json
└── tsconfig.json
图 1:代码是契约来源,OpenAPI 文件连接在线文档与客户端 SDK。
第 1 步:添加 Springdoc 依赖
Spring Boot 4 使用 Spring Framework 7,需要选择兼容的 Springdoc 3.x。根 POM 声明版本:
<properties>
<!-- 课程固定版本,便于所有读者得到一致结果。 -->
<springdoc.version>3.0.3</springdoc.version>
</properties>demo-web/pom.xml 中添加:
<dependency>
<!-- 同时提供 OpenAPI 文档和 Swagger UI。 -->
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>不要照抄 Spring Boot 2 教程里的 Springdoc 1.x。主版本不匹配时,即使依赖能下载,也可能在启动阶段因为 Spring API 变化失败。
第 2 步:配置文档入口
在 demo-web/src/main/resources/application.yml 中增加:
springdoc:
api-docs:
# JSON 和 YAML 描述文件共用这个基础路径。
path: /v3/api-docs
swagger-ui:
# 浏览器入口,可以查看模型并直接调用接口。
path: /swagger-ui.html生产环境不应无条件暴露文档。可以按 Profile 关闭:
springdoc:
api-docs:
# 生产环境默认关闭,需要时由运维显式开启。
enabled: false
swagger-ui:
enabled: false公开 API 是否开放文档由产品和安全策略决定。内部接口文档通常还要接入统一认证。
第 3 步:定义文档基本信息
创建:
demo-web/src/main/java/com/susan/training/config/OpenApiConfiguration.java@Configuration
public class OpenApiConfiguration {
@Bean
public OpenAPI orderPlatformOpenApi() {
return new OpenAPI()
.info(new Info()
.title("Spring Boot 4 订单履约平台 API")
.version("v1")
.description("课程示例接口,覆盖订单、版本管理和实时事件")
.contact(new Contact().name("苏三")))
.servers(List.of(new Server()
.url("http://localhost:8080")
.description("本地开发环境")));
}
}servers 决定 Swagger UI 向哪个地址发请求。仅供本地调试的地址不要直接带到生产部署。
第 4 步:给 Controller 和参数补充语义
类级标签:
@RestController
@RequestMapping("/api/v1/orders")
@Tag(name = "订单 API v1", description = "订单创建、查询与金额更新")
public class OrderController {
// Controller 实现保持不变,注解只补充文档语义。
}方法级说明:
@GetMapping
@Operation(summary = "按客户查询订单", description = "返回指定客户的有限数量订单")
public ApiResponse<List<OrderView>> findByCustomer(
@RequestParam
@Parameter(description = "客户编号", example = "C1001")
String customerId,
@RequestParam(defaultValue = "20")
@Parameter(description = "最大返回条数", example = "20")
int limit
) {
// 这些注解不会改变业务行为,但会进入 OpenAPI 契约。
return ApiResponse.success(orderService.findByCustomerId(customerId, limit));
}Swagger 注解不能修补错误设计。如果接口语义混乱,再完整的 @Operation 也只是把混乱描述得更详细。
第 5 步:运行并检查文档
构建、启动:
mvn clean package -DskipTests
java -jar demo-web/target/demo-web-1.0.0-SNAPSHOT.jar检查 JSON:
curl "http://localhost:8080/v3/api-docs"检查 YAML:
curl "http://localhost:8080/v3/api-docs.yaml"YAML 开头应包含:
openapi: 3.1.0
info:
title: Spring Boot 4 订单履约平台 API
version: v1
servers:
- url: http://localhost:8080
description: 本地开发环境打开 Swagger UI,展开 POST /api/v1/orders,填写:
{
"customerId": "C1001",
"amount": 99.00
}点击 Execute,应得到 201。Swagger UI 能调用成功,至少说明文档中的路径、参数和服务器地址与运行服务一致。
第 6 步:把契约保存到仓库
在线接口会随代码变化。发布或生成客户端前,应保存一份明确的契约:
mkdir -p openapi
curl "http://localhost:8080/v3/api-docs.yaml" \
-o openapi/order-api.yaml本项目已经保存:
openapi/order-api.yaml契约文件应随代码评审。删除字段、修改类型或新增必填项时,评审可以直接看到对客户端的影响。
第 7 步:生成 TypeScript 客户端
项目脚本:
scripts/generate-order-client.sh核心命令:
npx --yes @openapitools/openapi-generator-cli@2.25.2 generate \
--input-spec openapi/order-api.yaml \
--generator-name typescript-fetch \
--output target/generated-order-client \
--additional-properties=npmName=order-api-client,npmVersion=1.0.0,supportsES6=true执行:
./scripts/generate-order-client.sh生成结果放在 target/ 下,因为它可以由契约重新生成。是否把客户端提交到仓库取决于发布方式:
- 独立 SDK 仓库:提交并发布 npm 包。
- 前端 Monorepo:生成到固定包目录并纳入构建。
- 临时验证:放在
target/,构建后丢弃。
第 8 步:编译生成的客户端
进入生成目录:
cd target/generated-order-client
npm install
npm run build本项目已经实际执行过 tsc 和 ESM 构建。成功后会在 dist/ 下得到 JavaScript 和类型声明。
一个最小的调用示例:
import { APIV1Api, Configuration } from "./generated-order-client/src";
// basePath 指向正在运行的 Spring Boot 服务。
const api = new APIV1Api(new Configuration({
basePath: "http://localhost:8080"
}));
const created = await api.create({
createOrderRequest: {
customerId: "C1001",
amount: 99.00
}
});
console.log(created.data?.orderNo);客户端方法名来自 operationId 或 Controller 方法名。如果名称变化,调用方源码会直接编译失败,这比运行时才发现字段变化更容易发现。
常见问题排查
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
/v3/api-docs 返回 404 | 没引入兼容的 Springdoc | 检查依赖和启动日志 |
| Swagger UI 页面空白 | 静态资源被网关拦截 | 检查 /swagger-ui/** 路由 |
| 文档中缺少接口 | Controller 不在扫描范围 | 检查启动类包路径 |
| 生成的客户端编译失败 | 契约包含 3.1 特性但生成器版本过旧 | 固定并升级 OpenAPI Generator |
| TypeScript 可选字段报错 | 生成器把可空字段映射成可选 | 在业务代码中显式处理 undefined |
| 生成文件被业务手改后丢失 | 下次生成覆盖输出目录 | 自定义模板或包装层,不直接改生成文件 |
本章小结
OpenAPI 不是一份额外文档,而是代码契约的机器可读表达。
推荐流程:
- Controller 和 DTO 定义真实行为。
- Springdoc 从运行代码生成 OpenAPI。
- 把 YAML 纳入版本控制或发布流水线。
- OpenAPI Generator 生成客户端。
- 编译客户端并运行契约测试。
这样接口变更会出现在代码评审、文档差异和客户端编译三个阶段,而不是等上线后由调用方发现。
练习题
为错误响应补充明确 schema:
- 在 OpenAPI 中声明
400、404和422。 - 让
FieldViolation出现在 400 响应示例中。 - 重新生成客户端。
- 在客户端中对 404 做明确处理。
上一节:参数校验、分组与国际化
下一节:RestClient 与 HTTP Interface
相关阅读:
13 个企业级实战项目 · 源码 + 教程 + 答疑
简历包装指导AI Agent 实战高并发·微服务Offer 导向持续更新3000+ 同行交流1V1 答疑3 天包退
覆盖高并发、微服务、分布式与 AI Agent:100万QPS短链、企业智能知识库、商城微服务、SaaS点餐、秒杀、刷题小程序、代码生成器、AI Agent 等。券后 ¥129(原价 ¥159),不满意 3 天包退。
