useLocal / useSession

带 AES 加密的浏览器存储响应式封装:useLocal(localStorage,跨会话持久)与 useSession(sessionStorage,关标签页即清),以及对应的全量清空函数 clearLocal / clearSession。4 个导出全部 auto-import。

📌 前端缓存一律走这两个 util,禁止裸用 localStorage / sessionStorage / document.cookie(CACHE-001,blocker)。裸用漏三件事:漏 cachePrefix(多项目共用域名时 key 互撞串数据)、明文存储(无 AES)、模块顶层访问在 SSR 期 500。例外仅限第三方 SDK 自管的 storage。

工作原理

  1. 在 VueUse useLocalStorage / useSessionStorage 之上注入自定义 serializer:读写时透明调用 aes 的 decryptData / encryptData,密钥取 runtimeConfig.public.cacheAesKey。
  2. 键名自动拼接 runtimeConfig.public.cachePrefix,多项目共用域名时互不干扰。
  3. SSR 环境下降级为 initialValue,不访问 window —— 服务端也能安全调用。
  4. clearLocal / clearSession 在服务端为 no-op。

配置在 nuxt.config.ts 的 runtimeConfig.public(项目常量直接写实值,随代码走;运行时同名 NUXT_PUBLIC_* 进程环境变量仍可临时覆盖):

cachePrefix: 'c_'                 // 缓存 key 前缀
cacheAesKey: 'your-16-byte-key'   // AES 密钥(16 / 24 / 32 字节)

⚠️ cacheAesKey 在 runtimeConfig.public.*,会进客户端 bundle —— 加密仅作 obfuscation,不是安全屏障。详见客户端加密的安全边界。

⚠️ cacheAesKey 必须是 16 / 24 / 32 字节(按 utf8 字节数计)。非法长度时 CryptoJS 加密能产出密文、解密回空 —— 写入成功、读取永远失败、且无任何报错(静默数据丢失)。framework 在构建期与 dev 运行期都会检出并告警。

API

函数签名说明
useLocal<T>(key: string, initialValue: T) => RemovableReflocalStorage 响应式 Ref,直接赋值写入,.remove() 删除该 key
clearLocal() => void清空整个 localStorage(含非本项目 key,慎用);服务端 no-op
useSession<T>(key: string, initialValue: T) => RemovableRefsessionStorage 响应式 Ref,标签页关闭自动清除
clearSession() => void清空整个 sessionStorage;服务端 no-op

使用示例

useLocal 写读 + localStorage 密文对照

加载中...
// 用户偏好(长期保留 → useLocal)
const prefs = useLocal('ui-prefs', { theme: 'auto', lang: 'zh-CN' })
prefs.value.theme = 'dark' // 加密写入

// 多步表单草稿(关标签页即清 → useSession)
const draft = useSession('post-draft', '')
draft.value = '暂存的正文…'
draft.remove() // 删除该 key

// typed nullable:赋 null 即删除
const account = useLocal<AccountInfo | null>('account', null)
account.value = null

// 登出时全量清理
function logout() {
  clearLocal()
  clearSession()
}

生命周期选型(CACHE-002,warning)

这份数据…用
要跨会话保留 / 跨标签页共享(偏好、草稿箱、记住我)useLocal
一次性 / 单标签中转(多步表单草稿、列表→详情标志)useSession

典型翻车:长期偏好用了 useSession(关标签页数据没了);想跨标签页同步的状态用了 useSession(另一个标签页读不到)。

注意事项

  • clearLocal() / clearSession() 是全清、不按 cachePrefix 过滤(CACHE-003):内部就是裸 localStorage.clear() —— 多项目共用域名时会把其它项目的数据一起清掉。独占域名(绝大多数 applier)的 logout 全清可接受;共用域名 / 只清本模块时改用各 Ref 的 .remove() 精确删。
  • hydration mismatch:服务端首次渲染返回 initialValue,水合后切换到客户端实际值;若初值与实际不同且直接渲染进模板,可能触发 hydration 告警,可搭配 <ClientOnly> 规避。
  • 敏感字段(密码 / 私钥)禁止放 cookie(随每个请求上线路 + DevTools 可见),应放 useLocal(仅主动 JS 读取时使用)。

framework 以同名 auto-import 覆盖了 Nuxt 内置 useCookie(framework#199)——applier 裸写 useCookie(key, opts) 拿到的即 framework 加密版:键名自动拼 cachePrefix、值经 AES 透明加解密(与 useLocal / useSession 同一安全模型:obfuscation 非安全屏障),返回 WritableComputedRef<T | null>,赋 null 即删除。支持透传 maxAge / sameSite / secure / path / domain / default。

const token = useCookie<string>('token', { maxAge: 60 * 60 * 24 * 30, sameSite: 'lax', secure: true })
token.value = 'abc'   // 自动加密写入
token.value = null    // 删除 cookie

需要 Nuxt 原生明文版时显式 import { useCookie } from 'nuxt/app'(极少数场景,如读第三方写入的明文 cookie)。