第 67 章:接口文档 OpenAPI
学习目标
- 集成 springdoc-openapi 自动生成接口文档
- 学会使用 Swagger 注解丰富文档
- 学会接口分组、认证配置、导出 Postman
一、为什么需要接口文档?
痛点:
- 手写 Word/Confluence → 容易过期
- 改了代码忘了改文档
- 前端经常来问"这个接口返回啥?"
自动生成:用注解描述接口,启动后自动产出在线文档(Swagger UI),代码改文档自动同步。
二、OpenAPI 规范
OpenAPI(前身 Swagger)是 REST API 的标准描述格式。
yaml
openapi: 3.0.0
info:
title: TaskFlow API
version: 1.0.0
paths:
/api/user/{id}:
get:
summary: 查询用户
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/User'三、集成 springdoc-openapi
xml
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>yaml
springdoc:
api-docs:
path: /v3/api-docs # OpenAPI JSON 路径
swagger-ui:
path: /swagger-ui.html # Swagger UI 路径
operations-sorter: alpha # 接口排序
tags-sorter: alpha
default-flat-param-object: true # 扁平化参数java
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("TaskFlow API 文档")
.description("企业级 RBAC 系统接口文档")
.version("1.0.0")
.contact(new Contact()
.name("研发团队")
.email("dev@taskflow.com"))
.license(new License()
.name("Apache 2.0")))
.components(new Components()
// ① 全局安全配置
.addSecuritySchemes("bearer-jwt",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")))
.addSecurityItem(new SecurityRequirement().addList("bearer-jwt"));
}
}启动后访问:
http://localhost:8080/swagger-ui.html← Swagger UI(在线调试)http://localhost:8080/v3/api-docs← OpenAPI JSON
四、Controller 注解
java
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
@Tag(name = "用户管理", description = "用户的增删改查") // ① 分组
public class UserController {
private final UserService userService;
@Operation(summary = "查询用户", description = "根据 ID 查询用户详情")
@Parameter(name = "id", description = "用户 ID", required = true, example = "1") // ②
@ApiResponse(responseCode = "200", description = "成功")
@ApiResponse(responseCode = "404", description = "用户不存在")
@GetMapping("/{id}")
public Result<UserVO> getById(@PathVariable Long id) {
return Result.ok(userService.getVOById(id));
}
@Operation(summary = "分页查询")
@GetMapping("/page")
public Result<PageResult<UserVO>> page(
@ParameterObject UserPageQuery query) { // ③ 自动展开 query 参数
return Result.ok(userService.page(query));
}
@Operation(summary = "创建用户")
@PostMapping
public Result<Long> create(@RequestBody @Valid UserCreateDTO dto) {
return Result.ok(userService.create(dto));
}
}五、DTO 注解
java
@Data
@Schema(description = "用户创建请求") // ① 类说明
public class UserCreateDTO {
@Schema(description = "用户名", example = "zhangsan", requiredMode = RequiredMode.REQUIRED) // ②
@NotBlank(message = "用户名不能为空")
@Length(min = 3, max = 20)
private String username;
@Schema(description = "昵称", example = "张三")
private String nickname;
@Schema(description = "邮箱", example = "zhangsan@taskflow.com")
@Email(message = "邮箱格式不正确")
private String email;
@Schema(description = "手机号", example = "13800138000")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Schema(description = "性别:0=未知 1=男 2=女", example = "1")
private Integer gender;
@Schema(description = "角色 ID 列表", example = "[1, 2]")
private List<Long> roleIds;
}
@Data
@Schema(description = "用户返回")
public class UserVO {
@Schema(description = "用户 ID", example = "1")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "角色列表")
private List<RoleVO> roles;
}六、统一响应格式
java
@Data
@Schema(description = "统一响应")
public class Result<T> {
@Schema(description = "业务状态码", example = "200")
private Integer code;
@Schema(description = "提示信息", example = "操作成功")
private String message;
@Schema(description = "响应数据")
private T data;
@Schema(description = "追踪 ID", example = "abc123")
private String traceId;
@Schema(description = "时间戳", example = "1700000000000")
private Long timestamp;
}springdoc 会自动识别 Result<T> 的泛型参数,正确展示 data 结构。
七、分组与多版本
java
// ① 多个 OpenAPI Bean(不同版本/分组)
@Configuration
public class OpenApiConfig {
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("01-管理后台")
.pathsToMatch("/api/admin/**")
.build();
}
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("02-开放接口")
.pathsToMatch("/api/public/**")
.build();
}
@Bean
public GroupedOpenApi appApi() {
return GroupedOpenApi.builder()
.group("03-移动端")
.pathsToMatch("/api/app/**")
.build();
}
}访问 http://localhost:8080/swagger-ui.html 顶部会有下拉切换分组。
八、接口认证配置
java
// 在 Controller 或方法上声明
@RestController
@RequestMapping("/api/admin")
@RequiredArgsConstructor
@SecurityRequirement(name = "bearer-jwt") // ① 类级别:所有方法都需要 JWT
@Tag(name = "管理后台")
public class AdminController {
@Operation(summary = "查询日志")
@GetMapping("/log")
public Result<PageResult<LogVO>> list() { ... }
}Swagger UI 会自动出现 "Authorize" 按钮,输入 JWT 后所有接口自动带上 Authorization: Bearer xxx。
九、离线导出
导出为 Postman
bash
# 1. 启动应用
mvn spring-boot:run
# 2. 访问 OpenAPI JSON
curl http://localhost:8080/v3/api-docs > openapi.json
# 3. 在 Postman 里 Import → 选择 openapi.json导出为 HTML / PDF
java
// 用 springdoc-openapi-maven-plugin 构建时生成静态文档
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>2.6.0</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>integration-test</phase>
<goals><goal>generate</goal></goals>
</execution>
</executions>
<configuration>
<apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl>
<outputFileName>openapi.json</outputFileName>
<outputDir>${project.build.directory}/docs</outputDir>
</configuration>
</plugin>导出为 Markdown
bash
# 用 widdershins
npx widdershins --language_tabs 'java:Java' http://localhost:8080/v3/api-docs -o api.md十、生产环境安全
yaml
spring:
profiles: prod
springdoc:
api-docs:
enabled: false # ① 生产环境关闭 API JSON
swagger-ui:
enabled: false # ② 生产环境关闭 Swagger UI
# 或者用 IP 白名单java
@Configuration
@Profile("!prod") // ③ 非生产才启用
public class OpenApiConfig { ... }java
// 或者用拦截器限制 IP
@Component
public class SwaggerAccessInterceptor implements HandlerInterceptor {
private static final List<String> ALLOWED_IPS = List.of(
"127.0.0.1", "::1", "10.0.0.0/8"
);
@Override
public boolean preHandle(HttpServletRequest req, ...) {
if (!ALLOWED_IPS.contains(req.getRemoteAddr())) {
resp.sendError(403);
return false;
}
return true;
}
}十一、Knife4j(Swagger 增强 UI)
Knife4j 是国内开源的 Swagger 增强 UI,比原生 Swagger UI 更适合中文用户。
xml
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>yaml
knife4j:
enable: true
setting:
language: zh_cn
enable-version: true
enable-swagger-models: true
swagger-model-name: 实体类列表
enable-debug: true访问 http://localhost:8080/doc.html,UI 更清爽,支持离线文档下载、接口分组等。
十二、最佳实践
| 实践 | 说明 |
|---|---|
统一 @Operation(summary=...) | 简洁说明接口作用 |
@Schema(description=..., example=...) | 描述字段含义和示例值 |
响应都用 Result<T> | 文档自动识别泛型 |
| DTO 不要暴露数据库实体 | UserVO 而不是 User 实体 |
| 错误码定义成枚举 | errors: 数组里列出所有错误码 |
| 接口分多版本 | /api/v1、/api/v2 |
| 生产环境关掉文档 | 避免泄露内部接口 |
| CI 集成校验 | 测试时请求 /v3/api-docs,确保所有 Controller 都有 @Operation |
十三、本章小结
| 要点 | 关键 |
|---|---|
| 工具 | springdoc-openapi(OpenAPI 3) |
| 依赖 | springdoc-openapi-starter-webmvc-ui |
| 核心注解 | @Tag(分组)@Operation(接口)@Parameter(参数)@Schema(字段) |
| 访问 | /swagger-ui.html + /v3/api-docs |
| 分组 | GroupedOpenApi 按路径前缀 |
| 认证 | @SecurityRequirement(name = "bearer-jwt") |
| 离线导出 | Postman / HTML / Markdown |
| 生产安全 | 关闭文档 / IP 白名单 |
| 增强 UI | Knife4j(国产) |
动手练习
练习 1:基础题
为你的 UserController 加完整 OpenAPI 注解,启动后访问 Swagger UI 能看到所有接口,并尝试在线调试。
练习 2:进阶题
实现多版本文档:/api/v1/** 和 /api/v2/** 分别配置不同的 OpenAPI Bean,Swagger UI 顶部有下拉切换。
练习 3:思考题
接口文档是给前端看的,但权限规则、错误码字典、字段字典这些"补充文档"如何整合到 OpenAPI 里?
下一章:第 68 章:幂等性设计 →