工作上因为前后端联调的问题,项目经理安排我给项目集成一个swagger工具;这个东西我以前在业余时候也是有玩过的,但是不同的是,我当初是在SpringBoot项目中进行的一个集成,我现在所做的项目是一个SpringMVC的项目,各种配置都是在xml文件中做的,虽然我们同样可以使用@Configuration去做一个配置类,以及各种配置也都可以说是类似;(网上很多配置的文章,我这就不再说明了)
但是有一个问题就是:我的项目对请求接口限制了一下后缀,接口统一得使用.htm的后缀,但是swagger的请求是不带后缀的;我相信很多SpringMVC的项目都是有做这类限制的例如:.do、.action等等,这个问题对于集成swagger2还是knife4j都是一样的,网上对于它们集成的文章多的一批,但大多是说集成SpringBoot项目的,罕见的几个SpringMVC项目集成的也没有提到过这个问题;
面对这个问题,我想到了两个解决方案,这里提供给大家:
一、增加过滤器处理URL
这是最直接的一个方案,但是有个风险如果没控制好,可能会对系统原有功能造成影响;
这是我过滤器的代码:
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Map;
/**
* URL增加后缀过滤器
*
* @author xiejinwei
* @date 2021/8/5
*/
@Service
public class URLFilter implements Filter {
private static String PATH = "xxx";
private static String PORT = "8080";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// 获取请求URL
StringBuffer urlBuffer = httpServletRequest.getRequestURL();
// 根据默认请求地址拆分请求
String[] urls = urlBuffer.toString().split(PORT + "/" + PATH + "/" );
// 如果默认请求后还有值,并且请求中不带.说明这是非项目的接口请求
if (urls.length > 1 && !urls[1].contains(".")) {
// 获取请求参数
Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
// 给请求URL增加请求后缀
urlBuffer.append(".htm");
// 如果有请求参数把请求参数也给拼接上
if (parameterMap.size() > 0) {
urlBuffer.append("?");
for (String key : parameterMap.keySet()) {
urlBuffer.append(key).append("=");
for (String value : parameterMap.get(key)) {
// 不为空的参数对参数进行编码
urlBuffer.append(StringUtils.isBlank(value)? "" : URLEncoder.encode(value));
}
urlBuffer.append("&");
}
// 去掉遍历时最后加上的&
urlBuffer = new StringBuffer(urlBuffer.substring(0, urlBuffer.length() - 1));
}
// 根据新的URL重定向转发
httpServletResponse.sendRedirect(urlBuffer.toString());
return;
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
有几个点说明一下:
1.PATH和PORT根据自己的项目来设置;
2.这个判断条件主要是因为我的项目有个默认访问地址,就是 “/” 的请求;然后只用 “.” 而不是直接用接口后缀 “.htm” 是为了放行静态资源 “.js”、“.css” 等;
3.之后的对参数进行编码是因为swagger原来的请求的参数就是已经编码了的,但是到了过滤器这里时已经是解码好了,如果直接进行重定向请求会报404,所以在这对参数进行了编码;(亲测,不知道是不是就我的项目会)
4.在这些之后别忘了在自己的web.xml中增加这个过滤器的配置;
5.在这之后可能个别同学的项目还是可能会有请求404的问题,这种时候就要找找自己项目对controller层包扫描的配置了,一般情况下是只会扫描自己项目controller包路径下的类,但是我们还需要把swagger的请求接口所在的包路径放在扫描目录里,一般情况下,大概是“springfox.documentation.swagger2”这个包路径;
在做完这些之后,我这就可以正常使用集成的swagger了,knife4j也是一样;
在我原来的做法中,是把判断范围缩的很小,只要F12看一下,你们也会发现,swagger的主要请求其实就三个:
“/swagger-resources/configuration/ui”、“/swagger-resources”、“/v2/api-docs”
所以我一开始直接限制了这三个接口进行了URL后缀的增加,但是后续发现如果要进行接口调试的话,它的所有请求都是不带 “.htm” 请求后缀的,所以我后面增大了if中条件的判断范围;如果只是需要查看接口参数,不需要进行调试的同学可以只对上面的三个接口进行后缀的增加操作,这样对项目的影响程度也是会比较小;
二、修改knife4j-vue源码
这个方案看起来很难其实也还好,主要是看你想改到什么程度了,我就只是给请求都加上后缀,其他的都没改动(作为后端开发,我的前端技能还是比较薄弱的,前端大神们可以去用更好的办法去实现);我是把knife4j的源码从GitHub上拉下来改的,swagger的源码我没去看过了,从UI上看的话还是会选择knife4j的吧;
给个GitHub的地址吧:https://github.com/xiaoymin/swagger-bootstrap-ui
看它的README.md中对项目模块的说明还是比较清楚的,当我看到它knife4j-vue的模块名时,我第一想法就是,它做了前后端分离了,前端还是用VUE框架写的(它其实还有一个knife4j-vue-v3的模块,我粗略看它们是一样的);既然它前后端分离了,那我直接抛弃了其他模块,专注改它knife4j-vue的模块;
既然是改请求地址嘛,我第一个想法就是请求拦截,看main.js文件导入了axios,并且有一个response的拦截了,我也就加上request的拦截器,用console.log一直没反应;所以我直接跟着页面渲染创建的顺序一步步走,发现调用初始化信息那几个接口的请求根本没用到axios,而是通过Knife4jAsync.js中自定的SwaggerBootstrapUi.prototype.ajax函数,我在这里增加了一个request拦截器,给请求接口地址增加一个后缀就可以请求到后端了:
SwaggerBootstrapUi.prototype.ajax = function (config, success, error) {
var ajax = DebugAxios.create();
ajax.interceptors.response.use(response => {
var data = response.data;
return data
}, error => {
return Promise.reject(error)
})
// 增加request请求拦截器
ajax.interceptors.request.use(function (config) {
// url不为空的情况下给它增加一个后缀
if(config.url !== undefined) {
config.url = config.url + ".htm";
}
return config
}, function (error) { return Promise.reject(error)
})
ajax.request(config).then(data => {
success(data);
}).catch(err => {
error(err);
})
}
到这里只是对接口文档的配置信息的获取没问题了,但是到了调试时还是会有问题,看Debug.vue中的发送按钮绑定的事件,进行请求时用的也不是main.js文件中导入了axios,而是使用create新建了一个axios去请求;而且还会根据不同的请求类型去调用不同的函数,每个函数中都会使用create新建axios去请求;
DebugAxios.create()
.request(requestConfig)
.then(res => {
this.debugLoading = false;
this.handleDebugSuccess(startTime,new Date(), res);
})
.catch(err => {
this.debugLoading = false;
if (err.response) {
this.handleDebugError(startTime,new Date(), err.response);
} else {
this.$message.error(err.message);
}
});
但是这些函数在组装请求参数的时候对URL都会调用一次this.debugCheckUrl(url)检测URL,所以我在这个函数的返回值上加上了我们的请求后缀 “.htm”,这样所有接口不管是什么请求类型,都会给URL加上后缀;
debugCheckUrl(url){
var checkUrl=url;
try{
var reg=new RegExp('.*?(\{.*?\})$',"ig");
if(reg.test(url)){
var rr=RegExp.$1;
checkUrl=url.replace(rr,"");
}
}catch(e){
//ignore
if(window.console){
console.error(e);
}
}
return checkUrl + ".htm";
}
这个方法有几个注意点:
1.如果是就保持这样前后端分离,要记得在后端做跨越的配置,前端项目中的vue.config.js的devServer.proxy也记得改成自己的后端地址,changeOrigin:true允许跨域也记得开启;
2.对于后端有设置spring.app.web.path的在它的接口调试的页面中的接口地址是默认带上这个path的,所以最好是在main.js里配置上axios.defaults.baseURL=[你后端配置的path],代理换成我这样的,[path]、[IP地址]、[端口]请换成对应项目的;
3.如果不做前后端分离的,直接下面的步骤就免了,直接打包,然后扔回源码中knife4j模块的knife4j-springdoc-ui子模块中,项目集成改过的源码就可以和原来的用法一样了;
devServer: {
watchOptions:{
ignored: /node_modules/
},
proxy: {
"/[path]": {
target: 'http://[IP地址]:[端口]/[path]',
ws: true,
changeOrigin: true,
pathRewrite: {
'^/[path]' : '/'
}
}
}
}
这就是我目前对于这个问题研究出来的解决方案,有不对的地方很欢迎有同学提出来,共同学习、共同进步;有兴趣的同学还可以把knife4j的其他源码都拿来看看,它是开源的项目,包含的解决方案也很多。