前言
为什么非要写这个博客呢?因为这件事让我有一种蛋蛋的优疼。剩下的都别问,反正问我也不会说。因为流程图我都不想(懒得)画。
开发架构
- 前端页面:Vue
- 网络请求:Axios;方式:vue add axios
- 缓存方案
- 全局变量:Vuex
- 本地缓存:LocalStorage
技术依赖
你猜
背景
公司开发一个嵌入App的Web页面,安全方面使用老套路:App通过URL传参给前端(包含签名),前端把参数透传给H5后端验签,完事儿之后前端再决定用户是否合法。另外定义了N个JS方法前端根据固定GET参数判断是安卓还是苹果来调用。
初步设想
关于token设计方案的初步设想是这样的:第一次进入的时候获取token,后端检查签名是否通过。不通过则弹框请从合法途径进入页面并且不消失。
否则就可以让用户继续后续操作,直到后端返回token过期特定状态码回来前端在用户无感的情况下调用JS方法重新获取URL参数请求token,完事儿之后继续用户的请求操作。(为避免用户使用旧token在其他地方操作数据,每次获取token都重新从App中获取并验证,而不是在接口中刷新并返回新的token)
蛋疼事项
- 一期的时候定义URL参数时没有版本控制,导致二期新增JS方法迭代版本时前端新增页面调用了未知方法页面毫无反应;埋点数据也不知道是几期的…
- 为尽量避免请求过程中出现token过期导致的1次请求变3次请求现象每次调用请求之前需要先检查token时效的异步方法(如果token过期则调用getToken获取新的token并存储在本地)导致block嵌套。
- 后面又封装了N个方法就不说了…
升级设想
版本什么的这个先不说,就这个token问题我总不能每次新增一个请求就复制粘贴复制粘贴的吧?能烦死人!那我只能在axios请求之前判断token时效性啦。
直奔主题
-
函数声明
- getToken:从本地取已存储token
- checkToken:检查token时效,失效调用refreshToken函数成功则存储本地,否则返回错误原因
- refreshToken:调用JS方法从App获取签名参数重新请求token
-
注意事项
- 在checkToken过程中token过期时,先移除本地已过期token缓存数据。
/* eslint-disable no-console */
/* eslint-disable no-unused-vars */
"use strict";
import Vue from 'vue';
import axios from "axios";
import { getToken } from '../utils/storage.js'
import { checkToken, refreshToken, clearCache } from "../utils/utils.js";
// Full config: https://github.com/axios/axios#request-config
// axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || '';
// axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.headers.post["Content-Type"] = "application/json";
let cancel,
promiseArr = {};
let config = {
baseURL: process.env.VUE_APP_BASE_URL,
timeout: 8 * 1000, // Timeout
withCredentials: true, // Check cross-site Access-Control
};
const _axios = axios.create(config);
_axios.interceptors.request.use(
function (config) {
// Do something before request is sent
let token = getToken();
// alert("token1:" + token);
//发起请求时,取消掉当前正在进行的相同请求
if (promiseArr[config.url]) {
promiseArr[config.url]("请稍后");
promiseArr[config.url] = cancel;
} else {
promiseArr[config.url] = cancel;
}
if (token) {
return checkToken(null)
.then((result) => {
// console.log("refreshToken result:", result);
if (result === true) {
token = getToken()
// alert("token2:" + token);
config.headers.common["authorization"] = token;
return config;
} else {
return Promise.reject(Error(result))
}
}).catch((err) => {
// 终止这个请求
return Promise.reject(err);
});
}
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
// Add a response interceptor
_axios.interceptors.response.use(
function (response) {
// Do something with response data
let { status, statusText, data } = response;
if (err_check(status, statusText, data) && data) {
// var randomColor = `rgba(${parseInt(Math.random() * 255)},${parseInt(
// Math.random() * 255
// )},${parseInt(Math.random() * 255)})`;
// console.log(
// "%c┍------------------------------------------------------------------┑",
// `color:${randomColor};`
// );
// console.log("| 请求地址:", response.config.url);
// console.log("| 请求参数:", response.config.data);
// console.log("| 返回数据:", response.data);
// console.log(
// "%c┕------------------------------------------------------------------┙",
// `color:${randomColor};`
// );
if (data.resCode === "0001") {
clearCache()
var config = response.config;
var url = config.url;
url = url.replace("/apis", "").replace(process.env.VUE_APP_BASE_URL, "")
config.url = url;
// alert(JSON.stringify(config))
return refreshToken(null)
.then((result) => {
// console.log("refreshToken result:", result);
if (result == true) {
let token = getToken()
if (token) {
config.headers["authorization"] = token;
}
return axios(config)
.then((result) => {
let { status, statusText, data } = result;
// console.log('接口二次请求 result:', result);
if (err_check(status, statusText, data) && data) {
return Promise.resolve(data)
} else {
return Promise.reject(Error(data.resDesc));
}
}).catch((err) => {
// console.log('接口二次请求 err:' + err);
return Promise.reject(err);
});
} else {
// alert("result:" + result)
return Promise.reject(Error(data.resDesc))
}
}).catch((err) => {
// 终止这个请求
// alert("终止这个请求:" + err.message)
// console.log("refreshToken err:", err);
return Promise.reject(err);
});
} else {
return Promise.resolve(data);
}
} else {
return Promise.reject(Error(statusText));
}
// return response;
},
function (error) {
// Do something with response error
// console.log("error", error);
return Promise.reject(error);
}
);
// eslint-disable-next-line no-unused-vars
const err_check = (code, message, data) => {
if (code == 200) {
return true;
}
return false;
};
Plugin.install = function (Vue, options) {
Vue.axios = _axios;
window.axios = _axios;
Object.defineProperties(Vue.prototype, {
axios: {
get() {
return _axios;
}
},
$axios: {
get() {
return _axios;
}
},
});
};
Vue.use(Plugin)
export default Plugin;