IndexedDB 可可靠存储断点续传元数据并恢复进度,需以唯一 ID 为主键设计任务对象,通过事务原子更新状态,查询未完成任务恢复上传,并配合清理与降级策略保障健壮性。

IndexedDB 可以可靠地存储断点续传任务的元数据(如文件 ID、已上传字节、总大小、状态、URL、分片信息等),在页面刷新或崩溃后恢复上传进度。关键在于把每个任务的状态作为结构化对象存入 object store,并用事务保证读写一致性。
设计任务状态的数据结构
每个任务建议用唯一 ID(如 fileId + uploadId 组合)作主键,存储字段包括:
-
id:字符串主键(推荐 UUID 或语义化组合,如
"file_abc123_upload_v2") - fileName、fileSize、mimeType
- uploadedBytes:已成功上传的字节数(用于计算下一片起始位置)
-
status:枚举值,如
"pending"、"uploading"、"paused"、"completed"、"failed" - uploadUrl:服务端接收分片的 endpoint
- lastModified:Date 时间戳,便于清理过期任务
- chunks:可选,存储已上传分片的 hash 或索引列表(用于幂等校验)
创建 DB 并定义 object store
初始化时打开数据库并确保 object store 存在,启用 autoIncrement 不必要,因主键由业务生成:
const DB_NAME = 'uploadDB';
const STORE_NAME = 'uploadTasks';
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 2);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
// keyPath 设为 'id',不 autoIncrement
const store = db.createObjectStore(STORE_NAME, { keyPath: 'id' });
// 添加索引便于按状态/时间查询
store.createIndex('byStatus', 'status');
store.createIndex('byLastModified', 'lastModified');
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
原子化更新任务状态(含错误回滚)
每次上传分片成功后,必须用 readwrite 事务 更新对应任务的 uploadedBytes 和 status,避免并发写入丢失进度:
立即学习“Java免费学习笔记(深入)”;
async function updateTaskProgress(taskId, bytesAdded) {
const db = await openDB();
const tx = db.transaction(STORE_NAME, 'readwrite');
const store = tx.objectStore(STORE_NAME);
return new Promise((resolve, reject) => {
const getRequest = store.get(taskId);
getRequest.onsuccess = () => {
const task = getRequest.result;
if (!task) return reject(new Error('Task not found'));
task.uploadedBytes += bytesAdded;
if (task.uploadedBytes >= task.fileSize) {
task.status = 'completed';
}
const putRequest = store.put(task);
putRequest.onsuccess = () => resolve(task);
putRequest.onerror = () => reject(putRequest.error);
};
getRequest.onerror = () => reject(getRequest.error);
});
}
注意:不要先 get 再手动 put —— 应始终在同一个事务中完成读+写,防止中间被其他 tab 修改。
启动时恢复未完成任务
页面加载后,查询所有 status 为 "uploading" 或 "paused" 的任务,按 lastModified 倒序排列,优先恢复最近活动的任务:
async function getActiveTasks() {
const db = await openDB();
const tx = db.transaction(STORE_NAME, 'readonly');
const store = tx.objectStore(STORE_NAME);
const index = store.index('byStatus');
return new Promise((resolve) => {
const req = index.getAll(IDBKeyRange.only('uploading'));
req.onsuccess = () => resolve(req.result);
});
}
// 使用示例
getActiveTasks().then(tasks => {
tasks.forEach(task => {
resumeUpload(task); // 自定义恢复逻辑:从 uploadedBytes 处继续切片上传
});
});
也可扩展支持用户点击“继续”时才恢复,而非自动触发,提升可控性。
清理与降级策略
长期运行需考虑空间与一致性:
- 上传成功后调用
delete()清理已完成任务,避免 DB 膨胀 - 添加定时任务(如每 24 小时)用
byLastModified索引清除 7 天前的"failed"任务 - 监听
storage事件,在多 tab 场景下同步状态变更(例如一个 tab 完成,通知另一个 tab 刷新 UI) - 兜底方案:若 IndexedDB 不可用(如私密模式限制),退回到内存 Map + localStorage 简单序列化(仅存关键字段,不支持大文件元数据)


















