Swagger从入门到实战

第一章:初识 Swagger

1.1 什么是 Swagger?

简单来说,Swagger 是一个接口文档生成工具。但它不是普通的文档工具,它能做到:

  • 自动生成:根据你的代码自动生成接口文档
  • 实时更新:代码改了,文档自动跟着改
  • 在线测试:不用 Postman,直接在文档页面就能测试接口

这就像是给你的接口装了一面"魔镜",代码长什么样,文档就长什么样。

1.2 为什么要用 Swagger?

说实话,刚开始我也觉得多写注解挺麻烦的。但用了一段时间后发现:

省时间:以前写文档要半小时,现在加几个注解 5 分钟搞定。

不会忘:代码即文档,改代码的时候注解就在眼前,想忘都难。

前后端协作更顺畅:前端同学直接看文档就能测试,不用老是来问"这个字段什么意思"。

新人上手快:新同事来了,给他一个 Swagger 地址,所有接口一目了然。

1.3 搞清楚这些概念(重要!)

刚接触 Swagger 的时候,你肯定会被一堆名词搞晕:Swagger、SpringDoc、Springfox、Knife4j,还有各种不同的访问地址。别慌,我来帮你理清楚。

Swagger 是什么?

Swagger 其实是一个规范 ,全称叫 OpenAPI Specification(OAS)。就像 Java 有 Java 语言规范一样,Swagger 定义了"接口文档应该长什么样"。

所以 Swagger 本身不是一个具体的工具,而是一套标准。基于这套标准,不同的人开发了不同的实现工具。

SpringDoc vs Springfox:两个不同的实现

这两个都是在 Spring Boot 项目中实现 Swagger 规范的工具库,但它们是不同团队开发的:

Springfox(老一代):

  • 最早在 Spring 中实现 Swagger 的库
  • 支持 Swagger 2.0 规范
  • 最后一个版本是 3.0.0(2020年发布)
  • 之后就不再维护了,对 Spring Boot 3.x 支持不好
  • Maven 坐标:io.springfox:springfox-boot-starter

SpringDoc(新一代):

  • 后起之秀,专门为了替代 Springfox
  • 支持最新的 OpenAPI 3.0 规范
  • 持续维护更新,对 Spring Boot 3.x 完美支持
  • 配置更简单,开箱即用
  • Maven 坐标:org.springdoc:springdoc-openapi-ui

简单来说:

  • 老项目可能还在用 Springfox
  • 新项目都应该用 SpringDoc
  • 它们不能同时用,选一个就行
访问地址的区别

这里最容易混淆。不同的工具,访问地址不一样:

1. SpringDoc 的默认地址:

复制代码
http://localhost:8080/swagger-ui.html
或
http://localhost:8080/swagger-ui/index.html

这是 SpringDoc 自带的 UI 界面,样式是 Swagger 官方的那种蓝色主题。

2. Springfox 的默认地址:

复制代码
http://localhost:8080/swagger-ui/index.html
或
http://localhost:8080/swagger-ui/

和 SpringDoc 差不多,但配置方式不同。

3. Knife4j 的地址(重点来了):

复制代码
http://localhost:8080/doc.html
Knife4j 是什么?

Knife4j 是一个增强工具 ,它不是替代 SpringDoc 或 Springfox,而是给它们换了一套更好看的 UI 界面。

想象一下:

  • SpringDoc/Springfox 负责生成接口文档的数据
  • Knife4j 负责把这些数据展示得更漂亮、更好用

Knife4j 的优势:

  • 界面更美观,中文支持更好
  • 功能更强大(搜索、排序、离线文档等)
  • 可以配合 SpringDoc 或 Springfox 使用

使用 Knife4j 的依赖:

xml 复制代码
<!-- Knife4j + SpringDoc -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>

或者

xml 复制代码
<!-- Knife4j + Springfox (老版本) -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

引入 Knife4j 后,访问 http://localhost:8080/doc.html 就能看到它提供的界面了。

同时 ,原来的 /swagger-ui.html 也还能访问,两个 UI 共存,用哪个都行。

总结一下
名称 作用 访问地址 推荐度
Swagger/OpenAPI 接口文档规范(标准) - 必须知道
SpringDoc Swagger 的 Spring Boot 实现(新) /swagger-ui.html ⭐⭐⭐⭐⭐ 强烈推荐
Springfox Swagger 的 Spring Boot 实现(老) /swagger-ui/ ⭐⭐ 老项目才用
Knife4j UI 增强工具(可选) /doc.html ⭐⭐⭐⭐ 推荐加上

我的建议:

  • 新手入门 :先用 SpringDoc,访问 /swagger-ui.html,最简单
  • 想要更好体验 :加上 Knife4j,访问 /doc.html,界面更友好
  • 看别人的项目 :先看 pom.xml 里用的是 SpringDoc 还是 Springfox,然后试试不同的访问地址

看到这里,你应该能明白:

  • 访问 /swagger-ui.html 是在用 SpringDoc 或 Springfox 自带的 UI
  • 访问 /doc.html 是在用 Knife4j 提供的增强 UI
  • 它们显示的接口数据是一样的,只是界面不同

第二章:快速上手

2.1 环境准备

我们用 Spring Boot 来演示,这是最常见的场景。

Maven 依赖(以 Swagger 3.0 也就是 SpringDoc 为例):

xml 复制代码
<!-- Swagger 3.0 (SpringDoc) -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.7.0</version>
</dependency>

如果你用的是老版本 Swagger 2.x (Springfox):

xml 复制代码
<!-- Swagger 2.x (老版本) -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

小提示:现在推荐用 SpringDoc(Swagger 3.0),它对 Spring Boot 3.x 支持更好,配置也更简单。下面我会两个版本都讲。

2.2 第一个 Swagger 文档

使用 SpringDoc(推荐):

Spring Boot 项目引入依赖后,什么都不用配置,直接启动项目,访问:

复制代码
http://localhost:8080/swagger-ui.html

就能看到你的接口文档了!是不是很简单?

使用 Springfox(老版本):

需要添加配置类:

java 复制代码
@Configuration
@EnableOpenApi  // Springfox 3.x 用这个
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.OAS_30)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.controller"))
                .paths(PathSelectors.any())
                .build();
    }
}

2.3 写你的第一个接口文档

来写个用户查询接口:

java 复制代码
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户相关的增删改查接口")  // SpringDoc
// @Api(tags = "用户管理")  // Springfox 的写法
public class UserController {

    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户", description = "传入用户ID,返回用户详细信息")  // SpringDoc
    // @ApiOperation("根据ID查询用户")  // Springfox 的写法
    public User getUserById(
            @Parameter(description = "用户ID", required = true, example = "1001")  // SpringDoc
            // @ApiParam(value = "用户ID", required = true, example = "1001")  // Springfox
            @PathVariable Long id) {

        User user = new User();
        user.setId(id);
        user.setUsername("张三");
        user.setEmail("zhangsan@example.com");
        return user;
    }
}

实体类也要加注解:

java 复制代码
@Schema(description = "用户实体")  // SpringDoc
// @ApiModel("用户实体")  // Springfox
public class User {

    @Schema(description = "用户ID", example = "1001")  // SpringDoc
    // @ApiModelProperty(value = "用户ID", example = "1001")  // Springfox
    private Long id;

    @Schema(description = "用户名", required = true, example = "张三")
    // @ApiModelProperty(value = "用户名", required = true, example = "张三")
    private String username;

    @Schema(description = "邮箱", example = "zhangsan@example.com")
    // @ApiModelProperty(value = "邮箱", example = "zhangsan@example.com")
    private String email;

    // getter/setter 省略
}

重启项目,刷新 Swagger 页面,你就能看到漂亮的文档了!

2.4 使用 Knife4j(推荐)

前面说了,Swagger 官方的 UI 界面比较简陋,功能也少。Knife4j 就是为了解决这个问题而生的。

Knife4j 和 Swagger 的关系

用一个类比来说明:

  • Swagger/OpenAPI:标准规范(就像 HTML 标准)
  • SpringDoc/Springfox:实现工具(生成符合标准的接口数据)
  • Swagger UI:官方的展示界面(像浏览器显示 HTML)
  • Knife4j:增强版的展示界面(像加了各种插件的浏览器)

关键点:

  • Knife4j 不是替代 SpringDoc 或 Springfox 的
  • Knife4j 是在 SpringDoc/Springfox 基础上提供更好的 UI 界面
  • 它们是搭配使用的关系,不是二选一

Knife4j 的优势:

  • 界面更现代化,中文支持好
  • 支持接口搜索、排序
  • 可以导出离线文档(Markdown、HTML、Word)
  • 支持多主题切换
  • 接口调试更方便(支持全局参数)
快速上手 Knife4j

第一步:引入依赖

如果你用的是 SpringDoc(推荐):

xml 复制代码
<!-- Spring Boot 2.x + SpringDoc + Knife4j -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.7.0</version>
</dependency>

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>

如果你的项目是 Spring Boot 3.x:

xml 复制代码
<!-- Spring Boot 3.x + SpringDoc + Knife4j -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.0.2</version>
</dependency>

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>

如果你用的是老的 Springfox:

xml 复制代码
<!-- Springfox + Knife4j (老版本) -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

第二步:配置(可选)

Knife4j 大部分情况下零配置 就能用,但你也可以在 application.yml 中自定义:

yaml 复制代码
knife4j:
  # 是否启用 Knife4j 增强功能
  enable: true
  # 是否开启生产环境保护(生产环境建议开启)
  production: false
  # 基本信息
  setting:
    language: zh_cn  # 中文界面
    enable-swagger-models: true  # 显示 Models
    enable-footer: false  # 去掉底部的 footer
    enable-footer-custom: false

第三步:访问 Knife4j 界面

启动项目后,访问:

复制代码
http://localhost:8080/doc.html

你会看到一个全新的界面!

Knife4j 界面功能介绍

打开 http://localhost:8080/doc.html 后,你会看到:

1. 左侧菜单:

  • 主页:显示项目信息和配置
  • 接口分组:可以切换不同的 API 分组
  • 全局参数设置:设置公共的请求头(比如 Token)

2. 中间区域:

  • 接口列表和详情
  • 支持搜索框,快速查找接口

3. 右上角工具栏:

  • 搜索:全局搜索接口
  • 个性化设置:调整字体大小、主题颜色
  • 离线文档:导出 Markdown、HTML 等格式
  • 授权:设置全局 Token
实际使用示例

设置全局 Token:

很多接口需要登录才能访问,每次调试都输入 Token 很麻烦。Knife4j 可以设置全局参数:

  1. 点击右上角的 "Authorize" 按钮(或者点击左侧菜单的"全局参数设置")
  2. 输入你的 Token,比如:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  3. 点击保存

之后所有接口调试时都会自动带上这个 Token,不用每次手动输入。

调试接口:

  1. 在左侧找到你要测试的接口,点击展开
  2. 填写参数(如果设置了 example,会自动填充)
  3. 点击 "调试" 按钮
  4. 查看返回结果

导出离线文档:

  1. 点击右上角的 "离线文档" 图标
  2. 选择导出格式(Markdown、HTML、Word)
  3. 点击下载

这个功能对于需要交付文档给客户或者归档很有用。

Knife4j vs Swagger UI 对比
功能 Swagger UI Knife4j
基本接口展示 ✅ ✅
接口调试 ✅ ✅ 更方便
全局搜索 ❌ ✅
全局参数 ❌ ✅ 支持全局 Token
离线文档导出 ❌ ✅ 多种格式
中文界面 ❌ ✅
主题切换 ❌ ✅
请求记录 ❌ ✅
两个 UI 可以共存

引入 Knife4j 后,两个 UI 界面都可以访问:

  • Swagger 官方 UI :http://localhost:8080/swagger-ui.html
  • Knife4j UI :http://localhost:8080/doc.html

它们显示的接口数据是一样的,只是界面不同。你可以根据喜好选择用哪个。

我的建议:

  • 开发调试 :用 Knife4j 的 /doc.html,功能更强大
  • 给前端看:也用 Knife4j,中文界面更友好
  • 习惯 Swagger 官方 UI :那就继续用 /swagger-ui.html
注意事项

1. 版本匹配问题

Knife4j 的版本要和 SpringDoc/Springfox 的版本匹配:

Spring Boot 版本 基础库 Knife4j 版本
2.x SpringDoc knife4j-openapi3-spring-boot-starter:4.3.0
3.x SpringDoc knife4j-openapi3-jakarta-spring-boot-starter:4.3.0
2.x Springfox knife4j-spring-boot-starter:3.0.3

2. 生产环境记得关闭

和 Swagger UI 一样,生产环境不要开启文档访问:

yaml 复制代码
knife4j:
  production: true  # 生产环境设为 true,会禁用文档访问

或者在配置类中:

java 复制代码
@Configuration
@Profile({"dev", "test"})  // 仅在开发和测试环境生效
public class Knife4jConfig {
    // 配置
}

3. 如果访问 /doc.html 报 404

检查几点:

  • 确认依赖已经引入
  • 确认项目已经重新编译启动
  • 试试访问 /swagger-ui.html,看基础的 Swagger 是否正常
  • 检查是否配置了 Spring Security 拦截了这个路径
完整示例

假设我们有一个用户管理的接口,用 Knife4j 调试的完整流程:

1. 代码(和之前一样,不用改):

java 复制代码
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理")
public class UserController {

    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户")
    public Result<User> getUserById(@PathVariable Long id) {
        // 业务逻辑
        return Result.success(new User());
    }
}

2. 访问文档:

打开 http://localhost:8080/doc.html

3. 设置全局 Token(如果需要):

点击右上角"Authorize",输入:

复制代码
Bearer your-token-here

4. 调试接口:

  • 展开"用户管理" → "根据ID查询用户"
  • 输入参数 id = 1
  • 点击"调试"
  • 查看返回结果

5. 导出文档(如果需要):

点击右上角"离线文档" → 选择 Markdown → 下载

小结

Knife4j 和 Swagger 的关系:

  • Swagger 定义标准,SpringDoc/Springfox 实现标准,Knife4j 提供更好的展示界面
  • 它们是搭配关系,不是替代关系

推荐组合:

  • SpringDoc(生成文档数据)+ Knife4j(展示界面)= 完美组合 ⭐⭐⭐⭐⭐

核心优势:

  • 界面更美观,功能更强大
  • 全局参数、搜索、导出一应俱全
  • 零配置,开箱即用

试试 Knife4j,你会发现写接口文档变成了一件愉快的事情!


第三章:常用注解详解

前面快速上手的时候,我们用了一些注解,但可能你还不太清楚每个注解具体是干什么的,有哪些属性可以配置。这一章我们详细讲解。

注意:SpringDoc 和 Springfox 的注解是不一样的!下面我会同时列出两个版本的用法。

3.1 类级别注解

@Tag(SpringDoc)/ @Api(Springfox)

作用:用在 Controller 类上,给整个接口分组添加说明。

SpringDoc 的 @Tag:

java 复制代码
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户相关的增删改查接口")
public class UserController {
    // ...
}

常用属性:

  • name:分组名称(必填),会显示在文档的左侧菜单
  • description:分组描述,鼠标悬停时显示

Springfox 的 @Api:

java 复制代码
@RestController
@RequestMapping("/api/users")
@Api(tags = "用户管理", description = "用户相关的增删改查接口")
public class UserController {
    // ...
}

常用属性:

  • tags:分组名称(可以是数组)
  • description:描述信息

多个标签:

一个 Controller 可以属于多个分组:

java 复制代码
// SpringDoc
@Tag(name = "用户管理")
@Tag(name = "权限管理")
public class UserController { }

// Springfox
@Api(tags = {"用户管理", "权限管理"})
public class UserController { }

3.2 方法级别注解

@Operation(SpringDoc)/ @ApiOperation(Springfox)

作用:用在接口方法上,描述这个接口是干什么的。

SpringDoc 的 @Operation:

java 复制代码
@GetMapping("/{id}")
@Operation(
    summary = "根据ID查询用户",
    description = "传入用户ID,返回用户的详细信息,包括用户名、邮箱、创建时间等",
    tags = {"用户管理"}  // 可选,覆盖类上的 @Tag
)
public User getUserById(@PathVariable Long id) {
    // ...
}

常用属性:

  • summary:简短描述(必填),显示在接口列表
  • description:详细描述,点开接口后显示
  • tags:可以重新指定分组
  • hidden:是否隐藏这个接口(true 则不显示)

Springfox 的 @ApiOperation:

java 复制代码
@GetMapping("/{id}")
@ApiOperation(
    value = "根据ID查询用户",
    notes = "传入用户ID,返回用户的详细信息",
    tags = {"用户管理"}
)
public User getUserById(@PathVariable Long id) {
    // ...
}

常用属性:

  • value:简短描述
  • notes:详细描述
  • tags:指定分组
  • hidden:是否隐藏

3.3 参数注解

@Parameter(SpringDoc)/ @ApiParam(Springfox)

作用:用在方法参数上,描述参数的含义。

SpringDoc 的 @Parameter:

java 复制代码
@GetMapping("/{id}")
public User getUserById(
    @Parameter(
        name = "id",
        description = "用户ID",
        required = true,
        example = "1001",
        schema = @Schema(type = "integer", format = "int64")
    )
    @PathVariable Long id) {
    // ...
}

常用属性:

  • name:参数名(一般会自动识别,可以不写)
  • description:参数说明
  • required:是否必填
  • example:示例值(很重要!)
  • schema:数据类型定义
  • hidden:是否隐藏该参数

更多示例:

java 复制代码
// 查询参数
@GetMapping("/search")
public List<User> searchUsers(
    @Parameter(description = "用户名(支持模糊查询)", example = "张三")
    @RequestParam(required = false) String username,

    @Parameter(description = "页码,从1开始", example = "1")
    @RequestParam(defaultValue = "1") Integer pageNum,

    @Parameter(description = "每页数量,最大100", example = "10")
    @RequestParam(defaultValue = "10") Integer pageSize) {
    // ...
}

// 路径参数
@DeleteMapping("/{id}")
public Result deleteUser(
    @Parameter(description = "要删除的用户ID", required = true, example = "1001")
    @PathVariable Long id) {
    // ...
}

Springfox 的 @ApiParam:

java 复制代码
@GetMapping("/{id}")
public User getUserById(
    @ApiParam(
        value = "用户ID",
        required = true,
        example = "1001"
    )
    @PathVariable Long id) {
    // ...
}

常用属性:

  • value:参数说明
  • required:是否必填
  • example:示例值
  • defaultValue:默认值
  • allowableValues:允许的值(枚举)

3.4 实体类注解

@Schema(SpringDoc)/ @ApiModel + @ApiModelProperty(Springfox)

作用:用在实体类和实体类的字段上,描述数据模型。

SpringDoc 的 @Schema:

java 复制代码
@Schema(description = "用户实体")
public class User {

    @Schema(
        description = "用户ID",
        example = "1001",
        required = true,
        accessMode = Schema.AccessMode.READ_ONLY  // 只读,不能修改
    )
    private Long id;

    @Schema(
        description = "用户名",
        example = "zhangsan",
        required = true,
        minLength = 3,
        maxLength = 20
    )
    private String username;

    @Schema(
        description = "密码",
        example = "123456",
        minLength = 6,
        accessMode = Schema.AccessMode.WRITE_ONLY  // 只写,不返回
    )
    private String password;

    @Schema(
        description = "邮箱",
        example = "zhangsan@example.com",
        pattern = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$"
    )
    private String email;

    @Schema(
        description = "年龄",
        example = "25",
        minimum = "0",
        maximum = "150"
    )
    private Integer age;

    @Schema(
        description = "用户状态",
        example = "ACTIVE",
        allowableValues = {"ACTIVE", "DISABLED", "DELETED"}
    )
    private String status;

    @Schema(description = "创建时间", example = "2024-01-01 12:00:00")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    @Schema(hidden = true)  // 隐藏该字段,不在文档中显示
    private String internalField;

    // getter/setter
}

常用属性:

  • description:字段描述
  • example:示例值
  • required:是否必填
  • minLength / maxLength:字符串长度限制
  • minimum / maximum:数值范围限制
  • pattern:正则表达式验证
  • allowableValues:允许的值(枚举)
  • accessMode:访问模式(READ_ONLY、WRITE_ONLY、READ_WRITE)
  • hidden:是否隐藏
  • defaultValue:默认值

Springfox 的 @ApiModel + @ApiModelProperty:

java 复制代码
@ApiModel(description = "用户实体")
public class User {

    @ApiModelProperty(
        value = "用户ID",
        example = "1001",
        required = true,
        accessMode = ApiModelProperty.AccessMode.READ_ONLY
    )
    private Long id;

    @ApiModelProperty(
        value = "用户名",
        example = "zhangsan",
        required = true
    )
    private String username;

    @ApiModelProperty(
        value = "密码",
        example = "123456",
        accessMode = ApiModelProperty.AccessMode.WRITE_ONLY
    )
    private String password;

    // getter/setter
}

DTO 实体示例:

创建用户的请求参数:

java 复制代码
@Schema(description = "创建用户请求参数")
public class UserCreateDTO {

    @Schema(description = "用户名", required = true, example = "zhangsan", minLength = 3, maxLength = 20)
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Schema(description = "密码", required = true, example = "123456", minLength = 6)
    @NotBlank(message = "密码不能为空")
    private String password;

    @Schema(description = "邮箱", example = "zhangsan@example.com")
    @Email(message = "邮箱格式不正确")
    private String email;

    @Schema(description = "年龄", example = "25", minimum = "1", maximum = "150")
    @Min(value = 1, message = "年龄必须大于0")
    @Max(value = 150, message = "年龄不能超过150")
    private Integer age;

    // getter/setter
}

注意 :@Schema 的验证属性(如 minLength、minimum)只是用于文档展示,不会真正执行验证 。真正的验证需要用 JSR-303 的注解(@NotBlank、@Email、@Min 等)。

3.5 响应注解

@ApiResponse / @ApiResponses

作用:描述接口的返回结果,特别是不同的 HTTP 状态码对应的返回内容。

SpringDoc 的用法:

java 复制代码
@GetMapping("/{id}")
@Operation(summary = "根据ID查询用户")
@ApiResponses({
    @ApiResponse(
        responseCode = "200",
        description = "查询成功",
        content = @Content(
            mediaType = "application/json",
            schema = @Schema(implementation = User.class)
        )
    ),
    @ApiResponse(
        responseCode = "404",
        description = "用户不存在",
        content = @Content(
            mediaType = "application/json",
            schema = @Schema(implementation = ErrorResponse.class)
        )
    ),
    @ApiResponse(
        responseCode = "500",
        description = "服务器错误"
    )
})
public Result<User> getUserById(@PathVariable Long id) {
    // ...
}

Springfox 的用法:

java 复制代码
@ApiResponses({
    @ApiResponse(code = 200, message = "查询成功", response = User.class),
    @ApiResponse(code = 404, message = "用户不存在"),
    @ApiResponse(code = 500, message = "服务器错误")
})
public Result<User> getUserById(@PathVariable Long id) {
    // ...
}

统一返回格式的处理:

如果你的项目用了统一返回格式(如 Result<T>),可以这样写:

java 复制代码
@ApiResponse(
    responseCode = "200",
    description = "成功",
    content = @Content(schema = @Schema(implementation = UserResult.class))
)
public Result<User> getUser() {
    // ...
}

// 为 Result<User> 创建一个具体的类
class UserResult extends Result<User> { }

3.6 请求体注解

@io.swagger.v3.oas.annotations.parameters.RequestBody(SpringDoc)

作用:描述 POST/PUT 请求的请求体。

注意 :这里用的是 Swagger 的 @RequestBody 注解,不是 Spring 的 @RequestBody!

java 复制代码
@PostMapping
@Operation(summary = "创建用户")
public Result<User> createUser(
    @io.swagger.v3.oas.annotations.parameters.RequestBody(
        description = "用户信息",
        required = true,
        content = @Content(
            mediaType = "application/json",
            schema = @Schema(implementation = UserCreateDTO.class),
            examples = {
                @ExampleObject(
                    name = "示例1",
                    summary = "创建普通用户",
                    value = """
                        {
                          "username": "zhangsan",
                          "password": "123456",
                          "email": "zhangsan@example.com",
                          "age": 25
                        }
                        """
                ),
                @ExampleObject(
                    name = "示例2",
                    summary = "创建管理员",
                    value = """
                        {
                          "username": "admin",
                          "password": "admin123",
                          "email": "admin@example.com",
                          "role": "ADMIN"
                        }
                        """
                )
            }
        )
    )
    @org.springframework.web.bind.annotation.RequestBody UserCreateDTO userDTO) {
    // ...
}

大部分情况下不用写:

如果你的 DTO 类已经用 @Schema 标注了字段,Spring 会自动识别,不需要额外写 @RequestBody 注解:

java 复制代码
@PostMapping
@Operation(summary = "创建用户")
public Result<User> createUser(@RequestBody UserCreateDTO userDTO) {
    // Spring 会自动根据 UserCreateDTO 里的 @Schema 注解生成文档
}

3.7 隐藏接口/参数

@Hidden(SpringDoc)/ @ApiIgnore(Springfox)

作用:隐藏不想在文档中显示的接口或参数。

隐藏整个接口:

java 复制代码
@Hidden  // SpringDoc
// @ApiIgnore  // Springfox
@GetMapping("/internal/debug")
public String debugInfo() {
    return "debug info";
}

隐藏整个 Controller:

java 复制代码
@Hidden
@RestController
public class InternalController {
    // 这个类的所有接口都不会出现在文档中
}

隐藏某个参数:

java 复制代码
@GetMapping("/list")
public List<User> listUsers(
    @Parameter(hidden = true)  // 这个参数不会在文档中显示
    @RequestParam(required = false) String internalParam,

    @Parameter(description = "用户名")
    @RequestParam(required = false) String username) {
    // ...
}

隐藏实体类的某个字段:

java 复制代码
public class User {
    @Schema(hidden = true)
    private String password;  // 密码不在文档中显示
}

3.8 其他常用注解

@Content

作用:描述响应内容的格式和类型。

java 复制代码
@ApiResponse(
    responseCode = "200",
    description = "成功",
    content = {
        @Content(
            mediaType = "application/json",
            schema = @Schema(implementation = User.class)
        ),
        @Content(
            mediaType = "application/xml",
            schema = @Schema(implementation = User.class)
        )
    }
)
@ExampleObject

作用:提供请求或响应的示例数据。

java 复制代码
@io.swagger.v3.oas.annotations.parameters.RequestBody(
    content = @Content(
        examples = @ExampleObject(
            name = "创建用户示例",
            value = """
                {
                  "username": "zhangsan",
                  "email": "zhangsan@example.com"
                }
                """
        )
    )
)

3.9 注解使用技巧

技巧1:善用 example

example 很重要!它能让前端一眼看懂参数格式,也能方便测试。

java 复制代码
// 不好的例子
@Schema(description = "手机号")
private String phone;

// 好的例子
@Schema(description = "手机号", example = "13800138000")
private String phone;

// 更好的例子
@Schema(
    description = "手机号",
    example = "13800138000",
    pattern = "^1[3-9]\\d{9}$"
)
private String phone;
技巧2:必填参数一定要标注
java 复制代码
@Schema(description = "用户名", required = true, example = "zhangsan")
@NotBlank(message = "用户名不能为空")  // 真正的验证
private String username;
技巧3:枚举类型要说明取值范围
java 复制代码
@Schema(
    description = "用户状态",
    example = "ACTIVE",
    allowableValues = {"ACTIVE", "DISABLED", "DELETED"}
)
private String status;

或者直接用枚举类:

java 复制代码
@Schema(description = "用户状态", example = "ACTIVE")
private UserStatus status;  // UserStatus 是枚举类
技巧4:敏感字段要隐藏
java 复制代码
@Schema(description = "密码", accessMode = Schema.AccessMode.WRITE_ONLY)
private String password;  // 只能写入,不会在响应中返回
技巧5:只读字段标注清楚
java 复制代码
@Schema(description = "用户ID", accessMode = Schema.AccessMode.READ_ONLY)
private Long id;  // 只读,创建/更新时不能传这个字段

3.10 SpringDoc vs Springfox 注解对照表

功能 SpringDoc (推荐) Springfox (老版本)
类级别分组 @Tag @Api
方法级别描述 @Operation @ApiOperation
参数描述 @Parameter @ApiParam
实体类 @Schema @ApiModel
实体字段 @Schema @ApiModelProperty
响应描述 @ApiResponse / @ApiResponses @ApiResponse / @ApiResponses
请求体 @io.swagger.v3.oas.annotations.parameters.RequestBody (不需要单独注解)
隐藏 @Hidden @ApiIgnore

包路径:

  • SpringDoc:io.swagger.v3.oas.annotations.*
  • Springfox:io.swagger.annotations.*

3.11 完整示例

来看一个综合运用各种注解的完整例子:

java 复制代码
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户相关的增删改查接口")
public class UserController {

    @PostMapping
    @Operation(
        summary = "创建用户",
        description = "创建一个新用户,用户名必须唯一"
    )
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "创建成功"),
        @ApiResponse(responseCode = "400", description = "参数错误"),
        @ApiResponse(responseCode = "409", description = "用户名已存在")
    })
    public Result<User> createUser(
        @io.swagger.v3.oas.annotations.parameters.RequestBody(
            description = "用户信息",
            required = true
        )
        @RequestBody @Valid UserCreateDTO userDTO) {
        // 业务逻辑
        return Result.success(new User());
    }

    @GetMapping
    @Operation(summary = "分页查询用户列表")
    public Result<PageResult<User>> listUsers(
        @Parameter(description = "用户名(模糊查询)", example = "张")
        @RequestParam(required = false) String username,

        @Parameter(description = "用户状态", example = "ACTIVE")
        @RequestParam(required = false) UserStatus status,

        @Parameter(description = "页码", example = "1")
        @RequestParam(defaultValue = "1") Integer pageNum,

        @Parameter(description = "每页数量", example = "10")
        @RequestParam(defaultValue = "10") Integer pageSize) {
        // 业务逻辑
        return Result.success(new PageResult<>());
    }

    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户")
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "查询成功"),
        @ApiResponse(responseCode = "404", description = "用户不存在")
    })
    public Result<User> getUserById(
        @Parameter(description = "用户ID", required = true, example = "1001")
        @PathVariable Long id) {
        // 业务逻辑
        return Result.success(new User());
    }

    @PutMapping("/{id}")
    @Operation(summary = "更新用户信息")
    public Result<User> updateUser(
        @Parameter(description = "用户ID", required = true, example = "1001")
        @PathVariable Long id,

        @RequestBody @Valid UserUpdateDTO userDTO) {
        // 业务逻辑
        return Result.success(new User());
    }

    @DeleteMapping("/{id}")
    @Operation(summary = "删除用户")
    public Result<Void> deleteUser(
        @Parameter(description = "用户ID", required = true, example = "1001")
        @PathVariable Long id) {
        // 业务逻辑
        return Result.success(null);
    }
}
java 复制代码
@Schema(description = "用户实体")
public class User {

    @Schema(description = "用户ID", example = "1001", accessMode = Schema.AccessMode.READ_ONLY)
    private Long id;

    @Schema(description = "用户名", example = "zhangsan", required = true)
    private String username;

    @Schema(description = "邮箱", example = "zhangsan@example.com")
    private String email;

    @Schema(description = "年龄", example = "25", minimum = "0", maximum = "150")
    private Integer age;

    @Schema(description = "用户状态", example = "ACTIVE")
    private UserStatus status;

    @Schema(description = "创建时间", example = "2024-01-01 12:00:00")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    @Schema(description = "更新时间", example = "2024-01-01 12:00:00")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;

    // getter/setter
}
java 复制代码
@Schema(description = "创建用户请求参数")
public class UserCreateDTO {

    @Schema(description = "用户名", required = true, example = "zhangsan", minLength = 3, maxLength = 20)
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
    private String username;

    @Schema(description = "密码", required = true, example = "123456", minLength = 6)
    @NotBlank(message = "密码不能为空")
    @Size(min = 6, message = "密码长度不能少于6位")
    private String password;

    @Schema(description = "邮箱", example = "zhangsan@example.com")
    @Email(message = "邮箱格式不正确")
    private String email;

    @Schema(description = "年龄", example = "25", minimum = "1", maximum = "150")
    @Min(value = 1, message = "年龄必须大于0")
    @Max(value = 150, message = "年龄不能超过150")
    private Integer age;

    // getter/setter
}

第四章:进阶配置

4.1 自定义文档信息

默认的文档标题和描述太简陋?来自定义一下:

SpringDoc 配置:

在 application.yml 中:

yaml 复制代码
springdoc:
  api-docs:
    path: /api-docs  # API 文档的路径
  swagger-ui:
    path: /swagger-ui.html  # Swagger UI 的路径
    tags-sorter: alpha  # 标签按字母排序
    operations-sorter: alpha  # 接口按字母排序

或者写配置类:

java 复制代码
@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("我的项目 API 文档")
                        .version("1.0.0")
                        .description("这是一个用 Spring Boot 构建的 RESTful API")
                        .contact(new Contact()
                                .name("张三")
                                .email("zhangsan@example.com")
                                .url("https://github.com/zhangsan"))
                        .license(new License()
                                .name("Apache 2.0")
                                .url("http://www.apache.org/licenses/LICENSE-2.0.html")));
    }
}

Springfox 配置:

java 复制代码
@Bean
public Docket api() {
    return new Docket(DocumentationType.OAS_30)
            .apiInfo(apiInfo())
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example.controller"))
            .paths(PathSelectors.any())
            .build();
}

private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
            .title("我的项目 API 文档")
            .description("这是一个用 Spring Boot 构建的 RESTful API")
            .version("1.0.0")
            .contact(new Contact("张三", "https://github.com/zhangsan", "zhangsan@example.com"))
            .license("Apache 2.0")
            .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
            .build();
}

3.2 接口分组

项目大了以后,接口会很多。可以按模块分组:

SpringDoc:

java 复制代码
@Configuration
public class SwaggerConfig {

    @Bean
    public GroupedOpenApi userApi() {
        return GroupedOpenApi.builder()
                .group("用户模块")
                .pathsToMatch("/api/users/**")
                .build();
    }

    @Bean
    public GroupedOpenApi orderApi() {
        return GroupedOpenApi.builder()
                .group("订单模块")
                .pathsToMatch("/api/orders/**")
                .build();
    }
}

Springfox:

java 复制代码
@Bean
public Docket userApi() {
    return new Docket(DocumentationType.OAS_30)
            .groupName("用户模块")
            .select()
            .paths(PathSelectors.ant("/api/users/**"))
            .build();
}

@Bean
public Docket orderApi() {
    return new Docket(DocumentationType.OAS_30)
            .groupName("订单模块")
            .select()
            .paths(PathSelectors.ant("/api/orders/**"))
            .build();
}

这样在 Swagger UI 右上角就能切换不同的模块了。

3.3 添加认证配置

很多接口需要登录才能访问,怎么在 Swagger 里加认证?

JWT Token 认证(SpringDoc):

java 复制代码
@Bean
public OpenAPI openAPI() {
    return new OpenAPI()
            .components(new Components()
                    .addSecuritySchemes("bearer-jwt",
                            new SecurityScheme()
                                    .type(SecurityScheme.Type.HTTP)
                                    .scheme("bearer")
                                    .bearerFormat("JWT")
                                    .in(SecurityScheme.In.HEADER)
                                    .name("Authorization")))
            .addSecurityItem(new SecurityRequirement().addList("bearer-jwt"));
}

配置好后,Swagger UI 右上角会出现一个 "Authorize" 按钮,点击输入 Token 就能测试需要认证的接口了。

Springfox 的写法:

java 复制代码
@Bean
public Docket api() {
    return new Docket(DocumentationType.OAS_30)
            .securitySchemes(Collections.singletonList(securityScheme()))
            .securityContexts(Collections.singletonList(securityContext()))
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example.controller"))
            .build();
}

private SecurityScheme securityScheme() {
    return new ApiKey("Authorization", "Authorization", "header");
}

private SecurityContext securityContext() {
    return SecurityContext.builder()
            .securityReferences(defaultAuth())
            .forPaths(PathSelectors.any())
            .build();
}

private List<SecurityReference> defaultAuth() {
    AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
    AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
    authorizationScopes[0] = authorizationScope;
    return Collections.singletonList(new SecurityReference("Authorization", authorizationScopes));
}

第四章:实战技巧

4.1 处理复杂请求参数

POST 请求示例:

java 复制代码
@PostMapping
@Operation(summary = "创建用户", description = "创建一个新用户")
@ApiResponses(value = {
    @ApiResponse(responseCode = "200", description = "创建成功"),
    @ApiResponse(responseCode = "400", description = "参数错误"),
    @ApiResponse(responseCode = "500", description = "服务器错误")
})
public Result<User> createUser(
        @io.swagger.v3.oas.annotations.parameters.RequestBody(
                description = "用户信息",
                required = true,
                content = @Content(schema = @Schema(implementation = UserCreateDTO.class)))
        @RequestBody UserCreateDTO userDTO) {

    // 业务逻辑
    return Result.success(new User());
}

UserCreateDTO:

java 复制代码
@Schema(description = "创建用户请求参数")
public class UserCreateDTO {

    @Schema(description = "用户名", required = true, example = "zhangsan", minLength = 3, maxLength = 20)
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Schema(description = "密码", required = true, example = "123456", minLength = 6)
    @NotBlank(message = "密码不能为空")
    private String password;

    @Schema(description = "邮箱", example = "zhangsan@example.com")
    @Email(message = "邮箱格式不正确")
    private String email;

    @Schema(description = "年龄", example = "25", minimum = "1", maximum = "150")
    private Integer age;

    // getter/setter
}

4.2 枚举类型的优雅处理

枚举在接口文档中很容易让人困惑,要说清楚每个值的含义:

java 复制代码
@Schema(description = "用户状态")
public enum UserStatus {

    @Schema(description = "正常")
    ACTIVE(1, "正常"),

    @Schema(description = "禁用")
    DISABLED(0, "禁用"),

    @Schema(description = "已删除")
    DELETED(-1, "已删除");

    @Schema(description = "状态码")
    private final Integer code;

    @Schema(description = "状态描述")
    private final String desc;

    UserStatus(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    // getter
}

在实体类中使用:

java 复制代码
@Schema(description = "用户状态", example = "ACTIVE",
        allowableValues = {"ACTIVE", "DISABLED", "DELETED"})
private UserStatus status;

4.3 分页查询文档

分页是最常见的场景:

java 复制代码
@GetMapping
@Operation(summary = "分页查询用户列表")
public PageResult<User> getUserList(
        @Parameter(description = "页码", example = "1")
        @RequestParam(defaultValue = "1") Integer pageNum,

        @Parameter(description = "每页数量", example = "10")
        @RequestParam(defaultValue = "10") Integer pageSize,

        @Parameter(description = "用户名(模糊查询)", example = "张")
        @RequestParam(required = false) String username,

        @Parameter(description = "用户状态", example = "ACTIVE")
        @RequestParam(required = false) UserStatus status) {

    // 业务逻辑
    return new PageResult<>();
}

分页结果封装:

java 复制代码
@Schema(description = "分页结果")
public class PageResult<T> {

    @Schema(description = "总记录数", example = "100")
    private Long total;

    @Schema(description = "当前页码", example = "1")
    private Integer pageNum;

    @Schema(description = "每页数量", example = "10")
    private Integer pageSize;

    @Schema(description = "数据列表")
    private List<T> list;

    // getter/setter
}

4.4 文件上传接口

java 复制代码
@PostMapping("/upload")
@Operation(summary = "上传用户头像")
public Result<String> uploadAvatar(
        @Parameter(description = "用户ID", required = true)
        @RequestParam Long userId,

        @Parameter(description = "头像文件", required = true,
                   content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
        @RequestParam("file") MultipartFile file) {

    // 文件上传逻辑
    return Result.success("http://example.com/avatar/123.jpg");
}

4.5 隐藏不想暴露的接口

有些内部接口不想在文档中显示:

java 复制代码
@Hidden  // SpringDoc
// @ApiIgnore  // Springfox
@GetMapping("/internal/debug")
public String debugInfo() {
    return "debug info";
}

或者整个 Controller 都隐藏:

java 复制代码
@Hidden  // SpringDoc
@RestController
public class InternalController {
    // 这个类的所有接口都不会出现在文档中
}

第五章:常见问题与解决方案

5.1 Swagger UI 访问 404

问题 :启动项目后访问 /swagger-ui.html 返回 404。

解决方案:

  1. 检查依赖是否引入 :确认 pom.xml 中有 Swagger 依赖。

  2. 检查路径:

    • SpringDoc 默认路径是 /swagger-ui.html 或 /swagger-ui/index.html
    • Springfox 默认路径是 /swagger-ui/index.html 或 /swagger-ui/
  3. 如果用了 Spring Security,需要放行 Swagger 路径:

java 复制代码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()  // SpringDoc
            .antMatchers("/swagger-ui/**", "/swagger-resources/**", "/v2/api-docs").permitAll()  // Springfox
            .anyRequest().authenticated();
    }
}

5.2 接口文档不显示某些字段

问题:实体类有些字段在文档中不显示。

原因 :可能是这些字段没有 getter 方法,或者被 @JsonIgnore 标记了。

解决方案:

  1. 检查是否有 getter 方法
  2. 检查是否有 @JsonIgnore 注解
  3. 如果确实不想序列化但想在文档中显示:
java 复制代码
@JsonIgnore  // JSON 序列化时忽略
@Schema(hidden = false)  // 但在 Swagger 文档中显示
private String internalField;

5.3 日期格式问题

问题:日期字段在文档中显示不友好。

解决方案:

java 复制代码
@Schema(description = "创建时间", example = "2024-01-01 12:00:00",
        type = "string", format = "date-time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;

或者全局配置:

yaml 复制代码
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

5.4 泛型类型在文档中显示为 Object

问题 :Result<User> 在文档中显示为 Result<Object>。

解决方案:

java 复制代码
@Operation(summary = "查询用户")
@ApiResponse(responseCode = "200", description = "成功",
             content = @Content(schema = @Schema(implementation = UserResult.class)))
public Result<User> getUser(@PathVariable Long id) {
    // ...
}

// 创建一个具体的返回类型
@Schema(description = "用户查询结果")
class UserResult extends Result<User> {
}

或者使用:

java 复制代码
@Schema(implementation = User.class)

5.5 SpringBoot 3.x 兼容性问题

问题:Spring Boot 3.x 使用 Springfox 报错。

解决方案:Spring Boot 3.x 建议使用 SpringDoc,不再使用 Springfox。

xml 复制代码
<!-- Spring Boot 3.x 推荐使用 -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.0.2</version>
</dependency>

第六章:最佳实践

6.1 注解书写规范

好的写法:

java 复制代码
@Operation(
    summary = "创建订单",  // 简短描述,一句话说清楚
    description = "用户下单接口,支持多商品下单,会进行库存校验和价格计算"  // 详细说明
)
@Parameter(
    name = "userId",
    description = "用户ID",
    required = true,
    example = "10001"  // 一定要给示例值
)

不好的写法:

java 复制代码
@Operation(summary = "创建订单")  // 太简单,看不懂干什么的
@Parameter(name = "userId")  // 缺少必要信息

6.2 统一返回格式

建议使用统一的返回格式:

java 复制代码
@Schema(description = "统一返回结果")
public class Result<T> {

    @Schema(description = "状态码,200表示成功", example = "200")
    private Integer code;

    @Schema(description = "返回消息", example = "操作成功")
    private String message;

    @Schema(description = "返回数据")
    private T data;

    @Schema(description = "时间戳", example = "1640000000000")
    private Long timestamp;

    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage("操作成功");
        result.setData(data);
        result.setTimestamp(System.currentTimeMillis());
        return result;
    }

    public static <T> Result<T> error(String message) {
        Result<T> result = new Result<>();
        result.setCode(500);
        result.setMessage(message);
        result.setTimestamp(System.currentTimeMillis());
        return result;
    }

    // getter/setter
}

6.3 合理使用 example

example 很重要,它能让前端同学一眼看懂参数格式:

java 复制代码
// 不好的例子
@Schema(description = "手机号")
private String phone;

// 好的例子
@Schema(description = "手机号", example = "13800138000", pattern = "^1[3-9]\\d{9}$")
private String phone;

6.4 环境隔离

生产环境不要开启 Swagger,会有安全风险:

方法一:配置文件控制

yaml 复制代码
# application-prod.yml
springdoc:
  api-docs:
    enabled: false
  swagger-ui:
    enabled: false

方法二:代码控制

java 复制代码
@Configuration
@Profile({"dev", "test"})  // 仅在开发和测试环境启用
public class SwaggerConfig {
    // 配置
}

6.5 版本管理

接口升级后,可以通过版本号来管理:

java 复制代码
@RestController
@RequestMapping("/api/v1/users")
@Tag(name = "用户管理 V1")
public class UserControllerV1 {
    // V1 版本接口
}

@RestController
@RequestMapping("/api/v2/users")
@Tag(name = "用户管理 V2", description = "V2版本,新增了更多字段")
public class UserControllerV2 {
    // V2 版本接口
}

第七章:实际项目案例

来看一个完整的电商项目接口示例:

7.1 商品查询接口

java 复制代码
@RestController
@RequestMapping("/api/products")
@Tag(name = "商品管理", description = "商品的增删改查接口")
public class ProductController {

    @GetMapping("/{id}")
    @Operation(
        summary = "查询商品详情",
        description = "根据商品ID查询商品的详细信息,包括库存、价格、评价等"
    )
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "查询成功"),
        @ApiResponse(responseCode = "404", description = "商品不存在",
                     content = @Content(schema = @Schema(implementation = Result.class)))
    })
    public Result<ProductDetailVO> getProductDetail(
            @Parameter(description = "商品ID", required = true, example = "10001")
            @PathVariable Long id) {

        // 模拟业务逻辑
        ProductDetailVO product = new ProductDetailVO();
        product.setId(id);
        product.setName("iPhone 15 Pro");
        product.setPrice(new BigDecimal("7999.00"));
        product.setStock(100);
        product.setStatus(ProductStatus.ON_SALE);

        return Result.success(product);
    }

    @GetMapping("/search")
    @Operation(summary = "搜索商品", description = "支持按分类、价格区间、关键词搜索商品")
    public Result<PageResult<ProductListVO>> searchProducts(
            @Parameter(description = "关键词", example = "iPhone")
            @RequestParam(required = false) String keyword,

            @Parameter(description = "分类ID", example = "1")
            @RequestParam(required = false) Long categoryId,

            @Parameter(description = "最低价格", example = "1000")
            @RequestParam(required = false) BigDecimal minPrice,

            @Parameter(description = "最高价格", example = "10000")
            @RequestParam(required = false) BigDecimal maxPrice,

            @Parameter(description = "排序方式", example = "PRICE_ASC",
                      schema = @Schema(implementation = SortType.class))
            @RequestParam(defaultValue = "CREATE_TIME_DESC") SortType sortType,

            @Parameter(description = "页码", example = "1")
            @RequestParam(defaultValue = "1") Integer pageNum,

            @Parameter(description = "每页数量", example = "20")
            @RequestParam(defaultValue = "20") Integer pageSize) {

        // 业务逻辑
        PageResult<ProductListVO> result = new PageResult<>();
        result.setTotal(100L);
        result.setPageNum(pageNum);
        result.setPageSize(pageSize);
        result.setList(new ArrayList<>());

        return Result.success(result);
    }
}

7.2 订单创建接口

java 复制代码
@RestController
@RequestMapping("/api/orders")
@Tag(name = "订单管理", description = "订单相关接口")
public class OrderController {

    @PostMapping
    @Operation(summary = "创建订单", description = "用户下单接口,支持多商品购买")
    @ApiResponse(responseCode = "200", description = "下单成功",
                 content = @Content(schema = @Schema(implementation = OrderResult.class)))
    public Result<OrderVO> createOrder(
            @io.swagger.v3.oas.annotations.parameters.RequestBody(
                description = "订单信息",
                required = true,
                content = @Content(
                    schema = @Schema(implementation = CreateOrderDTO.class),
                    examples = @ExampleObject(
                        name = "订单示例",
                        value = """
                            {
                              "userId": 10001,
                              "items": [
                                {
                                  "productId": 20001,
                                  "quantity": 2
                                },
                                {
                                  "productId": 20002,
                                  "quantity": 1
                                }
                              ],
                              "addressId": 30001,
                              "remark": "请在工作日配送"
                            }
                            """
                    )
                )
            )
            @RequestBody @Valid CreateOrderDTO orderDTO) {

        // 业务逻辑
        OrderVO order = new OrderVO();
        order.setOrderId(System.currentTimeMillis());
        order.setTotalAmount(new BigDecimal("15999.00"));
        order.setStatus(OrderStatus.待付款);

        return Result.success(order);
    }
}

7.3 实体类定义

java 复制代码
@Schema(description = "商品详情")
public class ProductDetailVO {

    @Schema(description = "商品ID", example = "10001")
    private Long id;

    @Schema(description = "商品名称", example = "iPhone 15 Pro 256GB 黑色")
    private String name;

    @Schema(description = "商品价格", example = "7999.00")
    private BigDecimal price;

    @Schema(description = "库存数量", example = "100")
    private Integer stock;

    @Schema(description = "商品状态", example = "ON_SALE")
    private ProductStatus status;

    @Schema(description = "商品图片", example = "['http://example.com/img1.jpg', 'http://example.com/img2.jpg']")
    private List<String> images;

    @Schema(description = "商品描述")
    private String description;

    @Schema(description = "创建时间", example = "2024-01-01 12:00:00")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    // getter/setter 省略
}

@Schema(description = "创建订单请求")
public class CreateOrderDTO {

    @Schema(description = "用户ID", required = true, example = "10001")
    @NotNull(message = "用户ID不能为空")
    private Long userId;

    @Schema(description = "订单商品列表", required = true)
    @NotEmpty(message = "商品列表不能为空")
    @Valid
    private List<OrderItemDTO> items;

    @Schema(description = "收货地址ID", required = true, example = "30001")
    @NotNull(message = "收货地址不能为空")
    private Long addressId;

    @Schema(description = "订单备注", example = "请在工作日配送")
    private String remark;

    // getter/setter
}

@Schema(description = "订单商品项")
public class OrderItemDTO {

    @Schema(description = "商品ID", required = true, example = "20001")
    @NotNull(message = "商品ID不能为空")
    private Long productId;

    @Schema(description = "购买数量", required = true, example = "2", minimum = "1")
    @Min(value = 1, message = "数量至少为1")
    private Integer quantity;

    // getter/setter
}

@Schema(description = "排序方式")
public enum SortType {
    @Schema(description = "按创建时间倒序")
    CREATE_TIME_DESC,

    @Schema(description = "按价格升序")
    PRICE_ASC,

    @Schema(description = "按价格降序")
    PRICE_DESC,

    @Schema(description = "按销量降序")
    SALES_DESC
}

第八章:总结与建议

8.1 我的使用心得

用了这么久 Swagger,总结几点经验:

1. 养成好习惯:写接口的时候顺手加上注解,别等到最后再补,那时候你会忘记很多细节。

2. 注解要详细 :@Operation 的 description 多写几句话不会死,但能让看文档的人少问你几个问题。

3. example 很重要:每个字段都写上示例值,前端联调的时候会感谢你的。

4. 生产环境记得关:别让你的接口文档暴露在公网上,这是安全事故。

5. 定期检查文档:代码改了,文档注解也要跟着改,别让文档和实际不符。

8.2 SpringDoc vs Springfox

如果你现在要选,我建议:

  • 新项目:直接用 SpringDoc,配置简单,支持更好
  • 老项目:Spring Boot 2.x 可以继续用 Springfox,Spring Boot 3.x 建议迁移到 SpringDoc
  • 学习目的:两个都了解一下,因为你遇到的老项目可能还在用 Springfox

8.3 不是所有接口都要写文档

有些内部接口、调试接口不需要暴露出来,用 @Hidden 隐藏即可。文档要精简,太多无用接口反而让人找不到重点。

8.4 文档只是辅助

虽然 Swagger 很方便,但它不能代替:

  • 代码注释:复杂的业务逻辑还是要写清楚
  • 沟通:前后端有疑问还是要沟通,别完全依赖文档
  • 测试:文档能测试接口,但不能保证业务逻辑正确

8.5 扩展阅读

相关推荐
ZealSinger几秒前
Java虚拟线程上线后瓶颈转移到哪看三层
java·开发语言·firefox
2601_961133286 分钟前
盘立方软件指标文华期货公式下载
java
蜗牛互联网13 分钟前
长任务多Agent共享文件系统的Manifest交接模式
java·人工智能·后端
Sirens.35 分钟前
Java并发锁详解:六类锁策略与 synchronized 底层原理
java·前端·算法
sunshine22 girl1 小时前
Java学习五 面向对象高级5 内部类1
java·学习
weixin_440730502 小时前
函数return、参数、参数组、递归函数、高阶函数等小结
开发语言·python
苏supper2 小时前
Spring Boot多环境配置与打包运行
spring boot·后端·spring
sugarzhangnotes2 小时前
【无标题】
开发语言·人工智能·python
geovindu2 小时前
rust: Composite Pattern
开发语言·后端·设计模式·rust·组合模式
用户1305180712153 小时前
JDBC学习DAY2:从Statement到PreparedStatement
java