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

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


前言

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


一、流程分析

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

### 黑马点评自动登录解决方案 为了有效解决黑马点评应用中的自动登录问题,可以从以下几个方面入手: #### 1. **Token 唯一性和有效性管理** 通过生成唯一的 Token 并存储在 Redis 中来标识每次用户的登录。每当用户成功登录,都会生成一个新的 Token,并将其作为 Key 存储在 Redis 中,同将用户的相关信息作为 Value 进行关联[^3]。 如果检测到同一个账户尝试多次登录,则可以通过以下方式处理: - 在 Redis 中设置一个标志位,用于标记当前用户的在线状。 - 如果发现已有有效的 Token 对应该用户,则可以强制下线之前的登录设备或将新请求的登录操作拒绝并提示错误信息。 #### 2. **拦截器机制增强** 利用拦截器实现更严格的登录校验逻辑,在每次 HTTP 请求到达业务层之前先验证其携带的 Token 是否合法以及是否属于同一账号的同多处登录情况[^1]。 具体做法如下: - 当接收到客户端发来的请求,提取其中附带的 Authorization Header 或其他形式传递过来的身份认证凭证(即上述提到过的 token); - 接着调用服务端预先定义好的方法对该令牌的有效期、所属主体等属性逐一核对确认无误之后才允许继续执行后续流程;否则立即中断响应返回相应的错误码告知前端新引导用户完成身份验证过程。 以下是基于 Spring Boot 的简单代码示例展示如何构建这样的过滤链路结构: ```java @Component public class LoginInterceptor implements HandlerInterceptor { @Autowired private StringRedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (StringUtils.isEmpty(token)) { throw new RuntimeException("未获取到授权信息!"); } // 判断token是否存在且有效 Boolean hasKey = redisTemplate.hasKey(token); if (!hasKey) { throw new RuntimeException("非法访问,token已失效存在!"); } // 获取对应用户ID String userId = redisTemplate.opsForValue().get(token); // 可在此进一步扩展检查是否有多个相同userId处于活跃状... return true; } } ``` #### 3. **单点登录控制策略** 对于希望严格限制单一实例登录的应用场景来说,还可以引入 SSO 单点注销功能或者直接修改数据库表设计增加 last_login_time 字段用来追踪最近一次活动间戳以便于及清理过期连接记[^2]。 例如可以在用户登出的候主动清除掉自己所持有的那个特定 session id 所指向的数据项从而达到即生效的目的同也减少了内存占用率提高了整体性能表现水平等等优点... --- ### 总结 综上所述,针对黑马点评项目中存在的潜在安全隐患——即可能出现因各种原因造成的意外性的多并发接入现象可通过综合运用以上三种技术手段相结合的方式来予以妥善应对处置最终达成预期目标效果最佳实践方案推荐采用分布式缓存加AOP切面编程思想共同协作完成整个任务流闭环运作模式最为理想可行路径之一!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值