提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
在一个系统工程中,也许我们会遇到这样的一个情况。在操作系统过程中由于登录态失效,这时候需要重新登录。一般情况是在拦截器中进行拦截,未登录跳转路由到登录页面登录成功后再重定向到登录前到页面。这样有一点不好的体验是在用户进行复杂的业务流程时,重新登录后需要再走一遍业务流程。
今天我们要实现在当前页面不中断业务流程实现登录,避免用户因登录态原因重新走一遍业务流程。
一、流程分析
二、序列图分析
简单分析下业务链路时序
三、代码实现
要实现这样的需求关键逻辑是需要调用checkLogin方法检测到接口失效时,把所有失效的接口存起来。待登录成功后再把失效的接口重新再请求一次。下面是request请求的封装:
// request.js
import axios from 'axios'
import {message} from 'antd'
import checkLogin from './checkLogin'
// 公共配置
const BASE_CONFIG = {
baseURL: '/api/',
timeout: 6000
}
const defaultMessage = '接口异常'
async function requestWrapper(url, baseConfig, method, params, options) {
// 这里可以定义一些默认的配置
const _baseConfig ={
...baseConfig,
method,
paramsSerializer(_params) {
return qs.stringify(_params, { arrayFormat: 'repeat' });
}
}
// 合并配置
const config = {
..._baseConfig,
...options,
};
const {
needFullRes, // 配置是否需要完成的数据返回
showLoading, // 是否需要展示loading
headers, // headers配置
suppressWarning, // 是否需要展示错误提示
data, // 传入的参数
} = params
//中间需要什么处理自己加
if (showLoading) message.loading(``, config.timeout);
try {
const response = await aixos(url, config)
const data = response.data
const {
errorCode: code, // 错误码
}=data
const checkResult = await checkLogin(code)
if (checkResult.isLogout){
response = await axios(url,config)
data = res.data
}
const {
errorCode,
message,
success
} = data
if (success){
if (needFullRes) return response
return data
}
message.destroy();
const errorMessage = message || defaultMessage
if (!suppressWarning) {
message.error(errorMessage);
}
if (needFullRes) return response;
if (throwError) throw { data, errorMessage };
return data;
} catch (e){
message.destory()
const displayMessage =
e?.code === 'timeout'
? '请求超时,请稍后重试'
: e?.errorMessage || defaultMessage;
message.error(displayMessage);
if (throwError) throw e?.data || e;
return { success: false };
}
}
export default function iquicRequest(url, options) {
const method = options?.method?.toLowerCase() as Method;
const { params, ...otherOptions } = options ?? {};
return requestWrapper(
url,
{ ...BASE_CONFIG, baseURL: '' },
method,
params,
otherOptions,
);
}
// checkLogin.jsx
import {Modal} from 'antd'
import {getLoginState} from './service'
const NOT_LOGIN = '12342' // 登录失效的错误码
let modal = null
const resolveHandles = []
export const checkLogin = async (errorCode) => {
if (errorCode !== NOT_LOGIN) return { isLogout: false };
// hold住每个失效的请求
let _resolve: (value?: any) => void;
const handleState = () => {
return new Promise((resolve) => {
_resolve = resolve;
});
};
resolveHandles.push(() => _resolve());
// 第一个失效状态的请求触发弹出重新登录modal
if (!modal) {
modal = Modal.info({
title: '系统提示',
content: '登录已过期,请重新登录',
okText: '前往登录',
onOk() {
const subWin = openWindow(url); // url为登录页面的地址
const closeSubWin = () => subWin?.close();
window.addEventListener('beforeunload', closeSubWin); // 主页刷新/关闭前,关闭子窗口
firstRequestTimestamp = Date.now();
const timer = setInterval(async () => {
const now = Date.now();
if (now - firstRequestTimestamp > 60 * 30 * 1000 || subWin!.self === null) {
clearInterval(timer);
subWin?.close();
window.removeEventListener('beforeunload', closeSubWin);
return;
}
const isLogin = await getLoginState();
if (isLogin) {
modal?.destroy();
clearInterval(timer);
subWin?.close();
resolveHandles.forEach((handle) => handle()); // 改变hold住的请求的状态为resolve
resolveHandles.length = 0;
modal = null;
window.removeEventListener('beforeunload', closeSubWin);
}
}, 2000);
return Promise.reject();
},
});
}
// 失效状态下的所有请求
await handleState();
return { isLogout: true };
};
// service.js
import request from './request'
export const getLoginState =async(payload) => {
const res = await request('api/getUser.json',payload)
if (res?.success) retrun true
return false
}
总结
以上就是全部实现思路了,请求的配置可以根据实际需求来略作调整。当然如果登录页面如果不是前端项目自己实现的也可以通过后端返回的结果加上外部登录地址来跳转登录。也可以直接在modal的content中引入登录组件来直接登录。
还有点需要优化的是在登录失效时再重新登录后会把期间一直查询登录结果的接口请求多次。