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 导出做成对外文档。当文档成为代码的一部分,接口变更就再也藏不住——这恰恰是高质量协作的开始。




