Files
TrueGrowth/docs/UNIFIED_CACHE_DESIGN.md

13 KiB
Raw Permalink Blame History

统一图片缓存方案设计文档

文档信息

  • 创建日期: 2026-01-07
  • 版本: 1.0
  • 状态: 实现中

背景

当前项目中存在三个独立的缓存系统:

  1. Service Worker 的 Cache APIdrawnix-images
  2. 应用层的 Media Cache Servicedrawnix-media-cache IndexedDB
  3. 应用层的 URL Cache Serviceaitu-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

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

class UnifiedCacheService {
  /**
   * 注册图片元数据(任务完成时调用)
   */
  async registerImageMetadata(
    url: string,
    metadata: {
      taskId: string;
      prompt?: string;
      model?: string;
      params?: any;
    }
  ): Promise<void>;

  /**
   * 获取适合传给 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<boolean>;

  /**
   * 删除缓存
   */
  async deleteCache(url: string): Promise<void>;

  /**
   * 批量删除缓存
   */
  async deleteCacheBatch(urls: string[]): Promise<number>;

  /**
   * 获取所有缓存项元数据
   */
  async getAllCacheMetadata(): Promise<CachedMedia[]>;

  /**
   * 获取存储使用情况
   */
  async getStorageUsage(): Promise<{
    used: number;
    quota: number;
    percentage: number;
  }>;

  /**
   * 清空所有缓存
   */
  async clearAllCache(): Promise<void>;

  /**
   * 监听缓存满事件
   */
  onQuotaExceeded(callback: () => void): () => void;

  /**
   * 订阅缓存变化
   */
  subscribe(callback: () => void): () => void;
}

Service Worker 消息协议

主线程 → SW

// 删除缓存请求
{
  type: 'DELETE_CACHE',
  url: string
}

// 批量删除缓存请求
{
  type: 'DELETE_CACHE_BATCH',
  urls: string[]
}

// 清空所有缓存
{
  type: 'CLEAR_ALL_CACHE'
}

SW → 主线程

// 图片已缓存通知
{
  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 数据

    // 从 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 数据

    // 从 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. 删除旧数据库

    indexedDB.deleteDatabase('aitu-media-cache');
    indexedDB.deleteDatabase('aitu-url-cache');
    
  6. 记录迁移状态

    • localStorage 设置标记:drawnix_cache_migrated: 'true'

迁移触发时机

  • 应用启动时自动检测并迁移
  • 迁移过程显示进度提示(可选)
  • 迁移完成后发送通知

实现计划

Phase 1: 基础服务实现(核心)

  • 创建设计文档
  • 实现 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 来源
    • 限制缓存大小
    • 清理过期缓存

参考资料

更新日志

  • 2026-01-07: 初始版本,完成架构设计