API 文档生成实战:Swagger 与 SpringDoc

API 文档生成是现代后端工程的标配:用 SpringDoc 在 Spring Boot 项目里自动产出 OpenAPI 描述,再用 Swagger UI 或 Redoc 渲染成可交互文档,开发联调不再依赖手写 Word。很多团队文档长期滞后于代码,接口改了文档没改,前端对着过时说明反复踩坑。本文给出一套从接入、注解补全到安全暴露的完整落地方案,让文档随代码一起演进。

一、为什么 API 文档要”自动生成”

手写文档有三个绕不开的痛点:一是维护成本高,改一个字段要同步改文档;二是容易过期,没人愿意在写完代码后再去补说明;三是无法交互,前端同学只能凭文字猜测请求体和返回结构。自动生成把文档从”额外负担”变成”代码产物”——只要接口变了,文档跟着变。这与API 调试工具选型:Postman 与 Bruno 实战是同一目标的两面:调试解决”怎么调通”,文档解决”怎么看懂”。

更重要的是,规范化的 API 描述能直接喂给前端 Mock、代码生成器甚至契约测试,让前后端真正并行开发。文档即代码(Docs as Code)已经成为中大型团队的标配实践。

我们曾遇到过一个真实案例:支付回调接口悄悄加了一个 refundId 字段,文档没更新,前端用了三周才发现对账对不上。自动生成后,这类”代码改了文档没改”的低级事故几乎归零——因为文档本身就是编译产物,接口编译通过的同时文档也就同步了。

二、OpenAPI 规范与 Swagger 生态

Swagger UI、Redoc 与 SpringDoc 的角色

OpenAPI 是一份描述 REST 接口的开放规范(本质是 JSON/YAML),Swagger 是围绕它的工具集。三者分工明确:SpringDoc 负责在 Java 代码里扫描注解、产出 OpenAPI JSON;Swagger UI 把它渲染成网页,支持在线填参数、点按钮直接发请求;Redoc 则偏向”阅读型”文档,排版更克制、更适合对外展示。在 Spring Boot 3 升级实战 的项目里,SpringDoc 对 Jakarta 命名空间与 Jakarta EE 9+ 的支持已经很成熟,几乎零改动接入。

三、SpringDoc 快速接入 Spring Boot 3

Spring Boot 3 中只需要引入 springdoc-openapi-starter-webmvc-ui,它自动装配好一切,无需再写 Swagger 2 那套 @EnableSwagger2 配置。Maven 依赖如下:

<!-- pom.xml -->
<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  <version>2.6.0</version>
</dependency>

# 启动后访问:
#   Swagger UI:  http://localhost:8080/swagger-ui.html
#   OpenAPI JSON: http://localhost:8080/v3/api-docs

基础元信息(标题、版本、联系人)通过 application.yml 声明即可,无需写 Java 配置类:

# application.yml
springdoc:
  api-docs:
    enabled: true
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
    ops-sorter: method
    tags-sorter: alpha
  info:
    title: 订单服务 API
    version: v1.0.0
    description: 订单创建、查询与履约接口

如果你的项目按业务域拆分了多个 Controller,可以用 springdoc.group-configs 把接口分组,前端按模块查阅更清晰。例如订单、用户、支付各成一组,文档顶部直接切换,避免单页接口过多导致加载慢、查找难。

四、用注解把文档写”活”

光有依赖只能生成骨架,真正的价值在于用注解补全业务语义。用 @Operation 描述接口意图,用 @Parameter 标注参数含义,用 @Schema 约束字段格式。一个带完整注解的 Controller 长这样:

@RestController
@RequestMapping("/api/orders")
@Tag(name = "订单", description = "订单的增删查接口")
public class OrderController {

  @Operation(summary = "创建订单", description = "根据购物车生成一笔待支付订单")
  @PostMapping
  public ResponseEntity<OrderDTO> create(
      @Parameter(description = "订单请求体", required = true)
      @RequestBody @Valid CreateOrderReq req) {
    return ResponseEntity.ok(orderService.create(req));
  }

  @Operation(summary = "分页查询订单")
  @GetMapping
  public Page<OrderDTO> list(
      @Parameter(description = "页码,从 0 开始") @RequestParam(defaultValue = "0") int page,
      @Parameter(description = "每页大小") @RequestParam(defaultValue = "20") int size) {
    return orderService.list(page, size);
  }
}

对 DTO 字段用 @Schema 标明含义与示例,前端拿到的文档就能显示”金额单位:分””状态枚举:PENDING/PAID”,大幅减少沟通成本。配合 MyBatis 与 JPA 性能优化实战 里提到的实体分层,把对外 DTO 与持久化实体分开,文档天然就干净。

一个常见误区是只给 Controller 加注解、却忘了 DTO 字段。实际上 DTO 上的 @Schema 才是前端最关心的部分——它决定了文档里每个字段的类型、示例与是否必填。花十分钟补完核心 DTO,文档的可读性会提升一个量级,前端联调时不再追着你问”这个字段能不能为空”。

五、离线文档与 Redoc 美化

Swagger UI 适合内部联调,对外交付时 Redoc 的阅读体验更友好。最轻量的做法是用官方 Redoc 镜像直接挂载 OpenAPI JSON,一条命令起一个静态文档站:

docker run -d --name api-docs -p 8081:80 \
  -e SPEC_URL=https://api.example.com/v3/api-docs \
  redocly/redoc

# 浏览器打开 http://localhost:8081 即看到排版优雅的三栏文档

也可以把 /v3/api-docs 导出的 JSON 提交进仓库,配合静态站点托管,做到”文档版本与代码版本一一对应”。三种渲染方式的对比如下:

方案定位交互性适合场景
SpringDoc 自动产出OpenAPI 数据源无界面被 UI/Redoc 消费、喂 Mock
Swagger UI在线调试强(能发请求)内部联调、自测
Redoc阅读型文档弱(仅展示)对外交付、API 门户

六、安全:别把 api-docs 暴露到公网

文档默认带全量接口细节,一旦暴露到公网等于把系统地图送给攻击者。生产环境务必收敛:要么关闭 api-docs,要么用 Nginx 限制内网或加鉴权。相关防护思路可参考Nginx 限流防刷实战服务器安全加固指南。典型的 Nginx 收敛配置:

# 仅允许内网与办公网段访问文档
location ~ ^/(v3/api-docs|swagger-ui) {
    allow 10.0.0.0/8;
    allow 192.168.0.0/16;
    deny all;
    proxy_pass http://backend;
}

更稳妥的做法是:生产 profile 下直接 springdoc.api-docs.enabled=false,只在内网预发环境开启文档。对外文档走 Redoc 静态站,且只暴露白名单接口。

七、文档即代码:接入 CI 门禁

把 OpenAPI JSON 在构建产物里导出并做契约校验,能让”接口破坏式变更”在合并前就暴露。参考GitHub Actions 实战:从零搭建 CI/CD 流水线 的编排,可加一步导出与归档:

# 构建后拉取并归档 OpenAPI 描述
- name: Export OpenAPI
  run: curl -s http://localhost:8080/v3/api-docs -o openapi.json
- name: Upload doc artifact
  uses: actions/upload-artifact@v4
  with:
    name: openapi
    path: openapi.json

配合 前端安全实战:XSS 与 CSRF 防御 里提到的 CORS 策略,文档域与业务域分离时要在网关层显式放行,避免 Redoc 站点跨域拉不到 JSON。

更进一步,可以把导出的 JSON 与上一版本的 JSON 做 diff,一旦有破坏性变更(删除字段、修改类型)就在 PR 评论里报警。这种”契约守护”比人工 review 文档可靠得多,也让接口演进有了可追溯的历史,新同事接手时一眼就能看清边界在哪里。

八、常见坑对照

现象解法
Spring Boot 3 用旧 starter启动报 ClassNotFound换 springdoc-openapi-starter-webmvc-ui
泛型返回被擦除文档里响应类型显示为 Object用具体 DTO 或 @Schema 显式标注
生产暴露全量接口攻击者拿到系统地图生产关 api-docs 或 Nginx 限网段
文档与代码漂移注解没更新CI 里导出 JSON 做契约校验
Redoc 跨域拉不到页面空白网关放行 CORS 或内联 JSON

落地节奏建议:先引入 starter 跑通 Swagger UI 内部联调,再补 @Operation/@Schema 注解提升可读性,最后用 Redoc + CI 导出做成对外文档。当文档成为代码的一部分,接口变更就再也藏不住——这恰恰是高质量协作的开始。

上一篇 前端自动化测试:Vitest 与 Playwright 实战
下一篇 LangChain Agent 实战:工具调用与自主编排