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 相关文章推荐
xtree.js 代码
Mar 13 Javascript
javascript css在IE和Firefox中区别分析
Feb 18 Javascript
jquery 笔记 事件
Nov 02 Javascript
jquery鼠标滑过提示title具体实现代码
Aug 06 Javascript
js实现简洁大方的二级下拉菜单效果代码
Sep 01 Javascript
javascript设计模式Constructor(构造器)模式
Aug 19 Javascript
Bootstrap3 多选和单选框(checkbox)
Dec 29 Javascript
基于JavaScript实现拖动滑块效果
Feb 16 Javascript
利用angularjs1.4制作的简易滑动门效果
Feb 28 Javascript
vue cli构建的项目中请求代理与项目打包问题
Feb 26 Javascript
jquery获取元素到屏幕四周可视距离的方法
Sep 05 jQuery
详解JavaScript之Array.reduce源码解读
Nov 01 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使用PDO操作MySQL数据库实例
2014/12/30 PHP
php源码之将图片转化为data/base64数据流实例详解
2016/11/27 PHP
PHP调用API接口实现天气查询功能的示例
2017/09/21 PHP
优化网页之快速的呈现我们的网页
2007/06/29 Javascript
Knockoutjs的环境搭建教程
2012/11/26 Javascript
jquery实现简单易懂的图片展示小例子
2013/11/21 Javascript
JS基于MSClass和setInterval实现ajax定时采集信息并滚动显示的方法
2016/04/18 Javascript
基于JavaScript实现窗口拖动效果
2017/01/18 Javascript
Bootstrap面板学习使用
2017/02/09 Javascript
Vue2.0实现将页面中表格数据导出excel的实例
2017/08/09 Javascript
JavaScript实用代码小技巧
2018/08/23 Javascript
对angularJs中自定义指令replace的属性详解
2018/10/09 Javascript
使用JavaScript解析URL的方法示例
2019/03/01 Javascript
vue 父组件通过$refs获取子组件的值和方法详解
2019/11/07 Javascript
JavaScript实现简单的图片切换功能(实例代码)
2020/04/10 Javascript
vue组件暴露和.js文件暴露接口操作
2020/08/11 Javascript
如何利用node转发请求详解
2020/09/17 Javascript
分享一个常用的Python模拟登陆类
2015/03/29 Python
Python输出带颜色的字符串实例
2017/10/10 Python
利用Python如何批量修改数据库执行Sql文件
2018/07/29 Python
浅析PyTorch中nn.Module的使用
2019/08/18 Python
使用pytorch搭建AlexNet操作(微调预训练模型及手动搭建)
2020/01/18 Python
Python中的sys.stdout.write实现打印刷新功能
2020/02/21 Python
Staples美国官方网站:办公用品一站式采购
2016/07/28 全球购物
幼儿园教研活动总结
2014/04/30 职场文书
客户经理竞聘演讲稿
2014/05/15 职场文书
中国梦口号
2014/06/13 职场文书
关于读书的演讲稿300字
2014/08/27 职场文书
党员批评与自我批评范文
2014/09/23 职场文书
2014年第四季度入党积极分子思想汇报(十八届四中全会)
2014/11/03 职场文书
期末考试复习计划
2015/01/19 职场文书
学校党支部公开承诺书
2015/04/30 职场文书
独生子女证明范本
2015/06/19 职场文书
导游词之山西关帝庙
2019/11/01 职场文书
springboot layui hutool Excel导入的实现
2022/03/31 Java/Android
分享几个实用的CSS代码块
2022/06/10 HTML / CSS