有时候我们需要多系统多数据库共用一个后管平台,让运维人员通过一个入口登录系统管理后台数据。后管系统用的是开源系统–若依,现在我需要对系统做改造,以满足管理多系统多数据库的需求。我们想到了两种方案:配置多数据源和使用代理。
我们选择了使用代理的方式,我记录一下怎么实现的,和实现过程中遇到的问题是如何解决的。
-
配置代理
修改若依原有的service
引入org.mitre.dsmiley.httpproxy
在pom文件中增加依赖
<dependency> <groupId>org.mitre.dsmiley.httpproxy</groupId> <artifactId>smiley-http-proxy-servlet</artifactId> <version>1.7</version> </dependency>
application.yml中增加
canislupus: proxy: servlet_url: /canislupus/* target_url: http://localhost:8883/canislupus
访问/canislupus/* 的请求都会被转发
增加代理类
package com.ruoyi.framework.config.proxy; import java.util.Map; import javax.servlet.Servlet; import org.mitre.dsmiley.httpproxy.ProxyServlet; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.google.common.collect.ImmutableMap; @Configuration public class CanislupusProxyServletConfiguration { @Value("${canislupus.proxy.servlet_url}") private String servletUrl; /** * 读取配置中代理目标地址 */ @Value("${canislupus.proxy.target_url}") private String targetUrl; @Bean public Servlet createProxyServlet(){ // 创建新的ProxyServlet return new ProxyServlet(); } @Bean public ServletRegistrationBean<Servlet> proxyServletRegistration(){ ServletRegistrationBean<Servlet> registrationBean = new ServletRegistrationBean<Servlet>(createProxyServlet(), servletUrl); //设置网址以及参数 Map<String, String> params = ImmutableMap.of( "targetUri", targetUrl, "log", "true"); registrationBean.setInitParameters(params); registrationBean.setName("canislupus"); return registrationBean; } }
如果http://localhost:8883/canislupus 是可以访问的,
访问http://host:port/canislupus 的时候请求会被转发,可以看到如下日志:
canislupus: proxy GET uri: /canislupus/platform/convergepre/list -- http://localhost:8883/canislupus/platform/convergepre/list
-
解决文件导出的问题
若依的文件下载方式是在服务短生成文件,将文件名返回,通过共用的download方法接收文件名来下载文件。当后台是多服务的时候,因为文件生成在不同的机器上无法通过共用的download方法来下载。需要对export方法做改造,是方法返回流而不仅仅是文件名。
@PreAuthorize("@ss.hasPermi('platform:convergepre:export')") @Log(title = "平台列表", businessType = BusinessType.EXPORT) @GetMapping("/export") public void export(TlParamConvergepre tlParamConvergepre,HttpServletResponse response, HttpServletRequest request) { List<TlParamConvergepre> list = tlParamConvergepreService.selectTlParamConvergepreList(tlParamConvergepre); ExcelUtil<TlParamConvergepre> util = new ExcelUtil<TlParamConvergepre>(TlParamConvergepre.class); AjaxResult result = util.exportExcel(list, "convergepre"); String fileName = String.valueOf(result.get("msg")); System.out.println(fileName); String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); String filePath = RuoYiConfig.getDownloadPath() + fileName; response.setCharacterEncoding("utf-8"); response.setContentType("multipart/form-data"); try { response.setHeader("Content-Disposition", "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, realFileName)); FileUtils.writeBytes(filePath, response.getOutputStream()); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
前端下载的时候需要通过接收流的方式来生成文件
if (getToken()) { var token = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } var url = '/dev-api/canislupus/platform/convergepre/export'; console.log(url) axios.get(url,{ responseType: 'blob', //指定reponseType 为blog headers:{'Authorization':token} }).then(res => { let blob = res.data let reader = new FileReader() reader.readAsDataURL(blob) reader.onload = (e) => { let a = document.createElement('a') let fileName = decodeURI(res.headers['content-disposition'].split('fileName=')[1]) a.download = fileName a.href = e.target.result document.body.appendChild(a) a.click() document.body.removeChild(a) } })
上面这个是网上搜到的标准写法。
当使用若依封装的request的时候总是报错,排查了很长时间才发现是response拦截器的问题,因为返回流的时候没有data.code拦截器报错了。
需要把request.js中的拦截器修改一下
// 响应拦截器 service.interceptors.response.use(res => { //下面是增加的 if(res.request.responseType) { console.log(res.request.responseType) if("blob" === res.request.responseType) { return res } } //上面是增加的 const code = res.data.code if (code === 401) { MessageBox.confirm( '登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' } ).then(() => { store.dispatch('LogOut').then(() => { location.reload() // 为了重新实例化vue-router对象 避免bug }) }) } else if (code !== 200) { Notification.error({ title: res.data.msg }) return Promise.reject('error') } else { return res.data } }, error => { console.log('err' + error) Message({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } )
生成的js中也需要把responseType加上
// 导出平台列表 export function exportConvergepre(query) { var req = request({ url: '/canislupus/platform/convergepre/export', method: 'get', responseType: 'blob', params: query }); console.log(req); return req; }
这样就可以实现文件导出了。
以上