一杯茶时间带你封装一个带加密、解密、过期处理的localstorage 库并上传npm

956 阅读5分钟

背景

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

但是想当如果想要开发一个 npm 的库,个人认为最好还是使用 Typescript 来编写,因为它能在我们编码的时候已经用户在使用的时候提供更好的类型提示,方便进行开发已经代码的维护。

项目初始化

配置 Typescript 这么麻烦当然不自己从零开始啊,我们可以借助 create-neat 来帮我们快速创建,我们来看看它的文档 Github 文档

执行以下命令初始化项目:

npx create-neat local-storage

当出现以下界面的时候说明你命令能正确运行了: 20230620121109

因为我们要创建的是一个普通的 JavaScript 库,选择一个即可,如果你想开发一个 React 组件库可以选择第二个,第二个也是会为你创建一个可以开箱即用的模板。

当选择了第一个模板之后,我们就可以进行傻逼式的按 回车键 就可以了: 20230620121506

耐心等待项目创建完成,创建完成之后会出现以下界面: 20230620121551

最终项目生成的目录文件有如下结构所示:

├───📁 .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,
};

到这里代码就编辑完成了。

代码打包

代码编写完成之后,因为我们要写一个同时支持 ESMUMD 的方式,所以我们要修改 package.json 文件中的 script 字段,如下代码所示:

 "build": "rollup-script build --format esm,umd --name Local",

首先使用 npm build 执行代码打包,如果代码被顺利进行打包,最终生成的代码如下所示:

20230620123706

├───📄 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 提交。 20230620155305

当代码提交完成之后,你可以使用以下命令来根据你实际情况来更新你的 version 字段: 20230620155746

当执行这些命令之后它会自动为你生成 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 仓库上的 名字不同: 20230620160322

因为我是拿来测试的,所以名字就随便起了一个,如果你们要真想拿来用的话一定要想个又骚又有意义的名字。

如果不存在的话我们执行 npm publish 发布: 20230620160951

发布成功......

使用

既然已经发布成功了,那么我们接下来就在项目中看看是否能正常使用,在项目中安装:

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());

查看控制台,正常输出: 20230620162022

20230620162143

完全没问题,npm 上的测试完全,没有问题 💯💯💯

总结

本文的 localstorage 封装代码全部来自 还在直接用 localStorage 么?全网最细:本地存储二次封装(含加密、解密、过期处理) 更详细的内容可以查看该文章。

更多关于 create-neat 的信息,可以查看该文档 Github 地址

如果该脚手架对你有帮助,欢迎 star ⭐️⭐️⭐️