import Request from "@/lib/request/index.js"; import { refreshTokenFn } from "@/api/login.js"; import storage from "@/utils/storage.js"; import { md5 } from "@/utils/md5.js"; import Foundation from "@/utils/Foundation.js"; import api from "@/config/api.js"; import uuid from "@/utils/uuid.modified.js"; /** * 无痛刷新token思路(如果不使用无痛刷新token,忽略此处注释) * 看了很多,有个问题一直得不到解决----‘多个接口请求,token失效,如何让获取token只获取一遍’、 * 于是想到了闭包防抖...... * 本方案并不是最佳方案,只是给你们提供一种思路。如果你有完美解决方案,可以分享一下 */ const expireToken = []; // 储存过期的token // 防抖闭包来一波 function getTokenDebounce() { let lock = false; let success = false; return async function () { if (!lock) { lock = true; await refreshTokenFn(storage.getRefreshToken()) .then((res) => { if (res.data.success) { let { accessToken, refreshToken } = res.data.result; storage.setAccessToken(accessToken); storage.setRefreshToken(refreshToken); success = true; lock = false; } else { cleanStorage(); success = false; lock = false; } }) .catch((error) => { cleanStorage(); success = false; lock = false; }); } return new Promise((resolve) => { // XXX 我只能想到通过轮询来看获取新的token是否结束,有好的方案可以说。一直看lock,直到请求失败或者成功 const timer = setInterval(() => { if (!lock) { clearInterval(timer); if (success) { resolve("success"); } else { cleanStorage(); resolve("fail"); } } }, 100); // 轮询时间可以自己看改成多少合适 }); }; } function cleanStorage() { uni.showToast({ title: "你的登录状态已过期,请重新登录", icon: "none", duration: 1500, }); if (uni.showLoading()) { uni.hideLoading(); } storage.setHasLogin(false); storage.setAccessToken(""); storage.setRefreshToken(""); console.log("清空token"); storage.setUuid(""); storage.setUserInfo({}); // #ifdef MP-WEIXIN uni.navigateTo({ url: "/pages/passport/wechatMPLogin", }); // #endif // #ifndef MP-WEIXIN uni.navigateTo({ url: "/pages/passport/login", }); // #endif } let http = new Request(); const refreshToken = getTokenDebounce(); http.setConfig((config) => { // 没有uuid创建 if (!storage.getUuid()) { storage.setUuid(uuid.v1()); } /* 设置全局配置 */ config.baseURL = api.buyer; config.header = { ...config.header, }; config.validateStatus = (statusCode) => { // 不论什么状态,统一在正确中处理 return true; }; return config; }); http.interceptors.request.use( (config) => { /* 请求之前拦截器。可以使用async await 做异步操作 */ let accessToken = storage.getAccessToken(); if (accessToken) { const nonce = Foundation.randomString(6); const timestamp = parseInt(new Date().getTime() / 1000); const sign = md5(nonce + timestamp + accessToken); const _params = { nonce, timestamp, sign, }; let params = config.params || {}; params = { ...params, ..._params }; config.params = params; config.header.accessToken = accessToken; /** * jwt 因为安卓以及ios没有window的属性 * window.atob()这个函数 base64编码的使用方法就是btoa(),而用于解码的使用方法是atob(), * 所以使用手写 base-64 编码的字符串数据。 */ const atob = (str) => Buffer.from(str, "base64").toString("binary"); // 判断如果过期时间小于我的当前时间,在请求上重新刷新token if (accessToken.split(".").length <= 1) { refresh(); } else { if ( JSON.parse( atob( accessToken.split(".")[1].replace(/-/g, "+").replace(/_/g, "/") ) ).exp < Math.round(new Date() / 1000) ) { refresh(); } } } config.header = { ...config.header, uuid: storage.getUuid() || uuid.v1(), }; return config; }, (config) => { return Promise.reject(config); } ); async function refresh() { // 本地储存的是过期token了,重新获取 const getTokenResult = await refreshToken(); if (getTokenResult === "success") { // 获取新的token成功 刷新当前页面 let routes = getCurrentPages(); // 获取当前打开过的页面路由数组 let curRoute = routes[routes.length - 1].route; //获取当前页面路由 let curParam = routes[routes.length - 1].options; //获取路由参数 // 拼接参数 let param = ""; for (let key in curParam) { param += "&" + key + "=" + curParam[key]; } // 判断当前路径 if (curRoute.indexOf("pages/tabbar") == 1) { uni.switchTab({ url: "/" + curRoute + param.replace("&", "?"), }); } uni.redirectTo({ url: "/" + curRoute + param.replace("&", "?"), }); } } // 必须使用异步函数,注意 http.interceptors.response.use( async (response) => { /* 请求之后拦截器。可以使用async await 做异步操作 */ // token存在并且token过期 let token = storage.getAccessToken(); if ( (token && response.statusCode === 403) || response.data.status === 403 ) { // jwt token 过期了 expireToken.push(token); // 把过期token 储存 const currentToken = storage.getAccessToken(); if (expireToken.includes(currentToken)) { refresh(); } // 如果当前返回没登录 } else if ( (!token && response.statusCode === 403) || response.data.code === 403 ) { cleanStorage(); // 如果当前状态码为正常但是success为不正常时 } else if ( (response.statusCode == 200 && !response.data.success) || response.statusCode == 400 ) { if (response.data.message) { uni.showToast({ title: response.data.message, icon: "none", duration: 1500, }); } } return response; }, (error) => { return error; } ); export { http }; export const Method = { GET: "GET", POST: "POST", PUT: "PUT", DELETE: "DELETE", };