当前位置: 首页 > article >正文

【前端开发】小程序无感登录验证

概述

封装的网络请求库,主要用于处理 API 请求并支持自动处理 token 过期 和 token 刷新,适用于需要身份验证的应用场景,特别是在移动端中。

主要功能

  1. 自动附加 Token

在每个请求中自动附加 Authorization 头部,使用存储的 accessToken。如果某个请求不需要 Token,则可以通过设置 isToken: false 来排除。

  1. Token 过期自动刷新
  • 当请求返回提示 accessToken 过期时,自动尝试使用 refreshToken 刷新 accessToken
  • 在刷新过程中,新的请求会被放入一个队列中,等到 refreshToken 成功后再依次重试
  1. 请求重试机制
  • 当 accessToken 刷新成功后,队列中的所有待处理请求都会自动重发
  • 如果 refreshToken 刷新失败,跳转到登录页面,让用户重新登录。
  1. 统一的请求和响应处理
  • 请求的 Content-Type 和 Accept 头部统一设置
  • 统一处理服务器返回的状态码,若返回 200 且不含 ErrType: -10011,则正常返回数据,否则进行相关的错误处理

代码概览

  1. refreshToken 函数
  • 用于发送请求以刷新 accessToken,通过 refreshToken 获取新的 accessToken,并保存在本地
  • 若成功,返回新的 accessToken;若失败,返回错误信息。
  1. request 函数
  • 封装了 uni.request 请求方法,提供了 GET、POST 请求支持。
    检查是否有有效的 accessToken,如果有,自动将其附加到请求头的 Authorization 中
  • 如果请求参数中包含 params,则自动将其转换为查询字符串附加到 URL 中
  • 在请求成功时,判断返回数据的 ErrType 是否为 -10011(表示 accessToken 过期),若过期,则调用 handleTokenExpiration 函数刷新 Token
  1. handleTokenExpiration 函数
  • 处理 accessToken 过期的逻辑,避免在刷新 Token 的过程中发起多次刷新请求
  • 如果当前没有刷新请求正在进行,则调用 refreshToken 函数尝试刷新 Token,并将待重试的请求保存在队列中
  • 如果刷新 Token 成功,则重试队列中的请求,重新发送
  • 如果刷新 Token 失败,清空队列并跳转到登录页面
  1. isRefreshing 和 requestQueue
  • isRefreshing 用于确保只有一个刷新请求在进行,避免并发刷新 Token 的情况
  • requestQueue 用于存储等待 Token 刷新完成后的请求,确保刷新完成后再逐一处理这些请求

工作流程

  • 用户首次登录时,后端会返回 accessToken 和 refreshToken
  • 后续的 API 请求都会自动附带 accessToken
  • 当 accessToken 过期时,后端返回 ErrType: -10011,触发 handleTokenExpiration
  • handleTokenExpiration 检查是否正在进行刷新操作,如果没有,则开始刷新并保存待重试的请求
  • 刷新完成后,重试这些请求,确保请求使用最新的 accessToken

Request.js

import store from "@/store";
import config from "@/common/config";
import { getToken, getRefreshToken, setToken } from "@/common/auth";
import { tansParams } from "@/common/index";

let timeout = 10000;
const baseUrl = config.baseUrl;

const refreshToken = () => {
  return new Promise((resolve, reject) => {
    uni
      .request({
        method: "post",
        url: `${baseUrl}/auth/refresh`,
        data: { refreshToken: getRefreshToken() },
        header: { "Content-Type": "application/json" },
      })
      .then((response) => {
        if (response.statusCode === 200 && response.data.accessToken) {
          setToken(response.data.accessToken);
          resolve(response.data.accessToken);
        } else {
          reject("刷新令牌失败");
        }
      })
      .catch((error) => {
        reject(error);
      });
  });
};

const request = (config) => {
  const isToken = (config.headers || {}).isToken === false;
  config.header = config.header || {};
  if (getToken() && !isToken) {
    config.header["Authorization"] = "Bearer " + getToken();
  }

  if (config.params) {
    let url = config.url + "?" + tansParams(config.params);
    url = url.slice(0, -1);
    config.url = url;
  }

  config.header["Content-Type"] =
    "application/x-www-form-urlencoded; charset=UTF-8";
  config.header["Accept"] = "application/json, text/javascript, */*; q=0.01";

  return new Promise((resolve, reject) => {
    uni
      .request({
        method: config.method || "get",
        timeout: config.timeout || timeout,
        url: config.baseUrl || baseUrl + config.url,
        data: config.data,
        header: config.header,
        dataType: "json",
      })
      .then((response) => {
        if (response.statusCode === 200) {
          //登录过期
          if (response.data.ErrType == "-10011") {
            // Access Token 过期
            handleTokenExpiration(config, resolve, reject);
          } else {
            resolve(response.data);
          }
        } else {
          reject("服务器连接异常");
        }
      })
      .catch((error) => {
        reject(error);
      });
  });
};

let isRefreshing = false; // 防止多次刷新
let requestQueue = []; // 队列存储待重试的请求

const handleTokenExpiration = (config, resolve, reject) => {
  if (!isRefreshing) {
    isRefreshing = true; // 开始刷新

    refreshToken()
      .then((newToken) => {
        isRefreshing = false;
        // 刷新成功后,重试队列中的请求
        requestQueue.forEach((callback) => callback(newToken));
        requestQueue = []; // 清空队列

        // 重新发送当前请求
        resolve(
          request({
            ...config,
            header: {
              ...config.header,
              Authorization: "Bearer " + newToken,
            },
          })
        );
      })
      .catch((error) => {
        isRefreshing = false;
        requestQueue = []; // 清空队列

        // 刷新失败,跳转到登录页面
        uni.reLaunch({
          url: "/pages/login",
        });
        reject("登录已过期,请重新登录");
      });
  } else {
    // 如果已经在刷新,将请求加入队列
    requestQueue.push((newToken) => {
      resolve(
        request({
          ...config,
          header: {
            ...config.header,
            Authorization: "Bearer " + newToken,
          },
        })
      );
    });
  }
};

export default request;


http://www.kler.cn/a/415492.html

相关文章:

  • FastAPI 跨域访问cors设置
  • 微知-arp如何删除所有表项?(arp -d; ip neighbor delete 192.168.0.100)
  • 量化交易系统开发-实时行情自动化交易-8.1.TradingView平台
  • TDengine在debian安装
  • 如何启用本机GPU硬件加速猿大师播放器网页同时播放多路RTSP H.265 1080P高清摄像头RTSP视频流?
  • 小游戏聚合SDK的工具类封装
  • windows下使用WSL
  • AI智算-正式上架GPU资源监控概览 Grafana Dashboard
  • 小程序-基于java+SpringBoot+Vue的戏曲文化苑小程序设计与实现
  • tomcat 8.5.35安装及配置
  • 【Leetcode Top 100】206. 反转链表
  • 消息传递神经网络(Message Passing Neural Networks, MPNN)
  • Unity类银河战士恶魔城学习总结(P150 End Screen结束重启按钮)
  • 学习threejs,使用specularMap设置高光贴图
  • 实习冲刺第三十四天
  • 基于单片机的仓库环境无线监测系统(论文+源码)
  • Linux,如何将文件从一台服务器传到另一台服务器上
  • 基于STM32的智能农业灌溉系统设计与实现
  • Java 基础之 List 深度探秘
  • ChatGPT 能否克服金融领域中的行为偏见?分类与重新思考:黄金投资中的多步零样本推理
  • k8s容器存储接口 CSI 相关知识
  • ElasticSearch学习笔记把:Springboot整合ES(二)
  • 内核模块里获取当前进程和父进程的cmdline的方法及注意事项,涉及父子进程管理,和rcu的初步介绍
  • 设计模式学习之——策略模式
  • 使用命令行来刷写ELRS接收器的固件
  • 小程序-基于java+SpringBoot+Vue的乡村研学旅行平台设计与实现