当前位置:网站首页>还在直接用localStorage么?全网最细:本地存储二次封装(含加密、解密、过期处理)
还在直接用localStorage么?全网最细:本地存储二次封装(含加密、解密、过期处理)
2022-06-21 09:29:00 【冯心心爱吃肉】
背景
很多人在用 localStorage 或 sessionStorage 的时候喜欢直接用,明文存储,直接将信息暴露在;浏览器中,虽然一般场景下都能应付得了且简单粗暴,但特殊需求情况下,比如设置定时功能,就不能实现。就需要对其进行二次封装,为了在使用上增加些安全感,那加密也必然是少不了的了。为方便项目使用,特对常规操作进行封装。
结构设计
在封装一系列操作本地存储的API之前,先准备了一个全局对象,对具体的操作进行判断,如下:
interface globalConfig {
type: 'localStorage' | 'sessionStorage';
prefix: string;
expire: number;
isEncrypt: boolean;
}
const config: globalConfig = {
type: 'localStorage', //存储类型,localStorage | sessionStorage
prefix: 'react-view-ui_0.0.1', //版本号
expire: 24 * 60, //过期时间,默认为一天,单位为分钟
isEncrypt: true, //支持加密、解密数据处理
};
- type 表示存储类型,为 localStorage 或 sessionStorage ;
- prefix 表示视图唯一标识,如果配置可在浏览器视图中放在前缀显示;
- expire 表示过期时间,默认为一天,单位为分钟;
- isEncrypt 表示支持加密、解密数据处理;
加密准备工作
这里是用到了 crypto-js 来处理加密和解密,可先下载包并导入。
npm i --save-dev crypto-js
import CryptoJS from 'crypto-js';
对 crypto-js 设置密钥和密钥偏移量,可以采用将一个私钥经 MD5 加密生成16位密钥获得。
const SECRET_KEY = CryptoJS.enc.Utf8.parse('3333e6e143439161'); //十六位十六进制数作为密钥
const SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a'); //十六位十六进制数作为密钥偏移量
加密
const encrypt = (data: object | string): string => {
//加密
if (typeof data === 'object') {
try {
data = JSON.stringify(data);
} catch (e) {
throw new Error('encrypt error' + e);
}
}
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();
};
解密
const decrypt = (data: string) => {
//解密
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();
};
这两个API都是将获取到的本地存储的value作为参数进行传递,这样就实现了加密和解密。
在传入数据进行处理、改变的时候需要进行解密;
在数据需要传出时需要进行加密。
核心API实现
setStorage 设置值
Storage 本身是不支持过期时间设置的,要支持设置过期时间,可以效仿 Cookie 的做法,setStorage(key, value, expire) 方法,接收三个参数,第三个参数就是设置过期时间的,用相对时间,单位分钟,要对所传参数进行类型检查。可以设置统一的过期时间,也可以对单个值得过期时间进行单独配置。
const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => {
//设定值
if (value === '' || value === null || value === undefined) {
//空值重置
value = null;
}
if (isNaN(expire) || expire < 0) {
//过期时间值合理性判断
throw new Error('Expire must be a number');
}
const data = {
value, //存储值
time: Date.now(), //存储日期
expire: Date.now() + 1000 * 60 * expire, //过期时间
};
//是否需要加密,判断装载加密数据或原数据
window[config.type].setItem(
autoAddPreFix(key),
config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data),
);
return true;
};
getStorageFromKey 根据key获取value
首先要对 key 是否存在进行判断,防止获取不存在的值而报错。对获取方法进一步扩展,只要在有效期内就可以获取 Storage 值,如果过期则直接删除该值,并返回 null。
const getStorageFromKey = (key: string) => {
//获取指定值
if (config.prefix) {
key = autoAddPreFix(key);
}
if (!window[config.type].getItem(key)) {
//不存在判断
return null;
}
const storageVal = config.isEncrypt
? JSON.parse(decrypt(window[config.type].getItem(key) as string))
: JSON.parse(window[config.type].getItem(key) as string);
const now = Date.now();
if (now >= storageVal.expire) {
//过期销毁
removeStorageFromKey(key);
return null;
//不过期回值
} else {
return storageVal.value;
}
};
getAllStorage 获取所有存储值
const getAllStorage = () => {
//获取所有值
const storageList: any = {
};
const keys = Object.keys(window[config.type]);
keys.forEach((key) => {
const value = getStorageFromKey(key);
if (value !== null) {
//如果值没有过期,加入到列表中
storageList[key] = value;
}
});
return storageList;
};
getStorageLength 获取存储值数量
const getStorageLength = () => {
//获取值列表长度
return window[config.type].length;
};
removeStorageFromKey 根据key删除存储值
const removeStorageFromKey = (key: string) => {
//删除值
if (config.prefix) {
key = autoAddPreFix(key);
}
window[config.type].removeItem(key);
};
clearStorage 清空存储列表
const clearStorage = () => {
window[config.type].clear();
};
autoAddPreFix 基于全局配置的prefix参数添加前缀
const autoAddPreFix = (key: string) => {
//添加前缀,保持浏览器Application视图唯一性
const prefix = config.prefix || '';
return `${
prefix}_${
key}`;
};
这是一个不导出的函数,作为整体封装的内部工具函数,在setStorage、getStorageFromKey、removeStorageFromKey会使用到。
导出函数列表
提供了6个函数的处理能力,足够应对实际业务的大部分操作。
export {
setStorage,
getStorageFromKey,
getAllStorage,
getStorageLength,
removeStorageFromKey,
clearStorage,
};
使用
在实际业务中使用,则将函数导入即可,这里先看下笔者的文件目录吧:
实际使用:
import {
setStorage,
getStorageFromKey,
getAllStorage,
getStorageLength,
removeStorageFromKey,
clearStorage
} from '../../_util/storage/config'
setStorage('name', 'fx', 1)
setStorage('age', {
now: 18 }, 100000)
setStorage('history', [1, 2, 3], 100000)
console.log(getStorageFromKey('name'))
removeStorageFromKey('name')
console.log(getStorageFromKey('name'))
console.log(getStorageLength());
console.log(getAllStorage());
clearStorage();
接下来看一下浏览器视图:

可以看到,key经过处理加入了config.prefix的前缀,有了唯一性。
value经过了加密处理。
再看一下通过get方式获取到的控制台值输出:

很完美,实际业务会把前缀清除返回进行处理,视图中有前缀绑定以及加密处理,保证了本地存储的安全性。
完整代码
config.ts:
import {
encrypt, decrypt } from './encry';
import {
globalConfig } from './interface';
const config: globalConfig = {
type: 'localStorage', //存储类型,localStorage | sessionStorage
prefix: 'react-view-ui_0.0.1', //版本号
expire: 24 * 60, //过期时间,默认为一天,单位为分钟
isEncrypt: true, //支持加密、解密数据处理
};
const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => {
//设定值
if (value === '' || value === null || value === undefined) {
//空值重置
value = null;
}
if (isNaN(expire) || expire < 0) {
//过期时间值合理性判断
throw new Error('Expire must be a number');
}
const data = {
value, //存储值
time: Date.now(), //存储日期
expire: Date.now() + 1000 * 60 * expire, //过期时间
};
//是否需要加密,判断装载加密数据或原数据
window[config.type].setItem(
autoAddPreFix(key),
config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data),
);
return true;
};
const getStorageFromKey = (key: string) => {
//获取指定值
if (config.prefix) {
key = autoAddPreFix(key);
}
if (!window[config.type].getItem(key)) {
//不存在判断
return null;
}
const storageVal = config.isEncrypt
? JSON.parse(decrypt(window[config.type].getItem(key) as string))
: JSON.parse(window[config.type].getItem(key) as string);
const now = Date.now();
if (now >= storageVal.expire) {
//过期销毁
removeStorageFromKey(key);
return null;
//不过期回值
} else {
return storageVal.value;
}
};
const getAllStorage = () => {
//获取所有值
const storageList: any = {
};
const keys = Object.keys(window[config.type]);
keys.forEach((key) => {
const value = getStorageFromKey(autoRemovePreFix(key));
if (value !== null) {
//如果值没有过期,加入到列表中
storageList[autoRemovePreFix(key)] = value;
}
});
return storageList;
};
const getStorageLength = () => {
//获取值列表长度
return window[config.type].length;
};
const removeStorageFromKey = (key: string) => {
//删除值
if (config.prefix) {
key = autoAddPreFix(key);
}
window[config.type].removeItem(key);
};
const clearStorage = () => {
window[config.type].clear();
};
const autoAddPreFix = (key: string) => {
//添加前缀,保持唯一性
const prefix = config.prefix || '';
return `${
prefix}_${
key}`;
};
const autoRemovePreFix = (key: string) => {
//删除前缀,进行增删改查
const lineIndex = config.prefix.length + 1;
return key.substr(lineIndex);
};
export {
setStorage,
getStorageFromKey,
getAllStorage,
getStorageLength,
removeStorageFromKey,
clearStorage,
};
encry.ts:
import CryptoJS from 'crypto-js';
const SECRET_KEY = CryptoJS.enc.Utf8.parse('3333e6e143439161'); //十六位十六进制数作为密钥
const SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a'); //十六位十六进制数作为密钥偏移量
const encrypt = (data: object | string): string => {
//加密
if (typeof data === 'object') {
try {
data = JSON.stringify(data);
} catch (e) {
throw new Error('encrypt error' + e);
}
}
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();
};
const decrypt = (data: string) => {
//解密
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();
};
export {
encrypt, decrypt };
interface.ts:
interface globalConfig {
type: 'localStorage' | 'sessionStorage';
prefix: string;
expire: number;
isEncrypt: boolean;
}
export type {
globalConfig };
总结
前端开发中直接使用明文存储在本地是比较常见的一件事情同时也是不安全的一件事,对本地存储进行二次封装可以提高安全性,并且有了API的支持,可以在本地存储操作时更加简单。
边栏推荐
- 118. summary of basic knowledge of typescript (data type, interface, abstract class, inheritance, attribute encapsulation, modifier)
- Leetcode: print the common part of two ordered linked lists
- Stm32mp1 cortex M4 Development Part 9: expansion board air temperature and humidity sensor control
- R language sets na Rm=true parameter, deleting missing values in calculation and analysis to obtain valid calculation results (excluding missing values from analyses)
- [vs], [usage problem], [solution] when VS2010 is opened, it stays in the startup interface
- Junit5 unit test
- 基于Retrotfit2.1+Material Design+ijkplayer开发的一个APP
- An app developed based on retrotfit2.1+material design+ijkplayer
- Storage of floating point numbers in C language in memory
- 如何选择嵌入式练手项目、嵌入式开源项目大全
猜你喜欢

应用配置管理,基础原理分析

ADO. Net - invalid size for size property, 0 - ado NET - The Size property has an invalid size of 0

stm32mp1 Cortex M4开发篇12:扩展板震动马达控制

111. solve the problem of prohibiting scripts from running on vs code. For more information, see error reporting

PingCAP 入选 2022 Gartner 云数据库“客户之声”,获评“卓越表现者”最高分

1. is god horse a meta universe?

Float floating layout clear floating
![[actual combat] STM32 FreeRTOS migration series tutorial 7: FreeRTOS event flag group](/img/1c/10add042271c11cd129ddfce66f719.jpg)
[actual combat] STM32 FreeRTOS migration series tutorial 7: FreeRTOS event flag group

How to connect the Internet - FTTH

Storage of floating point numbers in C language in memory
随机推荐
Ali has been working for 8 years. This learning note is left when he reaches P8. He has helped his friends get 10 offers
Binary search (integer binary)
The internal structure of MySQL and how an SQL statement is executed
Common basic functions of R language: call the data editor with edit function to manually customize and edit the data object without changing the content of the original data object, and call the data
Retrofit扩展阅读
The R language uses the sink function to export the string to the txt file in the specified directory. If no directory is specified, it will be output to the current working dir
A command starts the monitoring journey!
R language through rprofile Site file, user-defined configuration of R language development environment startup parameters, shutdown parameters, user-defined specified cran local image source download
一条命令开启监控之旅!
\Processing method of ufeff
How to use ADB shell to query process traffic
Form Validation
Shortcut keys accumulated when using various software
adb使用技巧和usb通信原理
Float floating layout clear floating
【云驻共创】企业数字化加速“新智造”
R language uses as The date function converts a single character variable to date data and specifies the format of the data format conversion
Alibaba P6 employees came to a small company for an interview and asked for an annual salary increase of 500000 yuan. How dare you speak
High precision calculation
110. JS event loop and setimmediate, process.nexttick