web-storage-cache 核心知识汇总(Vue3 + CRM 本地存储实战)
web-storage-cache 是对浏览器 localStorage/sessionStorage 的增强封装库,解决了原生存储的核心痛点 ——过期时间、数据类型自动转换、命名空间隔离、批量操作,在 CRM 系统中主要用于「用户偏好设置」「临时筛选条件」「token 缓存」「本地字典数据」等场景,是本地存储的 “增强版方案”。
一、核心定位(为什么选 web-storage-cache?)
原生 localStorage 痛点 & web-storage-cache 解决方案:
表格
|
原生 localStorage 问题 |
web-storage-cache 优势 |
CRM 适配场景 |
|
仅支持字符串存储(需手动 JSON 转换) |
自动支持 Object/Array/Number/Boolean 类型 |
缓存客户筛选条件(对象)、订单列表(数组) |
|
无过期时间(数据永久存储) |
支持设置过期时间(秒 / 毫秒 / 日期) |
缓存临时 token(2 小时过期)、验证码(5 分钟过期) |
|
无命名空间(易键名冲突) |
支持命名空间隔离,不同模块数据分开存储 |
客户模块 / 订单模块 / 系统模块数据隔离 |
|
无批量操作(需循环读写) |
支持批量设置 / 获取 / 删除数据 |
批量缓存字典数据(行业类型、订单状态) |
|
无加密(敏感数据明文存储) |
可结合加密库实现加密存储(扩展) |
缓存用户手机号、临时凭证等敏感数据 |
二、快速集成(Vue3 + Vite 项目)
1. 安装依赖
bash
运行
# 核心库
npm install web-storage-cache -S
# TS 类型(可选,社区维护)
npm install @types/web-storage-cache -D
2. 封装全局存储工具(CRM 首选)
封装统一的存储工具类,统一配置命名空间、过期时间,结合加密实现敏感数据保护:
typescript
运行
// src/utils/storage.ts
import WebStorageCache from 'web-storage-cache';
import { aesEncrypt, aesDecrypt } from '@/utils/crypto'; // 之前封装的加密工具
// 初始化 WebStorageCache 实例
// 配置1:命名空间(避免不同项目/模块键名冲突)
// 配置2:存储类型(localStorage/sessionStorage)
const cache = new WebStorageCache({
namespace: 'crm_2026_', // CRM 专属命名空间
storage: 'localStorage', // 默认用 localStorage,临时数据用 sessionStorage
exp: 0 // 默认永不过期(按需覆盖)
});
// 临时存储实例(sessionStorage,页面关闭即失效)
const sessionCache = new WebStorageCache({
namespace: 'crm_temp_',
storage: 'sessionStorage'
});
/**
* 通用存储方法(支持加密)
* @param key 存储键名
* @param value 存储值(任意类型)
* @param expire 过期时间(秒,0=永不过期)
* @param isEncrypt 是否加密(默认 false)
* @param isSession 是否用 sessionStorage(默认 false)
*/
export const setCache = (
key: string,
value: any,
expire = 0,
isEncrypt = false,
isSession = false
) => {
const targetCache = isSession ? sessionCache : cache;
// 敏感数据加密
const finalValue = isEncrypt ? aesEncrypt(value) : value;
// 设置存储(支持过期时间)
targetCache.set(key, finalValue, { exp: expire });
};
/**
* 通用获取方法(支持解密)
* @param key 存储键名
* @param isEncrypt 是否加密存储(需和 set 一致)
* @param isSession 是否用 sessionStorage
* @returns 存储值(自动转换类型)
*/
export const getCache = (
key: string,
isEncrypt = false,
isSession = false
) => {
const targetCache = isSession ? sessionCache : cache;
const value = targetCache.get(key);
// 过期/不存在返回 null
if (value === null || value === undefined) return null;
// 解密敏感数据
return isEncrypt ? aesDecrypt(value) : value;
};
/**
* 删除指定缓存
* @param key 存储键名
* @param isSession 是否用 sessionStorage
*/
export const removeCache = (key: string, isSession = false) => {
const targetCache = isSession ? sessionCache : cache;
targetCache.remove(key);
};
/**
* 批量设置缓存
* @param data 键值对对象 { key1: value1, key2: value2 }
* @param expire 统一过期时间(秒)
* @param isSession 是否用 sessionStorage
*/
export const setCacheBatch = (
data: Record<string, any>,
expire = 0,
isSession = false
) => {
const targetCache = isSession ? sessionCache : cache;
Object.keys(data).forEach(key => {
targetCache.set(key, data[key], { exp: expire });
});
};
/**
* 批量获取缓存
* @param keys 键名数组
* @param isSession 是否用 sessionStorage
* @returns 键值对对象
*/
export const getCacheBatch = (keys: string[], isSession = false) => {
const targetCache = isSession ? sessionCache : cache;
const result: Record<string, any> = {};
keys.forEach(key => {
result[key] = targetCache.get(key);
});
return result;
};
/**
* 清空指定命名空间下的所有缓存
* @param isSession 是否清空 sessionStorage
*/
export const clearCache = (isSession = false) => {
const targetCache = isSession ? sessionCache : cache;
targetCache.clear();
};
/**
* 获取缓存剩余过期时间(秒)
* @param key 存储键名
* @param isSession 是否用 sessionStorage
* @returns 剩余秒数(-1=永不过期,0=已过期)
*/
export const getCacheExpire = (key: string, isSession = false) => {
const targetCache = isSession ? sessionCache : cache;
return targetCache.getExpire(key);
};
// 导出原始实例(自定义扩展用)
export { cache, sessionCache };
三、CRM 实战场景(核心用法)
场景 1:缓存用户 Token(带过期时间 + 加密)
CRM 登录后缓存 Token,设置 2 小时过期,且加密存储避免明文泄露:
vue
<script setup lang="ts">
import { setCache, getCache, removeCache } from '@/utils/storage';
import { useUserStore } from '@/stores/user';
const userStore = useUserStore();
// 登录成功后缓存 Token
const handleLoginSuccess = (tokenInfo) => {
// Token 加密存储,2小时(7200秒)过期
setCache('access_token', tokenInfo.accessToken, 7200, true);
setCache('refresh_token', tokenInfo.refreshToken, 86400, true); // 刷新令牌 24 小时过期
// 缓存用户基本信息(不加密,永不过期)
setCache('user_info', {
id: tokenInfo.userId,
name: tokenInfo.userName,
role: tokenInfo.role
});
};
// 初始化:从缓存获取 Token
const initToken = () => {
const accessToken = getCache('access_token', true);
const userInfo = getCache('user_info');
if (accessToken && userInfo) {
userStore.setToken(accessToken);
userStore.setUserInfo(userInfo);
}
};
// 退出登录:清除 Token 缓存
const handleLogout = () => {
removeCache('access_token');
removeCache('refresh_token');
removeCache('user_info');
userStore.clearToken();
};
// 页面初始化时调用
initToken();
</script>
场景 2:缓存客户筛选条件(临时存储)
CRM 客户列表的筛选条件(如行业、地区、状态)缓存到 sessionStorage,页面刷新不丢失,关闭页面清空:
vue
<template>
<div class="customer-filter">
<el-select v-model="filterForm.industry" placeholder="选择行业" @change="saveFilter">
<el-option label="电商" value="ecommerce" />
<el-option label="金融" value="finance" />
</el-select>
<el-select v-model="filterForm.region" placeholder="选择地区" @change="saveFilter">
<el-option label="华东" value="east" />
<el-option label="华北" value="north" />
</el-select>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { setCache, getCache } from '@/utils/storage';
// 筛选表单
const filterForm = ref({
industry: '',
region: '',
status: 'active'
});
// 保存筛选条件到 sessionStorage(临时存储,页面关闭失效)
const saveFilter = () => {
setCache('customer_filter', filterForm.value, 0, false, true);
};
// 初始化:从缓存恢复筛选条件
onMounted(() => {
const cachedFilter = getCache('customer_filter', false, true);
if (cachedFilter) {
filterForm.value = { ...filterForm.value, ...cachedFilter };
// 恢复筛选后立即查询数据
// queryCustomerList(filterForm.value);
}
});
</script>
场景 3:缓存字典数据(批量 + 长期)
CRM 中的通用字典数据(如订单状态、客户类型)批量缓存,设置 1 天过期,减少接口请求:
vue
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { setCacheBatch, getCacheBatch, getCacheExpire } from '@/utils/storage';
import { getDictData } from '@/api/common';
// 字典数据
const dictData = ref({
orderStatus: [],
customerType: [],
industryType: []
});
// 加载字典数据
const loadDictData = async () => {
// 1. 先从缓存获取
const cachedDict = getCacheBatch(['orderStatus', 'customerType', 'industryType']);
const expire = getCacheExpire('orderStatus');
// 2. 缓存未过期且有数据,直接使用
if (expire > 0 && cachedDict.orderStatus?.length) {
dictData.value = cachedDict;
return;
}
// 3. 缓存过期/无数据,从接口获取
const res = await getDictData(['orderStatus', 'customerType', 'industryType']);
dictData.value = res.data;
// 4. 批量缓存字典数据,1天(86400秒)过期
setCacheBatch(res.data, 86400);
};
onMounted(() => {
loadDictData();
});
</script>
场景 4:缓存临时表单数据(防丢失)
CRM 新建订单 / 客户时,缓存临时表单数据,防止页面刷新 / 意外关闭导致数据丢失:
vue
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue';
import { setCache, getCache, removeCache } from '@/utils/storage';
// 新建客户表单
const customerForm = ref({
name: '',
phone: '',
industry: '',
address: '',
remark: ''
});
// 表单是否提交中
const submitting = ref(false);
// 监听表单变化,实时缓存(5分钟过期)
watch(
customerForm,
(newVal) => {
if (!submitting.value) {
setCache('temp_customer_form', newVal, 300, false, true); // 300秒=5分钟
}
},
{ deep: true, immediate: true }
);
// 初始化:恢复临时表单数据
onMounted(() => {
const cachedForm = getCache('temp_customer_form', false, true);
if (cachedForm) {
customerForm.value = cachedForm;
}
});
// 提交表单:成功后清除临时缓存
const submitForm = async () => {
submitting.value = true;
try {
// await createCustomer(customerForm.value);
removeCache('temp_customer_form', true); // 清除临时缓存
// ElMessage.success('客户创建成功');
} catch (err) {
// ElMessage.error('创建失败');
} finally {
submitting.value = false;
}
};
</script>
四、核心避坑点
1. 过期时间单位错误
- 问题:设置过期时间时把 “毫秒” 当成 “秒”,导致数据立即过期;
- 解决方案:
-
- web-storage-cache 的
exp参数单位是秒(如 2 小时 = 7200 秒); - 如需用毫秒,可转换:
exp: ms / 1000。
- web-storage-cache 的
2. 命名空间冲突
- 问题:多个项目 / 环境共用同一域名,缓存键名冲突;
- 解决方案:
-
- 初始化时设置唯一
namespace(如crm_prod_/crm_test_); - 不同模块用不同子前缀(如
customer_/order_)。
- 初始化时设置唯一
3. 加密 / 解密不匹配
- 问题:存储时加密,获取时未解密,导致数据是加密字符串;
- 解决方案:
-
setCache和getCache的isEncrypt参数必须一致;- 敏感数据(Token / 手机号)强制加密存储。
4. 大数据存储导致性能问题
- 问题:缓存大量数组 / 对象(如上万条订单数据),导致页面卡顿;
- 解决方案:
-
- 只缓存核心数据,而非全量数据;
- 定期清理过期缓存(如登录时清理无用数据);
- 大数据优先用后端分页,而非本地缓存。
5. 浏览器禁用 localStorage
- 问题:部分浏览器 / 隐私模式禁用本地存储,调用
setCache报错; - 解决方案:
-
- 封装容错逻辑,捕获存储异常:typescript运行
export const setCache = (key, value, expire = 0, isEncrypt = false, isSession = false) => {
try {
const targetCache = isSession ? sessionCache : cache;
const finalValue = isEncrypt ? aesEncrypt(value) : value;
targetCache.set(key, finalValue, { exp: expire });
} catch (err) {
console.warn('本地存储失败:', err);
// 降级方案:用内存临时存储
window.__tempCache = window.__tempCache || {};
window.__tempCache[key] = value;
}
};
五、扩展功能(CRM 进阶)
1. 缓存自动刷新(如 Token 刷新)
监听 Token 过期前 5 分钟自动刷新:
typescript
运行
// 检查 Token 剩余时间,提前刷新
const checkTokenExpire = () => {
const expire = getCacheExpire('access_token');
// 剩余时间小于 5 分钟(300秒),触发刷新
if (expire > 0 && expire < 300) {
// refreshTokenApi().then(res => {
// setCache('access_token', res.accessToken, 7200, true);
// });
}
};
// 定时检查(每分钟一次)
setInterval(checkTokenExpire, 60 * 1000);
2. 多环境缓存隔离
根据环境变量设置不同命名空间:
typescript
运行
const namespace = import.meta.env.VITE_ENV === 'production'
? 'crm_prod_'
: import.meta.env.VITE_ENV === 'test'
? 'crm_test_'
: 'crm_dev_';
const cache = new WebStorageCache({
namespace,
storage: 'localStorage'
});
六、总结(CRM 开发最佳实践)
- 封装统一工具:所有本地存储通过封装的
setCache/getCache操作,统一处理加密、过期、命名空间; - 场景化存储策略:
-
- 永久数据(用户偏好):localStorage + 无过期;
- 临时数据(筛选条件 / 表单):sessionStorage + 短期过期;
- 敏感数据(Token / 手机号):加密存储 + 合理过期;
- 字典数据:批量缓存 + 1 天过期(减少接口请求);
- 容错与降级:处理浏览器禁用存储、大数据存储异常等场景,避免页面崩溃;
- 性能优化:
-
- 避免缓存超大数据,优先后端分页;
- 定期清理过期缓存,减少存储占用;
- 表单数据实时缓存,防止意外丢失;
- 安全注意:
-
- 敏感数据必须加密存储,避免明文泄露;
- Token 等核心数据设置合理过期时间,降低泄露风险。
web-storage-cache 是 Vue3 + CRM 本地存储的 “增强工具”,解决了原生存储的所有痛点;封装后的工具类可直接复用,兼顾安全性、易用性和性能,能完美支撑 CRM 系统的所有本地存储需求。
Vue3 + Pinia 开发者会纠结的点 ——并非完全不需要 web-storage-cache,而是两者适用场景不同,可互补而非互替。下面我会从「核心差异」「场景选择」「最佳实践」三个维度讲清楚,帮你判断何时该用 Pinia 持久化插件,何时该用 web-storage-cache。
一、核心差异:Pinia 持久化插件 vs web-storage-cache
先看两者的核心定位和能力对比,就能明白为什么不能完全替代:
表格
|
特性 |
Pinia-plugin-persistedstate(Pinia 持久化插件) |
web-storage-cache |
|
核心定位 |
专为 Pinia Store 设计,自动同步 Store 数据到本地存储 |
通用本地存储增强工具,独立于框架,操作原生 localStorage/sessionStorage |
|
数据粒度 |
以「Store 模块」为单位持久化(如 整个模块) |
以「单个键值对」为单位,支持精细化控制(如只存一个 、一个筛选条件) |
|
过期时间 |
无原生支持(需手动扩展) |
核心能力,支持秒 / 毫秒 / 日期级过期,精准控制单条数据有效期 |
|
命名空间隔离 |
需手动给不同 Store 加前缀,易冲突 |
初始化时支持全局命名空间,天然隔离不同模块 / 项目数据 |
|
加密能力 |
无原生支持(需手动结合加密库) |
可无缝结合加密工具,单条数据可按需加密(如 Token 加密,普通数据不加密) |
|
批量操作 |
无原生支持(需循环操作 Store) |
支持批量设置 / 获取 / 删除,适合字典数据等批量缓存场景 |
|
容错处理 |
无原生容错,存储失败会导致 Store 同步异常 |
可封装容错逻辑,适配浏览器禁用本地存储的场景 |
|
适用场景 |
全局状态持久化(如用户信息、全局主题、侧边栏状态) |
精细化本地存储(如临时筛选条件、单条 Token、表单草稿、字典缓存) |
二、什么时候可以只用 Pinia 持久化插件?
如果你的 CRM 项目中,本地存储需求仅满足以下场景,确实可以不用 web-storage-cache:
- 只需要持久化 Pinia Store 中的全局状态:比如
userStore(用户信息)、appStore(主题 / 布局)、cartStore(购物车,若有),且这些状态不需要设置过期时间; - 无需精细化控制单条数据:比如整个
userStore要么全存,要么全清,不需要只存其中的token而不存userName; - 无加密 / 过期 / 批量操作需求:比如存储的都是非敏感数据(如主题色、侧边栏折叠状态),不需要加密,也不需要设置过期。
示例:用 Pinia 持久化插件实现用户信息持久化(无过期)
typescript
运行
// stores/user.ts
import { defineStore } from 'pinia';
import { createPersistedState } from 'pinia-plugin-persistedstate';
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
userName: '',
role: ''
}),
actions: {
setToken(token) {
this.token = token;
},
clearUser() {
this.$reset();
}
},
// 持久化配置:默认存 localStorage,以 store 名为键
persist: true
});
// main.ts 注册插件
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
三、什么时候必须用 web-storage-cache?
只要你的 CRM 项目有以下需求,Pinia 持久化插件就满足不了,必须用 web-storage-cache:
场景 1:需要给单条数据设置过期时间
比如:
- Token 需 2 小时过期,刷新 Token 需 24 小时过期;
- 表单草稿需 5 分钟过期(避免长期缓存无效数据);
- 字典数据需 1 天过期(定期刷新最新字典)。
Pinia 持久化插件只能同步 Store 数据,但无法给 userStore.token 单独设置 2 小时过期 —— 要么整个 userStore 永不过期,要么手动写大量代码扩展过期逻辑,远不如 web-storage-cache 简洁:
typescript
运行
// web-storage-cache 一行设置 Token 过期,简单高效
setCache('access_token', token, 7200, true); // 7200秒=2小时,加密存储
场景 2:需要精细化控制单条数据,而非整个 Store
比如:
- 客户列表的筛选条件(仅缓存
industry/region/status三个字段,而非整个customerStore); - 临时表单草稿(仅缓存当前编辑的客户表单,不需要关联 Store);
- 单独缓存一个验证码(60 秒过期),无需放入 Store。
这些场景下,用 Pinia 持久化插件会 “大材小用”—— 为了存一个筛选条件,要新建一个 Store,还要配置持久化,反而增加代码冗余;而 web-storage-cache 可直接操作单条数据,更轻量。
场景 3:需要加密 / 批量操作 / 命名空间隔离
比如:
- Token 需加密存储,而用户昵称无需加密(web-storage-cache 可单条数据按需加密,Pinia 插件需整个 Store 加密 / 解密);
- 批量缓存 10 个字典数据(web-storage-cache 批量设置,Pinia 需循环给 Store 赋值);
- 多环境 / 多项目共用域名(web-storage-cache 命名空间隔离,避免缓存冲突,Pinia 插件需手动加前缀)。
场景 4:非 Store 相关的临时存储
比如:
- 页面级临时数据(如当前页面的分页页码、排序方式),不需要全局共享,仅页面刷新保留;
- 下载 / 上传的临时进度,不需要放入 Store,仅本地临时存储。
四、CRM 项目最佳实践:两者结合使用
在实际的 CRM 开发中,最优方案是两者结合,各司其职:
1. Pinia 持久化插件:负责「全局状态」的持久化
- 存储范围:需要全局共享、无过期需求、非敏感的状态(如用户基本信息、全局主题、侧边栏折叠状态、系统配置);
- 优势:自动同步 Store 和本地存储,无需手动调用
set/get,符合 Vue 响应式思维。
2. web-storage-cache:负责「精细化本地存储」
- 存储范围:
-
- 敏感数据(Token、刷新令牌):加密 + 精准过期;
- 临时数据(筛选条件、表单草稿、验证码):sessionStorage + 短期过期;
- 批量数据(字典缓存):批量操作 + 1 天过期;
- 非全局数据(页面级临时状态):独立于 Store,轻量存储。
示例:CRM 项目中的典型结合方式
typescript
运行
// 1. Pinia 持久化:存储全局用户基本信息(无过期,非敏感)
export const useUserStore = defineStore('user', {
state: () => ({
userName: '',
role: '',
avatar: ''
}),
persist: true // 自动存 localStorage
});
// 2. web-storage-cache:存储 Token(加密 + 2小时过期)
const handleLogin = async (loginForm) => {
const res = await loginApi(loginForm);
// Pinia 存非敏感信息
const userStore = useUserStore();
userStore.userName = res.userName;
userStore.role = res.role;
// web-storage-cache 存敏感 Token(加密 + 过期)
setCache('access_token', res.accessToken, 7200, true);
setCache('refresh_token', res.refreshToken, 86400, true);
};
// 3. web-storage-cache:存储客户筛选条件(sessionStorage + 无过期)
const saveFilter = () => {
setCache('customer_filter', filterForm.value, 0, false, true);
};
总结
关键点回顾
- Pinia 持久化插件≠web-storage-cache:前者是「全局状态同步工具」,后者是「精细化本地存储工具」,核心定位不同;
- 无需互相替代,而是互补:Pinia 插件管 “全局状态持久化”,web-storage-cache 管 “单条数据精细化存储”;
- 取舍原则:
-
- 只存 Pinia Store 全局状态、无过期 / 加密需求 → 只用 Pinia 插件;
- 需过期、加密、批量操作、精细化单条存储 → 必须用 web-storage-cache;
- 实际 CRM 项目 → 两者结合,各司其职,效率最高。
简单来说:Pinia 持久化插件解决 “Store 数据不丢” 的问题,web-storage-cache 解决 “本地存储用得爽” 的问题 —— 前者是框架层面的状态同步,后者是通用层面的存储增强,缺一不可。
你的这个想法很直接 ——“既然都要持久化,那把所有数据都塞到缓存里(不管是 Pinia 持久化还是 web-storage-cache)不就行了?” 理论上可以这么做,但在实际的 CRM 项目中,这么做会带来性能、维护、安全三大核心问题,反而得不偿失。下面我会从「为什么不能全存」「哪些能存 / 哪些不能存」「正确的存储策略」三个维度讲清楚,帮你避开这个坑。
一、为什么 “全存储到缓存里” 不可行?
1. 性能问题:缓存膨胀导致页面卡顿
浏览器的 localStorage/sessionStorage 有存储上限(一般是 5MB),CRM 系统的核心数据量远不止这个量级:
- 如果你把客户列表(几百 / 几千条)、订单明细、字典数据、表单草稿、Token、用户信息全塞进去,很快会触发存储上限,导致:
-
- 新数据存不进去,页面报
QuotaExceededError错误; - 每次读取 / 写入缓存时,浏览器需要遍历大量数据,页面加载速度变慢、操作卡顿;
- 甚至会影响其他依赖本地存储的功能(如第三方插件、埋点工具)。
- 新数据存不进去,页面报
举个例子:一个客户信息对象约 1KB,存 5000 条就占满 5MB 上限,而 CRM 中客户数远不止这个数 —— 全存缓存等于 “把数据库搬到浏览器里”,完全违背前端缓存的设计初衷。
2. 维护问题:数据冗余 + 同步混乱
“全存缓存” 会让缓存里的数据和后端数据、Pinia 状态形成 “三方同步”,极易出现不一致:
- 数据冗余:比如客户信息在 Pinia 里存一份、缓存里存一份、后端数据库里存一份,修改时要同时改三处,漏改一处就会导致数据错乱(比如改了 Pinia 里的客户名称,没更缓存,刷新页面又变回旧名称);
- 过期失控:所有数据都存缓存,你无法精准控制 “哪些数据该过期、哪些该永久存”—— 比如 Token 需 2 小时过期,客户筛选条件只需页面级临时存储,全存后要么全设过期(丢了永久数据),要么全不设过期(Token 长期有效,有安全风险);
- 调试困难:缓存里塞满了各种数据,排查问题时要翻几十上百个键值对,分不清哪些是有用的、哪些是冗余的,维护成本翻倍。
3. 安全问题:敏感数据泄露风险
CRM 里有大量敏感数据(客户手机号、身份证、订单金额、企业机密),如果 “全存缓存”:
- 缓存是明文存储在浏览器里的(即使加密,全存也会增加解密风险),一旦浏览器被劫持 / XSS 攻击,所有敏感数据都会泄露;
- 缓存数据没有 “权限隔离”:比如普通员工的账号能缓存到管理员的客户数据,导致权限越界;
- 过期数据未清理:比如离职员工的 Token 还存在缓存里,若未及时清理,可能被恶意利用。
二、缓存的核心原则:“按需存储” 而非 “全量存储”
前端缓存的本质是“临时缓存高频访问的少量核心数据”,而非 “存储全量数据”。针对 CRM 系统,我帮你梳理了「能存」和「不能存」的边界:
✅ 可以存到缓存里的(核心是 “少量、高频、非全量”)
表格
|
数据类型 |
存储方式 |
原因 |
|
全局状态(用户信息 / 主题) |
Pinia 持久化插件 |
少量、全局共享、无过期需求 |
|
敏感凭证(Token / 刷新令牌) |
web-storage-cache(加密 + 过期) |
少量、高频使用、需精准过期 |
|
临时数据(筛选条件 / 表单草稿) |
web-storage-cache(sessionStorage) |
页面级临时存储,关闭页面可清空 |
|
高频字典数据(订单状态 / 行业类型) |
web-storage-cache(批量 + 1 天过期) |
减少接口请求,1 天过期保证数据新鲜 |
|
用户偏好(分页大小 / 排序方式) |
Pinia 持久化插件 |
少量、个性化设置,无需频繁请求后端 |
❌ 绝对不能存到缓存里的
表格
|
数据类型 |
原因 |
|
全量列表数据(客户 / 订单列表) |
数据量大、易过期、占满存储上限 |
|
大文件 / 二进制数据(合同图片 / 附件) |
缓存不支持二进制存储,且体积远超上限 |
|
实时性要求高的数据(订单实时状态) |
缓存数据易滞后,需实时从后端获取 |
|
超敏感数据(客户身份证 / 银行卡) |
即使加密,缓存也有泄露风险,仅后端存储 |
|
临时会话数据(验证码 / 临时链接) |
应存在后端 Redis,前端仅存临时标识 |
三、CRM 项目的正确存储策略:“分层存储”
与其 “全存缓存”,不如按数据的 “使用场景 + 重要性” 做分层存储,既保证性能,又兼顾安全和维护性:
第一层:内存层(Pinia 非持久化)
- 存储内容:临时计算数据(如页面分页结果、筛选后的客户列表、表单临时校验状态);
- 特点:页面刷新 / 关闭即丢失,无需持久化,用完即弃;
- 优势:响应式、操作快,不占用缓存空间。
第二层:缓存层(Pinia 持久化 + web-storage-cache)
- 存储内容:少量核心持久化数据(Token、用户信息、字典数据、筛选条件);
- 规则:
-
- 全局状态用 Pinia 持久化(无过期、非敏感);
- 精细化数据用 web-storage-cache(加密、过期、批量操作);
- 缓存数据量控制在 1MB 以内,定期清理过期数据。
第三层:后端层(数据库 / Redis)
- 存储内容:全量业务数据(客户、订单、合同、附件)、超敏感数据、实时数据;
- 特点:前端仅在需要时通过接口获取,不缓存全量数据;
- 优势:数据安全、可扩展、支持权限控制,是 CRM 数据的 “唯一可信源”。
四、实操示例:CRM 正确的存储方式(对比 “全存” 和 “分层存”)
反面示例:全存缓存(错误做法)
typescript
运行
// ❶ 把全量客户列表存缓存(5000条,占满5MB)
setCache('all_customers', allCustomerList);
// ❷ 把订单明细存缓存(冗余)
setCache('order_detail_123', orderDetail);
// ❸ 把Token和客户身份证全存缓存(无加密、无过期)
setCache('token', token);
setCache('customer_idcard_456', idCard);
→ 结果:缓存爆满、页面卡顿、身份证泄露、Token 长期有效。
正面示例:分层存储(正确做法)
typescript
运行
// 第一层:内存层(Pinia 非持久化)—— 临时客户列表
export const useCustomerStore = defineStore('customer', {
state: () => ({
list: [], // 仅存当前页的客户列表(内存中,刷新丢失)
page: 1,
size: 10
})
});
// 第二层:缓存层
// ❶ Pinia 持久化:用户基本信息(非敏感、全局共享)
export const useUserStore = defineStore('user', {
state: () => ({ userName: '', role: '' }),
persist: true
});
// ❷ web-storage-cache:Token(加密+2小时过期)
setCache('access_token', token, 7200, true);
// ❸ web-storage-cache:客户筛选条件(sessionStorage+临时存储)
setCache('customer_filter', filterForm, 0, false, true);
// ❹ web-storage-cache:字典数据(批量+1天过期)
setCacheBatch(dictData, 86400);
// 第三层:后端层 —— 全量客户/订单数据,用时接口获取
const getCustomerList = async (params) => {
const res = await api.getCustomerList(params); // 后端分页返回10条
customerStore.list = res.data; // 仅存当前页到内存
};
→ 结果:缓存占用<1MB、页面流畅、敏感数据加密过期、数据与后端一致。
总结
关键点回顾
- 不是 “不能全存”,是 “全存必出问题”:浏览器缓存有上限、无权限控制、同步成本高,全存会导致性能卡顿、数据错乱、安全泄露;
- 缓存的核心是 “按需存储”:只存 “少量、高频、非全量” 的核心数据,全量数据必须从后端获取;
- 正确策略是 “分层存储”:
-
- 内存层(Pinia 非持久化)存临时计算数据;
- 缓存层(Pinia 持久化 + web-storage-cache)存少量核心持久化数据;
- 后端层存全量 / 敏感 / 实时数据(唯一可信源)。
简单来说:前端缓存是 “便利店”,只放常用的少量商品;后端数据库是 “仓库”,放所有商品 —— 你不可能把仓库里的东西全搬到便利店里,否则便利店会挤爆,反而没法正常营业。CRM 开发中,“合理缓存少量核心数据 + 按需请求后端全量数据”,才是兼顾性能和稳定性的最优解。
更多推荐

所有评论(0)