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