# 统一图片缓存系统实施总结 ## 概述 本次实施完成了一个全新的统一图片缓存系统,整合了 Service Worker 和应用层的缓存管理,实现了智能图片传递、元数据记录、缓存配额监控等功能。 --- ## 系统架构 ### 整体架构图 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 用户界面层 │ ├─────────────────────────────────────────────────────────────────┤ │ CacheQuotaProvider │ MediaLibrary │ AIInputBar │ Canvas │ └──────────┬──────────────────┬─────────────────┬────────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ React Hooks 层 │ ├─────────────────────────────────────────────────────────────────┤ │ useUnifiedCache │ useCacheStats │ useCacheQuotaMonitor │ └──────────┬──────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ unifiedCacheService (核心) │ │ - IndexedDB 元数据管理 │ │ - SW 通信协调 │ │ - 智能图片传递 │ │ - 数据迁移 │ └──────────┬───────────────────────────┬──────────────────────────┘ │ │ ▼ ▼ ┌──────────────────────┐ ┌──────────────────────────────────┐ │ IndexedDB │ │ Service Worker │ │ drawnix-unified- │ │ - Cache API (快速响应) │ │ cache │ │ - 图片拦截和缓存 │ │ - images (元数据) │ │ - 配额监控 │ │ - migrations (标记) │ │ - PostMessage 通信 │ └──────────────────────┘ └──────────────────────────────────┘ ``` ### 双层存储策略 1. **Service Worker Cache API** - 用途: 快速图片响应 - 内容: 只存储图片 Blob 数据 - 优势: 拦截网络请求,无需应用层介入 2. **IndexedDB (`drawnix-unified-cache`)** - 用途: 元数据管理 - 内容: URL、缓存时间、模型、提示词、参数等 - 优势: 查询灵活,支持复杂查询 --- ## 核心功能实现 ### 1. Service Worker 图片缓存拦截 **文件**: `apps/web/src/sw/index.ts` **实现要点**: - 拦截所有图片请求(JPEG, PNG, WebP, GIF) - 优先返回缓存,缓存未命中时从网络获取 - 缓存成功后通知主线程注册元数据 - 监控存储配额,接近上限时发出警告 **关键代码**: ```typescript // 拦截图片请求 self.addEventListener('fetch', (event) => { const request = event.request; if (request.method === 'GET' && isImageRequest(request)) { event.respondWith(handleImageRequest(request)); } }); // 缓存后通知主线程 async function notifyImageCached(url, size, mimeType) { const clients = await self.clients.matchAll(); clients.forEach(client => { client.postMessage({ type: 'IMAGE_CACHED', url, size, mimeType, timestamp: Date.now(), }); }); } ``` **消息协议**: - `IMAGE_CACHED`: SW → Main (图片已缓存) - `QUOTA_WARNING`: SW → Main (配额警告) - `DELETE_CACHE`: Main → SW (删除缓存) - `DELETE_CACHE_BATCH`: Main → SW (批量删除) - `CLEAR_ALL_CACHE`: Main → SW (清空所有缓存) --- ### 2. 统一缓存管理服务 **文件**: `packages/drawnix/src/services/unified-cache-service.ts` **核心功能**: #### 2.1 元数据注册 ```typescript async registerImageMetadata( url: string, metadata: { taskId: string; model?: string; prompt?: string; params?: any; } ): Promise ``` - 在 IndexedDB 中记录图片元数据 - 支持任务 ID、模型、提示词、参数等信息 - 自动记录缓存时间和最后访问时间 #### 2.2 智能图片传递 ```typescript async getImageForAI( url: string, options?: { maxAge?: number; // 默认 24 小时 maxSize?: number; // 默认 3MB quality?: number; // 默认 0.8 } ): Promise ``` **决策逻辑**: ``` 缓存时间 < maxAge (默认 24h) ↓ 返回 { type: 'url', value: url } 缓存时间 ≥ maxAge ↓ 从缓存获取图片 Blob ↓ 图片大小 > maxSize (默认 3MB)? ↓ 是 压缩图片 (quality=0.8) ↓ 转换为 base64 ↓ 返回 { type: 'base64', value: base64String } ``` **使用示例**: ```typescript // 在 canvas-insertion.ts 中 const imageData = await unifiedCacheService.getImageForAI(imageUrl); // imageData 可能是 { type: 'url', value: 'https://...' } // 或 { type: 'base64', value: 'data:image/png;base64,...' } const image = await loadHTMLImageElement(imageData.value, false); ``` #### 2.3 数据迁移 ```typescript private async migrateFromLegacyDatabases(): Promise ``` - 自动检测旧数据库 (`aitu-media-cache`, `aitu-url-cache`) - 将数据转换为新格式并导入 - 迁移完成后删除旧数据库 - 使用 localStorage 标记避免重复迁移 **迁移流程**: ``` 启动时检查 localStorage['CACHE_MIGRATION_DONE'] ↓ 未标记 检测旧数据库 ↓ 存在 读取旧数据 → 转换格式 → 写入新数据库 ↓ 删除旧数据库 ↓ 设置迁移完成标记 ``` --- ### 3. React Hooks 封装 **文件**: `packages/drawnix/src/hooks/useUnifiedCache.ts` #### 3.1 `useUnifiedCache(url)` 单个图片的缓存管理 ```typescript const { cacheInfo, // { isCached, cachedAt, size, ... } isLoading, // 加载状态 isCached, // 是否已缓存 cacheAge, // 缓存时长(毫秒) cacheImage, // 手动缓存函数 deleteCache, // 删除缓存函数 reload, // 重新加载缓存信息 } = useUnifiedCache(imageUrl); ``` **特点**: - 自动订阅缓存变化 - 响应式更新 UI - 错误处理和加载状态 #### 3.2 `useCacheStats()` 整体缓存统计 ```typescript const { totalCount, // 总缓存数量 storageUsage, // { usage, quota, percentage } isLoading, // 加载状态 reload, // 刷新统计 formatSize, // 格式化大小函数 } = useCacheStats(); ``` #### 3.3 `useCacheQuotaMonitor(callback)` 配额监控 ```typescript const { isQuotaExceeded, // 是否超过配额 resetQuotaFlag, // 重置标记 } = useCacheQuotaMonitor(() => { // 配额超限回调 showDialog(); }); ``` --- ### 4. 缓存配额提供者 **文件**: `packages/drawnix/src/components/cache-quota-provider/CacheQuotaProvider.tsx` **功能**: - 全局监听缓存配额事件 - 配额超限时显示 TDesign 确认对话框 - 提供 "打开素材库" 和 "稍后处理" 选项 - 防止重复弹窗 **集成**: ```tsx // 在 drawnix.tsx 中 ``` **对话框内容**: ``` 标题: 缓存空间已满 内容: 图片缓存空间已满,无法继续缓存新图片。是否打开素材库清理缓存? 按钮: [打开素材库] [稍后处理] 主题: warning ``` --- ### 5. 任务完成时自动注册元数据 **文件**: `packages/drawnix/src/hooks/useTaskExecutor.ts` **实现位置**: 1. 正常任务完成后 (line 419-432) 2. 恢复视频任务完成后 (line 173-186) **注册逻辑**: ```typescript // 任务完成后 if (result.url) { try { await unifiedCacheService.registerImageMetadata(result.url, { taskId: task.id, model: task.params.model, prompt: task.params.prompt, params: task.params, }); console.log(`[TaskExecutor] Registered metadata for task ${taskId}`); } catch (error) { console.error(`[TaskExecutor] Failed to register metadata:`, error); } } ``` **元数据包含**: - `url`: 图片/视频 URL - `taskId`: 任务 ID - `model`: 使用的模型(如 'imagen-3.0-generate-001') - `prompt`: 用户提示词 - `params`: 完整的生成参数(size, quality 等) - `cachedAt`: 缓存时间戳(自动添加) --- ## 数据库结构 ### IndexedDB: `drawnix-unified-cache` #### Object Store: `images` 主键: `url` (string) 字段: ```typescript interface CachedImage { url: string; // 图片 URL (主键) cachedAt: number; // 缓存时间戳 lastAccessed: number; // 最后访问时间 size: number; // 文件大小(字节) mimeType: string; // MIME 类型 taskId?: string; // 关联的任务 ID model?: string; // 生成模型 prompt?: string; // 提示词 params?: any; // 生成参数 } ``` 索引: - `by-cachedAt`: 按缓存时间排序 - `by-lastAccessed`: 按访问时间排序(用于 LRU 清理) - `by-taskId`: 按任务 ID 查询 #### Object Store: `migrations` 用于记录数据迁移状态 --- ## PostMessage 通信协议 ### Service Worker → Main Thread #### 1. `IMAGE_CACHED` 图片已成功缓存 ```typescript { type: 'IMAGE_CACHED', url: string, // 图片 URL size: number, // 文件大小 mimeType: string, // MIME 类型 timestamp: number, // 缓存时间 } ``` #### 2. `QUOTA_WARNING` 存储配额警告(≥95%) ```typescript { type: 'QUOTA_WARNING', usage: number, // 已用空间(字节) quota: number, // 总配额(字节) percentage: number, // 使用百分比 } ``` #### 3. `CACHE_DELETED` 缓存已删除确认 ```typescript { type: 'CACHE_DELETED', url: string, // 删除的 URL } ``` ### Main Thread → Service Worker #### 1. `DELETE_CACHE` 删除单个缓存 ```typescript { type: 'DELETE_CACHE', url: string, // 要删除的 URL } ``` #### 2. `DELETE_CACHE_BATCH` 批量删除缓存 ```typescript { type: 'DELETE_CACHE_BATCH', urls: string[], // URL 数组 } ``` #### 3. `CLEAR_ALL_CACHE` 清空所有缓存 ```typescript { type: 'CLEAR_ALL_CACHE', } ``` --- ## 文件清单 ### 新建文件 1. **`docs/UNIFIED_CACHE_DESIGN.md`** - 详细的设计文档 - 架构图和数据流 - API 规范 2. **`packages/drawnix/src/services/unified-cache-service.ts`** - 核心缓存管理服务 - 661 行代码 - 单例模式 3. **`packages/drawnix/src/hooks/useUnifiedCache.ts`** - React Hooks 封装 - 3 个导出钩子 - 172 行代码 4. **`packages/drawnix/src/components/cache-quota-provider/CacheQuotaProvider.tsx`** - 全局配额监控组件 - TDesign Dialog 集成 - 60 行代码 5. **`docs/UNIFIED_CACHE_TESTING.md`** - 完整测试清单 - 14 个测试用例 - 调试工具和脚本 6. **`docs/UNIFIED_CACHE_IMPLEMENTATION_SUMMARY.md`** (本文件) - 实施总结文档 ### 修改文件 1. **`apps/web/src/sw/index.ts`** - 添加消息处理器(DELETE_CACHE, DELETE_CACHE_BATCH, CLEAR_ALL_CACHE) - 添加辅助函数(deleteCacheByUrl, notifyImageCached, checkStorageQuota) - 在缓存成功后调用通知函数 - 约 200 行新增代码 2. **`packages/drawnix/src/mcp/tools/canvas-insertion.ts`** - 修改 `getImageDimensions()` 函数 - 使用 `unifiedCacheService.getImageForAI()` 替代直接使用 URL - 约 10 行修改 3. **`packages/drawnix/src/drawnix.tsx`** - 导入 `CacheQuotaProvider` 和 `MediaLibraryModal` - 添加 `mediaLibraryOpen` 状态 - 创建 `handleOpenMediaLibrary` 回调 - 包裹应用在 `CacheQuotaProvider` 中 - 渲染 `MediaLibraryModal` - 约 20 行修改 4. **`packages/drawnix/src/hooks/useTaskExecutor.ts`** - 导入 `unifiedCacheService` - 在任务完成时注册元数据(2 处) - 约 30 行新增代码 --- ## 配置常量 位置: `packages/drawnix/src/services/unified-cache-service.ts` ```typescript const CACHE_CONSTANTS = { DB_NAME: 'drawnix-unified-cache', DB_VERSION: 1, IMAGES_STORE: 'images', MIGRATIONS_STORE: 'migrations', DEFAULT_MAX_AGE: 24 * 60 * 60 * 1000, // 24 小时 MAX_IMAGE_SIZE: 3 * 1024 * 1024, // 3MB DEFAULT_QUALITY: 0.8, // 压缩质量 80% QUOTA_WARNING_THRESHOLD: 0.95, // 95% 配额警告 LEGACY_DBS: ['aitu-media-cache', 'aitu-url-cache'], MIGRATION_KEY: 'CACHE_MIGRATION_DONE', }; ``` 可根据需求调整: - `DEFAULT_MAX_AGE`: 修改 URL/Base64 切换阈值 - `MAX_IMAGE_SIZE`: 修改压缩触发大小 - `DEFAULT_QUALITY`: 修改压缩质量 - `QUOTA_WARNING_THRESHOLD`: 修改配额警告阈值 --- ## 向下兼容性 ### 数据迁移 自动迁移旧版本的两个数据库: 1. `aitu-media-cache` (旧版媒体缓存 - 包含元数据) 2. `aitu-url-cache` (旧版 URL 缓存 - 基本信息) #### 迁移流程 ``` 应用启动 ↓ 检查 localStorage['drawnix_cache_migrated'] ↓ 未标记 第一步: 迁移 aitu-media-cache - 读取所有数据(包含 taskId、prompt) - 转换为新格式 - 写入 drawnix-unified-cache ↓ 第二步: 迁移 aitu-url-cache(智能合并) - 读取所有数据 - 检查 URL 是否已存在 - 已存在: 合并数据,保留元数据 - 不存在: 创建新记录 ↓ 删除两个旧数据库 ↓ 设置迁移完成标记 ``` #### 智能合并逻辑 当同一张图片在两个旧数据库中都存在时: ```typescript // 检查是否已存在(来自 media-cache) const existing = await this.getItem(url); if (existing) { // 智能合并:保留元数据,更新缺失字段 const merged = { ...existing, // 保留 taskId、prompt、model mimeType: existing.mimeType || item.mimeType, size: existing.size || item.size, cachedAt: Math.min(existing.cachedAt, item.cachedAt), // 保留更早时间 }; await this.putItem(merged); } else { // 创建新记录 await this.putItem(newEntry); } ``` #### 迁移验证 使用 `test-migration.js` 验证迁移是否成功: ```javascript // 在浏览器控制台执行 const script = document.createElement('script'); script.src = '/test-migration.js'; document.head.appendChild(script); // 自动验证: // ✓ 迁移标记状态 // ✓ 旧数据库是否删除 // ✓ 新数据库数据完整性 // ✓ 元数据是否保留 // 手动操作: cleanupOldDatabases() // 清理旧数据库 forceMigration() // 强制重新迁移 ``` #### 注意事项 - ⚠️ 迁移只运行一次(localStorage 标记) - ⚠️ 同一 URL 在两个库中存在时,元数据不会丢失 - ⚠️ 迁移失败不会影响应用运行 - ⚠️ 可以手动清理旧数据库 - ✅ 迁移后旧数据库自动删除 - ✅ 支持强制重新迁移 ### 旧代码兼容 - 旧的 `mediaCacheService` 和 `urlCacheService` 可以继续使用 - 但建议逐步迁移到 `unifiedCacheService` - 新功能(如智能传递、元数据)只在新服务中可用 --- ## 性能优化 ### 1. 缓存命中优先 Service Worker 拦截请求后优先返回缓存,避免不必要的网络请求。 ### 2. 异步操作 所有 IndexedDB 操作都是异步的,不阻塞主线程。 ### 3. 订阅模式 使用 RxJS Subject 实现发布-订阅,避免轮询。 ### 4. 懒加载元数据 只在需要时才从 IndexedDB 查询详细信息。 ### 5. 批量操作 支持批量删除缓存,减少事务开销。 ### 6. 智能压缩 只在图片超过阈值时才压缩,避免不必要的计算。 --- ## 安全性考虑 ### 1. 同源策略 Service Worker 只缓存同源或启用 CORS 的图片。 ### 2. HTTPS 要求 Service Worker 需要 HTTPS 环境(开发环境除外)。 ### 3. 配额限制 浏览器会限制存储配额,系统会监控并提示用户。 ### 4. 数据清理 用户可以通过媒体库手动清理缓存。 ### 5. 错误处理 所有操作都有 try-catch 保护,不会导致应用崩溃。 --- ## 监控和日志 ### 控制台日志 #### Service Worker 日志 - `[SW Cache] Successfully cached image: ` - `[SW Cache] Notified main thread about cached image: ` - `[SW Cache] Storage quota: X.XX GB / X.XX GB (XX%)` - `[SW Cache] Warning: Storage quota exceeded 95%` #### 主线程日志 - `[UnifiedCache] Registered metadata for: ` - `[UnifiedCache] Migration completed from ` - `[TaskExecutor] Registered metadata for task ` - `[TaskExecutor] Failed to register metadata: ` ### 错误追踪 所有关键操作都有日志输出,便于调试: - 缓存操作成功/失败 - 元数据注册成功/失败 - 数据迁移进度 - 配额监控状态 --- ## 未来优化方向 ### 1. LRU 自动清理 当缓存空间不足时,自动删除最近最少使用的图片。 实现思路: - 使用 `lastAccessed` 索引 - 定期清理超过 N 天未访问的图片 - 保留最近生成的图片 ### 2. 缓存预热 预加载常用模型的样例图片,提升首次使用体验。 ### 3. 差异化策略 - 高质量模型图片保留更长时间 - 测试图片优先清理 - 用户收藏的图片永不清理 ### 4. 压缩算法优化 - 使用 WebP 格式替代 JPEG/PNG - 根据模型特点选择压缩参数 - 支持有损和无损压缩切换 ### 5. 统计分析 - 缓存命中率统计 - 存储空间使用趋势 - 最常用模型分析 - 用户行为洞察 ### 6. 云端同步 - 支持将缓存同步到云端 - 跨设备共享缓存 - 增量同步减少带宽 ### 7. 用户设置 - 允许配置缓存大小上限 - 自定义清理策略 - 选择是否启用自动缓存 ### 8. 多格式支持 - 支持视频缓存(目前主要是图片) - 支持音频缓存 - 支持 3D 模型缓存 --- ## 故障排查指南 ### 问题 1: 图片没有被缓存 **症状**: 图片每次都从网络加载,没有命中缓存 **可能原因**: 1. Service Worker 未正确注册 2. 图片 URL 不符合缓存条件(非 HTTPS、CORS 问题) 3. 缓存空间已满 **排查步骤**: 1. 检查 `chrome://serviceworker-internals/` 确认 SW 运行 2. 查看 Network 标签页,确认响应头包含正确的 CORS 头 3. 检查 IndexedDB 和 Cache Storage 的使用量 4. 查看 Service Worker 控制台日志 **解决方法**: - 确保 Service Worker 正确注册并激活 - 配置服务器返回正确的 CORS 头 - 清理旧缓存释放空间 --- ### 问题 2: 元数据没有注册 **症状**: IndexedDB 中没有图片元数据记录 **可能原因**: 1. 任务没有正确完成(status 不是 'completed') 2. IndexedDB 权限被阻止 3. useTaskExecutor 钩子没有加载 **排查步骤**: 1. 查看任务队列中任务的 status 2. 检查浏览器是否阻止了 IndexedDB 3. 查看控制台是否有注册失败的错误 4. 确认 useTaskExecutor 钩子在 Drawnix 组件中被调用 **解决方法**: - 等待任务完全完成 - 检查浏览器隐私设置,允许 IndexedDB - 刷新页面重新加载钩子 --- ### 问题 3: 缓存满对话框不显示 **症状**: 存储空间满时没有弹出提示对话框 **可能原因**: 1. CacheQuotaProvider 未正确挂载 2. useCacheQuotaMonitor 钩子没有订阅消息 3. Service Worker 没有发送 QUOTA_WARNING 消息 **排查步骤**: 1. 检查 Drawnix 组件树,确认 CacheQuotaProvider 存在 2. 查看控制台是否有配额监控相关日志 3. 手动触发配额警告测试 4. 检查 Service Worker 是否正常运行 **解决方法**: - 确保 CacheQuotaProvider 正确包裹应用 - 检查 onOpenMediaLibrary 回调是否正确传递 - 测试手动触发配额警告 --- ### 问题 4: 数据迁移失败 **症状**: 旧数据没有迁移到新数据库 **可能原因**: 1. 旧数据库不存在或已被删除 2. 迁移过程中出现错误 3. 迁移标记已设置,跳过了迁移 **排查步骤**: 1. 检查 `chrome://indexeddb-internals/` 查看数据库列表 2. 查看控制台迁移日志 3. 检查 localStorage['CACHE_MIGRATION_DONE'] 标记 4. 查看 IndexedDB 中新数据是否正确 **解决方法**: - 如果需要重新迁移,删除 localStorage 标记 - 确保旧数据库存在且可访问 - 查看错误日志定位具体问题 --- ## 测试建议 详细的测试清单请参考 `/docs/UNIFIED_CACHE_TESTING.md`。 ### 快速冒烟测试 1. **基础缓存测试**: - 生成一张图片 - 检查 Network 显示从 Service Worker 返回 - 检查 IndexedDB 有元数据记录 2. **智能传递测试**: - 生成图片 A - 立即用 A 生成新图片 B(应使用 URL) - 修改 A 的缓存时间为 2 天前 - 再用 A 生成图片 C(应使用 base64) 3. **配额警告测试**: ```javascript navigator.serviceWorker.controller.postMessage({ type: 'QUOTA_WARNING', usage: 4900000000, quota: 5000000000, percentage: 98 }); ``` - 应显示对话框 4. **媒体库集成测试**: - 打开媒体库 - 删除一张图片 - 检查 IndexedDB 和 Cache Storage 都已清理 --- ## API 快速参考 ### UnifiedCacheService ```typescript // 注册元数据 await unifiedCacheService.registerImageMetadata(url, { taskId: 'task-123', model: 'imagen-3.0', prompt: 'A beautiful sunset', params: { size: '1024x1024' } }); // 智能获取图片 const imageData = await unifiedCacheService.getImageForAI(url, { maxAge: 24 * 60 * 60 * 1000, // 24 hours maxSize: 3 * 1024 * 1024, // 3MB quality: 0.8, // 80% }); // 获取缓存信息 const info = await unifiedCacheService.getCacheInfo(url); // { isCached: true, cachedAt: 1234567890, size: 1024000, ... } // 删除缓存 await unifiedCacheService.deleteCache(url); // 获取统计 const usage = await unifiedCacheService.getStorageUsage(); // { usage: 1000000000, quota: 5000000000, percentage: 20 } // 获取所有缓存 URL const urls = await unifiedCacheService.getAllCachedUrls(); // 订阅变化 const unsubscribe = unifiedCacheService.subscribe(() => { console.log('Cache changed!'); }); ``` ### React Hooks ```typescript // 单个图片管理 const { isCached, cacheImage, deleteCache } = useUnifiedCache(url); // 整体统计 const { totalCount, storageUsage } = useCacheStats(); // 配额监控 const { isQuotaExceeded } = useCacheQuotaMonitor(onQuotaExceeded); ``` --- ## 总结 本次实施完成了一个全面、健壮、易用的统一图片缓存系统,具有以下特点: ### 优势 ✅ 双层存储架构,兼顾性能和灵活性 ✅ 智能图片传递,优化 AI API 调用 ✅ 完整的元数据记录,支持复杂查询 ✅ 配额监控和用户提示,避免空间耗尽 ✅ 自动数据迁移,向下兼容 ✅ React Hooks 封装,易于使用 ✅ 全面的错误处理和日志 ✅ 详细的文档和测试清单 ### 技术亮点 🚀 Service Worker 拦截,零延迟响应 🚀 PostMessage 通信,松耦合架构 🚀 IndexedDB 索引,高效查询 🚀 RxJS 订阅模式,响应式更新 🚀 异步操作,不阻塞 UI 🚀 单例模式,统一管理 ### 用户价值 💡 更快的图片加载速度 💡 智能的存储空间管理 💡 完整的生成历史追溯 💡 离线也能访问已缓存图片 💡 清晰的缓存使用提示 --- ## 相关文档 - [设计文档](./UNIFIED_CACHE_DESIGN.md) - 详细的架构设计 - [测试清单](./UNIFIED_CACHE_TESTING.md) - 完整的测试用例 - [CLAUDE.md](../CLAUDE.md) - 项目整体文档 --- **实施完成日期**: 2026-01-07 **版本**: 1.0.0 **状态**: ✅ 已完成并通过构建