在油猴脚本里做数据备份,通常只往本地服务器 POST 一份。但服务器挂掉或网络问题时,这批数据就丢了。IndexedDB 可以作为本地兜底——不管服务器是否正常,先把数据存本地一份。
IndexedDB 基础封装
const DB_NAME = "localBackupDB";
const STORE_NAME = "backups";
let db;
function initDB() {
return new Promise((resolve, reject) => {
const req = indexedDB.open(DB_NAME, 1);
req.onupgradeneeded = (e) => {
const d = e.target.result;
if (!d.objectStoreNames.contains(STORE_NAME)) {
d.createObjectStore(STORE_NAME, { keyPath: "id" });
}
};
req.onsuccess = (e) => {
db = e.target.result;
resolve(db);
};
req.onerror = (e) => reject(e);
});
}
function saveToIndexedDB(fileName, content) {
if (!db) return;
const tx = db.transaction(STORE_NAME, "readwrite");
const store = tx.objectStore(STORE_NAME);
store.put({ id: fileName, content, timestamp: Date.now() });
}
function getAllBackups() {
return new Promise((resolve, reject) => {
if (!db) return resolve([]);
const tx = db.transaction(STORE_NAME, "readonly");
const req = tx.objectStore(STORE_NAME).getAll();
req.onsuccess = () => resolve(req.result);
req.onerror = (e) => reject(e);
});
}
集成到备份队列
已有服务端备份逻辑时,在 enqueueSave 里同时存 IndexedDB:
const savedSet = new Set();
const queue = [];
let timer = null;
function enqueueSave(fileName, content) {
if (savedSet.has(fileName)) return;
savedSet.add(fileName);
// 本地 IndexedDB 兜底(不管服务端是否正常)
saveToIndexedDB(fileName, content);
queue.push({ fileName, content });
if (!timer) timer = setTimeout(flushQueue, 1000);
}
function flushQueue() {
const batch = [...queue];
queue.length = 0;
timer = null;
batch.forEach(({ fileName, content }) => saveToServer(fileName, content));
}
这样即使 saveToServer 失败,IndexedDB 里还有一份。
可视化面板
挂到 window.showPanel,在控制台调用即可弹出历史备份列表:
function showPanel() {
if (document.getElementById("__backup_panel")) return;
const panel = document.createElement("div");
panel.id = "__backup_panel";
Object.assign(panel.style, {
position: "fixed", top: "20px", right: "20px",
width: "320px", maxHeight: "420px", overflowY: "auto",
background: "#fff", border: "1px solid #ccc",
boxShadow: "0 0 10px rgba(0,0,0,.3)",
padding: "12px", zIndex: 99999,
fontSize: "12px", fontFamily: "monospace"
});
panel.innerHTML = `<div style="font-weight:bold;margin-bottom:8px">📦 历史备份</div>
<div id="__backup_list"></div>
<button id="__backup_close" style="margin-top:10px">关闭</button>`;
document.body.appendChild(panel);
document.getElementById("__backup_close").onclick = () => panel.remove();
getAllBackups().then(backups => {
const list = document.getElementById("__backup_list");
backups.sort((a, b) => b.timestamp - a.timestamp);
backups.forEach(item => {
const row = document.createElement("div");
row.style.cssText = "display:flex;justify-content:space-between;margin-bottom:4px";
const name = document.createElement("span");
name.textContent = item.id;
name.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
const btn = document.createElement("button");
btn.textContent = "恢复";
btn.onclick = () => {
try {
console.log("🔄 恢复备份:", item.id);
console.log(JSON.parse(item.content));
} catch {
console.log(item.content);
}
};
row.appendChild(name);
row.appendChild(btn);
list.appendChild(row);
});
});
}
window.showPanel = showPanel;
使用时在浏览器控制台输入 showPanel() 即可查看。
初始化调用
initDB()
.then(() => console.log("IndexedDB 已就绪"))
.catch(console.error);
注意事项
- IndexedDB 存储受同源策略限制,每个域名独立,跨域无法访问
- 浏览器私有模式下 IndexedDB 数据在关闭标签页后会清除
- 存储上限一般是磁盘空间的 50% 左右(浏览器决定),备份大量 JSON 不用担心空间
- 用
store.put()(不是add()),同 key 会覆盖而不报错 - 可以在 DevTools → Application → IndexedDB 直接查看和清理数据
