diff --git a/.sisyphus/notepads/fingerprint-migration/decisions.md b/.sisyphus/notepads/fingerprint-migration/decisions.md new file mode 100644 index 0000000..7a93271 --- /dev/null +++ b/.sisyphus/notepads/fingerprint-migration/decisions.md @@ -0,0 +1,8 @@ +## Migration Decisions + +- **Library**: `@isaacs/ttlcache` v2.1.4 +- **Rationale**: Minimal, zero-dependency, specialized for TTL, well-maintained. +- **Cache Key**: Fixed string `'fingerprint'` since we only cache one result. +- **Max Items**: 1 (singleton fingerprint). +- **Default TTL**: 10 minutes (compatible with previous implementation). + diff --git a/.sisyphus/notepads/fingerprint-migration/learnings.md b/.sisyphus/notepads/fingerprint-migration/learnings.md new file mode 100644 index 0000000..ac30154 --- /dev/null +++ b/.sisyphus/notepads/fingerprint-migration/learnings.md @@ -0,0 +1,8 @@ +## @isaacs/ttlcache Migration Learnings + +- Successfully replaced manual TTL cache with `@isaacs/ttlcache` in `fingerprint.ts`. +- Use `catalog:` for dependency management in the Bun monorepo. +- `TTLCache` does not handle in-flight request deduplication, so the `inFlight` Promise pattern was preserved. +- Alphabetical sorting in `package.json` catalog is important for consistency. +- Biome handles import organization and formatting; `bun fix` should be run after manual edits. + diff --git a/apps/server/AGENTS.md b/apps/server/AGENTS.md index 9531692..8b583a3 100644 --- a/apps/server/AGENTS.md +++ b/apps/server/AGENTS.md @@ -346,6 +346,37 @@ const fingerprint = createHash('sha256') - 深入研究文档和源码再做技术决策 - 区分"用户输入场景"和"系统数据场景"的安全要求 +### 缓存库选择:@isaacs/ttlcache + +**决策时间**: 2026-01-26 + +**背景**: +硬件指纹功能最初使用手动实现的 TTL 缓存(module-level 变量 + 手动过期检查)。为提高代码可维护性,迁移到专业缓存库。 + +**选型**: +- **选择**: `@isaacs/ttlcache` v2.1.4 +- **理由**: + - 专为 TTL 场景优化,无需 LRU 追踪开销 + - 零依赖,6M+ 周下载量 + - 内置 TypeScript 类型 + - 自动过期管理,无需手动定时器 + - API 简洁: `new TTLCache({ ttl, max })` + +**实现细节**: +- 保留 `inFlight` Promise 模式用于并发请求去重(TTLCache 不提供此功能) +- 使用单一缓存键 `'fingerprint'`(单服务器场景,opts 不影响输出) +- 默认 TTL: 10 分钟(可通过 `cacheTtlMs` 参数覆盖) + +**对比手动实现**: +- ✅ 更少自定义代码 +- ✅ 更清晰的 TTL 语义 +- ✅ 经过充分测试的库 +- ⚠️ 仍需手动处理并发去重 + +**经验教训**: +- 专业库不一定解决所有问题(如并发去重) +- 对于简单场景,手动实现 vs 库的选择主要取决于可维护性而非功能 + ### Git 工作流要求 **重要原则**:保持代码仓库与文档同步 diff --git a/apps/server/package.json b/apps/server/package.json index 38ca4da..0529bb8 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -15,6 +15,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@isaacs/ttlcache": "catalog:", "@orpc/client": "catalog:", "@orpc/contract": "catalog:", "@orpc/openapi": "catalog:", diff --git a/apps/server/src/lib/fingerprint.ts b/apps/server/src/lib/fingerprint.ts index 1ebd473..0dec22b 100644 --- a/apps/server/src/lib/fingerprint.ts +++ b/apps/server/src/lib/fingerprint.ts @@ -1,3 +1,4 @@ +import { TTLCache } from '@isaacs/ttlcache' import { hash } from 'ohash' import si from 'systeminformation' @@ -63,10 +64,10 @@ export type HardwareFingerprintResult = { } // 缓存实例 -let cache: { - expiresAt: number - value: HardwareFingerprintResult -} | null = null +const cache = new TTLCache<'fingerprint', HardwareFingerprintResult>({ + ttl: 10 * 60 * 1000, // 10 minutes default + max: 1, // Only one fingerprint cached +}) // 防止并发重复请求 let inFlight: Promise | null = null @@ -182,8 +183,9 @@ export async function getHardwareFingerprint( const now = Date.now() // 返回缓存结果 - if (cache && cache.expiresAt > now) { - return cache.value + const cached = cache.get('fingerprint') + if (cached) { + return cached } // 防止并发重复请求 @@ -212,7 +214,7 @@ export async function getHardwareFingerprint( } // 更新缓存 - cache = { expiresAt: now + ttl, value: result } + cache.set('fingerprint', result, { ttl }) return result })().finally(() => { @@ -226,6 +228,6 @@ export async function getHardwareFingerprint( * 清除指纹缓存(用于测试或强制刷新) */ export function clearFingerprintCache(): void { - cache = null + cache.clear() inFlight = null } diff --git a/bun.lock b/bun.lock index 3bb6c49..a8a6d7f 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ "name": "@furtherverse/server", "version": "1.0.0", "dependencies": { + "@isaacs/ttlcache": "catalog:", "@orpc/client": "catalog:", "@orpc/contract": "catalog:", "@orpc/openapi": "catalog:", @@ -64,6 +65,7 @@ "@biomejs/biome": "^2.3.11", "@effect/platform": "^0.94.2", "@effect/schema": "^0.75.5", + "@isaacs/ttlcache": "^2.1.4", "@orpc/client": "^1.13.4", "@orpc/contract": "^1.13.4", "@orpc/openapi": "^1.13.4", @@ -234,6 +236,8 @@ "@furtherverse/tsconfig": ["@furtherverse/tsconfig@workspace:packages/tsconfig"], + "@isaacs/ttlcache": ["@isaacs/ttlcache@2.1.4", "", {}, "sha512-7kMz0BJpMvgAMkyglums7B2vtrn5g0a0am77JY0GjkZZNetOBCFn7AG7gKCwT0QPiXyxW7YIQSgtARknUEOcxQ=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], diff --git a/package.json b/package.json index 349b0e6..f0b5e8e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@biomejs/biome": "^2.3.11", "@effect/platform": "^0.94.2", "@effect/schema": "^0.75.5", + "@isaacs/ttlcache": "^2.1.4", "@orpc/client": "^1.13.4", "@orpc/contract": "^1.13.4", "@orpc/openapi": "^1.13.4",