13 KiB
13 KiB
统一图片缓存方案设计文档
文档信息
- 创建日期: 2026-01-07
- 版本: 1.0
- 状态: 实现中
背景
当前项目中存在三个独立的缓存系统:
- Service Worker 的 Cache API(
drawnix-images) - 应用层的 Media Cache Service(
drawnix-media-cacheIndexedDB) - 应用层的 URL Cache Service(
aitu-url-cacheIndexedDB)
这种分散的缓存架构导致了以下问题:
- 缓存逻辑不统一,维护困难
- Service Worker 缓存没有任务元数据(模型、提示词、参数)
- 无法根据缓存时间智能决策传 URL 还是 base64
- 缺少缓存满时的用户友好提示
设计目标
- 统一缓存管理:一个服务协调 SW 和应用层缓存
- 完整元数据存储:记录缓存时间、生成参数、模型信息
- 智能图片传递:调用 AI 接口时,1天内用 URL,超过1天用 base64
- 用户可控清理:缓存满时提示打开素材库手动删除
- 向下兼容:从旧数据库迁移数据
架构设计
整体架构
┌─────────────────────────────────────────────────────────────┐
│ 浏览器主线程 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 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
}
数据迁移方案
迁移步骤
-
检测旧数据库
- 检查
aitu-media-cache是否存在 - 检查
aitu-url-cache是否存在
- 检查
-
创建新数据库
- 打开
drawnix-unified-cache - 创建 object store 和索引
- 打开
-
迁移 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 } } -
迁移 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: {} } -
删除旧数据库
indexedDB.deleteDatabase('aitu-media-cache'); indexedDB.deleteDatabase('aitu-url-cache'); -
记录迁移状态
- localStorage 设置标记:
drawnix_cache_migrated: 'true'
- localStorage 设置标记:
迁移触发时机
- 应用启动时自动检测并迁移
- 迁移过程显示进度提示(可选)
- 迁移完成后发送通知
实现计划
Phase 1: 基础服务实现(核心)
- 创建设计文档
- 实现
UnifiedCacheService类 - 实现 IndexedDB 操作
- 实现与 SW 的通信协议
- 实现数据迁移逻辑
Phase 2: Service Worker 集成
- 修改 SW 图片缓存逻辑
- 添加缓存通知消息
- 实现 SW 端的缓存删除响应
Phase 3: 智能传递逻辑
- 实现
getImageForAI()方法 - 添加图片压缩功能
- 添加缓存时间检测
Phase 4: UI 集成
- 更新 TaskItem 使用新服务
- 实现缓存满提示 Dialog
- 更新媒体库显示缓存信息
- 添加缓存管理 UI
Phase 5: 测试与优化
- 单元测试
- 集成测试
- 性能优化
- 文档完善
注意事项
-
缓存策略
- Service Worker 的 Cache API 用于快速响应(不存元数据)
- IndexedDB 用于元数据管理和查询
- 不在 IndexedDB 中存储 blob,避免双重存储
-
性能考虑
- SW 缓存命中后立即返回,不阻塞
- 元数据更新使用异步消息,不影响图片加载速度
- 批量操作使用事务优化
-
兼容性
- 保持现有插入画布逻辑不变
- 渐进式迁移,旧代码依然可用
- 提供降级方案(如 SW 不可用)
-
安全性
- 验证 URL 来源
- 限制缓存大小
- 清理过期缓存
参考资料
更新日志
- 2026-01-07: 初始版本,完成架构设计