拦截器分两类:请求拦截器和响应拦截器
一、请求拦截器
在请求发出之前设置一些信息。比如说设置请求头,
在use方法参数即第一个函数的形参中通过config来做信息的配置,配置完之后,必须把config返回,这样才能完成拦截器的功能。第二个函数用于处理错误的信息。
用拦截器的方式配置请求头会更加灵活。拦截器中可以通过config获取更多的信息,比如url地址,这样就可以根据url作出判断,某些url可以添加一个请求头,而别的url不添加请求头。
通过拦截器,我们可以控制所有的请求。
下面来分析项目中的请求拦截器
// request拦截器 service.interceptors.request.use( config => { if (store.getters.token) { config.headers['Authorization'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } // params参数编码 let url = config.url; console.log(url) if (config.params) { console.log(config.params) url += '?'; let keys = Object.keys(config.params); console.log(keys) for (let key of keys) { if (config.params[key] !== null && config.params[key] !== "") { url += `${key}=${encodeURIComponent(config.params[key])}&`; } } console.log(url) url = url.substring(0, url.length - 1); console.log(url) config.params = {}; } config.url = url; return config; }, error => { console.log(error) // for debug Promise.reject(error) } )
下面来分析代码:
medMaterial.getAuditorsByPrepareId({
prepareId: edit.id,
productId: edit.productId,
enterpriseId: edit.enterpriseId1
}).then(res => {
if (res.success) {
.........
js中的代码为:
getAuditorsByPrepareId(query){ return request({ url: '/medMaterial/getAuditorsByPrepareId', method: 'post', params: query }) },
后台:
@PostMapping("/getAuditorsByPrepareId") public Result getAuditorsByPrepareId(@RequestParam(required = false) String productId,@RequestParam String enterpriseId,@RequestParam String prepareId) { 。。。。。 }
当我们发出上面的一个请求,请求拦截器就会将参数的值进行编码,然后将参数通过?号拼接到url中。拼接后的url如下所示:
http://localhost:8008/api/medMaterial/getAuditorsByPrepareId?prepareId=444&productId=20663&enterpriseId=81
1、先判断token是否存在,如果存在,让每个url都添加请求头Authorization
2、通过config获取url:/medMaterial/getAuditorsByPrepareId
3、通过url获取params:{prepareId: 444, productId: 20663, enterpriseId: 81}
4、先给url拼接一个?号:/medMaterial/getAuditorsByPrepareId?
5、获取config中参数params中所有的key即keys:["prepareId", "productId", "enterpriseId"]
6、遍历keys,如果params中key的值不为null或不为空字符串,先对key的值进行encodeURIComponent编码,编码后的结果放入${}中,key不编码。由于该方法encodeURIComponent不会对 ASCII 字母和数字进行编码,故对value进行编码后的结果url仍然为:/medMaterial/getAuditorsByPrepareId?prepareId=444&productId=20663&enterpriseId=81&
7、去掉最后的&得到的url:/medMaterial/getAuditorsByPrepareId?prepareId=444&productId=20663&enterpriseId=81
再比如备货详情中,点击查看按钮,发送的请求如下:
const query = { type: row.type,materialName: row.materialName,materialCode: row.materialCode,batch: row.originalBase } console.log(query) prepare.queryFullChain(query).then(response => { console.log(response) if(response.success){
js中的代码如下:
queryFullChain(query) { return request({ url: '/prepare/queryFullChain', method: 'post', params: query }); },
1、先判断token是否存在,如果存在,让每个url都添加请求头Authorization
2、通过config获取url:/prepare/queryFullChain
3、通过url获取params:{type: "0", materialName: "天麻", materialCode: "TM001", batch: "11"}
4、先给url拼接一个?号:/prepare/queryFullChain?
5、获取config中参数params中所有的key即keys:["type", "materialName", "materialCode", "batch"]
6、遍历keys,如果params中key的值不为null或不为空字符串,先对key的值进行encodeURIComponent编码,编码后的结果放入${}中,key不编码。由于该方法encodeURIComponent不会对 ASCII 字母和数字进行编码,故对value进行编码后的结果url为:/prepare/queryFullChain?type=0&materialName=%E5%A4%A9%E9%BA%BB&materialCode=TM001&batch=11&,注意对中文进行了编码。
7、去掉最后的&得到的url:/prepare/queryFullChain?type=0&materialName=%E5%A4%A9%E9%BA%BB&materialCode=TM001&batch=11
以上分析可知:当在向后台发起请求时,config中存在params时,参数会被拼接到url中,后台只能用@RequestParam来接收参数。
当向后台发起请求时,没有params,而是data,如下所示:
saveList(query) { return request({ url: '/intelDrugStore/saveList', method: 'post', data: query }) },
请求参数在JSON中,故后台只能用@RequestBody接收参数。
后台
@PostMapping("/saveList") @ResponseBody public Result saveList(@RequestBody List<IntelDrugStore> listData) { 。。。。 }
二、响应拦截器
浏览器在获取响应数据之前对数据做一些加工处理。
Use方法的第一个参数即第一个函数的形参res表示后台返回的具体数据信息。res并不是实际的数据,而是axios包装的对象,通过对象中的data才能拿到数据。这和之前获取后台数据的时候是一样的。
如果在调用接口的时候,只关心实际的数据而不需要包装对象,故可以集中的在响应拦截器做一些加工。如下所示
这样再次发请求调接口的时候,最终拿到的就是实际的数据了。
这样的话,以后我们调用任何接口,所有then当中得到的数据,都是我们实际需要的后台返回来的数据。不需要再通过点data的方式获取数据了。
项目中的响应拦截器如下:
// response 拦截器 service.interceptors.response.use( response => { /** * code为非20000是抛错 可结合自己业务进行修改 */ const headers = response.headers // 此类为下载流文件,不拦截 if (headers['content-type'] === 'application/octet-stream;charset=utf-8') { return response } if (headers['content-type'] === 'arrayBuffer;charset=UTF-8') { return response } const res = response.data if (res.code !== 1) { if(res.code === 301){ Message({ message: res.msg, type: 'error', duration: 5 * 1000 }) location.reload() } // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; if (res.code === 50008 || res.code === 50012 || res.code === 50014) { MessageBox.confirm( '你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' } ).then(() => { store.dispatch('FedLogOut').then(() => { location.reload() // 为了重新实例化vue-router对象 避免bug }) }) } return Promise.reject('请重新登录') } else { return response.data } }, error => { console.log('err' + error) // for debug Message({ message: '请求超时,请联系管理员!', type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } )
响应拦截器代码分析:
1、由于后台EasyPOIUtils在下载Excel时,设置了响应头,如下:
response.setHeader("Content-type", "text/html;charset=UTF-8"); response.setCharacterEncoding("utf-8");//设置编码集,文件名不会发生中文乱码 response.setContentType("application/force-download");// response.setHeader("content-type", "application/octet-stream"); response.addHeader("Content-Disposition", "attachment;fileName=" + new String(fileName.getBytes(), "utf-8"));// 设置文件名 response.addHeader("Content-Length", "" + file.length()); response.setHeader("Access-Control-Allow-Origin", "*");
故响应拦截器不拦截。
2、由于在下载图片时,PtsFileController中设置了响应头,
@RequestMapping(value = "/picDownload") public void picDownload( String filePath,HttpServletResponse response)throws Exception{ File file = new File(filePath); ServletOutputStream out = null ; BufferedInputStream buf = null ; response.setHeader("Content-Type","arrayBuffer"); try{ buf = new BufferedInputStream(new FileInputStream(file)); out = response.getOutputStream(); byte[] buffer = new byte[1024]; while(buf.read(buffer) != -1){ out.write(buffer); } out.flush(); }catch (Exception e){ e.printStackTrace(); }finally { if(buf!= null){ buf.close(); } if(out != null){ out.close(); } } }
故响应拦截器不拦截。
3、response并不是实际的数据,而是axios包装的对象,通过对象中的data才能拿到数据.
4、由于后台ResultCode类中设置请求成功的code为1,
/** * 请求成功Result */ public static final int SUCCESS=1;
当我们要返回数据给前端时,会设置code为success
result.setSuccess(false); result.setMsg("请修改密码后再登录!"); result.setCode(ResultCode.SUCCESS); return result;
此时res.code等于1,返回response.data给前端。
如果res.code不等于1,由于后台设置了用户登录超时的code为301
/** * 用户未登录或登录超时 */ public static final int NO_LOGIN = 301;
当我们要返回数据给前端时,会设置ResultCode为NO_LOGIN
User u = (User) redisService.get(pre + "TPS-TOKEN" + token); if (null == u) { rs.setSuccess(false); rs.setCode(ResultCode.NO_LOGIN); rs.setMsg("非法登录或者token已失效!"); return rs; }
此时res.code为301,则进行错误消息的提示,type为error,message为后台返回的消息,duration为持续的时长,这里为5s.
location.reload()方法用于刷新当前文档。reload() 方法类似于你浏览器上的刷新页面按钮。