目录
1.Swagger依赖导入和基本演示
2.Swagger中继承子类的配置
3.Swagger与拦截器联合使用注意事项
4.Swagger全局token配置和使用
5.Swagger分组进行token配置
6.Swagger多环境配置:开发和线上环境
首先我们先建立一个简单的例子,引入swagger相关的maven依赖,然后生成一个简单的spring boot测试工程,IDE当然是首选的Intelij idea。其中swagger相关依赖如下
io.springfox
springfox-swagger2
2.8.0
io.springfox
springfox-swagger-ui
2.8.0
把工程先建好,我这里偷懒了,使用了Easy Code的插件来完成,我记录了部分步骤以及说明,放上长图如下。
OK,借助插件我们的演示工程就快速的搭建起来了,下面进入正题,看下Swagger的配置文件,就是工程中我手动新增的config文件夹。
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createDocket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.cjt.swaggerdemo"))
.paths(PathSelectors.any())
.build();
}
/**
* 定义api信息
* @return
*/
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
//页面标题
.title("XXX平台API")
//创建人
.contact(new Contact("Cao Jiangtao", "https://blog.csdn.net/u010898329", "[email protected]"))
//版本号
.version("1.0.0")
.build();
}
}
要想项目成功运行起来,要注意以下几点
看下Controller中的配置
/**
* (TbStudent)表控制层
*
* @author Cao Jiangtao
* @since 2020-04-10 16:49:02
*/
@RestController
@RequestMapping(value = "/student", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@Api(description = "学生表操作接口",tags = "学生表操作接口",produces = MediaType.APPLICATION_JSON_UTF8_VALUE,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class TbStudentController {
/**
* 服务对象
*/
@Resource
private TbStudentService tbStudentService;
/**
* 通过主键查询单条数据
*
* @param id 主键
* @return 单条数据
*/
@GetMapping("/selectOne")
@ApiOperation(value = "通过主键查询单条学生数据")
public TbStudent selectOne(String id) {
return this.tbStudentService.queryById(id);
}
}
注意swagger的几个基本注解作用对象。
具体大家可以去网上搜索看看别人的介绍。Swagger常用注解使用详解
看下运行的效果
到这里的话,Swagger与SpringBoot的整合和使用就基本结束了,下面我会讲讲几个不常用,但是比较有用,而且需要注意的场景和细节问题。
在我们实际项目开发中,可能会遇到类似下面的情况,提出一个公共父类来给子类继承。
这种场景其实也很常用,继承也是Java的三大特性之一,让每个子类看起来不那么的臃肿。
这里特别指出,@ApiModel注解中子类要指定父类 ,我之前遇到的问题是,没有指定父类,然后Swagger中打开的时候,父类中的所有属性在子类中均不展示,因此这是个要注意的细节。
关于@ApiModel的注解
先说说我的需求:作者项目中有这样一个需求,拦截所有/api/**的请求,因为是对外提供接口,所以需要拦截验证。
这样的需求下,自然想到了要使用拦截器,但是在使用拦截器的时候遇到了问题:
Response body Unknown response type!!!
这个发生在拦截器拦截之后输出自己定义的错误提示的时候,只要请求都正常,过了拦截器之后,在controller层的自定义输出又都是正常的,遇到这个错误真是百思不得其解,卧槽了好久!!!但是我还得硬着头皮检查代码啊!!!最后还是被我解决了,原来我在拦截器中只定义了response返回体的编码格式为utf-8,但是没有定义返回体response的ContentType,最后把这句加上之后完美解决。
下面贴上我写的拦截器部分代码
public class TokenInterceptor implements HandlerInterceptor {
Logger logger = LoggerFactory.getLogger(TokenInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.debug("-------------->>>>>>>>>>>>>>>>>>>>>>>>>>>>> ----- preHandle ------------->>>>>>>>>>>>>>>>>>>>>>>>>");
Result result;
response.setCharacterEncoding("utf-8");
// 设置返回的数据格式
response.setContentType("application/json;charset=UTF8");
String accessToken = request.getHeader("x-access-token");
if (null != accessToken) {
try {
Map map = JwtUtil.validateToken(accessToken);
if (null != map) {
logger.debug("api map : {}", JSON.toJSONString(map));
// 解析出来的productKey传递到下一页
request.setAttribute("productKey", map.getOrDefault("productKey", null));
return true;
} else {
result = ResultUtil.error(ResultEnum.TOKEN_IS_ERROR.getCode(),ResultEnum.TOKEN_IS_ERROR.getMsg(),null);
}
} catch (Exception e) {
result = ResultUtil.error(ResultEnum.TOKEN_IS_ERROR.getCode(),ResultEnum.TOKEN_IS_ERROR.getMsg(),e.toString());
}
} else {
result = ResultUtil.error(ResultEnum.TOKEN_IS_ERROR.getCode(),ResultEnum.TOKEN_IS_ERROR.getMsg(),null);
}
logger.debug("-------------->>>>>>>>>>>>>>>>>>>>>>>>>>>>> ----- accessToken : {}", accessToken);
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(result));
writer.flush();
writer.close();
logger.debug("-------------->>>>>>>>>>>>>>>>>>>>>>>>>>>>> ----- baseResp : {}", JSON.toJSONString(result));
return false;
}
}
上面代码中的返回响应体的ContentType一定要设置
// 设置返回的数据格式
response.setContentType("application/json;charset=UTF8");
关于Contenttype设置可参考网上类似博客,我贴出常用部分给大家参考。
MediaType,即是Internet Media Type,互联网媒体类型;也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。
类型格式:type/subtype(;parameter)?
type 主类型,任意的字符串,如text,如果是号代表所有;
subtype 子类型,任意的字符串,如html,如果是号代表所有;
parameter 可选,一些参数,如Accept请求头的q参数, Content-Type的 charset参数。
常见的媒体格式类型如下:
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式
以application开头的媒体格式类型:
application/xhtml+xml :XHTML格式
application/xml : XML数据格式
application/atom+xml :Atom XML聚合格式
application/json : JSON数据格式
application/pdf :pdf格式
application/msword : Word文档格式
application/octet-stream : 二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded :
接着上面的需求继续说,token全局配置,这个网上有很多博客,当然笔者也尝试了下,贴出来我的使用过程。
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createDocket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.cjt.swaggerdemo"))
// .paths(PathSelectors.any())
.paths(PathSelectors.regex("^(?!auth).*$"))
.build()
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
/**
* 定义api信息
* @return
*/
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
//页面标题
.title("XXX平台API")
//创建人
.contact(new Contact("Cao Jiangtao", "https://blog.csdn.net/u010898329", "[email protected]"))
//版本号
.version("1.0.0")
.build();
}
private List securitySchemes() {
List apiKeyList= new ArrayList();
apiKeyList.add(new ApiKey("x-auth-token", "x-auth-token", "header"));
return apiKeyList;
}
private List securityContexts() {
List securityContexts=new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("^(?!auth).*$"))
.build());
return securityContexts;
}
List defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List securityReferences=new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
}
按照网上的博客进行的配置,基本就是复制粘贴的,配置好之后确实出现了全局配置token的弹窗。
但是…………
不知道是我太菜了还是我太菜了,请求中我根本就拿不到这里配置的token值。
另外,我的接口不是所有的都要用携带token,比如获取token的接口就不需要token,这种配置方式对我来说也没有什么价值,所以果断弃用了这种方式。如果大家有兴趣使用这种方式,可以去深入研究下。
直接贴代码,主要是swagger的配置文件,注意如果是分组的话,那么groupName这个属性一定要写,不然会有惊喜等着你。
public class Swagger2 {
@Bean
public Docket createDeviceApi() {
//添加head参数start
ParameterBuilder tokenPar = new ParameterBuilder();
List pars = new ArrayList<>();
tokenPar.name("x-access-token").description("令牌").modelRef(new ModelRef("string")).parameterType("header").required(true).build();
pars.add(tokenPar.build());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//为当前包路径
.apis(RequestHandlerSelectors.basePackage("com.cjt.demo.api.device"))
.paths(PathSelectors.any())
.build()
.globalOperationParameters(pars)
.groupName("操作接口-V1.0.0");
}
@Bean
public Docket createOpenApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//为当前包路径
.apis(RequestHandlerSelectors.basePackage("com.cjt.demo.api.open"))
.paths(PathSelectors.any())
.build()
.groupName("开放接口-V1.0.0");
}
//构建 api文档的详细信息函数,注意这里的注解引用的是哪个
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//页面标题
.title("XXXXAPI")
//创建人
.contact(new Contact("CaoJiangtao", "https://me.csdn.net/u010898329", "[email protected]"))
//版本号
.version("1.0.0")
.build();
}
}
注意我这里分了两组,一组开发接口,一组操作接口,而且操作接口中加入了token令牌,解决了上面开发接口不需要令牌的尴尬局面。看下展示效果,右上角会出现下拉选择框
实际项目中有很多多环境需求,可能在dev上需要展示swagger,但是生产prod上就要禁止了。那么swagger是否也支持多环境配置呢,答案是支持的,下面我们看下swagger多环境配置有两种方式。
这个是spring的全局注解,根据指定的profile来加载配置文件
@Configuration
@EnableSwagger2
@Profile({"dev"}) // 注解是在开发环境的时候加载该类
public class Swagger2 {
// 其他配置部分
……
}
public class Swagger2 {
/** Swagger开关配置 */
@Value("${swagger.enable}")
private boolean swaggerEnable;
@Bean
public Docket createOpenApi() {
return new Docket(DocumentationType.SWAGGER_2)
.enable(swaggerEnable)
.apiInfo(apiInfo())
.select()
//为当前包路径
.apis(RequestHandlerSelectors.basePackage("com.cjt.demo.api.open"))
.paths(PathSelectors.any())
.build()
.groupName("开放接口-V1.0.0");
}
//构建 api文档的详细信息函数,注意这里的注解引用的是哪个
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//页面标题
.title("XXXXAPI")
//创建人
.contact(new Contact("CaoJiangtao", "https://me.csdn.net/u010898329", "[email protected]"))
//版本号
.version("1.0.0")
.build();
}
}
在项目中呢,推荐使用方式二,因为我们一般会定制几个不同环境的application.yml配置文件,在不同的环境下,设置开关就OK了