Skip to content
第 67 / 250 章后端⏱ 10 分钟阅读

第 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 白名单
增强 UIKnife4j(国产)

动手练习

练习 1:基础题

为你的 UserController 加完整 OpenAPI 注解,启动后访问 Swagger UI 能看到所有接口,并尝试在线调试。

练习 2:进阶题

实现多版本文档:/api/v1/**/api/v2/** 分别配置不同的 OpenAPI Bean,Swagger UI 顶部有下拉切换。

练习 3:思考题

接口文档是给前端看的,但权限规则、错误码字典、字段字典这些"补充文档"如何整合到 OpenAPI 里?


下一章第 68 章:幂等性设计

本站基于 VitePress 构建 · 由 Codebook 团队维护