SpringBoot集成Swagger
背景
swagger2介绍
swagger官网https://swagger.io/
官方介绍
Simplify API development for users, teams, and enterprises with the Swagger open source and professional toolset
借助Swagger开源和专业工具集,为用户,团队和企业简化API开发
swagger2加分项
没有swagger
swagger最大的作用其实就是生成接口API文档,早在之前没有使用swagger的时候,一般公司的接口API文档都是word的形式去书写,把接口的Api,RequestMethod,params,Result一类的写在表格里。
如果需要调整接口
- 要么接口修改内容了,但是接口文档没做修改导致后续接口文档作为项目参考的话,对后续维护的开发人员们造成困扰
- 要么接口内容修改,修改了接口文档,前后台联调的时候就会出现同一个文件一直传输比较麻烦。
有swagger
- 实时同步api与文档:swagger可以在线自动生成接口文档,后台修改接口的话,接口文档随着项目的启动及时更新。
- 在线测试:wasgger支持在线测试接口,甚至后台可以完全抛弃postman的使用,前台也可以输入数据,测试接口的可通行。
- 多团队API方案:如果是以项目团队以项目线出发的话,swagger可以创建多个Docket以适用不同的项目线。
- 无需过多冗余的word文档:可以解决上边提到的更新的文档来回传输的冗余文档问题。
swagger2扣分项
就个人在学习和使用的过程中,感觉缺点无非就是,在一个比较庞大的项目中如果要引进swagger,那,把所有的Controller、model、params,都加上注解,会是一个比较大的工程。
缺点本人感觉只有一个那就是,除了写注解繁琐一点以外,没什么其他缺点,不过这点缺点来说对于以上提出的有点简直是忽略不计
swagger2注意事项
我们的项目接口肯定是对内不可对外的,由此可知,我们的本地环境可以设置swagger页面地址可访问,生产环境则一定不可以访问这个非常重要。
搭建
搭建SpringBoot项目
此篇不会可以看我的其他文章使用Maven及Spring Initializer快速创建Spring Boot项目
引入依赖
要想使用swagger需要引入以下两个依赖
如果不使用这个可以去下载其他版本(https://mvnrepository.com/)
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
后续补充:在使用过程中发现,使用这个依赖启动swagger的html页面经常报错
AbstractSerializableParameter : Illegal DefaultValue null for parameter type integer
原因是:这是由于实体类使用@ApiModelProperty时,example属性没有赋值导致的,在AbstractSerializableParameter的getExample方法中会将数值属性的example的转换数值类返回,example的默认值是"",因此当example没有赋值时,会出现上面的异常
解决方案pom中应该使用这样的dependency
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.5.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
YML文件
其实swagger是不需要配置yml文件的,我添加的yml文件是为了解决上边提到的swagge2r注意事项
比较简单只有两行
简单理解,本地使用true 线上使用false
原因在下边
swagger:
enable: true
创建文件
其中有提到以上yml文件中加入true和false的原因
package com.xiansheng.swaggerdemo.config;
/**
* @author luck_sheng
* @date 2021/1/26 14:33
* @Version 1.0
* @description Swagger Configuration description
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Value("${swagger.enable}")
private Boolean enable;
//生成环境yml文件可以指定为 false
//生产环境时不可显示swagger接口文档,接口对外不可开放(***重点***)
@Bean
public Docket docketA() {
//团队A
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).groupName("A");
}
@Bean
public Docket docketB() {
//团队B
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).groupName("B");
}
@Bean
public Docket docket(Environment environment) {
//是否在生产环境展示swagger
//方案一
//此方法可以解决项目处于测试环境时,项目要显示swagger,生产环境时不显示swagger
//设置要显示swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//判断当前是否处于该环境
//通过apiInfo(apiInfo()).enable()接收此参数判断是否要显示
boolean b = environment.acceptsProfiles(profiles);
System.out.println(b);
//方案二
//在yml文件中加入
//swagger:
// enable: true
//或者不加,项目则http://localhost:8080/swagger-ui.html访问不到
//swagger:
// enable:
//Configuration类中使用@Value取到值,通过apiInfo(apiInfo()).enable()接收此参数判断是否要显示
System.out.println(enable);
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())//docket实例关联apiInfo
.enable(enable)//配置是否启用swagger,true为启用,false为禁用,禁用则浏览器无法访问swagger-ui.html
.groupName("Test")//配置分组,想要配置几个分组就配置几个docket
.select()
.apis(RequestHandlerSelectors
// .any()//扫描所有,项目中所有的接口都能被扫描到
// .none()//不扫描接口
// .withClassAnnotation(Controller.class)//通过类上的注解扫描,扫描类上的接口
// .withMethodAnnotation(GetMapping.class)//通过方法上的注解扫描,扫描方法上的接口
.basePackage("com.xiansheng.swaggerdemo.controller")//根据包路径扫描接口
)
//过滤
// .paths(PathSelectors
// .ant("/test")//配置如何通过path过滤,这里扫描/test开头的接口
.any()//任何请求都扫描过滤
.regex()//通过正则表达式控制过滤
// )
.build()//工厂模式
;
}
//配置文档信息
private ApiInfo apiInfo() {
Contact contact = new Contact("", "", "");
return new ApiInfo(
"测试API文档",//标题
"XXXX项目的API接口文档",//描述
"1.0",//版本
"urn:tos",//组织链接
contact,//联系人信息
"Apache 2.0",//许可
"http://www.apache.org/licenses/LICENSE-2.0",//许可链接
new ArrayList()//扩展
);
}
}
访问失败原因
如果显示以下页面
token
关于token的问题网上的解决方案就比较多了,我也是照搬过来的代码,所以就不附上解决方案, 因为我还遇到了另外一个比较麻烦的问题,目前这个问题在网上还搜索不到类似的文章和解决方案,等我找到问题的解决方案再做补充
20210906补充
之前遇到的问题就是我们所用到的access_token为url跟的并不是header跟的,关于url的痛点是其他帖子上面没有找到相关的解决方案,基本全部都是url上面的token
后续根据通过文章了解到swagger的参数
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)–> 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType=“Integer”
defaultValue:参数的默认值
由此得知想要使用加入参数为url跟的token需要将 paramType的参数设置为query
附上我现在的全局的token是这样加的
private List<ApiKey> securitySchemes() {
List<ApiKey> apiKeyList = new ArrayList<>();
apiKeyList.add(new ApiKey("access_token", "access_token", "query"));
return apiKeyList;
}
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("^(?!auth).*$"))
.build());
return securityContexts;
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("access_token", authorizationScopes));
return securityReferences;
则new ApiKey中 的query(大部分文章为header)则完美实现token在url上的全局验证,亲测有效
访问失败解决方案
服务对url的拦截解决方案:
@EnableWebMvc
@Slf4j
public class ResponseHandler {
private static final List<String> EXCLUD_PATH = Lists.newArrayList(
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-resources/configuration/ui"
);
@RestControllerAdvice
static class CommonResultResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
if (EXCLUD_METHODS.contains(methodParameter.getMethod().getName())) {
return false;
}
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
String requestPath = serverHttpRequest.getURI().getPath();
log.info("requestPath:{}", requestPath);
if (EXCLUD_PATH.contains(requestPath)) {
return o;
}
}
}
}
spring security对url的拦截解决方案:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/swagger-resources/**",
"/swagger-resources", "/swagger-resources/configuration/security", "/swagger-resources/configuration/security",
"/swagger-ui.html", "/css/**", "/js/**", "/images/**", "/webjars/**", "**/favicon.ico", "/index");
}
}
swagger常用注解
注解 | description |
---|---|
@Api | 应用在请求的控制器类上边表示该类的说明 |
@ApiOperation | 应用在请求的方法上边,说明该方法的说明 |
@ApiParam | 应用在请求的参数前边,说明该参数的定义 |
@ApiImplicitParams | 应用在请求的方法上边 ,包含一组参数{@ApiImplicitParam} |
@ApiImplicitParam | 应用在请求的方法上边,ApiImplicitParams内,表示参数的定义 |
@ApiModel | 应用在实体类上边,表示该类的说明 |
@ApiModelProperty | 应用在实体类的属性上边,表示该属性的说明 |
swagger常用注解的常用参数
注解 | 参数 |
---|---|
@Api | @Api(tags=“Card(证件)”) |
@ApiOperation | @ApiOperation(value=“获取指定用户证件列表”) |
@ApiParam | @ApiParam(name = “id”,value = “主键”,required = true) Integer id |
@ApiImplicitParams | @ApiImplicitParams({}) |
@ApiImplicitParam | @ApiImplicitParam(name = “regId”,value = “用户id”) |
@ApiModel | @ApiModel(description = “证件信息”) |
@ApiModelProperty | @ApiModelProperty(“证件类型”) |
MybatisPlus逆向工程生成Swagger2注解
之前一直使用的都是myabtis的generator逆向生成code的Controller,Service,Mapper,Model部分,后来,后来学习了Mybatis Plus,并且该项技术从学习到使用项目已经维持了大概五个月的时间,已经到了稳定的状态,所以写下这边文章记录一下
官方文档
先讲MP的官方文档拿过来。
Mybtis-Plus官方地址
首先打开官网展示如下,有如下一句话:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
就我个人使用的话,感觉Mybtis-plus确实是在Mybaits的基础之上只做增加不做修改。
MP特性
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操
搭建
依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>Latest Version</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!-- mybatis plus 代码生成器依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 代码生成器模板 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-dts</artifactId>
<version>3.3.1</version>
</dependency>
yml配置只需要简单配置数据库连接即可
自动生成代码
package com.beta.condingmybatisplus.util;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* @author DC
* @date 2021/03/30
* @description mybatis-pus 代码生成器
*/
@Component
public class CodeGenerator {
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
String projectPath = System.getProperty("user.dir");
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setActiveRecord(false)
.setOutputDir(projectPath + ConfigDataSourceConstant.JAVA_LOCATION)//文件生成保存位置
.setAuthor("DC")//作者
.setOpen(false)默认true ,是否打开输出目录
.setServiceName("%sService")
.setDateType(DateType.ONLY_DATE)// 时间策略 默认TIME_PACK
.setServiceImplName("%sServiceImpl")
.setSwagger2(true)//启用swagger2
.setMapperName("%sMapper")
.setXmlName("%sMapper")
.setFileOverride(true)
.setActiveRecord(true)
.setEnableCache(false)// 默认false,是否开启二级缓存
.setBaseResultMap(true)
.setBaseColumnList(false);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl(ConfigDataSourceConstant.DATA_URL)
.setDriverName(ConfigDataSourceConstant.DRIVER_NAME)
.setUsername(ConfigDataSourceConstant.USER_NAME)
.setPassword(ConfigDataSourceConstant.PASS_WORD);
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
//pc.setModuleName(scanner("模块名"));
pc.setParent(ConfigDataSourceConstant.PACKAGE_NAME)
.setEntity(ConfigDataSourceConstant.ENTITY_NAME)
.setService("service")
.setServiceImpl("service.impl");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + ConfigDataSourceConstant.MAPPER_LOCATION +
"/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel)
.setColumnNaming(NamingStrategy.underline_to_camel)
.setEntityLombokModel(true)
.setRestControllerStyle(true)
.setInclude(scanner("表名,多个英文逗号分割").split(","))
.setControllerMappingHyphenStyle(true)
.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
配置参数类
package com.beta.condingmybatisplus.util;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author
* @description 数据源配置
*/
@Component
@Data
public class ConfigDataSourceConstant {
/**
* 数据库来源
*/
public static String DATA_URL = "XXXX";
public static String USER_NAME = "XXXX";
public static String PASS_WORD = "XXXX";
public static String DRIVER_NAME = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
/**
* java文件所在位置
*/
public static String JAVA_LOCATION = "/conding-mybatisplus/src/main/java/";
/**
* mapper 文件所在位置
*/
public static String MAPPER_LOCATION = "/conding-mybatisplus/src/main/resources/xml/";
/**
* 生成项目包的名字
*/
public static String PACKAGE_NAME = "com.newclass";
/**
* 实体名字
*/
public static String ENTITY_NAME = "entity";
}
其中 在自动生成的类中,
.setSwagger2(true)//启用swagger2
这句话便是启动swagger注解的关键
完结 biutifor 撒花