Skip to content

第53课:Swagger/OpenAPI

🎯 学习目标

  • 理解 Swagger、OpenAPI、springdoc-openapi 之间的关系。
  • 能在 Spring Boot 3 项目中接入 Swagger UI 并生成 OpenAPI 文档。
  • 掌握接口、参数、请求体、响应体、错误码和分组文档的常用注解。
  • 能把 Validation 约束和 DTO 设计反映到接口文档中。
  • 能识别生产环境暴露接口文档、文档与代码不一致等常见风险。

📖 一、Swagger 与 OpenAPI

很多人把 Swagger 当成“接口文档页面”,但更准确地说:

名称含义
OpenAPIREST API 描述规范,定义接口路径、参数、请求体、响应体、安全方案等
Swagger UI根据 OpenAPI 描述渲染出来的可交互网页
springdoc-openapiSpring 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);
    }
}

常用注解:

注解位置作用
@TagController 类接口分组
@OperationController 方法接口名称和说明
@Parameter方法参数参数说明、示例、是否必填
@SchemaDTO 类或字段数据模型说明
@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/IDLDTO 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 契约。