929 lines
24 KiB
Markdown
929 lines
24 KiB
Markdown
# 统一图片缓存系统实施总结
|
||
|
||
## 概述
|
||
|
||
本次实施完成了一个全新的统一图片缓存系统,整合了 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<void>
|
||
```
|
||
|
||
- 在 IndexedDB 中记录图片元数据
|
||
- 支持任务 ID、模型、提示词、参数等信息
|
||
- 自动记录缓存时间和最后访问时间
|
||
|
||
#### 2.2 智能图片传递
|
||
```typescript
|
||
async getImageForAI(
|
||
url: string,
|
||
options?: {
|
||
maxAge?: number; // 默认 24 小时
|
||
maxSize?: number; // 默认 3MB
|
||
quality?: number; // 默认 0.8
|
||
}
|
||
): Promise<ImageData>
|
||
```
|
||
|
||
**决策逻辑**:
|
||
```
|
||
缓存时间 < 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<void>
|
||
```
|
||
|
||
- 自动检测旧数据库 (`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 中
|
||
<CacheQuotaProvider onOpenMediaLibrary={handleOpenMediaLibrary}>
|
||
<App />
|
||
</CacheQuotaProvider>
|
||
```
|
||
|
||
**对话框内容**:
|
||
```
|
||
标题: 缓存空间已满
|
||
内容: 图片缓存空间已满,无法继续缓存新图片。是否打开素材库清理缓存?
|
||
按钮: [打开素材库] [稍后处理]
|
||
主题: 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: <url>`
|
||
- `[SW Cache] Notified main thread about cached image: <url>`
|
||
- `[SW Cache] Storage quota: X.XX GB / X.XX GB (XX%)`
|
||
- `[SW Cache] Warning: Storage quota exceeded 95%`
|
||
|
||
#### 主线程日志
|
||
- `[UnifiedCache] Registered metadata for: <url>`
|
||
- `[UnifiedCache] Migration completed from <old-db>`
|
||
- `[TaskExecutor] Registered metadata for task <id>`
|
||
- `[TaskExecutor] Failed to register metadata: <error>`
|
||
|
||
### 错误追踪
|
||
|
||
所有关键操作都有日志输出,便于调试:
|
||
- 缓存操作成功/失败
|
||
- 元数据注册成功/失败
|
||
- 数据迁移进度
|
||
- 配额监控状态
|
||
|
||
---
|
||
|
||
## 未来优化方向
|
||
|
||
### 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
|
||
**状态**: ✅ 已完成并通过构建
|