将加密和解密函数应用到 Pinia 状态管理的核心是通过 Pinia 插件实现自动化加解密,让状态在本地存储时加密、读取时解密,且对业务代码无侵入。以下是分7步的具体落地步骤,每一步都包含可直接复用的代码和关键说明:
整体流程概览
graph TD
A[环境准备] --> B[编写加解密工具函数]
B --> C[开发Pinia加密插件]
C --> D[配置Pinia并注册插件]
D --> E[定义加密的Store]
E --> F[组件中使用Store]
F --> G[登出清理加密数据]
具体步骤(附代码+说明)
步骤1:环境准备(安装核心依赖)
首先安装 Pinia(状态管理)和 crypto-js(加解密算法),这是实现功能的基础:
# npm 安装
npm install pinia crypto-js
# yarn 安装
yarn add pinia crypto-js
# pnpm 安装
pnpm add pinia crypto-js
步骤2:编写加密解密工具函数(封装可复用逻辑)
创建独立的工具文件,封装 AES 加解密逻辑(核心函数,可全局复用):
// src/utils/crypto.js
import CryptoJS from 'crypto-js';
// 🔥 生产环境:密钥/IV必须从后端动态获取(登录后返回),切勿硬编码!
// 示例密钥(AES要求16/24/32字节,IV固定16字节)
let SECRET_KEY = CryptoJS.enc.Utf8.parse('temp-16byte-key!!'); // 临时密钥
let IV = CryptoJS.enc.Utf8.parse('temp-16byte-iv__'); // 临时初始向量
// 动态更新密钥(登录后调用,替换临时密钥)
export const updateCryptoKey = (newKey, newIV) => {
SECRET_KEY = CryptoJS.enc.Utf8.parse(newKey);
IV = CryptoJS.enc.Utf8.parse(newIV);
};
// 加密函数:任意数据 → 加密后的字符串
export const encrypt = (data) => {
if (!data) return '';
try {
// 统一转为JSON字符串(支持对象/数组类型)
const dataStr = typeof data === 'string' ? data : JSON.stringify(data);
// AES-CBC模式加密(更安全)
return CryptoJS.AES.encrypt(dataStr, SECRET_KEY, {
iv: IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
} catch (err) {
console.error('加密失败:', err);
return '';
}
};
// 解密函数:加密字符串 → 原始数据(还原类型)
export const decrypt = (encryptedStr) => {
if (!encryptedStr) return null;
try {
const decryptedBytes = CryptoJS.AES.decrypt(encryptedStr, SECRET_KEY, {
iv: IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
const decryptedStr = decryptedBytes.toString(CryptoJS.enc.Utf8);
// 尝试还原为JSON对象(原始是对象则解析,否则返回字符串)
return decryptedStr ? JSON.parse(decryptedStr) : null;
} catch (err) {
console.error('解密失败:', err);
return null;
}
};
步骤3:开发 Pinia 加密插件(核心:自动化加解密)
创建 Pinia 插件,实现「初始化解密加载状态 + 状态变化加密存储」的自动化逻辑:
// src/plugins/piniaEncrypt.js
import {
encrypt, decrypt } from '@/utils/crypto';
/**
* 创建Pinia加密插件
* @param {Object} options 配置项
* @param {Storage} options.storage 存储方式(localStorage/sessionStorage)
* @param {string[]} options.include 需要加密的Store名称(空则全部加密)
*/
export const createEncryptPlugin = (options = {
}) => {
const {
storage = localStorage, include = [] } = options;
// 插件核心逻辑:每个Store初始化时执行
return (context) => {
const {
store } = context;
const storeId = store.$id; // 当前Store的唯一标识(如user/settings)
// 只对指定Store加密(按需加密,减少性能损耗)
const needEncrypt = include.length === 0 || include.includes(storeId);
if (!needEncrypt) return;
// 1. 初始化:从本地存储读取加密数据 → 解密 → 同步到Store
const loadDecryptedState = () => {
const encryptedData = storage.getItem(`pinia_${
storeId}`);
if (encryptedData) {
const decryptedData = decrypt(encryptedData);
if (decryptedData) {
store.$patch(decryptedData); // 合并解密后的数据到状态
}
}
};
// 2. 状态变化时:加密 → 存储到本地(防抖避免频繁操作)
const saveEncryptedState = () => {
const currentState = store.$state; // 获取当前Store的完整状态
const encryptedData = encrypt(currentState);
storage.setItem(`pinia_${
storeId}`, encryptedData);
};
// 初始化加载解密数据
loadDecryptedState();
// 监听Store状态变化(防抖:100ms内多次变化只执行一次)
let debounceTimer;
store.$subscribe(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(saveEncryptedState, 100);
});
// 3. 给Store扩展手动方法(可选)
store.$encryptSave = saveEncryptedState; // 手动触发加密存储
store.$clearEncryptedStorage = () => {
// 手动清除加密数据
storage.removeItem(`pinia_${
storeId}`);
};
};
};
步骤4:配置 Pinia 并注册加密插件
在 Pinia 初始化文件中注册插件,指定加密规则(如只加密userStore):
// src/store/index.js
import {
createPinia } from 'pinia';
import {
createEncryptPlugin } from '@/plugins/piniaEncrypt';
// 创建Pinia实例
const pinia = createPinia();
// 注册加密插件:按需加密(只加密user Store)
pinia.use(createEncryptPlugin({
storage: localStorage, // 可选:sessionStorage(会话级存储)
include: ['user'] // 指定需要加密的Store名称
}));
export default pinia;
步骤5:定义需要加密的 Pinia Store
创建普通的 Store(无需关心加密逻辑,插件自动处理):
// src/store/user.js
import {
defineStore } from 'pinia';
// 定义Store(id必须和插件中include的名称一致:user)
export const useUserStore = defineStore('user', {
state: () => ({
token: '', // 敏感:登录令牌
userInfo: {
// 敏感:用户信息
id: '',
name: '',
phone: ''
}
}),
actions: {
// 登录:更新状态(插件自动加密存储)
login(data) {
this.token = data.token;
this.userInfo = data.userInfo;
},
// 登出:重置状态 + 清除加密存储
logout() {
this.$reset(); // 重置Store状态
this.$clearEncryptedStorage(); // 清除本地加密数据
}
}
});
步骤6:在组件中使用 Store(业务无感知)
组件中正常使用 Store,加密/解密由插件自动完成,无需额外操作:
<!-- src/views/Login.vue -->
<template>
<div>
<button @click="handleLogin">登录</button>
<button @click="handleLogout">登出</button>
</div>
</template>
<script setup>
import { useUserStore } from '@/store/user';
import { updateCryptoKey } from '@/utils/crypto';
// 模拟登录接口
import { loginApi } from '@/api/user';
const userStore = useUserStore();
// 登录逻辑
const handleLogin = async () => {
// 1. 调用登录接口,获取用户数据 + 动态密钥(生产环境必须)
const res = await loginApi({ username: 'test', password: '123456' });
const { token, userInfo, cryptoKey, cryptoIV } = res.data;
// 2. 更新加密密钥(替换临时密钥,核心安全操作)
updateCryptoKey(cryptoKey, cryptoIV);
// 3. 更新Store状态(插件自动加密存储到本地)
userStore.login({ token, userInfo });
};
// 登出逻辑
const handleLogout = () => {
userStore.logout(); // 重置状态 + 清除加密数据
};
</script>
步骤7:验证效果(可选)
打开浏览器开发者工具(F12)→ Application → Local Storage,查看存储的pinia_user值:
- 加密后:是一串乱码字符串(如
U2FsdGVkX1+...),无法直接读取; - 解密后:插件会自动将其还原为
{ token: "...", userInfo: {...} }并同步到Store。
总结(核心关键点)
- 工具层封装:加解密函数独立封装,通过
updateCryptoKey动态更新密钥(生产环境必须); - 插件自动化:Pinia 插件监听状态变化,自动完成「加密存储 + 解密加载」,业务代码无感知;
- 按需加密:通过
include配置只加密敏感Store(如user),减少性能损耗; - 安全收尾:登出时必须调用
$clearEncryptedStorage清除本地加密数据,防止信息泄露。
通过以上7步,即可将加解密逻辑无缝集成到 Pinia 状态管理中,既保证敏感数据安全,又不影响开发体验。