1. 为什么需要 Redis 风格的 LocalStorage 封装 在全栈开发的世界里,一个有趣的“割裂”现象常常让开发者感到困扰:后端处理缓存时,Redis 是当仁不让的主力,其简洁而强大的 API 深受喜爱;而到了前端,LocalStorage 则承担着本地数据存储的重任。问题来了——这两者的
在全栈开发的世界里,一个有趣的“割裂”现象常常让开发者感到困扰:后端处理缓存时,Redis 是当仁不让的主力,其简洁而强大的 API 深受喜爱;而到了前端,LocalStorage 则承担着本地数据存储的重任。问题来了——这两者的操作接口差异可不小。对于习惯了前端开发,又想向全栈领域拓展的同学来说,这往往意味着额外的学习成本和思维切换。
那么,有没有一种方法能弥合这道鸿沟呢?答案就是封装一个 Redis 风格的 LocalStorage 工具类。这么做,至少能带来几个显而易见的好处:
长期稳定更新的攒劲资源: >>>点此立即查看<<<

export abstract class CacheUtil {
/**
* 设置缓存
* @param key 缓存键
* @param value 缓存值
* @param ttl 过期时间(单位:秒),-1 表示永不过期
*/
static set(key: string, value: any, ttl: number = -1) {
const data = { value, ttl: ttl === -1 ttl : Date.now() + ttl * 1000 }
localStorage.setItem(key, JSON.stringify(data))
}
/**
* 获取缓存
* @param key 缓存键
* @param defaultValue 缓存不存在或过期时的默认值
* @returns 缓存值或默认值
*/
static get(key: string, defaultValue: T | null = null): T | null {
try {
const jsonStr = localStorage.getItem(key)
if (!jsonStr) return defaultValue
const data = JSON.parse(jsonStr)
if (data.ttl === -1 || Date.now() <= data.ttl) return data.value
localStorage.removeItem(key)
return defaultValue
} catch (error: unknown) {
localStorage.removeItem(key)
return defaultValue
}
}
/**
* 获取缓存剩余过期时间(秒)
* -1 = 永久有效
* -2 = 已过期/不存在
*/
static ttl(key: string): number {
try {
const item = localStorage.getItem(key)
if (!item) return -2
const data = JSON.parse(item)
if (data.ttl === -1) return -1
const remaining = data.ttl - Date.now()
return remaining > 0 ? Math.floor(remaining / 1000) : -2
} catch {
return -2 // 解析失败,视为无效缓存
}
}
/**
* 动态设置缓存过期时间
* @param key 缓存键
* @param ttl 过期时间(秒)
* @returns 是否设置成功
*/
static expire(key: string, ttl: number): boolean {
const value = this.get(key)
if (value === null) return false
this.set(key, value, ttl)
return true
}
/**
* 删除缓存
* @param key 缓存键
*/
static del(key: string) {
localStorage.removeItem(key)
}
/**
* 清空所有缓存
*/
static flushall() {
localStorage.clear()
}
/**
* 查找缓存键(支持通配符 *,和 Redis 用法一致)
* @param pattern 匹配规则,例如 user*、*info、*token*,默认 *
* @returns 匹配的键数组
*/
static keys(pattern: string = '*'): string[] {
const allKeys = Object.keys(localStorage)
const regex = new RegExp(pattern.replace(/\*/g, '.*'))
return allKeys.filter((key) => regex.test(key))
}
/**
* 检查缓存是否存在且未过期
* @param key 缓存键
* @returns 是否存在有效缓存
*/
static exists(key: string): boolean {
return this.get(key) !== null
}
}
数据结构设计:这是整个封装的基石。我们采用 { value, ttl } 这样的结构来包裹实际数据。其中 ttl 字段很关键:它要么是 -1(代表永不过期),要么是一个未来的绝对时间戳(毫秒数)。这样一来,判断过期就变成了简单的时间戳比较。
过期时间处理:围绕上述数据结构,我们构建了一套完整的生命周期管理:
ttl 方法,可以随时查看某个缓存还剩多少“保质期”,非常直观。错误处理:LocalStorage 里存储的都是字符串,JSON 解析是必不可少的一步,但也是最容易出错的环节。通过 try-catch 包裹解析逻辑,即使遇到意外格式的数据,也能保证程序不会崩溃,而是返回预设的默认值并清理无效条目,确保了整体的健壮性。
Redis 风格 API:为了让后端同学感到亲切,我们几乎1:1复刻了 Redis 的常用命令:set, get, del, expire, keys, exists。用起来几乎感觉不到差别。
通配符支持:keys 方法是亮点之一。它支持使用 * 进行模糊匹配,比如 user* 可以找出所有以 “user” 开头的键。其内部通过将通配符模式转换为正则表达式来实现,用法和效果都与 Redis 保持一致。
| 方法 | 功能描述 | 参数说明 | 返回值 |
|---|---|---|---|
| set(key, value, ttl) | 设置缓存 | key: 缓存键 value: 缓存值 ttl: 过期时间(秒),默认 -1 |
无 |
| get(key, defaultValue) | 获取缓存 | key: 缓存键 defaultValue: 默认值,默认 null |
缓存值或默认值 |
| ttl(key) | 获取剩余过期时间 | key: 缓存键 | -1: 永久有效 -2: 已过期/不存在 正数: 剩余秒数 |
| expire(key, ttl) | 设置过期时间 | key: 缓存键 ttl: 过期时间(秒) |
是否设置成功 |
| del(key) | 删除缓存 | key: 缓存键 | 无 |
| flushall() | 清空所有缓存 | 无 | 无 |
| keys(pattern) | 查找匹配的键 | pattern: 匹配规则,默认 * | 匹配的键数组 |
| exists(key) | 检查缓存是否存在 | key: 缓存键 | 是否存在有效缓存 |
// 设置一个用户缓存,1小时后自动过期
CacheUtil.set('USER', { id: 1, name: 'John' }, 3600)
// 需要时,轻松获取
const user = CacheUtil.get('USER')
console.log(user) // 输出:{ id: 1, name: 'John' }
// 假设用户活动频繁,我们想给缓存续个期,再延长2小时
CacheUtil.expire('USER', 7200)
// 随时查看缓存还剩多少“寿命”
const remainingTime = CacheUtil.ttl('USER')
console.log(`剩余过期时间:${remainingTime}秒`)
// 使用通配符查找相关键,管理起来非常方便
const userKeys = CacheUtil.keys('USER*') // 找到所有USER开头的键
const infoKeys = CacheUtil.keys('*INFO') // 找到所有INFO结尾的键
console.log('用户相关键:', userKeys)
console.log('信息相关键:', infoKeys)
// 快速检查某个关键缓存是否还在
const exists = CacheUtil.exists('USER')
console.log('USER 缓存存在:', exists)
// 删除单个缓存项
CacheUtil.del('USER')
// 一键清空,适用于用户退出登录等场景
CacheUtil.flushall()
get 操作都附带一次时间戳比对,虽然开销很小,但在极端高频的调用下也需要纳入考量。stringify,拿出来要 parse。对于结构复杂、嵌套深的对象,这个成本会线性增长。所以,存储的数据结构还是尽量保持扁平为好。APP_USER_INFO),这是避免项目内甚至跨第三方库键名冲突的最简单有效的方法。侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述