前言:
小伙伴们,大家好,我是狂奔の蜗牛rz,当然你们可以叫我蜗牛君,我是一个学习Java半年多时间的小菜鸟,同时还有一个伟大的梦想,那就是有朝一日,成为一个优秀的Java架构师。
这个SpringBoot基础学习系列用来记录我学习SpringBoot框架基础知识的全过程 (这个系列是参照B站狂神的SpringBoot最新教程来写的,由于是之前整理的,但当时没有发布出来,所以有些地方可能有错误,希望大家能够及时指正!)
之后我将会尽量以一天一更的速度更新这个系列,还没有学习SpringBoot的小伙伴可以参照我的博客学习一下;当然学习过的小伙伴,也可以顺便跟我一起复习一下基础。最后,希望能够和大家一同进步吧!加油吧!少年们!
由于篇幅较长,所以这里我将其分为了上下两篇博客,上篇主要了解Swagger的概念,学习SpringBoot整合Swagger,以及配置Swagger和扫描接口;下篇主要学习Swagger配置是否启动 和 设置Swagger文档注释,以及Swagger在线测试等。
特别提醒: 上篇博客链接:SpringBoot基础学习之整合Swagger框架(上篇))
今天我们来到了SpringBoot基础学习的第九站:整合Swagger框架(下篇)。废话不多说,让我们开始今天的学习内容吧!
9.5 配置是否启动Swagger
9.5.1 编写Swagger配置类和访问页面测试
1.修改Swagger配置类代码
package com.kuang.swagger.config;
/**
* @ClassName SwaggerConfig
* @Description Swagger配置类
* @Author 狂奔de蜗牛rz
* @Date 2021/10/1
*/
// 将SwaggerConfig类注册为配置类
@Configuration
// 开启Swagger2的支持
@EnableSwagger2
public class SwaggerConfig {
// 使用@Bean注解, 将docket注册为IOC容器中的Bean节点
@Bean
public Docket docket() {
// 返回值为新建一个Docket类, 其构造函数的参数为DocumentationType.SWAGGER_2(表示文档类型为Swagger2)
return new Docket(DocumentationType.SWAGGER_2)
// 将apiInfo设置为自定义的
.apiInfo(myApiInfo())
// 是否启用Swagger(默认为true, 表示启用, 这里设置为false, 即不关闭启用)
.enable(false)
// Api的选择器
.select()
// 设置api信息, 传入参数为RequestHandlerSelectors(请求控制器选择器), 配置basePackage信息(扫描指定的基本包路径)
.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
// 构建(设计思想为工厂模式)
.build();
}
// 配置Swagger信息apiInfo
private ApiInfo myApiInfo() {
// 作者的名字, url和邮箱地址
Contact contact = new Contact("狂奔de蜗牛rz","https://mp.csdn.net/?spm=1000.2115.3001.4503","1441505359@qq.com");
// 返回值为创建一个新的ApiInfo类, 设置基本信息
return new ApiInfo("自定义SwaggerApi文档",
"乘风破浪会有时, 直挂云帆济沧海",
"V1.0",
"http://localhost:8080/hello",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<>());
}
}
2.访问页面测试结果
- 可以发现我们再次访问swagger-ui的index.html页面时,并没有出现我们期待的Swagger的API文档信息,取而代之的是页面中间出现 ”没有可以提供的API定义“的粗体字提醒,仔细看页面左上角还出现了一个震惊的emoji表情 (莫名感觉有些可爱),后面紧跟”不能表示n,看到控制台“的提示
9.5.2 设置Swagger只在开发环境使用
1.大致实现思路
如果我们只希望Swagger在开发环境中使用, 而在上线时不使用,应该如何实现呢?
实现思路:
- 判断是否为生产环境 flag = false
- 注入enable(flag)
2.查看项目结构和编写配置文件
2-1 基本项目结构如下
- 在resources文件目录下创建 application-dev.properties 配置文件 和 application-pro.properties配置文件,分别对应开发环境和上线环境
2-2 编写配置文件信息
- 编写application.properties配置文件
# 核心思想: 开发环境(dev)开启, 上线环境(pro)关闭
# 设置处于活动状态的环境为dev(即开发环境)
spring.profiles.active=dev
- 编写application-dev.properties配置文件
# 设置开发环境的端口号为8081
server.port=8081
- 编写application-pro.properties配置文件
# 设置上线环境的端口号为800
server.port=8080
3.Profiles和Environment类源码浅析
3-1 查看Profiles类源码
package org.springframework.core.env;
// 使用@FunctionalInterface注解, 表示其为函数式接口
@FunctionalInterface
public interface Profiles {
/**
* 测试Profiles实例是否与给定的处于活动状态的概要文件谓词相匹配
* @param activeProfiles为一个谓词, 用于测试给定的概要文件是否处于活动状态
*/
boolean matches(Predicate<String> var1);
/**
* of方法
* @param String类型的profiles, 其中 "..."表示可变长参数
* @return 返回一个新建的Profiles类实例
*/
static Profiles of(String... profiles) {
return ProfilesParser.parse(profiles);
}
}
3-2 查看Environment类源码
package org.springframework.core.env;
// Environment(环境)接口, 继承父类PropertyResolver(属性解析器)
public interface Environment extends PropertyResolver {
/**
* 返回的是否一个或者多个处于活动状态的指定的概要文件
* 在没有明确的处于活动状态的概要文件的情况下, 在默认的概要文件的set集合中, 是否包含一个或者多个指定的概要文件
* 如果一个概要文件以"!"开头, 逻辑是颠倒的, 如果指定的概要文件不处于活动状态, 方法将会返回true
* 例如, 使用env.acceptsProfiles("p1", "!p2"), 表示如果概要文件'p1'处于激活状态或者'p2'不处于活动状态, 将返回true
* @throws 如果调用时设置参数为0或者任意的概要文件为nul, 为空, 或者空格, 将抛出IllegalArgumentException(非法参数异常)
* @ param profiles 概要文件, 其类型为String..., 表示可变长的字符串类型
* @ return boolean类型值 true或者false
*/
@Deprecated // 使用@Deprecated注解, 表示当前版本已过时
boolean acceptsProfiles(String... profiles);
/**
* 返回调用getActiveProfiles()方法来获取处于活动状态的概要文件是否匹配指定的谓词Profiles
* @ param profiles 概要文件, 其类型为String..., 表示可变长的字符串类型
* @ return boolean类型值 true或者false
*/
boolean acceptsProfiles(Profiles profiles);
}
4.修改Swagger配置类和测试结果
4-1 编写Swagger配置类实现代码
package com.kuang.swagger.config;
/**
* @ClassName SwaggerConfig
* @Description Swagger配置类
* @Author 狂奔de蜗牛rz
* @Date 2021/10/1
*/
// 将SwaggerConfig类注册为配置类
@Configuration
// 开启Swagger2的支持
@EnableSwagger2
public class SwaggerConfig {
// 使用@Bean注解, 将docket注册为IOC容器中的Bean节点
@Bean
public Docket docket(Environment environment) {
// 设置要显示的Swagger环境(这里设置“dev”和"pro", 分别表示开发和上线环境)
Profiles profiles = Profiles.of("dev","pro");
// 通过environment类的acceptsProfiles()方法来判断是否处于自己所设置的环境中
boolean flag = environment.acceptsProfiles(profiles);
// 返回值为新建一个Docket类, 其构造函数的参数为DocumentationType.SWAGGER_2(表示文档类型为Swagger2)
return new Docket(DocumentationType.SWAGGER_2)
// 将apiInfo设置为自定义的
.apiInfo(myApiInfo())
// 是否启用Swagger(默认为true, 表示启用, 这里设置为false, 即不关闭启用)
// .enable(false)
//判断flag标志位的值, 来确定是否开启Swagger
.enable(flag)
// Api的选择器
.select()
// 设置api信息, 传入参数为RequestHandlerSelectors(请求控制器选择器), 配置basePackage信息(扫描指定的基本包路径)
.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
// 构建(设计思想为工厂模式)
.build();
}
// 配置Swagger信息apiInfo
private ApiInfo myApiInfo() {
// 作者的名字, url和邮箱地址
Contact contact = new Contact("狂奔de蜗牛rz","https://mp.csdn.net/?spm=1000.2115.3001.4503","1441505359@qq.com");
// 返回值为创建一个新的ApiInfo类, 设置基本信息
return new ApiInfo("自定义SwaggerApi文档",
"乘风破浪会有时, 直挂云帆济沧海",
"V1.0",
"http://localhost:8080/hello",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<>());
}
}
4-2 访问页面测试结果
- 可以发现,localhost后面的端口号为8081,即开发环境所对应的端口号
5. 修改活动状态环境为生产环境
5-1 修改配置文件信息
# 核心思想: 开发环境(dev)开启, 上线环境(pro)关闭
# 修改处于活动状态的环境为pro(即上线环境)
spring.profiles.active=pro
5-2 页面访问测试结果
- 可以发现,localhost后面的端口号为8080,即上线环境所对应的端口号
9.5.3 Swagger配置多个API文档分组
1.大致实现思路
- 如何配置多个API文档的分组?
创建多个Docket实例即可, 注意不要重名 !
2.修改Swagger配置类
package com.kuang.swagger.config;
/**
* @ClassName SwaggerConfig
* @Description Swagger配置类
* @Author 狂奔de蜗牛rz
* @Date 2021/10/1
*/
// 将SwaggerConfig类注册为配置类
@Configuration
// 开启Swagger2的支持
@EnableSwagger2
public class SwaggerConfig {
/**
* 配置API文档的多个分组
*/
// 使用@Bean注解, 将docket1注册为IOC容器中的Bean节点
@Bean
public Docket docket1() {
//返回值为创建一个Docket类(其参数文档类型为Swagger2), 设置其组别名为"开发B"
return new Docket(DocumentationType.SWAGGER_2).groupName("开发B");
}
// 使用@Bean注解, 将docket2注册为IOC容器中的Bean节点
@Bean
public Docket docket2() {
//返回值为创建一个Docket类(其参数文档类型为Swagger2), 设置其组别名为"测试1"
return new Docket(DocumentationType.SWAGGER_2).groupName("测试1");
}
// 使用@Bean注解, 将docket3注册为IOC容器中的Bean节点
@Bean
public Docket docket3() {
//返回值为创建一个Docket类(其参数文档类型为Swagger2), 设置其组别名为"测试2"
return new Docket(DocumentationType.SWAGGER_2).groupName("测试2");
}
// 使用@Bean注解, 将docket注册为IOC容器中的Bean节点
@Bean
public Docket docket(Environment environment) {
// 设置要显示的Swagger环境(这里设置“dev”和"pro", 分别表示开发和生产环境)
Profiles profiles = Profiles.of("dev","pro");
//通过environment类的acceptsProfiles()方法来判断是否处于自己所设置的环境中
boolean flag = environment.acceptsProfiles(profiles);
// 返回值为新建一个Docket类, 其构造函数的参数为DocumentationType.SWAGGER_2(表示文档类型为Swagger2)
return new Docket(DocumentationType.SWAGGER_2)
// 将apiInfo设置为自定义的
.apiInfo(myApiInfo())
//设置所属组名为"开发A"
.groupName("开发A")
//判断flag标志位的值, 来确定是否开启Swagger
.enable(flag)
// Api的选择器
.select()
// 设置api信息, 传入参数为RequestHandlerSelectors(请求控制器选择器), 配置basePackage信息(扫描指定的基本包路径)
.apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
// 构建(设计思想为工厂模式)
.build();
}
// 配置Swagger信息apiInfo
private ApiInfo myApiInfo() {
// 作者的名字, url和邮箱地址
Contact contact = new Contact("狂奔de蜗牛rz","https://mp.csdn.net/?spm=1000.2115.3001.4503","1441505359@qq.com");
// 返回值为创建一个新的ApiInfo类, 设置基本信息
return new ApiInfo("自定义SwaggerApi文档",
"乘风破浪会有时, 直挂云帆济沧海",
"V1.0",
"http://localhost:8080/hello",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<>());
}
}
3.访问页面测试结果
3-1 访问"开发A"分组
3-2 访问"开发B"分组
3-3 访问"测试1"分组
3-3 访问"测试2"分组
9.5.4 设置API文档的Models模型
1.项目基本结构
- 在src源文件下的com.kuang.swagger包下创建一个pojo包,创建User实体类
2.编写User实体类
注意:
为了方便演示, 这里User实体类的字段使用了public进行修饰,当然, 真实开发中是不会这么做的。
如果你不太习惯这样,只需要将实体类的字段修改为private进行修饰,然后再生成对应的get和set方法即可!
2-1 User实体类使用public修饰
package com.kuang.swagger.pojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* @ClassName User
* @Description User实体类
* @Author 狂奔de蜗牛rz
* @Date 2021/10/3
*/
public class User {
// 用户名
public String username;
// 密码
public String password;
}
2-2 User实体类使用private修饰
package com.kuang.swagger.pojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* @ClassName User
* @Description User实体类
* @Author 狂奔de蜗牛rz
* @Date 2021/10/3
*/
public class User {
// 用户名
private String username;
// 密码
private String password;
/**
* 生成对应的get和set方法
*/
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// 有参构造函数
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
3.修改Hello前端控制器代码
package com.kuang.swagger.controller;
import com.kuang.swagger.pojo.User;
import org.springframework.web.bind.annotation.*;
/**
* @ClassName HelloController
* @Description Hello前端控制器
* @Author 狂奔de蜗牛rz
* @Date 2021/10/3
*/
// 使用@RestController注解, 实现Controller接口, 使方法返回字符串
@RestController
public class HelloController {
// 使用@GetMapping注解, 设置请求映射路径, 请求方式为get类型
@GetMapping("/hello")
public String hello() {
return "hello";
}
// 使用@PostMapping注解, 设置请求映射路径, 请求方式为post类型
@PostMapping("/addUser")
// 只要在接口的返回值中存在实体类, 就会被扫描到Swagger中去
public User addUser () {
// 返回值为创建一个User对象
return new User();
}
}
4.User实体类中加上API文档注释
4-1 修改User实体类
package com.kuang.swagger.pojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* @ClassName User
* @Description User实体类
* @Author 狂奔de蜗牛rz
* @Date 2021/10/3
*/
// 使用@ApiModel注解, 给实体类加文档注释
@ApiModel("用户实体类")
public class User {
/**
* 用户名
* 使用@ApiModelProperty注解, 给实体类字段加文档注释
*/
@ApiModelProperty("用户名")
private String username;
/**
* 密码
* 使用@ApiModelProperty注解, 给实体类字段加文档注释
*/
@ApiModelProperty("用户名")
private String password;
/**
* 生成对应的get和set方法
*/
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// 有参构造函数
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
4-2 访问页面测试结果
-
访问开发B分组
在页面右上角的选择分组中选中开发B组
-
未加文档注释
可以发现Models模型中的User实体类和字段都没有任何注释
-
加文档注释后
可以发现Models模型中的User实体类和字段都出现了注释
9.6 设置Swagger文档注释
9.6.1 只加上方法注释
1.修改Hello前端控制器代码
package com.kuang.swagger.controller;
/**
* @ClassName HelloController
* @Description Hello前端控制器
* @Author 狂奔de蜗牛rz
* @Date 2021/10/4
*/
// 使用@RestController注解, 实现Controller接口, 使方法返回字符串
@RestController
public class HelloController {
// 使用@GetMapping注解, 设置请求映射路径, 请求方式为get类型
@GetMapping("/hello")
public String hello() {
return "hello";
}
// 使用@ApiOperation, 设置方法的文档注释
@ApiOperation("添加用户信息")
// 使用@PostMapping注解, 设置请求映射路径, 请求方式为post类型
@PostMapping("/addUser")
// 只要在接口的返回值中存在实体类, 就会被扫描到Swagger中去
public User addUser () {
// 返回值为创建一个User对象
return new User();
}
}
2.查看页面访问测试结果
- 可以发现在hello-controller控制器的addUser方法后面加上了“添加用户信息”的注释
9.6.2 加上方法注释和参数注释
1.修改Hello前端控制器代码
package com.kuang.swagger.controller;
/**
* @ClassName HelloController
* @Description Hello前端控制器
* @Author 狂奔de蜗牛rz
* @Date 2021/10/4
*/
// 使用@Api(tags = "XXX")注解, 设置控制器类的文档注释
@Api(tags = "Hello前端控制器类")
// 使用@RestController注解, 实现Controller接口, 使方法返回字符串
@RestController
public class HelloController {
// 使用@ApiOperation, 设置方法的文档注释
@ApiOperation("添加用户信息")
// 使用@PostMapping注解, 设置请求映射路径, 请求方式为post类型
@PostMapping("/addUser")
// 只要在接口的返回值中存在实体类, 就会被扫描到Swagger中去
public User addUser () {
// 返回值为创建一个User对象
return new User();
}
// 使用@ApiOperation, 设置方法的文档注释
@ApiOperation("获取用户信息")
// 使用@GetMapping注解, 设置请求映射路径, 请求方式为get类型, {username}表示占位
@GetMapping("/getUser")
// 使用@ApiParam注解, 设置方法的参数文档注释
public String getUser (@ApiParam("用户名") String username) {
//返回值为"helloXXX"
return "hello"+username;
}
}
2.查看页面访问测试结果
- 可以发现在hello-controller控制器的getUser方法后面加上了“用户信息”的注释,在该方法的参数username后也加上了“用户名”的注释!
9.6.3 加上方法注释和参数以及类注释
1.修改Hello前端控制器代码
package com.kuang.swagger.controller;
import com.kuang.swagger.pojo.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;
/**
* @ClassName HelloController
* @Description Hello前端控制器
* @Author 狂奔de蜗牛rz
* @Date 2021/10/4
*/
// 使用@Api(tags = "XXX")注解, 设置控制器类的文档注释
@Api(tags = "Hello前端控制器类")
// 使用@RestController注解, 实现Controller接口, 使方法返回字符串
@RestController
public class HelloController {
// 使用@ApiOperation, 设置方法的文档注释
@ApiOperation("添加用户信息")
// 使用@PostMapping注解, 设置请求映射路径, 请求方式为post类型
@PostMapping("/addUser")
// 只要在接口的返回值中存在实体类, 就会被扫描到Swagger中去
public User addUser () {
// 返回值为创建一个User对象
return new User();
}
// 使用@ApiOperation, 设置方法的文档注释
@ApiOperation("获取用户信息")
// 使用@GetMapping注解, 设置请求映射路径, 请求方式为get类型, {username}表示占位
@GetMapping("/getUser")
// 使用@ApiParam注解, 设置方法的参数文档注释
public String getUser (@ApiParam("用户名") String username) {
// 返回值为"helloXXX"
return "hello"+username;
}
}
2.查看页面访问测试结果
- 可以发现在Hello Controller控制器前面的hello-controller显示为"Hello前端控制器类"的注释
9.7 Swagger在线测试
9.7.1 获取用户信息测试
1.修改Hello前端控制器
package com.kuang.swagger.controller;
import com.kuang.swagger.pojo.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;
/**
* @ClassName HelloController
* @Description Hello前端控制器
* @Author 狂奔de蜗牛rz
* @Date 2021/10/4
*/
// 使用@Api(tags = "XXX")注解, 设置控制器类的文档注释
@Api(tags = "Hello前端控制器类")
// 使用@RestController注解, 实现Controller接口, 使方法返回字符串
@RestController
public class HelloController {
// 使用@ApiOperation, 设置方法的文档注释
@ApiOperation("获取用户信息")
// 使用@GetMapping注解, 设置请求映射路径, 请求方式为get类型, {username}表示占位
@GetMapping("/getUser/{username}")
// 使用@ApiParam注解, 设置方法的参数文档注释
// 使用@PathVariable注解, 将指定参数绑定到url路径上
public String getUser (@ApiParam("用户名") @PathVariable("username") String username) {
// 返回值为"helloXXX"
return "hello"+username;
}
}
2.测试过程
2-1 进入测试页面
- 首先点击getUser方法后,可以看到出现了一个包含了Parameters参数和Responses响应信息的在线测试页面
2-2 设置测试信息
- 然后点击测试页面右上角的"Try it out “按钮 (即尝试一下),就会进入到调试页面,在Prameters参数中的username(用户名)后面的输入框中输入"周杰伦”,接着点击"Execute"按钮进行测试
3.测试结果
- 最后我们在Responses(响应信息)中可以看到Code状态码为200,在Details(详细信息)中的response body(响应体)中成功显示"hello周杰伦"的信息
9.7.2 添加用户信息测试
1.修改Hello前端控制器
package com.kuang.swagger.controller;
import com.kuang.swagger.pojo.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;
/**
* @ClassName HelloController
* @Description Hello前端控制器
* @Author 狂奔de蜗牛rz
* @Date 2021/10/4
*/
// 使用@Api(tags = "XXX")注解, 设置控制器类的文档注释
@Api(tags = "Hello前端控制器类")
// 使用@RestController注解, 实现Controller接口, 使方法返回字符串
@RestController
public class HelloController {
// 使用@ApiOperation, 设置方法的文档注释
@ApiOperation("添加用户信息")
// 使用@PostMapping注解, 设置请求映射路径, 请求方式为post类型
@PostMapping("/addUser")
// 只要在接口的返回值中存在实体类, 就会被扫描到Swagger中去
// 使用@RequestBody注解, 将JSON格式参数自动绑定到User实体类上
public User addUser (@ApiParam("用户实体类") @RequestBody User user) {
//返回值为user对象
return user;
}
}
2.测试过程
2-1 进入测试页面
- 首先点击addUser方法后,可以看到出现了一个包含了Parameters参数和Responses响应信息的在线测试页面
2-2 设置测试信息
- 然后点击测试页面右上角的"Try it out “按钮 (即尝试一下),就会进入到调试页面,在Prameters参数中的user(用户)后面的"Edit Value”(编辑值)文本域中编写json格式的信息 (注意:编辑值下面的Parameter content type(参数内容类型)为"application/json"),例如 {“password”:“123456”,“username”:“周杰伦”} ,接着点击"Execute"按钮进行测试
3.测试结果
- 最后我们在Responses(响应信息)中可以看到Code状态码为200,在Details(详细信息)中的response body(响应体)中成功显示json格式的用户信息
9.8 Swagger的使用总结
总结:
- 通过使用Swagger将难懂的接口或属性,增加文档注释信息 (防止前后端开发吵架)!
- API接口文档可以实时更新,前端可以及时看到后端的更新信息 (这样后端也没法偷懒)!
- 同时支持在线测试,作用相当于postman,高效便捷!
- Swagger是一个优秀的API文档工具,很多所有大公司都在使用它!
注意:在正式发布项目时,记得关闭Swagger,一方面出于安全考虑,一方面节省内存占用!
好了,今天的有关 SpringBoot基础学习之整合Swagger框架(上篇) 的学习就到此结束啦,欢迎小伙伴们积极学习和讨论,喜欢的可以给蜗牛君点个关注,顺便来个一键三连,我们下期见,拜拜啦!
参考博客链接:https://zhuanlan.zhihu.com/p/40819897 (SpringFox 初体验)
参考视频链接:https://www.bilibili.com/video/BV1PE411i7CV(【狂神说Java】SpringBoot最新教程IDEA版通俗易懂)