拦截器讲解
前端拦截器
前置拦截器
前置拦截器的代码位于request.js文件中,具体路径为:src/utils/request.js
代码展示如下:
// request拦截器
// 其作用就是有一些功能,在从前端向后端发送数据时进行拦截,将数据进行自定义的处理操作,随后再发送给后端
// 即拦截下后,做一些公共操作
// 这里service就是创建的axios实例,由前面的const service = axios.create部分可知
service.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 1、让每个请求携带自定义token 请根据实际情况自行修改
}
// 2、get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else { // 3、避免重复提交重复请求
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}
}
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
可见request.js文件中有axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
这一句,其作用是对axios发送请求的组件,其含义是在每次发送请求时头部的内容(Content)类型(Type)都设置为此编码格式(application/json;charset=utf-8)
那么前置拦截器实际上做了哪些事清呢?
- 在请求头中添加token(这玩意每个用户都有其token值,相当于权限钥匙串,传到后台后就知道该用户可以完成什么操作)(若依框架是把这个添加token操作放到了公共地方,而不必在每次发请求时都传递一个token,以此提高了代码的利用率)
- get请求映射params参数
- 阻止重复请求重复提交(这里虽进行了处理,但在后台还是会有接口幂等性校验操作,即后端的不信任前端原则)
响应拦截器
其使用是在后台给前台返回数据,前端在渲染页面之前,进行拦截操作。
代码展示如下:
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态(code有值就取值,没值就赋200)
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
store.dispatch('LogOut').then(() => {
location.href = '/index';
})
}).catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
Message({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 601) {
Message({ message: msg, type: 'warning' })
return Promise.reject('error')
} else if (code !== 200) {
Notification.error({ title: msg })
return Promise.reject('error')
} else {
return res.data
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
Message({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
}
)
res实际就是后台给前台返回的数据对象
具体应用如图所示:
{
其中出现code==500 或 401等时,login.vue中就不会有后面的操作,而是会直接进行弹窗,显示出现的错误。
}
后端拦截器
若依框架中的后端拦截器是用于处理一些公共的判断逻辑
防止重复提交拦截器
(此处与前置拦截器中提到的不信任原则相呼应)
代码展示:
/**
* 防止重复提交拦截器
*
* @author ruoyi
*/
@Component
// 首先明确此为一个抽象类
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
{
// 这里为前置方法,即前端给后端发的请求都会经过此拦截器
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
// 判断该请求是否要请求一个资源或方法
if (handler instanceof HandlerMethod)
{
// 因为属于HandlerMethod,因而直接进行强转
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 使用getMethod方法获取到的Method这个方法对象就是指 传来的请求给到某个接口的方法
Method method = handlerMethod.getMethod();
// 看该method方法上是否有注解(仅能查看是否含有RepeatSubmit注解)(RepeatSubmit是若依自定义的)
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
// 若该方法打了RepeatSubmit注解
if (annotation != null)
{
// 此isRepeatSubmit方法会通过使用唯一标识(指定key+url+消息头),存入Redis,并在Redis中进行比对,看时间差是否小于5000ms,以此判断是否重复提交,若重复提交会返回true。
if (this.isRepeatSubmit(request, annotation))
{
AjaxResult ajaxResult = AjaxResult.error(annotation.message());
ServletUtils.renderString(response, JSON.toJSONString(ajaxResult));
return false;
}
}
return true;
}
else
{
// 直接放行(请求静态资源时)
return true;
}
}
/**
* 验证是否重复提交由子类实现具体的防重复提交的规则
*
* @param request
* @return
* @throws Exception
*/
public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
}
上述代码中提到的method就是这里的List方法(即method对象代表所请求的整个方法/接口),其中getAnnotation是用来查看是否含有@RepeatSubmit注解的。(至于为何要新建一个RepeatSubmit注解来防止重新提交,而不对所有方法都进行防重提交操作呢:原因为并非所有的方法都会进行提交操作,如此处的List方法,便仅仅是进行查询操作,因而会新建一个注解来对提交的方法进行拦截)
当preHandle
方法返回false后,通常表示对传递过来的请求进行拦截,并阻止执行后续操作,同时也不会返回响应给前端,使其不会进入到具体的业务逻辑中。