Appearance
第53课:Swagger/OpenAPI
🎯 学习目标
- 理解 Swagger、OpenAPI、springdoc-openapi 之间的关系。
- 能在 Spring Boot 3 项目中接入 Swagger UI 并生成 OpenAPI 文档。
- 掌握接口、参数、请求体、响应体、错误码和分组文档的常用注解。
- 能把 Validation 约束和 DTO 设计反映到接口文档中。
- 能识别生产环境暴露接口文档、文档与代码不一致等常见风险。
📖 一、Swagger 与 OpenAPI
很多人把 Swagger 当成“接口文档页面”,但更准确地说:
| 名称 | 含义 |
|---|---|
| OpenAPI | REST API 描述规范,定义接口路径、参数、请求体、响应体、安全方案等 |
| Swagger UI | 根据 OpenAPI 描述渲染出来的可交互网页 |
| springdoc-openapi | Spring Boot 项目中自动生成 OpenAPI 文档的常用库 |
关系可以理解为:
text
Spring Controller + DTO + 注解
↓
springdoc-openapi 扫描生成 /v3/api-docs
↓
Swagger UI 渲染成网页文档不是手写出来的,而是尽量从代码和注解生成。这样能减少接口变更后文档过期的问题。
📖 二、接入 springdoc-openapi
Spring Boot 3 推荐使用:
xml
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>如果是 WebFlux 项目,使用:
xml
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.6.0</version>
</dependency>启动项目后访问:
text
Swagger UI: http://localhost:8080/swagger-ui.html
OpenAPI JSON: http://localhost:8080/v3/api-docs部分版本也支持:
text
http://localhost:8080/swagger-ui/index.html📖 三、基础配置
yaml
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
operations-sorter: method
tags-sorter: alpha生产环境是否开启要谨慎。内部系统可以开放给内网,公网系统建议通过环境变量控制:
yaml
springdoc:
api-docs:
enabled: ${API_DOCS_ENABLED:false}
swagger-ui:
enabled: ${SWAGGER_UI_ENABLED:false}📖 四、全局 OpenAPI 信息
java
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("用户中心 API")
.version("1.0.0")
.description("用户注册、登录、资料管理接口")
.contact(new Contact()
.name("backend-team")
.email("backend@example.com")));
}
}这部分会显示在 Swagger UI 顶部。
📖 五、Controller 注解
java
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@Tag(name = "用户管理", description = "用户注册、查询、更新、禁用接口")
@RestController
@RequestMapping("/api/users")
public class UserController {
@Operation(summary = "分页查询用户", description = "按关键字和状态分页查询用户列表")
@GetMapping
public PageResult<UserDTO> list(
@Parameter(description = "搜索关键字")
@RequestParam(required = false) String keyword,
@Parameter(description = "页码,从 1 开始", example = "1")
@RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页大小", example = "20")
@RequestParam(defaultValue = "20") Integer size) {
return userService.list(keyword, page, size);
}
@Operation(summary = "创建用户")
@PostMapping
public UserDTO create(@Valid @RequestBody UserCreateRequest request) {
return userService.create(request);
}
}常用注解:
| 注解 | 位置 | 作用 |
|---|---|---|
@Tag | Controller 类 | 接口分组 |
@Operation | Controller 方法 | 接口名称和说明 |
@Parameter | 方法参数 | 参数说明、示例、是否必填 |
@Schema | DTO 类或字段 | 数据模型说明 |
@ApiResponse | 方法 | 响应状态和响应体说明 |
📖 六、DTO 文档
java
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Schema(description = "创建用户请求")
public class UserCreateRequest {
@Schema(description = "用户名", example = "alice", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在 3 到 20 之间")
private String username;
@Schema(description = "邮箱", example = "alice@example.com")
@Email(message = "邮箱格式不正确")
private String email;
@Schema(description = "密码", example = "P@ssw0rd123", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "密码不能为空")
private String password;
}Validation 注解能提供一部分约束信息,但业务说明、示例值、字段含义仍需要 @Schema 补充。
📖 七、响应与错误码
接口文档不能只写成功响应,也要写错误响应。
java
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@Operation(summary = "根据 ID 查询用户")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(
responseCode = "404",
description = "用户不存在",
content = @Content(schema = @Schema(implementation = ApiError.class))
),
@ApiResponse(
responseCode = "400",
description = "参数错误",
content = @Content(schema = @Schema(implementation = ApiError.class))
)
})
@GetMapping("/{id}")
public UserDTO getById(@PathVariable Long id) {
return userService.getById(id);
}统一错误模型:
java
@Schema(description = "统一错误响应")
public class ApiError {
@Schema(description = "错误码", example = "USER_NOT_FOUND")
private String code;
@Schema(description = "错误信息", example = "用户不存在")
private String message;
}📖 八、接口分组
大型项目可以按路径或包分组:
java
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenApiGroupConfig {
@Bean
public GroupedOpenApi userApi() {
return GroupedOpenApi.builder()
.group("user")
.pathsToMatch("/api/users/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/api/admin/**")
.build();
}
}这样 Swagger UI 中会出现多个分组,前端和测试人员查找接口更方便。
📖 九、认证配置
如果接口使用 Bearer Token,可以在 OpenAPI 中声明安全方案:
java
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
@Bean
public OpenAPI customOpenAPI() {
String schemeName = "bearerAuth";
return new OpenAPI()
.components(new Components()
.addSecuritySchemes(schemeName,
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")))
.addSecurityItem(new SecurityRequirement().addList(schemeName));
}Swagger UI 会出现 Authorize 按钮,调试接口时可以填入 Token。
⚠️ 十、常见陷阱
1. 文档暴露到公网
Swagger UI 能直接调接口。如果生产环境公网开放,可能暴露接口结构、参数字段、内部错误模型。
建议:
text
生产默认关闭
仅内网或 VPN 可访问
或通过 Spring Security 限制访问2. 只依赖自动推断
自动生成可以得到路径和参数,但字段含义、业务规则、错误码、示例值通常需要人工补充。
3. DTO 命名混乱
如果 Controller 直接使用 Entity,接口文档会暴露数据库字段,并且容易出现循环引用。应使用明确的 Request/Response DTO。
4. 文档和代码不一致
手写 Wiki 容易过期。OpenAPI 的优势是从代码生成,但前提是注解和 DTO 本身维护得好。
🆚 十一、Java vs C 对比
| 维度 | C 服务常见方式 | Java Spring Boot |
|---|---|---|
| 接口描述 | 手写文档或 IDL | 注解 + OpenAPI 自动生成 |
| 参数约束 | 文档和代码分离 | Validation + Schema 可联动 |
| 调试接口 | curl/Postman 手动维护 | Swagger UI 可交互调试 |
| 类型模型 | struct/IDL | DTO class + 注解 |
OpenAPI 在 Java 项目中最大的价值,是把 Controller、DTO、Validation 和文档连接起来。
💡 十二、最佳实践
- 新项目优先使用 springdoc-openapi,不再使用老的 Springfox。
- Controller 用
@Tag和@Operation描述业务语义。 - DTO 字段使用
@Schema提供说明和示例,使用 Validation 表达约束。 - 文档必须包含错误响应,不只写成功响应。
- 生产环境默认关闭 Swagger UI,至少限制访问来源。
- 对外 API 要固定 OpenAPI JSON,可用于前端生成客户端 SDK 或契约测试。
- 不要用 Entity 作为接口模型,避免泄露数据库结构。
- 文档质量由代码质量决定,DTO 命名、字段说明、错误模型要统一。
🎓 小结
Swagger/OpenAPI 不是“页面好看”的工具,而是接口契约工具。它把后端 Controller、DTO、Validation、错误模型和前端联调流程连接在一起。
企业项目中,一个好的接口文档应该能回答:接口做什么、参数怎么传、字段是什么意思、哪些错误会发生、如何认证、能否直接调试。springdoc-openapi 提供自动化基础,但最终质量仍取决于你是否认真设计接口边界。
🧭 十三、项目落地清单
上线 Swagger/OpenAPI 前,至少检查:
text
1. 是否使用 springdoc-openapi,而不是旧版 Springfox。
2. Swagger UI 在生产环境是否默认关闭或受权限保护。
3. 每个 Controller 是否有 @Tag。
4. 每个核心接口是否有 @Operation。
5. Request/Response DTO 字段是否有清晰 @Schema。
6. Validation 注解是否和文档约束一致。
7. 错误响应模型是否在 OpenAPI 中体现。
8. 认证方式是否配置到 Swagger UI。
9. 是否按业务域做接口分组。
10. 前端是否可以基于 /v3/api-docs 生成类型或客户端。推荐形成接口开发流程:
text
设计 DTO
添加 Validation
编写 Controller
补充 OpenAPI 注解
本地 Swagger UI 调试
导出 /v3/api-docs 给前端或测试
接口变更时同步更新 DTO 和注解这样文档就不是最后补的装饰,而是接口设计的一部分。
🔍 十四、自测问题
学习完本节后,应该能回答:
text
Swagger 和 OpenAPI 是什么关系?
springdoc-openapi 和 Springfox 有什么区别?
@Tag、@Operation、@Schema、@ApiResponse 分别写在哪里?
为什么接口文档不能只写成功响应?
为什么生产环境不能无保护暴露 Swagger UI?
Validation 注解能替代 @Schema 吗?
为什么使用 Entity 生成接口文档是坏习惯?
Bearer Token 如何在 Swagger UI 中配置?如果接口文档只能“看路径”,不能说明字段、错误和认证方式,它就还不是合格的 API 契约。