在 GeminiWorkflow 项目中,我们使用 IndexedDB 在浏览器本地缓存宫格图、分镜图等大尺寸 Base64 图片数据,显著减少重复网络请求,并为复杂工作流提供稳定、高性能的本地缓存能力。
本文将从技术选型、数据库设计、核心 API、智能更新机制以及工程实践经验几个维度,完整介绍这一套 IndexedDB 缓存方案。
一、背景与目标
在 GeminiWorkflow 的实际使用过程中,存在以下特点:
图片体积大(Base64,单张可达数 MB)
图片复用率高(反复查看历史任务)
用户频繁在同一会话内切换任务
网络请求成本高,影响交互体验
如果每次都从服务器拉取图片,会带来明显的性能问题。
核心目标
减少重复请求
提升页面响应速度
支持长期本地缓存
具备自动更新与清理能力
二、为什么选择 IndexedDB?
在浏览器端缓存方案中,IndexedDB 与 localStorage 的对比如下:
结论很明确:
当缓存对象是“大体量、结构化数据”时,IndexedDB 是浏览器端的唯一合理选择。
三、数据库架构设计
3.1 数据库基础配置
文件位置:src/utils/imageCache.js
const DB_NAME = 'GeminiWorkflowCache';
const DB_VERSION = 1;
const STORE_NAME = 'images';
单一数据库,集中管理图片缓存
使用版本号支持后续结构升级
3.2 对象存储(Object Store)结构
images Store 中的每条记录结构如下:
这种设计可以同时满足:
精准定位缓存
版本比对
后期统计与清理
3.3 索引设计
store.createIndex('timestamp', 'timestamp', { unique: false });
store.createIndex('expiry', 'expiry', { unique: false });
store.createIndex('size', 'size', { unique: false });
索引的主要用途:
快速清理过期缓存
统计缓存大小
支持后台扫描与维护任务
四、核心 API 设计
4.1 ImageCache 核心类
ImageCache 以单例模式导出:
export const imageCache = new ImageCache();
核心方法一览
4.2 基本使用示例
import { imageCache } from '@/utils/imageCache';
// 保存宫格图
await imageCache.saveImage(
'grid',
clientId,
taskId,
{ grid_image: 'data:image/png;base64,...' }
);
// 获取宫格图
const cached = await imageCache.getImage('grid', clientId, taskId);
五、智能缓存更新系统
单纯的缓存并不足够,缓存必须“可控、可更新”。
因此在项目中引入了智能缓存更新机制。
5.1 更新流程说明
核心思路:
周期性获取服务器任务元数据
对比本地缓存版本
找出版本不一致的缓存
并发更新对应图片
流程示意如下:
服务器元数据
↓
本地缓存版本对比
↓
筛选需要更新的缓存
↓
限制并发批量更新
5.2 启动智能更新
位置:src/App.jsx
import { startSmartCacheUpdate } from './utils/imageCache';
const stopUpdate = startSmartCacheUpdate(
getHistoryMeta,
getTaskGridImage,
getTaskSplitImages,
clientId,
5 * 60 * 1000
);
默认每 5 分钟 执行一次
并发数量限制为 3,防止阻塞主线程
六、API 层的缓存优先策略
缓存并不是独立存在的,而是深度集成在 API 层。
6.1 缓存优先流程
export async function getTaskGridImage(taskId, clientId) {
// 1. 尝试读取缓存
const cached = await imageCache.getImage('grid', clientId, taskId);
if (cached) {
return { ...cached, _fromCache: true };
}
// 2. 请求服务器
const response = await fetchWithRetry(url);
// 3. 写入缓存
await imageCache.saveImage(
'grid',
clientId,
taskId,
response,
task.updated_at
);
return { ...response, _fromCache: false };
}
特点:
缓存失败不影响主流程
接口返回中明确标识是否来自缓存
对业务层完全透明
6.2 缓存 Key 规范
统一规范便于调试和维护。
七、调试与运维工具
为方便开发和排查问题,项目内置了完整的缓存调试工具。
7.1 控制台调试 API
CacheDebug.stats(); // 缓存统计
CacheDebug.getAll(); // 所有缓存项
CacheDebug.inspect(...); // 单条缓存详情
CacheDebug.compare(...); // 与服务器对比
CacheDebug.clear(); // 清空缓存
CacheDebug.clean(); // 清理过期缓存
7.2 直接查看 IndexedDB
const request = indexedDB.open('GeminiWorkflowCache', 1);
request.onsuccess = () => {
const db = request.result;
const tx = db.transaction(['images'], 'readonly');
const store = tx.objectStore('images');
store.getAll().onsuccess = e => console.log(e.target.result);
};
八、缓存配置策略
const CACHE_CONFIG = {
DEFAULT_EXPIRY: 7 * 24 * 60 * 60 * 1000, // 7 天
MAX_CACHE_SIZE: 100, // 100 MB
FORCE_REFRESH_INTERVAL: 24 * 60 * 60 * 1000
};
自动维护机制
每 10 分钟清理过期缓存
每 5 分钟执行智能更新
超出最大容量自动回收旧数据
九、工程实践经验总结
9.1 缓存不应阻塞业务
try {
await imageCache.saveImage(...);
} catch (err) {
console.error('缓存失败,不影响主流程', err);
}
缓存永远是增强能力,而非依赖点。
9.2 防止重复请求
内部维护
pendingRequests Map相同请求复用进行中的 Promise
9.3 后台验证机制
命中缓存时立即返回
后台异步校验服务器版本
保证下次访问数据最新
十、结语
在 GeminiWorkflow 中,IndexedDB 不再只是“本地存储”,而是:
一个 具备版本控制、容量管理、自动更新能力的前端缓存系统
这套方案在保证数据一致性的同时,大幅提升了复杂工作流场景下的用户体验,也为后续的离线能力和 PWA 演进奠定了基础。