# 统一图片缓存方案设计文档 ## 文档信息 - **创建日期**: 2026-01-07 - **版本**: 1.0 - **状态**: 实现中 ## 背景 当前项目中存在三个独立的缓存系统: 1. Service Worker 的 Cache API(`drawnix-images`) 2. 应用层的 Media Cache Service(`drawnix-media-cache` IndexedDB) 3. 应用层的 URL Cache Service(`aitu-url-cache` IndexedDB) 这种分散的缓存架构导致了以下问题: - 缓存逻辑不统一,维护困难 - Service Worker 缓存没有任务元数据(模型、提示词、参数) - 无法根据缓存时间智能决策传 URL 还是 base64 - 缺少缓存满时的用户友好提示 ## 设计目标 1. **统一缓存管理**:一个服务协调 SW 和应用层缓存 2. **完整元数据存储**:记录缓存时间、生成参数、模型信息 3. **智能图片传递**:调用 AI 接口时,1天内用 URL,超过1天用 base64 4. **用户可控清理**:缓存满时提示打开素材库手动删除 5. **向下兼容**:从旧数据库迁移数据 ## 架构设计 ### 整体架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 浏览器主线程 │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Unified Cache Manager (统一缓存管理器) │ │ │ │ │ │ │ │ - 协调 SW 和 IndexedDB │ │ │ │ - 处理元数据存储 │ │ │ │ - 智能图片传递逻辑 │ │ │ │ - 缓存满检测和提示 │ │ │ └──────────────────────────────────────────────────────┘ │ │ ↕ (postMessage) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ IndexedDB: drawnix-unified-cache │ │ │ │ │ │ │ │ 存储结构: │ │ │ │ - url (主键) │ │ │ │ - type, mimeType, size │ │ │ │ - cachedAt, lastUsed │ │ │ │ - metadata: { taskId, prompt, model, params } │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↕ (fetch intercept) ┌─────────────────────────────────────────────────────────────┐ │ Service Worker 线程 │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ 图片请求拦截器 │ │ │ │ │ │ │ │ 1. 拦截图片请求 │ │ │ │ 2. 检查 Cache API │ │ │ │ ↓ 命中 → 直接返回 │ │ │ │ ↓ 未命中 → 从网络获取 │ │ │ │ 3. 存入 Cache API │ │ │ │ 4. 通知主线程更新 IndexedDB 元数据 │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Cache API: drawnix-images │ │ │ │ (快速响应,不存元数据) │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 数据库结构 #### IndexedDB: `drawnix-unified-cache` ```typescript interface CachedMedia { url: string; // 主键 type: 'image' | 'video'; mimeType: string; size: number; // 字节数 cachedAt: number; // 缓存时间戳 lastUsed: number; // 最后使用时间戳 metadata: { taskId?: string; // 任务ID prompt?: string; // 生成提示词 model?: string; // 使用的模型 params?: any; // 生成参数 [key: string]: any; // 扩展字段 }; } ``` **索引**: - `url` (主键) - `cachedAt` (用于按时间排序) - `lastUsed` (用于 LRU 清理) ### 核心流程 #### 1. 图片生成并插入画布 ``` 任务完成 → 获得图片 URL ↓ 插入画布(使用原始 URL,不变更现有逻辑) ↓ 浏览器发起图片请求 ↓ SW 拦截请求 ↓ 检查 Cache API ├─ 命中 → 立即返回缓存 └─ 未命中 ↓ 从网络获取图片 ↓ 存入 Cache API ↓ postMessage 通知主线程 { type: 'IMAGE_CACHED', url: '...', size: 12345, mimeType: 'image/png' } ↓ 主线程收到消息 ↓ 存入 IndexedDB (包含元数据) ``` #### 2. 调用 AI 接口传递图片 ``` 需要传图片给 AI 接口 ↓ 调用 UnifiedCacheManager.getImageForAI(url) ↓ 查询 IndexedDB 获取 cachedAt ↓ 计算 age = Date.now() - cachedAt ↓ age < 24小时? ├─ 是 → 返回 { type: 'url', value: originalUrl } └─ 否 ↓ 从 Cache API 获取 blob(通过 fetch) ↓ 压缩图片(如果需要) ↓ 转换为 base64 ↓ 返回 { type: 'base64', value: base64Data } ``` #### 3. 缓存满处理 ``` Cache API 或 IndexedDB 写入失败 ↓ 捕获 QuotaExceededError ↓ 触发 onQuotaExceeded 回调 ↓ 显示 TDesign Dialog: "缓存空间已满,是否打开素材库清理缓存?" [取消] [打开素材库] ↓ 用户点击"打开素材库" ↓ 打开媒体库面板(显示所有缓存项) ↓ 用户选择并删除缓存项 ↓ 删除流程: 1. 从 IndexedDB 删除记录 2. postMessage 通知 SW 删除 Cache API 条目 3. 刷新缓存列表 ``` ## API 设计 ### UnifiedCacheService ```typescript class UnifiedCacheService { /** * 注册图片元数据(任务完成时调用) */ async registerImageMetadata( url: string, metadata: { taskId: string; prompt?: string; model?: string; params?: any; } ): Promise; /** * 获取适合传给 AI 的图片数据 * 自动决策返回 URL 或 base64 */ async getImageForAI( url: string, options?: { maxAge?: number; // 默认 24 小时 maxSize?: number; // 最大文件大小(超过则压缩) quality?: number; // 压缩质量 0-1 } ): Promise<{ type: 'url' | 'base64'; value: string; }>; /** * 获取缓存信息 */ async getCacheInfo(url: string): Promise<{ isCached: boolean; cachedAt?: number; age?: number; size?: number; metadata?: any; }>; /** * 手动缓存图片 */ async cacheImage( url: string, metadata?: any ): Promise; /** * 删除缓存 */ async deleteCache(url: string): Promise; /** * 批量删除缓存 */ async deleteCacheBatch(urls: string[]): Promise; /** * 获取所有缓存项元数据 */ async getAllCacheMetadata(): Promise; /** * 获取存储使用情况 */ async getStorageUsage(): Promise<{ used: number; quota: number; percentage: number; }>; /** * 清空所有缓存 */ async clearAllCache(): Promise; /** * 监听缓存满事件 */ onQuotaExceeded(callback: () => void): () => void; /** * 订阅缓存变化 */ subscribe(callback: () => void): () => void; } ``` ### Service Worker 消息协议 #### 主线程 → SW ```typescript // 删除缓存请求 { type: 'DELETE_CACHE', url: string } // 批量删除缓存请求 { type: 'DELETE_CACHE_BATCH', urls: string[] } // 清空所有缓存 { type: 'CLEAR_ALL_CACHE' } ``` #### SW → 主线程 ```typescript // 图片已缓存通知 { type: 'IMAGE_CACHED', url: string, size: number, mimeType: string, timestamp: number } // 缓存已删除通知 { type: 'CACHE_DELETED', url: string } // 缓存空间不足警告 { type: 'QUOTA_WARNING', usage: number, quota: number } ``` ## 数据迁移方案 ### 迁移步骤 1. **检测旧数据库** - 检查 `aitu-media-cache` 是否存在 - 检查 `aitu-url-cache` 是否存在 2. **创建新数据库** - 打开 `drawnix-unified-cache` - 创建 object store 和索引 3. **迁移 Media Cache 数据** ```typescript // 从 aitu-media-cache.media 读取 oldRecord = { url, originalUrl, taskId, prompt, blob, type, mimeType, size, cachedAt } // 转换为新格式 newRecord = { url: oldRecord.originalUrl || oldRecord.url, type: oldRecord.type, mimeType: oldRecord.mimeType, size: oldRecord.size, cachedAt: oldRecord.cachedAt || Date.now(), lastUsed: Date.now(), metadata: { taskId: oldRecord.taskId, prompt: oldRecord.prompt } } ``` 4. **迁移 URL Cache 数据** ```typescript // 从 aitu-url-cache.media-cache 读取 oldRecord = { url, type, data (base64), blob, mimeType, size, cachedAt } // 转换为新格式(不迁移 blob,让 SW 重新缓存) newRecord = { url: oldRecord.url, type: oldRecord.type, mimeType: oldRecord.mimeType, size: oldRecord.size, cachedAt: oldRecord.cachedAt || Date.now(), lastUsed: Date.now(), metadata: {} } ``` 5. **删除旧数据库** ```typescript indexedDB.deleteDatabase('aitu-media-cache'); indexedDB.deleteDatabase('aitu-url-cache'); ``` 6. **记录迁移状态** - localStorage 设置标记:`drawnix_cache_migrated: 'true'` ### 迁移触发时机 - 应用启动时自动检测并迁移 - 迁移过程显示进度提示(可选) - 迁移完成后发送通知 ## 实现计划 ### Phase 1: 基础服务实现(核心) - [x] 创建设计文档 - [ ] 实现 `UnifiedCacheService` 类 - [ ] 实现 IndexedDB 操作 - [ ] 实现与 SW 的通信协议 - [ ] 实现数据迁移逻辑 ### Phase 2: Service Worker 集成 - [ ] 修改 SW 图片缓存逻辑 - [ ] 添加缓存通知消息 - [ ] 实现 SW 端的缓存删除响应 ### Phase 3: 智能传递逻辑 - [ ] 实现 `getImageForAI()` 方法 - [ ] 添加图片压缩功能 - [ ] 添加缓存时间检测 ### Phase 4: UI 集成 - [ ] 更新 TaskItem 使用新服务 - [ ] 实现缓存满提示 Dialog - [ ] 更新媒体库显示缓存信息 - [ ] 添加缓存管理 UI ### Phase 5: 测试与优化 - [ ] 单元测试 - [ ] 集成测试 - [ ] 性能优化 - [ ] 文档完善 ## 注意事项 1. **缓存策略** - Service Worker 的 Cache API 用于快速响应(不存元数据) - IndexedDB 用于元数据管理和查询 - 不在 IndexedDB 中存储 blob,避免双重存储 2. **性能考虑** - SW 缓存命中后立即返回,不阻塞 - 元数据更新使用异步消息,不影响图片加载速度 - 批量操作使用事务优化 3. **兼容性** - 保持现有插入画布逻辑不变 - 渐进式迁移,旧代码依然可用 - 提供降级方案(如 SW 不可用) 4. **安全性** - 验证 URL 来源 - 限制缓存大小 - 清理过期缓存 ## 参考资料 - [Service Worker API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) - [IndexedDB API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) - [Cache API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Cache) - [Storage API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API) ## 更新日志 - 2026-01-07: 初始版本,完成架构设计