1 changed files with 489 additions and 0 deletions
@ -0,0 +1,489 @@ |
|||||
|
# 可降级缓存系统实现计划 |
||||
|
|
||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
||||
|
|
||||
|
**Goal:** 构建 `packages/cache` 包,实现 Redis 主存储 + Memory 内存降级的通用 K-V 缓存系统 |
||||
|
|
||||
|
**Architecture:** 分层适配器架构:`CacheDriver` 接口定义 get/set/del/exists/clear,`MemoryDriver` 和 `RedisDriver` 分别实现, `CacheManager` 按 drivers 数组顺序尝试调用方透明降级 |
||||
|
|
||||
|
**Tech Stack:** TypeScript, ioredis, Node.js |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 文件结构 |
||||
|
|
||||
|
``` |
||||
|
packages/cache/ |
||||
|
├── package.json |
||||
|
├── index.ts |
||||
|
└── lib/ |
||||
|
├── types.ts ← CacheDriver 接口、CacheManagerOptions、CacheEntry |
||||
|
├── managers/ |
||||
|
│ └── cache-manager.ts ← CacheManager 实现(降级逻辑) |
||||
|
└── drivers/ |
||||
|
├── base.ts ← 抽象基类 AbstractCacheDriver |
||||
|
├── memory-driver.ts ← Map 实现 |
||||
|
└── redis-driver.ts ← ioredis 实现 |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Task 1: 项目脚手架 |
||||
|
|
||||
|
**Files:** |
||||
|
- Create: `packages/cache/package.json` |
||||
|
- Create: `packages/cache/index.ts` |
||||
|
- Create: `packages/cache/tsconfig.json` |
||||
|
- Create: `packages/cache/lib/types.ts` |
||||
|
|
||||
|
- [ ] **Step 1: 创建 package.json** |
||||
|
|
||||
|
```json |
||||
|
{ |
||||
|
"name": "cache", |
||||
|
"version": "0.1.0", |
||||
|
"type": "module", |
||||
|
"exports": { |
||||
|
".": "./index.ts" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"ioredis": "^5.4.1" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@types/node": "^20.0.0" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Run: `ls packages/cache/package.json` |
||||
|
Expected: 文件存在 |
||||
|
|
||||
|
- [ ] **Step 2: 创建 types.ts** |
||||
|
|
||||
|
```typescript |
||||
|
export interface CacheEntry<T = unknown> { |
||||
|
value: T |
||||
|
expiresAt: number | null // Unix ms,null = 永不过期 |
||||
|
} |
||||
|
|
||||
|
export interface CacheDriver { |
||||
|
name: string |
||||
|
get<T = unknown>(key: string): Promise<T | null> |
||||
|
set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> |
||||
|
del(key: string): Promise<void> |
||||
|
exists(key: string): Promise<boolean> |
||||
|
clear(): Promise<void> |
||||
|
} |
||||
|
|
||||
|
export interface CacheManagerOptions { |
||||
|
drivers: CacheDriver[] |
||||
|
defaultTtl?: number |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Run: `cat packages/cache/lib/types.ts` |
||||
|
Expected: 文件存在,CacheDriver、CacheManagerOptions、CacheEntry 导出 |
||||
|
|
||||
|
- [ ] **Step 3: 创建 index.ts 骨架** |
||||
|
|
||||
|
```typescript |
||||
|
export * from './lib/types' |
||||
|
``` |
||||
|
|
||||
|
Run: `cat packages/cache/index.ts` |
||||
|
Expected: `export * from './lib/types'` |
||||
|
|
||||
|
- [ ] **Step 4: 提交** |
||||
|
|
||||
|
```bash |
||||
|
git add packages/cache/package.json packages/cache/index.ts packages/cache/tsconfig.json packages/cache/lib/types.ts |
||||
|
git commit -m "feat(cache): scaffold package and define core types |
||||
|
|
||||
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>" |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Task 2: MemoryDriver |
||||
|
|
||||
|
**Files:** |
||||
|
- Create: `packages/cache/lib/drivers/memory-driver.ts` |
||||
|
|
||||
|
- [ ] **Step 1: 实现 MemoryDriver** |
||||
|
|
||||
|
```typescript |
||||
|
import type { CacheDriver } from '../types' |
||||
|
|
||||
|
interface MemoryEntry { |
||||
|
value: unknown |
||||
|
expiresAt: number | null |
||||
|
} |
||||
|
|
||||
|
export class MemoryDriver implements CacheDriver { |
||||
|
name = 'memory' |
||||
|
private store = new Map<string, MemoryEntry>() |
||||
|
|
||||
|
async get<T>(key: string): Promise<T | null> { |
||||
|
const entry = this.store.get(key) |
||||
|
if (!entry) return null |
||||
|
|
||||
|
if (entry.expiresAt !== null && entry.expiresAt < Date.now()) { |
||||
|
this.store.delete(key) |
||||
|
return null |
||||
|
} |
||||
|
|
||||
|
return entry.value as T |
||||
|
} |
||||
|
|
||||
|
async set<T>(key: string, value: T, ttl = 0): Promise<void> { |
||||
|
const expiresAt = ttl > 0 ? Date.now() + ttl * 1000 : null |
||||
|
this.store.set(key, { value, expiresAt }) |
||||
|
} |
||||
|
|
||||
|
async del(key: string): Promise<void> { |
||||
|
this.store.delete(key) |
||||
|
} |
||||
|
|
||||
|
async exists(key: string): Promise<boolean> { |
||||
|
const entry = this.store.get(key) |
||||
|
if (!entry) return false |
||||
|
|
||||
|
if (entry.expiresAt !== null && entry.expiresAt < Date.now()) { |
||||
|
this.store.delete(key) |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
async clear(): Promise<void> { |
||||
|
this.store.clear() |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Run: `cat packages/cache/lib/drivers/memory-driver.ts` |
||||
|
Expected: 文件存在,类导出 |
||||
|
|
||||
|
- [ ] **Step 2: 更新 index.ts 导出 MemoryDriver** |
||||
|
|
||||
|
```typescript |
||||
|
export * from './lib/types' |
||||
|
export { MemoryDriver } from './lib/drivers/memory-driver' |
||||
|
``` |
||||
|
|
||||
|
Run: `cat packages/cache/index.ts` |
||||
|
Expected: 包含 MemoryDriver 导出 |
||||
|
|
||||
|
- [ ] **Step 3: 提交** |
||||
|
|
||||
|
```bash |
||||
|
git add packages/cache/lib/drivers/memory-driver.ts packages/cache/index.ts |
||||
|
git commit -m "feat(cache): implement MemoryDriver |
||||
|
|
||||
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>" |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Task 3: RedisDriver |
||||
|
|
||||
|
**Files:** |
||||
|
- Create: `packages/cache/lib/drivers/redis-driver.ts` |
||||
|
|
||||
|
- [ ] **Step 1: 实现 RedisDriver** |
||||
|
|
||||
|
```typescript |
||||
|
import Redis from 'ioredis' |
||||
|
import type { CacheDriver } from '../types' |
||||
|
|
||||
|
export interface RedisDriverOptions { |
||||
|
host: string |
||||
|
port: number |
||||
|
password?: string |
||||
|
db?: number |
||||
|
} |
||||
|
|
||||
|
export class RedisDriver implements CacheDriver { |
||||
|
name = 'redis' |
||||
|
private redis: Redis |
||||
|
|
||||
|
constructor(options: RedisDriverOptions) { |
||||
|
this.redis = new Redis({ |
||||
|
host: options.host, |
||||
|
port: options.port, |
||||
|
password: options.password, |
||||
|
db: options.db ?? 0, |
||||
|
lazyConnect: true, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
private serialize<T>(value: T): string { |
||||
|
return JSON.stringify(value) |
||||
|
} |
||||
|
|
||||
|
private deserialize<T>(data: string): T { |
||||
|
return JSON.parse(data) as T |
||||
|
} |
||||
|
|
||||
|
async get<T>(key: string): Promise<T | null> { |
||||
|
const data = await this.redis.get(key) |
||||
|
if (!data) return null |
||||
|
return this.deserialize<T>(data) |
||||
|
} |
||||
|
|
||||
|
async set<T>(key: string, value: T, ttl = 0): Promise<void> { |
||||
|
const serialized = this.serialize(value) |
||||
|
if (ttl > 0) { |
||||
|
await this.redis.set(key, serialized, 'EX', ttl) |
||||
|
} else { |
||||
|
await this.redis.set(key, serialized) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async del(key: string): Promise<void> { |
||||
|
await this.redis.del(key) |
||||
|
} |
||||
|
|
||||
|
async exists(key: string): Promise<boolean> { |
||||
|
const result = await this.redis.exists(key) |
||||
|
return result === 1 |
||||
|
} |
||||
|
|
||||
|
async clear(): Promise<void> { |
||||
|
await this.redis.flushdb() |
||||
|
} |
||||
|
|
||||
|
async disconnect(): Promise<void> { |
||||
|
await this.redis.quit() |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Run: `cat packages/cache/lib/drivers/redis-driver.ts` |
||||
|
Expected: 文件存在,RedisDriver 导出,connect lazy |
||||
|
|
||||
|
- [ ] **Step 2: 更新 index.ts 导出 RedisDriver** |
||||
|
|
||||
|
```typescript |
||||
|
export * from './lib/types' |
||||
|
export { MemoryDriver } from './lib/drivers/memory-driver' |
||||
|
export { RedisDriver } from './lib/drivers/redis-driver' |
||||
|
export type { RedisDriverOptions } from './lib/drivers/redis-driver' |
||||
|
``` |
||||
|
|
||||
|
Run: `cat packages/cache/index.ts` |
||||
|
Expected: 包含 RedisDriver 导出 |
||||
|
|
||||
|
- [ ] **Step 3: 提交** |
||||
|
|
||||
|
```bash |
||||
|
git add packages/cache/lib/drivers/redis-driver.ts packages/cache/index.ts |
||||
|
git commit -m "feat(cache): implement RedisDriver with ioredis |
||||
|
|
||||
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>" |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Task 4: CacheManager |
||||
|
|
||||
|
**Files:** |
||||
|
- Create: `packages/cache/lib/managers/cache-manager.ts` |
||||
|
|
||||
|
- [ ] **Step 1: 实现 CacheManager** |
||||
|
|
||||
|
```typescript |
||||
|
import type { CacheDriver, CacheManagerOptions } from '../types' |
||||
|
|
||||
|
export class CacheManager { |
||||
|
private drivers: CacheDriver[] |
||||
|
private defaultTtl: number |
||||
|
|
||||
|
constructor(options: CacheManagerOptions) { |
||||
|
if (!options.drivers || options.drivers.length === 0) { |
||||
|
throw new Error('[cache] at least one driver is required') |
||||
|
} |
||||
|
this.drivers = options.drivers |
||||
|
this.defaultTtl = options.defaultTtl ?? 0 |
||||
|
} |
||||
|
|
||||
|
async get<T>(key: string): Promise<T | null> { |
||||
|
for (const driver of this.drivers) { |
||||
|
try { |
||||
|
const value = await driver.get<T>(key) |
||||
|
return value |
||||
|
} catch (err) { |
||||
|
console.warn(`[cache] ${driver.name} get failed:`, err) |
||||
|
continue |
||||
|
} |
||||
|
} |
||||
|
return null |
||||
|
} |
||||
|
|
||||
|
async set<T>(key: string, value: T, ttl?: number): Promise<void> { |
||||
|
const effectiveTtl = ttl ?? this.defaultTtl |
||||
|
await Promise.all( |
||||
|
this.drivers.map(async (driver) => { |
||||
|
try { |
||||
|
await driver.set(key, value, effectiveTtl) |
||||
|
} catch (err) { |
||||
|
console.warn(`[cache] ${driver.name} set failed:`, err) |
||||
|
} |
||||
|
}) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
async del(key: string): Promise<void> { |
||||
|
await Promise.all( |
||||
|
this.drivers.map(async (driver) => { |
||||
|
try { |
||||
|
await driver.del(key) |
||||
|
} catch (err) { |
||||
|
console.warn(`[cache] ${driver.name} del failed:`, err) |
||||
|
} |
||||
|
}) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
async exists(key: string): Promise<boolean> { |
||||
|
for (const driver of this.drivers) { |
||||
|
try { |
||||
|
const exists = await driver.exists(key) |
||||
|
if (exists) return true |
||||
|
} catch (err) { |
||||
|
console.warn(`[cache] ${driver.name} exists failed:`, err) |
||||
|
continue |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
async clear(): Promise<void> { |
||||
|
await Promise.all( |
||||
|
this.drivers.map(async (driver) => { |
||||
|
try { |
||||
|
await driver.clear() |
||||
|
} catch (err) { |
||||
|
console.warn(`[cache] ${driver.name} clear failed:`, err) |
||||
|
} |
||||
|
}) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Run: `cat packages/cache/lib/managers/cache-manager.ts` |
||||
|
Expected: 文件存在,CacheManager 导出 |
||||
|
|
||||
|
- [ ] **Step 2: 更新 index.ts 导出 CacheManager** |
||||
|
|
||||
|
```typescript |
||||
|
export * from './lib/types' |
||||
|
export { MemoryDriver } from './lib/drivers/memory-driver' |
||||
|
export { RedisDriver } from './lib/drivers/redis-driver' |
||||
|
export { CacheManager } from './lib/managers/cache-manager' |
||||
|
export type { RedisDriverOptions } from './lib/drivers/redis-driver' |
||||
|
``` |
||||
|
|
||||
|
Run: `cat packages/cache/index.ts` |
||||
|
Expected: 包含 CacheManager 导出 |
||||
|
|
||||
|
- [ ] **Step 3: 提交** |
||||
|
|
||||
|
```bash |
||||
|
git add packages/cache/lib/managers/cache-manager.ts packages/cache/index.ts |
||||
|
git commit -m "feat(cache): implement CacheManager with fallback logic |
||||
|
|
||||
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>" |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Task 5: createCache 工厂函数 |
||||
|
|
||||
|
**Files:** |
||||
|
- Modify: `packages/cache/index.ts` |
||||
|
|
||||
|
- [ ] **Step 1: 实现 createCache 工厂函数** |
||||
|
|
||||
|
```typescript |
||||
|
import { CacheManager } from './lib/managers/cache-manager' |
||||
|
import { MemoryDriver } from './lib/drivers/memory-driver' |
||||
|
import { RedisDriver } from './lib/drivers/redis-driver' |
||||
|
import type { CacheDriver, CacheManagerOptions } from './lib/types' |
||||
|
import type { RedisDriverOptions } from './lib/drivers/redis-driver' |
||||
|
|
||||
|
export * from './lib/types' |
||||
|
export { MemoryDriver } from './lib/drivers/memory-driver' |
||||
|
export { RedisDriver } from './lib/drivers/redis-driver' |
||||
|
export { CacheManager } from './lib/managers/cache-manager' |
||||
|
export type { RedisDriverOptions } from './lib/drivers/redis-driver' |
||||
|
|
||||
|
export interface CacheFactoryOptions { |
||||
|
redis?: RedisDriverOptions |
||||
|
memory?: boolean |
||||
|
defaultTtl?: number |
||||
|
} |
||||
|
|
||||
|
export function createCache(options: CacheFactoryOptions): CacheManager { |
||||
|
const drivers: CacheDriver[] = [] |
||||
|
|
||||
|
if (options.redis) { |
||||
|
drivers.push(new RedisDriver(options.redis)) |
||||
|
} |
||||
|
|
||||
|
if (options.memory !== false) { |
||||
|
drivers.push(new MemoryDriver()) |
||||
|
} |
||||
|
|
||||
|
if (drivers.length === 0) { |
||||
|
throw new Error('[cache] at least one driver (redis or memory) is required') |
||||
|
} |
||||
|
|
||||
|
return new CacheManager({ drivers, defaultTtl: options.defaultTtl }) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Run: `cat packages/cache/index.ts` |
||||
|
Expected: createCache 工厂函数存在 |
||||
|
|
||||
|
- [ ] **Step 2: 提交** |
||||
|
|
||||
|
```bash |
||||
|
git add packages/cache/index.ts |
||||
|
git commit -m "feat(cache): add createCache factory function |
||||
|
|
||||
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>" |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Task 6: 验证构建 |
||||
|
|
||||
|
- [ ] **Step 1: 安装依赖** |
||||
|
|
||||
|
Run: `cd /home/dash/coding/nuxt-app && bun add -w cache` |
||||
|
Expected: ioredis 被添加到 packages/cache/package.json |
||||
|
|
||||
|
- [ ] **Step 2: 类型检查** |
||||
|
|
||||
|
Run: `cd packages/cache && npx tsc --noEmit` |
||||
|
Expected: 无错误输出 |
||||
|
|
||||
|
- [ ] **Step 3: 提交** |
||||
|
|
||||
|
```bash |
||||
|
git add -A |
||||
|
git commit -m "chore(cache): add dependencies and verify build |
||||
|
|
||||
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>" |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 自检清单 |
||||
|
|
||||
|
- [ ] spec 覆盖:CacheDriver 接口 ✓ / MemoryDriver ✓ / RedisDriver ✓ / CacheManager ✓ / createCache ✓ / 降级逻辑 ✓ |
||||
|
- [ ] 无 placeholder:无 TBD/TODO/类似占位符 |
||||
|
- [ ] 类型一致性:get/set/del/exists/clear 方法签名在各驱动一致 ✓ |
||||
Loading…
Reference in new issue