前面咱们给系统加了 “门禁”(登录权限),但新问题来了:前端开发找你要接口文档,你发了个 Excel 表格,里面写着 “/book/add 是加图书的,参数有 bookName、author”—— 结果前端问 “bookName 是必填吗?长度限制多少?返回的 code=200 是成功,那 code=400 具体有哪些情况?需要管理员权限才能调吗?”,一天下来光回复这些问题就没精力写代码了。
这就像奶茶店的菜单只写 “珍珠奶茶”,没写 “是否含糖、可做热饮、价格 15 元”,顾客得反复问店员,效率极低。今天咱们用Knife4j(增强版 Swagger)生成 “活的接口文档”—— 不仅列清参数、返回格式,还标明朝夕相处的权限要求,支持在线试调用,前端看文档就能自己对接,不用再找你 “答疑”。
先掰扯清楚传统 Excel 文档和 Knife4j 的区别,你就懂它的价值了:
传统 Excel 文档:像手写的奶茶菜单,字小、没排版,参数填错了、返回格式变了,得重新发文件,前端可能没及时看,对接时还出错;Knife4j 文档:像奶茶店的电子菜单,字体大、分类清,参数是否必填、长度限制、返回示例全标清,还能 “试喝”(在线调用接口看实际返回),后端改了接口,文档自动更新,前端刷新就看到。企业里前后端对接的核心痛点,一张表帮你对号入座:
| 对接痛点 | 传统 Excel 文档解决方案 | Knife4j 解决方案 |
|---|---|---|
| 接口参数不明确(是否必填) | 反复微信问后端 | 文档标红 “必填”,鼠标悬浮看说明 |
| 不知道接口要什么权限 | 后端口头说 “这个要管理员” | 文档标注 “需要角色:ROLE_ADMIN” |
| 想测试接口要装 Postman | 后端帮前端写测试脚本 | 文档里 “在线调试” 按钮,填参数点一下就调用 |
| 接口返回格式记不住 | 翻聊天记录找后端发的 JSON 示例 | 文档里 “返回示例” 自动显示,含 code 含义 |
Knife4j 是在 Swagger 基础上优化的,支持 Spring Boot,配置简单,还能整合咱们之前的登录权限,步骤分 3 步:加依赖、写配置、加注解。
打开
pom.xml,添加 Knife4j 的 Spring Boot Starter 依赖,注意版本要和咱们的 Spring Boot(2.7.10)匹配:
xml
<!-- Knife4j接口文档(增强版Swagger) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<!-- 版本要和Spring Boot 2.7.x兼容,3.0.3是稳定版 -->
<version>3.0.3</version>
</dependency>
<!-- 解决Swagger和Spring Boot 2.7.x的兼容性问题(必须加) -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.6.6</version>
</dependency>
点 IDEA 右下角 “Import Changes”,依赖下载完,“电子菜单” 的基础就有了。
| 依赖名称 | 作用说明 | 注意点 |
|---|---|---|
| knife4j-spring-boot-starter | 核心依赖,提供文档生成和可视化界面 | 版本必须和 Spring Boot 兼容,2.7.x 用 3.0.3 |
| swagger-models | 解决 2.7.x 版本兼容性问题 | 不加会报 “NoSuchMethodError” 错误 |
在
config包下新建
Knife4jConfig.java,配置文档的基本信息(标题、版本、联系人),还要整合登录权限(避免文档公开访问):
java
运行
package com.example.bookmanage.config;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2 // 启用Swagger核心功能
@EnableKnife4j // 启用Knife4j增强功能(美化界面、在线调试)
@EnableWebSecurity // 整合Spring Security
public class Knife4jConfig extends WebSecurityConfigurerAdapter {
/**
* 配置Knife4j文档的核心信息(标题、版本、联系人等)
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// 1. 配置文档信息
.apiInfo(apiInfo())
// 2. 配置接口扫描范围(只扫描controller包下的接口)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.bookmanage.controller"))
.paths(PathSelectors.any()) // 扫描所有路径的接口
.build();
}
/**
* 定义文档的基本信息(显示在文档首页)
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("图书管理系统接口文档") // 文档标题
.description("包含图书CRUD、用户登录等接口,标注参数、权限、返回格式") // 文档描述
.version("1.0.0") // 接口版本
.contact(new Contact(
"Java实战专栏", // 联系人(团队名)
"https://xxx.com", // 团队链接(可选)
"xxx@xxx.com" // 联系邮箱(可选)
))
.build();
}
/**
* 配置Spring Security:允许访问Knife4j文档(避免被“门禁”拦截)
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// 1. 允许访问Knife4j的所有页面和接口(不然登录前看不了文档)
.antMatchers(
"/doc.html", // 文档首页
"/webjars/**", // 文档依赖的静态资源
"/v2/api-docs", // Swagger核心接口
"/swagger-resources/**" // 文档资源
).permitAll()
// 2. 其他请求按之前的权限规则(需要登录+对应角色)
.anyRequest().authenticated()
.and()
// 3. 开启登录(和之前的Security配置保持一致)
.formLogin().permitAll()
.and()
.logout().permitAll()
.and()
.csrf().disable();
}
}
| 配置模块 | 关键代码 | 作用说明 |
|---|---|---|
| 文档信息 |
apiInfo()设置 title/version/contact | 文档首页显示的基础信息,方便前端识别项目 |
| 接口扫描 |
apis(RequestHandlerSelectors.basePackage("com.example.bookmanage.controller")) | 只扫描 Controller 包,避免扫到非接口类 |
| 安全配置 | 允许
/doc.html等路径匿名访问 | 确保未登录也能看文档(不然前端得先登录,麻烦) |
光有配置还不够,得给 Controller、方法、参数加注解,告诉 Knife4j“这个接口是做什么的、参数要填什么、返回什么”。咱们以
BookController为例,逐个加注解:
java
运行
// BookController类上添加
@Api(tags = "图书管理接口") // tags:给这个Controller的接口分类,显示在文档左侧
@RestController
@RequestMapping("/book")
public class BookController {
// 方法代码...
}
java
运行
// 添加图书方法
@ApiOperation(value = "添加图书", notes = "需要管理员权限,bookName和author不能为空,price必须大于0")
// 标注需要的权限(让前端知道要登录什么角色)
@ApiImplicitParam(name = "角色要求", value = "必须是管理员(ROLE_ADMIN)", required = true, dataType = "String", hidden = false)
@PostMapping("/add")
@PreAuthorize("hasRole('ADMIN')")
public Result<Void> addBook(
@Valid
@RequestBody
@ApiParam(name = "图书信息", value = "包含bookName(书名)、author(作者)、price(价格)、publishTime(出版时间)", required = true)
Book book) {
boolean success = bookService.save(book);
return success ? Result.success() : Result.fail(400, "添加图书失败,再试试?");
}
// 查询所有图书方法
@ApiOperation(value = "查询所有图书", notes = "普通用户和管理员都能访问,返回图书列表")
@ApiImplicitParam(name = "角色要求", value = "普通用户(ROLE_USER)或管理员(ROLE_ADMIN)", required = true, dataType = "String")
@GetMapping("/list")
public Result<List<Book>> getBookList() {
List<Book> bookList = bookService.list();
return Result.success(bookList);
}
// 按作者查询图书方法
@ApiOperation(value = "按作者查询图书", notes = "传入作者名,返回该作者的所有图书,支持模糊查询(需配合前端传参)")
@GetMapping("/listByAuthor")
public Result<List<Book>> getBooksByAuthor(
@ApiParam(name = "author", value = "作者名,默认值是“未知作者”", required = false, defaultValue = "未知作者")
@RequestParam(defaultValue = "未知作者") String author) {
List<Book> bookList = bookService.getBooksByAuthor(author);
return Result.success(bookList);
}
// 按ID删除图书方法
@ApiOperation(value = "按ID删除图书", notes = "只能管理员删除,传入图书ID,成功返回操作成功")
@ApiImplicitParam(name = "角色要求", value = "必须是管理员(ROLE_ADMIN)", required = true, dataType = "String")
@DeleteMapping("/delete/{id}")
@PreAuthorize("hasRole('ADMIN')")
public Result<Void> deleteBook(
@ApiParam(name = "id", value = "图书ID,比如1、2", required = true)
@PathVariable Long id) {
boolean success = bookService.removeById(id);
return success ? Result.success() : Result.fail(400, "删除图书失败");
}
打开
Book.java,给字段加注解,说明每个参数的含义、是否必填、示例值:
java
运行
@Data
@TableName("book")
@ApiModel(value = "Book对象", description = "图书信息实体类,包含书名、作者、价格等")
public class Book {
@ApiModelProperty(value = "图书ID,自增,不用手动传", hidden = true) // hidden=true:文档不显示这个字段(前端不用传)
@TableId(type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "图书名称,必填,长度2-100字", required = true, example = "Java入门到精通", allowableValues = "length[2,100]")
@NotBlank(message = "图书名称不能为空!比如“Java入门到精通”")
@Size(min = 2, max = 100, message = "图书名称长度要在2-100字之间")
private String bookName;
@ApiModelProperty(value = "作者名,必填,长度1-50字", required = true, example = "张三", allowableValues = "length[1,50]")
@NotBlank(message = "作者不能为空!比如“张三”")
@Size(min = 1, max = 50, message = "作者名字长度要在1-50字之间")
private String author;
@ApiModelProperty(value = "图书价格,必填,正数,最多5位整数+2位小数", required = true, example = "59.90", allowableValues = "range(0, 99999.99]")
@NotNull(message = "价格不能为空!比如59.90")
@Positive(message = "价格必须大于0!不能填负数或0")
@Digits(integer = 5, fraction = 2, message = "价格格式不对!最多5位整数+2位小数,比如59.90")
private BigDecimal price;
@ApiModelProperty(value = "出版时间,可选,格式yyyy-MM-dd", required = false, example = "2025-01-15")
@PastOrPresent(message = "出版时间不能是未来的日期!比如今天是2025-10-01,不能填2025-10-02")
private Date publishTime;
}
| 注解名称 | 作用位置 | 核心作用 | 示例参数 |
|---|---|---|---|
@Api(tags="") | Controller 类上 | 给接口分类,显示在文档左侧菜单 |
tags = "图书管理接口" |
@ApiOperation() | 方法上 | 说明方法功能和注意事项 |
value="添加图书", notes="需要管理员权限" |
@ApiParam() | 方法参数上 | 说明参数含义、是否必填、示例值 |
name="id", value="图书ID", required=true |
@ApiModel() | 实体类上 | 说明实体类的用途 |
value="Book对象", description="图书信息实体类" |
@ApiModelProperty() | 实体类字段上 | 说明字段含义、示例、约束 |
required=true, example="Java入门到精通" |
启动项目,访问
http://localhost:8080/doc.html,就能看到 Knife4j 的文档首页,咱们一步步测试它的核心功能:
文档左侧是接口分类(比如 “图书管理接口”),点击展开能看到所有方法(添加图书、查询图书等),每个方法旁标着请求方式(POST/GET/DELETE),像奶茶菜单分 “奶茶类、果茶类”,清晰明了。
| 区域 | 内容 | 作用 |
|---|---|---|
| 顶部导航 | 文档标题、版本、联系人 | 快速了解项目基本信息 |
| 左侧菜单 | 接口分类(图书管理接口)→ 方法列表 | 快速定位要找的接口 |
| 右侧内容区 | 接口详情(参数、返回、示例) | 查看接口的具体信息 |
点击 “添加图书” 接口,右侧会显示 3 个核心部分:
请求参数:列清每个字段(bookName、author 等),是否必填(红色 “必填” 标识)、示例值、约束(比如价格 “最多 5 位整数 + 2 位小数”),前端不用再问 “这个参数要不要填”;请求示例:自动生成 JSON 示例,前端能直接复制用,比如:json
{
"bookName": "Java入门到精通",
"author": "张三",
"price": 59.90,
"publishTime": "2025-01-15"
}
返回示例:显示成功和失败的返回格式(结合咱们之前的 Result 类),比如成功返回:
json
{
"code": 200,
"msg": "操作成功",
"data": null
}
失败返回:
json
{
"code": 400,
"msg": "图书名称不能为空!比如“Java入门到精通”",
"data": null
}
| 详情模块 | 显示内容 | 前端受益点 |
|---|---|---|
| 请求参数(Schema) | 字段名、类型、必填、示例、约束 | 知道要传什么、怎么传才对 |
| 请求示例(Request Example) | 可复制的 JSON 示例 | 不用自己拼参数,直接粘到代码里 |
| 返回示例(Response Example) | 成功 / 失败的 JSON 格式 | 知道怎么解析返回数据,不用猜 code 含义 |
点击 “添加图书” 接口右侧的 “调试” 按钮,会弹出调试面板:
填请求参数(复制请求示例的 JSON,改改 bookName);因为接口需要管理员权限,先点击文档右上角的 “登录” 按钮,输入 admin/123456 登录;点击 “发送” 按钮,就能看到实际返回结果(比如 “操作成功”),前端不用装 Postman,在文档里就能测试接口是否能用。| 调试步骤 | 操作 | 效果 |
|---|---|---|
| 1. 登录 | 点击右上角 “登录”→ 输 admin/123456 | 获取登录会话,有权限调用接口 |
| 2. 填参数 | 粘贴 JSON 示例→修改 bookName | 准备测试数据 |
| 3. 发送请求 | 点击 “发送” 按钮 | 显示实际返回结果(成功 / 失败) |
| 4. 查看响应 | 看 “响应结果” 区域 | 验证接口是否正常返回 |
每个接口的 “接口说明” 里标了 “角色要求”(比如添加图书需要 ROLE_ADMIN),前端能清楚知道 “这个接口要登录管理员账号才能调”,不用试了才发现 403 无权限。
Knife4jConfig里是否配置了允许
/doc.html匿名访问,没配置会被 Spring Security 拦截,像奶茶店菜单藏起来不让顾客看;依赖版本不兼容:Spring Boot 2.7.x 必须用 Knife4j 3.0.x 版本,用 2.x 版本会报 “NoSuchMethodError”,像奶茶用错原料导致味道不对;注解加错地方:
@ApiParam要加在方法参数上,别加在实体类字段上(实体类字段用
@ApiModelProperty),不然文档显示错乱;实体类字段不显示:检查实体类是否加了
@ApiModel和
@ApiModelProperty,没加注解的字段不会显示在文档里,像奶茶菜单漏写 “珍珠” 配料;在线调试 403:调试需要权限的接口时,要先在文档右上角登录,没登录会返回 403,像没买单就想试喝奶茶。