问题起源
使用SpringCloud构建项目时,使用Swagger生成相应的接口文档是推荐的选项,Swagger能够提供页面访问,直接在网页上调试后端系统的接口, 非常方便。最近却遇到了一个有点困惑的问题,演示接口示例如下(原有功能接口带有业务实现逻辑,这里简化了接口):
/**
* @description: 演示类
* @author: Huang Ying
**/
@Api(tags = "演示类")
@RestController
@Slf4j
public class DemoController {
@ApiOperation(value = "测试接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "uid", value = "用户ID", paramType = "query", dataType = "Long")
})
@RequestMapping(value = "/api/json/demo", method = RequestMethod.GET)
public String auth(@RequestParam(value = "uid") Long uid) {
System.out.println(uid);
return "the uid: " + uid;
}
}
问题出在接口参数uid的必填性上,@RequestParam
注解里require默认为true,要求必填,但@ApiImplicitParam
注解里require默认为false,要求非必填,该业务接口在进行功能联调时,uid居然能得到一个null值,按照一般认知习惯@ApiImplicitParam
注解的主要作用是生成接口文档,不应该对@RequestParam
的属性有侵入性才对,目前反馈的bug,让我怀疑@ApiImplicitParam
是不是会侵入@RequestParam
的require属性?
框架选型、版本及主要功能
项目搭建
SpringBoot版本:2.1.6.RELEASE
SpringCloud版本:Greenwich.SR3
业务模块
SpringCloud业务模块使用的swagger:
swagger bootstrap ui 1.9.6 增强swagger ui样式
spring4all-swagger 1.9.0.RELEASE 配置化swagger参数,免去代码开发
业务网关
SpringCloud业务网关使用的swagger:
knife4j 2.0.1 增强swagger ui样式(网关用gateway搭建,swagger使用knife4j-spring-boot-starter依赖,可以聚合业务模块的swagger文档)
此次的范围只针对SpringCloud业务模块,暂时不涉及业务网关的Swagger文档。
测试工具
测试工具目前有两个:
swagger doc:使用浏览器进行访问,如下图:
postman:手动配置接口参数,示例:
案例实战
接口测试1
接口示例如开篇所示,我们先使用如下接口,全部使用默认值,即@ApiImplicitParam的required为false,@RequestParam的required为true:
@ApiOperation(value = "测试接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "uid", value = "用户ID", paramType = "query", dataType = "Long")
})
@RequestMapping(value = "/api/json/demo", method = RequestMethod.GET)
public String auth(@RequestParam(value = "uid") Long uid) {
System.out.println(uid);
return "the uid: " + uid;
}
看swagger的结果:
看postman的结果:
接口测试2
我们修改@ApiImplicitParam的required值为true,@RequestParam不变,重启模块
@ApiImplicitParam(name = "uid", value = "用户ID", paramType = "query", required = true, dataType = "Long")
看swagger的结果:
通过调试浏览器可以发现,为空校验是js完成的,js判断为空后,并未发起请求到后端,这样我们可以认为swagger内@ApiImplicitParam的required参数生效了。
接口测试3
在前面我们使用postman测试接口时,发现参数项是空的,我们加上参数,但不写值测试后,结果让人诧异:
并且无论@ApiImplicitParam的required值如何修改,结果都是一样的,肯定有一个地方是搞错了,导致我们误判。
后来仔细查阅资料,发现是我们对@RequestParam的required参数理解错了,这个required为true的含义是:接口参数名一定要存在,但参数后面有没有值它管不着。拿刚刚的例子来说:
这两个请求是通过的:
localhost:8080/api/json//demo?uid
localhost:8080/api/json//demo?uid=
只有这种请求是不通过的:
localhost:8080/api/json//demo?
小结论
经过上述三个接口的测试场景,我们至少可以明确3点:
- @ApiImplicitParam的required参数不会对@RequestParam的required值造成侵入,它们俩不相关。
- @ApiImplicitParam的required参数会影响swagger doc的js逻辑判断,为空校验是在js层面上完成的。
- @RequestParam的required参数默认情况下只会校验是否有该参数名,不校验它是否有值。
源码剖析
swagger部分
上一节当中提及swagger读取@ApiImplicitParam注解的required参数,最终会体现在js上,通过浏览器F12的追踪,定位到swaggerbootstrapui.js文件上,这里摘抄部分源码:
# 点击发送按钮时,逐行读取参数信息,并提取required参数
paramBody.find("tr").each(function () {
var paramtr=$(this);
var cked=paramtr.find("td:first").find(":checked").prop("checked");
var _urlAppendflag=true;
//that.log(cked)
if (cked){
//如果选中,留意此行的required:paramtr.data("required")信息提取
var trdata={
name:paramtr.find("td:eq(2)").find("input").val(),in:paramtr.data("in"),required:paramtr.data("required"),type:paramtr.data("type"),emflag:paramtr.data("emflag"),schemavalue:paramtr.data("schemavalue")};
//that.log("trdata....")
//that.log(trdata);
//获取key
//var key=paramtr.find("td:eq(1)").find("input").val();
var key=trdata["name"];
//获取value
var value="";
var reqflag=false;
// 后面代码省略
}