背景
很多人在用 localStorage
或 sessionStorage
的时候喜欢直接用,明文存储,直接将信息暴露在;浏览器中,虽然一般场景下都能应付得了且简单粗暴,但特殊需求情况下,比如设置定时功能,就不能实现。就需要对其进行二次封装,为了在使用上增加些安全感,那加密也必然是少不了的了。为方便项目使用,特对常规操作进行封装。
但是想当如果想要开发一个 npm
的库,个人认为最好还是使用 Typescript
来编写,因为它能在我们编码的时候已经用户在使用的时候提供更好的类型提示,方便进行开发已经代码的维护。
项目初始化
配置 Typescript
这么麻烦当然不自己从零开始啊,我们可以借助 create-neat
来帮我们快速创建,我们来看看它的文档 Github 文档
执行以下命令初始化项目:
npx create-neat local-storage
当出现以下界面的时候说明你命令能正确运行了:
因为我们要创建的是一个普通的 JavaScript
库,选择一个即可,如果你想开发一个 React
组件库可以选择第二个,第二个也是会为你创建一个可以开箱即用的模板。
当选择了第一个模板之后,我们就可以进行傻逼式的按 回车键
就可以了:
耐心等待项目创建完成,创建完成之后会出现以下界面:
最终项目生成的目录文件有如下结构所示:
├───? .husky/
│ ├───? commit-msg
│ └───? pre-commit
├───? .vscode/
│ └───? settings.json
├───? src/
│ └───? index.ts
├───? .eslintignore
├───? .eslintrc.json
├───? .gitignore
├───? .prettierignore
├───? .prettierrc.json
├───? commitlint.config.js
├───? package.json
├───? pnpm-lock.yaml
└───? tsconfig.json
项目初始化完成了,接下来我们就可以专心编码了。
代码实现
要想对存储信息进行加密,首先我们应该要接祖一个加密库,使用 npm
安装,如下:
pnpm add crypto-js
接下来看看代码设计吧!
types.ts
interface globalConfig {
type: "localStorage" | "sessionStorage";
prefix: string;
expire: number;
isEncrypt: boolean;
}
export type { globalConfig };
crypto.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 };
index.ts
import { encrypt, decrypt } from "./crypto";
import { globalConfig } from "./types";
const config: globalConfig = {
type: "localStorage", //存储类型,localStorage | sessionStorage
prefix: "react-view-ui_0.0.1", //版本号
expire: 24 * 60, //过期时间,默认为一天,单位为分钟
isEncrypt: true, //支持加密、解密数据处理
};
const setStorage = (
key: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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 = () => {
//获取所有值
// eslint-disable-next-line @typescript-eslint/no-explicit-any
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,
};
到这里代码就编辑完成了。
代码打包
代码编写完成之后,因为我们要写一个同时支持 ESM
和 UMD
的方式,所以我们要修改 package.json
文件中的 script
字段,如下代码所示:
"build": "rollup-script build --format esm,umd --name Local",
首先使用 npm build
执行代码打包,如果代码被顺利进行打包,最终生成的代码如下所示:
├───? crypto.d.ts
├───? index.d.ts
├───? index.mjs
├───? local.min.mjs
├───? local.min.mjs.map
├───? local.umd.development.cjs
├───? local.umd.development.cjs.map
├───? local.umd.production.min.cjs
├───? local.umd.production.min.cjs.map
└───? types.d.ts
上传 npm
在上传文件之前我们先对所有文件进行 git
提交。
当代码提交完成之后,你可以使用以下命令来根据你实际情况来更新你的 version
字段:
当执行这些命令之后它会自动为你生成 CHANGELOG
文件,也就是我们常说的更新日志:
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## 1.1.0 (2023-06-20)
### Features
- ? 几多天真的理想 几多找到是颓丧 6cf1020
这些步骤完成之后我们确保 package.json
文件中的 name
字段与 npm
仓库上的 名字不同:
因为我是拿来测试的,所以名字就随便起了一个,如果你们要真想拿来用的话一定要想个又骚又有意义的名字。
如果不存在的话我们执行 npm publish
发布:
发布成功……
使用
既然已经发布成功了,那么我们接下来就在项目中看看是否能正常使用,在项目中安装:
npm i local-storage-test-moment
下载完成后正常引入和使用:
import {
setStorage,
getStorageFromKey,
getAllStorage,
getStorageLength,
removeStorageFromKey,
clearStorage,
} from "local-storage-test-moment";
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());
查看控制台,正常输出:
完全没问题,npm
上的测试完全,没有问题 ???
总结
本文的 localstorage
封装代码全部来自 还在直接用 localStorage 么?全网最细:本地存储二次封装(含加密、解密、过期处理) 更详细的内容可以查看该文章。
更多关于 create-neat
的信息,可以查看该文档 Github 地址
如果该脚手架对你有帮助,欢迎 star ⭐️⭐️⭐️