JavaScript架构localStorage特殊场景下二次封装操作


Posted in Javascript onJune 21, 2022

前言

很多人在用 localStoragesessionStorage 的时候喜欢直接用,明文存储,直接将信息暴露在;浏览器中,虽然一般场景下都能应付得了且简单粗暴,但特殊需求情况下,比如设置定时功能,就不能实现。就需要对其进行二次封装,为了在使用上增加些安全感,那加密也必然是少不了的了。为方便项目使用,特对常规操作进行封装。不完善之处会进一步更新...(更新于:2022.06.02 16:30)

设计

封装之前先梳理下所需功能,并要做成什么样,采用什么样的规范,部分主要代码片段是以 localStorage作为示例,最后会贴出完整代码的。可以结合项目自行优化,也可以直接使用。

// 区分存储类型 type
// 自定义名称前缀 prefix
// 支持设置过期时间 expire
// 支持加密可选,开发环境下未方便调试可关闭加密
// 支持数据加密 这里采用 crypto-js 加密 也可使用其他方式
// 判断是否支持 Storage isSupportStorage
// 设置 setStorage
// 获取 getStorage
// 是否存在 hasStorage
// 获取所有key getStorageKeys
// 根据索引获取key getStorageForIndex
// 获取localStorage长度 getStorageLength
// 获取全部 getAllStorage
// 删除 removeStorage
// 清空 clearStorage
//定义参数 类型 window.localStorage,window.sessionStorage,
const config = {
    type: 'localStorage', // 本地存储类型 localStorage/sessionStorage
    prefix: 'SDF_0.0.1', // 名称前缀 建议:项目名 + 项目版本
    expire: 1, //过期时间 单位:秒
    isEncrypt: true // 默认加密 为了调试方便, 开发过程中可以不加密
}

设置 setStorage

Storage 本身是不支持过期时间设置的,要支持设置过期时间,可以效仿 Cookie 的做法,setStorage(key,value,expire) 方法,接收三个参数,第三个参数就是设置过期时间的,用相对时间,单位秒,要对所传参数进行类型检查。可以设置统一的过期时间,也可以对单个值得过期时间进行单独配置。两种方式按需配置。

代码实现:

// 设置 setStorage
export const setStorage = (key,value,expire=0) => {
    if (value === '' || value === null || value === undefined) {
        value = null;
    }
    if (isNaN(expire) || expire < 1) throw new Error("Expire must be a number");
    expire = (expire?expire:config.expire) * 60000;
    let data = {
        value: value, // 存储值
        time: Date.now(), //存值时间戳
        expire: expire // 过期时间
    }
    window[config.type].setItem(key, JSON.stringify(data));
}

获取 getStorage

首先要对 key 是否存在进行判断,防止获取不存在的值而报错。对获取方法进一步扩展,只要在有效期内获取 Storage 值,就对过期时间进行续期,如果过期则直接删除该值。并返回 null

// 获取 getStorage
export const getStorage = (key) => {
    // key 不存在判断
    if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null'){
        return null;
    }
    // 优化 持续使用中续期
    const storage = JSON.parse(window[config.type].getItem(key));
    console.log(storage)
    let nowTime = Date.now();
    console.log(config.expire*6000 ,(nowTime - storage.time))
    // 过期删除
    if (storage.expire && config.expire*6000 < (nowTime - storage.time)) {
        removeStorage(key);
        return null;
    } else {
        // 未过期期间被调用 则自动续期 进行保活
        setStorage(key,storage.value);
        return storage.value;
    }
}

获取所有值

// 获取全部 getAllStorage
export const getAllStorage = () => {
    let len = window[config.type].length // 获取长度
    let arr = new Array() // 定义数据集
    for (let i = 0; i < len; i++) {
        // 获取key 索引从0开始
        let getKey = window[config.type].key(i)
        // 获取key对应的值
        let getVal = window[config.type].getItem(getKey)
        // 放进数组
        arr[i] = { 'key': getKey, 'val': getVal, }
    }
    return arr
}

删除 removeStorage

// 名称前自动添加前缀
const autoAddPrefix = (key) => {
    const prefix = config.prefix ? config.prefix + '_' : '';
    return  prefix + key;
}
// 删除 removeStorage
export const removeStorage = (key) => {
    window[config.type].removeItem(autoAddPrefix(key));
}

清空 clearStorage

// 清空 clearStorage
export const clearStorage = () => {
    window[config.type].clear();
}

加密、解密

加密采用的是 crypto-js

// 安装crypto-js
npm install crypto-js
// 引入 crypto-js 有以下两种方式
import CryptoJS from "crypto-js";
// 或者
const CryptoJS = require("crypto-js");

crypto-js 设置密钥和密钥偏移量,可以采用将一个私钥经 MD5 加密生成16位密钥获得。

// 十六位十六进制数作为密钥
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161");
// 十六位十六进制数作为密钥偏移量
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a");

对加密方法进行封装

/**
 * 加密方法
 * @param data
 * @returns {string}
 */
export function encrypt(data) {
  if (typeof data === "object") {
    try {
      data = JSON.stringify(data);
    } catch (error) {
      console.log("encrypt error:", error);
    }
  }
  const dataHex = CryptoJS.enc.Utf8.parse(data);
  const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
    iv: SECRET_IV,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  return encrypted.ciphertext.toString();
}

对解密方法进行封装

/**
 * 解密方法
 * @param data
 * @returns {string}
 */
export function decrypt(data) {
  const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
  const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
  const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
    iv: SECRET_IV,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
  return decryptedStr.toString();
}

在存储数据及获取数据中进行使用:

这里我们主要看下进行加密和解密部分,部分方法在下面代码段中并未展示,请注意,不能直接运行。

const config = {
    type: 'localStorage', // 本地存储类型 sessionStorage
    prefix: 'SDF_0.0.1', // 名称前缀 建议:项目名 + 项目版本
    expire: 1, //过期时间 单位:秒
    isEncrypt: true // 默认加密 为了调试方便, 开发过程中可以不加密
}
// 设置 setStorage
export const setStorage = (key, value, expire = 0) => {
    if (value === '' || value === null || value === undefined) {
        value = null;
    }
    if (isNaN(expire) || expire < 0) throw new Error("Expire must be a number");
    expire = (expire ? expire : config.expire) * 1000;
    let data = {
        value: value, // 存储值
        time: Date.now(), //存值时间戳
        expire: expire // 过期时间
    }
    // 对存储数据进行加密 加密为可选配置
    const encryptString = config.isEncrypt ? encrypt(JSON.stringify(data)): JSON.stringify(data);
    window[config.type].setItem(autoAddPrefix(key), encryptString);
}
// 获取 getStorage
export const getStorage = (key) => {
    key = autoAddPrefix(key);
    // key 不存在判断
    if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null') {
        return null;
    }
    // 对存储数据进行解密
    const storage = config.isEncrypt ? JSON.parse(decrypt(window[config.type].getItem(key))) : JSON.parse(window[config.type].getItem(key));
    let nowTime = Date.now();
    // 过期删除
    if (storage.expire && config.expire * 6000 < (nowTime - storage.time)) {
        removeStorage(key);
        return null;
    } else {
        //  持续使用时会自动续期
        setStorage(autoRemovePrefix(key), storage.value);
        return storage.value;
    }
}

使用

使用的时候你可以通过 import 按需引入,也可以挂载到全局上使用,一般建议少用全局方式或全局变量,为后来接手项目继续开发维护的人,追查代码留条便捷之路!不要为了封装而封装,尽可能基于项目需求和后续的通用,以及使用上的便捷。比如获取全部存储变量,如果你项目上都未曾用到过,倒不如删减掉,留着过年也不见得有多香,不如为减小体积做点贡献!

import {isSupportStorage, hasStorage, setStorage,getStorage,getStorageKeys,getStorageForIndex,getStorageLength,removeStorage,getStorageAll,clearStorage} from '@/utils/storage'

完整代码

该代码已进一步完善,需要的可以直接进一步优化,也可以将可优化或可扩展的建议,留言说明,我会进一步迭代的。可以根据自己的需要删除一些不用的方法,以减小文件大小。

/***
 * title: storage.js
 * Author: Gaby
 * Email: xxx@126.com
 * Time: 2022/6/1 17:30
 * last: 2022/6/2 17:30
 * Desc: 对存储的简单封装
 */
import CryptoJS from 'crypto-js';
// 十六位十六进制数作为密钥
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161");
// 十六位十六进制数作为密钥偏移量
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a");
// 类型 window.localStorage,window.sessionStorage,
const config = {
    type: 'localStorage', // 本地存储类型 sessionStorage
    prefix: 'SDF_0.0.1', // 名称前缀 建议:项目名 + 项目版本
    expire: 1, //过期时间 单位:秒
    isEncrypt: true // 默认加密 为了调试方便, 开发过程中可以不加密
}
// 判断是否支持 Storage
export const isSupportStorage = () => {
    return (typeof (Storage) !== "undefined") ? true : false
}
// 设置 setStorage
export const setStorage = (key, value, expire = 0) => {
    if (value === '' || value === null || value === undefined) {
        value = null;
    }
    if (isNaN(expire) || expire < 0) throw new Error("Expire must be a number");
    expire = (expire ? expire : config.expire) * 1000;
    let data = {
        value: value, // 存储值
        time: Date.now(), //存值时间戳
        expire: expire // 过期时间
    }
    const encryptString = config.isEncrypt 
    ? encrypt(JSON.stringify(data))
    : JSON.stringify(data);
    window[config.type].setItem(autoAddPrefix(key), encryptString);
}
// 获取 getStorage
export const getStorage = (key) => {
    key = autoAddPrefix(key);
    // key 不存在判断
    if (!window[config.type].getItem(key) || JSON.stringify(window[config.type].getItem(key)) === 'null') {
        return null;
    }
    // 优化 持续使用中续期
    const storage = config.isEncrypt 
    ? JSON.parse(decrypt(window[config.type].getItem(key))) 
    : JSON.parse(window[config.type].getItem(key));
    let nowTime = Date.now();
    // 过期删除
    if (storage.expire && config.expire * 6000 < (nowTime - storage.time)) {
        removeStorage(key);
        return null;
    } else {
        // 未过期期间被调用 则自动续期 进行保活
        setStorage(autoRemovePrefix(key), storage.value);
        return storage.value;
    }
}
// 是否存在 hasStorage
export const hasStorage = (key) => {
    key = autoAddPrefix(key);
    let arr = getStorageAll().filter((item)=>{
        return item.key === key;
    })
    return arr.length ? true : false;
}
// 获取所有key
export const getStorageKeys = () => {
    let items = getStorageAll()
    let keys = []
    for (let index = 0; index < items.length; index++) {
        keys.push(items[index].key)
    }
    return keys
}
// 根据索引获取key
export const getStorageForIndex = (index) => {
    return window[config.type].key(index)
}
// 获取localStorage长度
export const getStorageLength = () => {
    return window[config.type].length
}
// 获取全部 getAllStorage
export const getStorageAll = () => {
    let len = window[config.type].length // 获取长度
    let arr = new Array() // 定义数据集
    for (let i = 0; i < len; i++) {
        // 获取key 索引从0开始
        let getKey = window[config.type].key(i)
        // 获取key对应的值
        let getVal = window[config.type].getItem(getKey)
        // 放进数组
        arr[i] = {'key': getKey, 'val': getVal,}
    }
    return arr
}
// 删除 removeStorage
export const removeStorage = (key) => {
    window[config.type].removeItem(autoAddPrefix(key));
}
// 清空 clearStorage
export const clearStorage = () => {
    window[config.type].clear();
}
// 名称前自动添加前缀
const autoAddPrefix = (key) => {
    const prefix = config.prefix ? config.prefix + '_' : '';
    return  prefix + key;
}
// 移除已添加的前缀
const autoRemovePrefix = (key) => {
    const len = config.prefix ? config.prefix.length+1 : '';
    return key.substr(len)
    // const prefix = config.prefix ? config.prefix + '_' : '';
    // return  prefix + key;
}
/**
 * 加密方法
 * @param data
 * @returns {string}
 */
const encrypt = (data) => {
    if (typeof data === "object") {
        try {
            data = JSON.stringify(data);
        } catch (error) {
            console.log("encrypt error:", error);
        }
    }
    const dataHex = CryptoJS.enc.Utf8.parse(data);
    const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
        iv: SECRET_IV,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return encrypted.ciphertext.toString();
}
/**
 * 解密方法
 * @param data
 * @returns {string}
 */
const decrypt = (data) => {
    const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
    const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
    const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
        iv: SECRET_IV,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
    return decryptedStr.toString();
}

以上就是JavaScript架构localStorage特殊场景下二次封装操作的详细内容,更多关于JavaScript localStorage二次封装的资料请关注三水点靠木其它相关文章!


Tags in this post...

Javascript 相关文章推荐
js创建对象的几种常用方式小结(推荐)
Oct 24 Javascript
JS中confirm,alert,prompt函数区别分析
Jan 17 Javascript
JavaScript面向对象设计二 构造函数模式
Dec 20 Javascript
JS检测输入字符是否包含非法字符的示例代码
Feb 11 Javascript
jquery的trigger和triggerHandler的区别示例介绍
Apr 20 Javascript
js实现文章文字大小字号功能完整实例
Nov 01 Javascript
微信小程序 出现错误:{&quot;baseresponse&quot;:{&quot;errcode&quot;:-80002,&quot;errmsg&quot;:&quot;&quot;}}解决办法
Feb 23 Javascript
canvas实现图片根据滑块放大缩小效果
Feb 24 Javascript
微信小程序 slider的简单实例
Apr 19 Javascript
php 修改密码实现代码
May 24 Javascript
Node.js 实现远程桌面监控的方法步骤
Jul 02 Javascript
原生JavaScript实现轮播图
Jan 10 Javascript
js前端图片加载异常兜底方案
Jun 21 #Javascript
JavaScript中10个Reduce常用场景技巧
Jun 21 #Javascript
js前端面试常见浏览器缓存强缓存及协商缓存实例
Jun 21 #Javascript
JavaScript前端面试组合函数
Jun 21 #Javascript
Vue2项目中对百度地图的封装使用详解
JavaScript原型链中函数和对象的理解
JS精髓原型链继承及构造函数继承问题纠正
Jun 16 #Javascript
You might like
php连接mysql数据库代码
2009/03/10 PHP
PHP中PDO基础教程 入门级
2011/09/04 PHP
简单的方法让你的后台登录更加安全(php中加session验证)
2012/08/22 PHP
php警告Creating default object from empty value 问题的解决方法
2014/04/02 PHP
PHP对象实例化单例方法
2017/01/19 PHP
PHP简单计算两个时间差的方法示例
2017/06/20 PHP
PHP+ajax实现上传、删除、修改单张图片及后台处理逻辑操作详解
2020/02/12 PHP
Javascript 面向对象 命名空间
2010/05/13 Javascript
jQuery 获取对象 根据属性、内容匹配, 还有表单元素匹配
2010/05/31 Javascript
javascript hasFocus使用实例
2010/06/29 Javascript
JS中confirm,alert,prompt函数区别分析
2011/01/17 Javascript
js操作IE浏览器弹出浏览文件夹可以返回目录路径
2014/07/14 Javascript
Jquery对select的增、删、改、查操作
2015/02/06 Javascript
json传值以及ajax接收详解
2016/05/24 Javascript
js与jquery分别实现tab标签页功能的方法
2016/11/18 Javascript
js实现仿购物车加减效果
2017/03/01 Javascript
JavaScript调用模式与this关键字绑定的关系
2018/04/21 Javascript
JavaScript常见事件处理程序实例总结
2019/01/05 Javascript
vue3修改link标签默认icon无效问题详解
2019/10/09 Javascript
简单了解JavaScript弹窗实现代码
2020/05/07 Javascript
[02:40]2014DOTA2 国际邀请赛中国区预选赛 四大豪门抵达华西村
2014/05/23 DOTA
pycharm 使用心得(九)解决No Python interpreter selected的问题
2014/06/06 Python
python中字符串比较使用is、==和cmp()总结
2018/03/18 Python
python3.6使用pymysql连接Mysql数据库
2018/05/25 Python
python简单实现矩阵的乘,加,转置和逆运算示例
2019/07/10 Python
Python实现K折交叉验证法的方法步骤
2019/07/11 Python
Python递归函数特点及原理解析
2020/03/04 Python
python3中sorted函数里cmp参数改变详解
2020/03/12 Python
python+selenium+chromedriver实现爬虫示例代码
2020/04/10 Python
简历中自我评价分享
2013/10/09 职场文书
总经理职责范文
2013/11/08 职场文书
《郑和远航》教学反思
2014/04/16 职场文书
工作岗位说明书模板
2014/05/09 职场文书
2019大学竞选班长发言稿
2019/06/27 职场文书
详解前端任务构建利器Gulp.js使用指南
2021/04/30 Javascript
nginx访问报403错误的几种情况详解
2022/07/23 Servers