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 官方 UIhttp://localhost:8080/swagger-ui.html
  • Knife4j UIhttp://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 的验证属性(如 minLengthminimum)只是用于文档展示,不会真正执行验证 。真正的验证需要用 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 扩展阅读

相关推荐
泥嚎泥嚎2 小时前
【Android】给App添加启动画面——SplashScreen
android·java
Java天梯之路2 小时前
09 Java 异常处理
java·后端
玖剹2 小时前
多线程编程:从日志到单例模式全解析
java·linux·c语言·c++·ubuntu·单例模式·策略模式
一 乐2 小时前
社区养老保障|智慧养老|基于springboot+小程序社区养老保障系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·小程序
emma羊羊2 小时前
【PHP反序列化】css夺旗赛
开发语言·网络安全·php
随机昵称_1234562 小时前
Linux如何从docker hub下载arm镜像
java·linux·arm开发·docker
threelab2 小时前
Merge3D:重塑三维可视化体验的 Cesium+Three.js 融合引擎
开发语言·javascript·3d
liu****2 小时前
16.udp_socket(三)
linux·开发语言·数据结构·c++·1024程序员节
长不大的蜡笔小新2 小时前
掌握NumPy:ndarray核心特性与创建
开发语言·python·numpy