JS实现检测当前操作用户和登录用户信息是否一致,并使用webpack将其封装成一个sdk

记录: 这两天在支援一个老项目时,发现了一个线上问题,在这里记录一下解决方案。

场景:同一个系统的地址链接,用户用两个浏览器标签页打开,分别登录不同的有效用户,当用户从一个标签页切换到另一个标签页,继续某些操作时,会因操作用户不一致导致数据信息展示错误或接口报错等问题。

解决方案: 和组内成员及领导讨论后,决定使用visibilitychange事件和focus焦点事件来监听用户的行为,从而完成校验用户信息的动作。

PS:至于为啥不让后端在接口层面做拦截?

  1. 让后端所有接口都做拦截不现实。
  2. 如果两个浏览器标签页上登录的用户都是有效用户且权限一样,那么后端接口依旧拦不住。

下面说说代码实现逻辑:

  1. 在页面初始化的时候,先从cookie中拿到对应的用户信息,存储到sessionStorage中。

  2. 使用visibilitychange事件监听用户切换浏览器标签页的行为,如果状态为用户进入页面,则从cookie中获取用户信息与之前sessionStorage中存储的信息是否一致。如果不一致就弹框提示让用户重新登录。

  3. 至于为啥还要用focus事件判断,因为我做的这个项目有部分页面是使用iframe嵌入到别的项目里面的,这种情况就会导致visibilitychange事件失效,所以再用focus事件做最后一层保险。focus事件处理的逻辑与visibilitychange事件内的逻辑一样。

在实现逻辑代码之前先来看看下面这两个问题。

关于visibilitychange事件的兼容性问题

此事件是用来检测用户进入标签页和离开标签页的行为。

caniuse网站上查看此事件的兼容性,其占比已经达到99%以上,完全能覆盖到我这个项目的使用用户,所以可以放心使用。

使用示例:

js 复制代码
document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "visible") {
        console.log('用户已进入', document.visibilityState);
    } else {
        console.log('用户已离开', document.visibilityState);
    }
});

效果:

为什么使用focus

  1. 因为focus事件的优先级要比click事件优先级高!
  2. js 是单线程机制,一次只能执行一个事件

当用户点击页面上某个按钮时,会先触发focus事件,由于处理用户信息代码逻辑是同步的,所以在执行完判断逻辑后才会执行click事件内的逻辑,如果用户信息不一致,会有弹框提示,阻断用户的下一步动作。

来看个代码示例:

js 复制代码
window.addEventListener('click', () => {
    console.log('click')
});
window.addEventListener('focus', () => {
    console.log('focus')
});

控制台打印效果:

代码实现

了解了上面两个事件的一些特点之后,接下来进行编码环节。

js 复制代码
// 获取sessionStorage中存储的用户信息
function getSessionCookie(id) {
    return window.sessionStorage.getItem(id);
}

// 将用户信息存储到sessionStorage中
function setSessionCookie(id, data) {
    window.sessionStorage.setItem(id, data);
}

// 从cookie中获取用户信息对应的字段
function getCookie(key) {
    return decodeURIComponent(document.cookie.replace(new RegExp(`(?:(?:^|.*;)\\s*${encodeURIComponent(key).replace(/[-.+*]/g, '\\$&')}\\s*\\=\\s*([^;]*).*$)|^.*$`), '$1')) || null;
}

// 对比当前cookie中的信息和sessionStorage中存储的信息是否一致
function compareData(data, key) {
    return getCookie(key) === JSON.parse(data);
}

// 检测用户信息
function checkUser(cb, key) {
    var data = getSessionCookie('checkusersdk');
    if (!data) {
        var k = getCookie(key);
        if (k) {
            setSessionCookie('checkusersdk', JSON.stringify(k));
        }
    } else {
        var flag = compareData(data, key);
        cb && cb(flag);
    }
}

function init(options) {
    var cb = options.cb;
    var key = options.key || 'checkuserKey';

    document.addEventListener('visibilitychange', function() {
        if (document.visibilityState === 'visible') {
            checkUser(cb, key);
        }
    });
    
    window.addEventListener('focus', function() {
        checkUser(cb, key);
    });
}

// result为回调函数。
init({ cb: result, key: 'custom'});

init方法接收一个回调函数cb和存储用户信息标识的key,共两个参数。init方法在监听时间内调用checkUser方法,将两个参数传递进去,checkUser方法内部逻辑如下:

  1. 获取存储在sessionStorage中的数据,如果数据为空,则从cookie中获取用户信息数据,然后再存储到sessionStorage
  2. 如果sessionStorage中的数据存在,再次从cookie中获取用户信息数据,二者进行比较,返回一个布尔值,传递给回调函数。

检测用户信息的核心代码已经完成了,不过为了更好的使用体验,将它封装成一个sdk,可以方便给别的项目使用。

使用webpack将代码封装一个SDK

1. 创建一个sdk项目

  1. 在自己的工作空间内创建一个空白文件夹,命名为checkuser-sdk,作为项目名称。
  2. 在上一步刚刚创建的项目根目录下再创建一个文件夹src,用来存放核心代码。
  3. src目录下创建一个index.js文件,用来当做入口文件。

2. 初始化项目

  1. 在项目根目录下执行命令npm init,根据命令提示编写项目的一些详细信息,然后回车确定即可。

此时项目目录应该如下:

js 复制代码
checkuser-sdk
| - src
| -- index.js
| - package.json

3. 编写配置文件webpack.config.js

  1. 执行下面命令安装一些必要的依赖包

    js 复制代码
    npm i webpack webpack-cli clean-webpack-plugin uglifyjs-webpack-plugin -D
  2. 在项目根目录下创建一个webpack.config.js文件

  3. webpack.config.js文件内编写如下代码:

js 复制代码
let path = require('path');

// 把本地已有的打包后的资源清空,来减少它们对磁盘空间的占用
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// 用来缩小(压缩优化)js文件
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
    mode: 'production',
    entry: {
        'sdk': ['./src/index.js']
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js',
        library: {
            name: 'CheckuserSdk',
            type: 'umd',
        }
    }, 
    optimization: {
        minimizer: [new UglifyJsPlugin({
            sourceMap: false,
            parallel: true,
            cache: false
        })],
    },
    plugins: [
        new CleanWebpackPlugin()
    ]
}

项目比较简单,使用这两个插件已足够

4. 编写核心代码

  1. src目录下新建一个目录,命名为lib
  2. lib下创建一个util.js文件,用来存储一些工具方法。
  3. src目录下的index.js引入工具方法。

两个文件的代码分别如下:

js 复制代码
// src/lib/util.js

// 获取sessionStorage中存储的用户信息
function getSessionCookie(id) {
    return window.sessionStorage.getItem(id);
}

// 将用户信息存储到sessionStorage中
function setSessionCookie(id, data) {
    window.sessionStorage.setItem(id, data);
}

// 从cookie中获取用户信息对应的字段
function getCookie(key) {
    return decodeURIComponent(document.cookie.replace(new RegExp(`(?:(?:^|.*;)\\s*${encodeURIComponent(key).replace(/[-.+*]/g, '\\$&')}\\s*\\=\\s*([^;]*).*$)|^.*$`), '$1')) || null;
}

// 对比当前cookie中的信息和sessionStorage中存储的信息是否一致
function compareData(data, key) {
    return getCookie(key) === JSON.parse(data);
}

// 检测用户信息
function checkUser(cb, key) {
    var data = getSessionCookie('checkusersdk');
    if (!data) {
        var k = getCookie(key);
        if (k) {
            setSessionCookie('checkusersdk', JSON.stringify(k));
        }
    } else {
        var flag = compareData(data, key);
        cb && cb(flag);
    }
}

module.exports = {
    getSessionCookie,
    setSessionCookie,
    getCookie,
    compareData,
    checkUser
}
js 复制代码
// src/index.js

var util = require('./lib/util');

function init(options) {
    var cb = options.cb;
    var key = options.key || 'e2mf';

    document.addEventListener('visibilitychange', function() {
        if (document.visibilityState === 'visible') {
            util.checkUser(cb, key);
        }
    });
    
    window.addEventListener('focus', function() {
        util.checkUser(cb, key);
    });
}

module.exports = {
    init
}

5. package.json中添加脚本命令

js 复制代码
...省略
"scripts": {
    "build": "webpack"
},
...省略

执行命令npm run build后,在项目的根目录下会创建一个dist文件夹,文件夹里面的sdk.js就是我们的打包文件了。

6. 使用方式

  1. 链接方式
js 复制代码
// 引用 --->临时
<script type="text/javascript" src="http://xxx.com/sdk.js"></script>

// 使用
if (window.CheckuserSdk) {
    window.CheckuserSdk.init({ cb: callback, key: 'customKey' });
}
  1. npm方式
js 复制代码
// 安装
npm i -S @xxx/checkuser-sdk;

// 引入
import CheckuserSdk from '@xxx/checkuser-sdk';

// 使用
if (window.CheckuserSdk) {
    window.CheckuserSdk.init({ cb: callback, key: 'customKey' });
}

文章内如有错误的地方,欢迎掘友留言纠正~!

相关推荐
理想不理想v8 分钟前
vue经典前端面试题
前端·javascript·vue.js
不收藏找不到我9 分钟前
浏览器交互事件汇总
前端·交互
小阮的学习笔记22 分钟前
Vue3中使用LogicFlow实现简单流程图
javascript·vue.js·流程图
YBN娜22 分钟前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=22 分钟前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css
minDuck27 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
小政爱学习!1 小时前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。1 小时前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼1 小时前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript