vue-vben-admin 缓存模块全解:基于策略模式的 StorageManager 异步存储架构

发布时间:2026/9/10 6:26:45
vue-vben-admin 缓存模块全解:基于策略模式的 StorageManager 异步存储架构 vue-vben-admin 缓存模块全解基于策略模式的 StorageManager 异步存储架构【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. Its fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin本篇技术指南以 vue-vben-admin 仓库内 缓存文档 为核心骨架深入讲解vben/utils中基于策略模式的异步存储管理方案StorageManager包括 LocalStorage、IndexedDB、Memory 三种存储驱动的选型与实现原理、TTL 过期机制、命名空间前缀隔离以及自定义 Driver 的完整实践。读完本文你将能够为项目任何模块接入统一、可切换、带过期能力的缓存 API并理解其底层源码设计与测试验证方式。概述为什么需要一个统一的异步存储层在 vue-vben-admin 这样的现代前端工程中数据缓存场景五花八门用户 Token、界面偏好、大型表格数据、离线缓存……它们对存储介质localStorage / IndexedDB / 内存、持久性、容量和性能的要求各不相同。如果业务代码直接操作底层 API一旦需要从 localStorage 切换到 IndexedDB或从浏览器端迁移到 SSR 环境改动成本会非常高。StorageManager正是为解决这一问题而设计它把存储介质差异抽象为一组固定的 KV 接口IStorageDriver并在上层统一处理命名空间前缀隔离与TTL 过期管理。所有方法均为异步返回 Promise因此底层 Driver 无论是同步的 localStorage 还是异步的 IndexedDB调用方代码完全无感可以无缝切换。在仓库中缓存模块的源码位于 packages/core/base/shared/src/cache并由 packages/utils/src/index.ts 通过export * from vben-core/shared/cache重新导出因此业务侧统一从vben/utils引入即可无需关心内部包结构。架构设计策略模式的三层职责模块整体遵循经典策略模式架构可抽象为下图┌───────────────────────────────────────────────┐ │ StorageManager │ │ ┌─────────────┐ ┌───────────────────────┐ │ │ │ Prefix 隔离 │ │ TTL 过期管理 │ │ │ └─────────────┘ └───────────────────────┘ │ ├───────────────────────────────────────────────┤ │ IStorageDriver │ ├──────────┬─────────────────┬──────────────────┤ │ Local │ IndexedDB │ Memory │ │ Storage │ Driver │ Driver │ │ Driver │ │ │ └──────────┴─────────────────┴──────────────────┘分层职责层级职责StorageManager命名空间前缀隔离、TTL 过期检查、统一对外 APIIStorageDriver纯粹的 KV 存取抽象接口各 Driver 实现对接具体存储引擎不感知前缀和 TTL从源码看这一分层非常严格storage-manager.ts 中StorageManager负责getFullKey拼接前缀与expiry计算过期时间戳等全部业务逻辑而IStorageDriver接口定义于 types.ts只暴露 5 个纯 KV 方法interface IStorageDriver { clear(): Promisevoid; getItemT(key: string): Promisenull | T; keys(): Promisestring[]; removeItem(key: string): Promisevoid; setItem(key: string, value: unknown): Promisevoid; }StorageItemT是驱动层实际存储的包装结构TTL 逻辑由StorageManager统一管理Driver 层完全不感知interface StorageItemT { expiry?: number; value: T; }快速开始基本使用不传driver时StorageManager会自动选择默认驱动浏览器在localStorage可用时使用LocalStorageDriver否则回退到MemoryStorageDriver如 Safari 隐私模式SSR/Node 环境使用MemoryStorageDriverimport { StorageManager } from vben/utils; const cache new StorageManager({ prefix: myapp }); // 存储数据 await cache.setItem(user, { name: 张三, age: 28 }); // 读取数据 const user await cache.getItem(user); // { name: 张三, age: 28 } // 带默认值读取 const settings await cache.getItem(settings, { theme: light }); // 如果不存在返回 { theme: light } // 删除数据 await cache.removeItem(user); // 清除当前前缀下所有数据 await cache.clear();这段默认驱动回退逻辑可以在源码createDefaultDriver()中看到storage-manager.ts 通过typeof window ! undefined window.localStorage判断浏览器环境一旦访问localStorage抛错如隐私模式会打印警告并回退到MemoryStorageDriver。带 TTL 过期setItem第三个参数为 TTL单位毫秒。过期后读取返回默认值惰性删除import { StorageManager } from vben/utils; const cache new StorageManager({ prefix: session }); // 设置 5 分钟后过期 await cache.setItem(token, abc123, 5 * 60 * 1000); // 5 分钟内可以正常读取 const token await cache.getItem(token); // abc123 // 5 分钟后自动返回 null const expiredToken await cache.getItem(token); // null // 主动清理所有过期项 await cache.clearExpiredItems();TTL 的实现细节在 storage-manager.tssetItem将过期时间戳Date.now() ttl写入expirygetItem读取时若发现raw.expiry Date.now() raw.expiry会先删除该项再返回默认值storage-manager.ts。存储驱动详解本地存储驱动默认LocalStorageDriver基于浏览器localStorage/sessionStorage数据持久化存储。import { LocalStorageDriver, StorageManager } from vben/utils; // 使用 localStorage默认 const cache new StorageManager({ driver: new LocalStorageDriver(), prefix: app, }); // 使用 sessionStorage const sessionCache new StorageManager({ driver: new LocalStorageDriver({ storageType: sessionStorage }), prefix: app, });特点同步 API 用 async 包装保持接口统一自动处理 JSON 序列化 / 反序列化数据损坏时自动清除并返回null存储上限约 5–10MB视浏览器而定适用场景用户偏好设置、小型配置数据、Token 存储源码佐证local-storage-driver.tsstorageType选项默认localStorage传入sessionStorage即切换存储对象构造函数在非浏览器环境typeof window undefined直接抛出TypeError提示应改用MemoryStorageDrivergetItem中JSON.parse失败会removeItem(key)清除损坏数据并返回null第 40-46 行。IndexedDB 驱动IndexedDBDriver基于浏览器 IndexedDB支持大容量结构化数据存储。import { IndexedDBDriver, StorageManager } from vben/utils; const cache new StorageManager({ driver: new IndexedDBDriver({ dbName: my-app-db, // 数据库名称默认 vben-storage dbVersion: 1, // 数据库版本默认 1 storeName: cache-store, // 对象存储名称默认 kv-store }), prefix: data, }); // 存储大量数据或复杂结构IndexedDB 原生支持结构化克隆 await cache.setItem(table-data, largeDataArray); await cache.setItem(config, { columns: [...], filters: [...], pagination: { page: 1, size: 20 }, });特点懒初始化首次操作时自动打开数据库无需手动调用init()存储容量大通常数百 MB 到 GB 级别支持结构化克隆可存储 Date、RegExp、Blob 等复杂类型天然异步不阻塞主线程适用场景离线数据缓存、大型表格数据、文件 / 图片缓存、复杂业务数据源码佐证indexeddb-driver.ts懒初始化通过dbPromise字段实现getDB()首次调用时执行openDB()并缓存 Promise后续所有操作复用同一个数据库连接若打开失败则重置dbPromise null允许下次调用重试第 108-117 行openDB()在upgradeneeded事件中自动createObjectStore(storeName)因此修改 objectStore 结构只需递增dbVersion第 119-133 行读写、清空均通过事务完成并监听complete/error/abort事件来 resolve / reject。内存存储驱动MemoryStorageDriver基于内存Map数据不持久化页面刷新即丢失。import { MemoryStorageDriver, StorageManager } from vben/utils; const cache new StorageManager({ driver: new MemoryStorageDriver(), prefix: test, });特点读写速度最快无浏览器 API 依赖数据随页面生命周期销毁适用场景单元测试、SSR 服务端渲染、临时运行时缓存源码中它是对原生Map的最薄封装memory-storage-driver.tsgetItem取不到值时返回null没有任何序列化开销因此性能最佳且可在任意 JS 环境运行。Driver 对比特性LocalStorageDriverIndexedDBDriverMemoryStorageDriver持久化✅✅❌容量5–10 MB数百 MB受内存限制速度快同步中等异步 I/O最快数据类型仅 JSON 可序列化结构化克隆任意 JS 对象浏览器支持所有现代浏览器所有现代浏览器任意环境阻塞主线程是否否适用场景配置、Token、小数据离线缓存、大数据测试、SSRAPI 参考StorageManager构造函数new StorageManager(options?: StorageManagerOptions)参数类型默认值说明driverIStorageDriver浏览器localStorage可用时new LocalStorageDriver()否则new MemoryStorageDriver()Safari 隐私模式、SSR/Node存储驱动实例prefixstring键前缀用于命名空间隔离值得注意的一个细节构造函数中若prefix为空且驱动是LocalStorageDriver源码会打印一条警告提示此时clear()/keys()将影响 localStorage 中所有条目storage-manager.ts。方法方法签名说明getItemgetItemT(key: string, defaultValue?: T \| null): PromiseT \| null获取存储项过期或不存在返回默认值setItemsetItem(key: string, value: unknown, ttl?: number): Promisevoid设置存储项可选 TTL毫秒removeItemremoveItem(key: string): Promisevoid删除指定存储项clearclear(): Promisevoid清除当前前缀下所有存储项clearExpiredItemsclearExpiredItems(): Promisevoid主动清理所有过期项keyskeys(): Promisestring[]返回当前前缀下所有键已去除前缀部分clear()与clearExpiredItems()的实现都遵循先keys()→ 按前缀过滤 → 逐个删除的流程storage-manager.ts且clear()使用Promise.all并发删除效率更高。IStorageDriver 接口自定义驱动需要实现此接口interface IStorageDriver { clear(): Promisevoid; getItemT(key: string): Promisenull | T; keys(): Promisestring[]; removeItem(key: string): Promisevoid; setItem(key: string, value: unknown): Promisevoid; }高级用法自定义 Driver实现IStorageDriver接口即可对接任意存储引擎。以 Cookie 为例import type { IStorageDriver } from vben/utils; class CookieStorageDriver implements IStorageDriver { async getItemT(key: string): Promisenull | T { const value getCookie(key); return value ? JSON.parse(value) : null; } async setItem(key: string, value: unknown): Promisevoid { setCookie(key, JSON.stringify(value)); } async removeItem(key: string): Promisevoid { deleteCookie(key); } async clear(): Promisevoid { clearAllCookies(); } async keys(): Promisestring[] { return getAllCookieNames(); } } const cache new StorageManager({ driver: new CookieStorageDriver(), prefix: ck, });只要把 5 个方法补齐StorageManager就会自动接管前缀拼接与 TTL 过期管理Cookie 驱动同样具备与内置驱动完全一致的完整能力。根据环境动态选择 Driverimport { IndexedDBDriver, LocalStorageDriver, MemoryStorageDriver, StorageManager, } from vben/utils; function createStorageManager(prefix: string) { // SSR 环境使用内存驱动 if (typeof window undefined) { return new StorageManager({ driver: new MemoryStorageDriver(), prefix, }); } // 大数据场景使用 IndexedDB if (needsLargeStorage()) { return new StorageManager({ driver: new IndexedDBDriver({ dbName: ${prefix}-db }), prefix, }); } // 默认使用 localStorage return new StorageManager({ prefix }); }命名空间隔离不同模块使用不同前缀互不干扰const userCache new StorageManager({ prefix: user }); const configCache new StorageManager({ prefix: config }); await userCache.setItem(profile, { name: 张三 }); await configCache.setItem(profile, { theme: dark }); await userCache.getItem(profile); // { name: 张三 } await configCache.getItem(profile); // { theme: dark } // 只清除 user 前缀的数据不影响 config await userCache.clear(); await configCache.getItem(profile); // { theme: dark }注意前缀隔离是逻辑隔离不同前缀的键只是名字不同user-profile与config-profile它们仍位于同一存储介质中但clear()/keys()只会操作匹配当前前缀的键互不干扰。定时清理过期数据由于过期数据只有在被读取惰性删除或主动调用clearExpiredItems()时才会被清理对于长期不访问的键建议按需做定时清理const cache new StorageManager({ prefix: app }); // 应用启动时清理一次 await cache.clearExpiredItems(); // 或者定时清理每 10 分钟 setInterval( async () { await cache.clearExpiredItems(); }, 10 * 60 * 1000, );数据存储格式StorageManager在 Driver 层存储的数据结构为interface StorageItemT { expiry?: number; // 过期时间戳毫秒undefined 表示永不过期 value: T; // 实际业务数据 }实际存储的 key 格式为{prefix}-{key}。例如prefix app、key user实际存储键为app-user。这一拼接逻辑由私有方法getFullKey统一完成storage-manager.tsDriver 层只见完整 key不见业务 key。过期策略采用惰性删除 主动清理双重策略策略触发时机说明惰性删除调用getItem时读取时检查过期过期则删除并返回默认值主动清理调用clearExpiredItems时遍历所有带前缀的 key删除已过期项这套策略在单元测试中被完整覆盖storage-manager.test.ts测试使用vi.useFakeTimers()与vi.advanceTimersByTime()快进时间验证了过期项读取时被清除、未过期项不受影响、多个不同 TTL 项各自独立过期、无 TTL 项永不过期等关键行为。同时测试还覆盖了默认值返回、键覆盖、clear()按前缀清除等基础能力可以作为你理解与验证该模块行为的参考。注意事项所有方法都是异步的—— 即使底层是同步的 localStorageAPI 也返回 Promise确保切换 Driver 时无需改动调用方。TTL 单位是毫秒——setItem(key, value, 60000)表示 60 秒后过期。IndexedDB 懒初始化—— 不需要手动调用init()或open()首次操作时自动打开数据库连接并复用。前缀隔离是逻辑隔离——clear()只清除当前前缀下的数据当前缀为空时clear()/keys()会操作所选 Driver 中的所有 key此时若使用LocalStorageDriver控制台会收到警告。LocalStorageDriver 错误处理—— JSON 解析失败时自动清除损坏数据并返回null。IndexedDB 版本升级—— 如需修改 objectStore 结构递增dbVersion当前实现在upgradeneeded事件中自动创建 objectStore。LocalStorageDriver 仅限浏览器—— 在 SSR / Node 环境直接实例化会抛错应选用MemoryStorageDriver或自定义驱动。结语StorageManager用一次优雅的策略模式抽象为 vue-vben-admin 解决了缓存场景中最棘手的两个问题存储介质可插拔与过期策略统一。无论你是想在业务中接入统一缓存 API、为大表格数据切换 IndexedDB还是为测试环境替换内存驱动都可以直接复用这套能力并依据本文给出的源码路径cache 目录深入理解其实现细节。【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. Its fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考