如何在登录态失效时不中断业务流程重新登录

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

  在一个系统工程中,也许我们会遇到这样的一个情况。在操作系统过程中由于登录态失效,这时候需要重新登录。一般情况是在拦截器中进行拦截,未登录跳转路由到登录页面登录成功后再重定向到登录前到页面。这样有一点不好的体验是在用户进行复杂的业务流程时,重新登录后需要再走一遍业务流程。
  今天我们要实现在当前页面不中断业务流程实现登录,避免用户因登录态原因重新走一遍业务流程。


一、流程分析

Created with Raphaël 2.3.0 业务操作 业务请求 登录失效? 挂起请求,并打开新窗口进行登录 登录成功? 返回结果 yes no yes no

二、序列图分析

简单分析下业务链路时序

用户 操作系统 封装的请求方法 checkLogin方法 服务端 操作某些业务 调用接口 发起业务请求 返回结果 调用方法判断是否失效,最终返回登录态失效的结果 第一个接口失效弹框去登录 把请求hold住 发送一个接口验证是否已登录成功 返回请求结果 结束loop循环、关闭弹框 把hold住的请求释放 继续loop请求 alt [登录成功] [登录失败] loop 重新请求一次登录失效的接口 返回请求结果 返回请求结果 alt [登录态失效] [登录态正常] 返回请求结果 继续其他业务处理 用户 操作系统 封装的请求方法 checkLogin方法 服务端

三、代码实现

要实现这样的需求关键逻辑是需要调用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中引入登录组件来直接登录。
还有点需要优化的是在登录失效时再重新登录后会把期间一直查询登录结果的接口请求多次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值