边缘离线同步队列实战:基于 BackgroundSync 与 IndexedDB

在移动端使用清单打卡或情绪手账时,用户经常处于电梯、地铁隧道或海边弱网等网络频繁中断的场景中。
很多依赖在线 API 的应用,在断网时点击“完成任务”会直接弹出一个红色的“网络错误,请稍后重试”,甚至直接将用户的操作丢失。
在本地优先(Local-First)与离线自愈架构中,核心原则是:用户的任何操作(打卡、新建、删除)在本地 IndexedDB 中必须 0 毫秒立即生效,同时将该变更动作写入本地「离线待同步变更队列(Offline Sync Queue)」。
当设备重新恢复网络连接时,通过现代浏览器的 Service Worker Background Sync API 在后台静默重放并批量上报至边缘网关,用户完全感知不到网络波动的存在。
+——————————————————————–+
| 基于 BackgroundSync 的离线事务自愈队列架构 |
+——————————————————————–+
| [用户点击完成待办 (0ms 离线操作)] |
| ├── 1. 立即更新本地 IndexedDB (UI 瞬间打上朱砂印章) |
| └── 2. 写入本地 sync_queue 待办队列: { action: "DONE", id, ts }|
| |
| [网络断开中: 队列安全持久化在本地磁盘,即便关闭浏览器也绝不丢失] |
| |
| [网络恢复重连 (触发 window.online 或 Service Worker sync 事件)] |
| | |
| v (后台 Worker 批量读取 sync_queue 队列) |
| [POST /api/batch-sync (一次网络请求打包发送 20 条离线操作)] |
| ├── 边缘 Workers CRDT 合并器原子落盘 |
| └── 成功后清空本地已同步队列,状态 100% 达成最终一致 |
+——————————————————————–+
1. 声明本地离线同步队列 Schema
在 src/db/syncQueue.ts 中定义操作日志格式:
import Dexie, { Table } from "dexie";
export interface SyncMutation {
id?: number;
type: "CREATE" | "UPDATE" | "DELETE";
entityId: string;
payload: any;
timestamp: number;
retryCount: number;
}
export class SyncQueueDatabase extends Dexie {
mutations!: Table<SyncMutation, number>;
constructor() {
super("TideSyncQueueDB");
this.version(1).stores({
mutations: "++id, timestamp, retryCount",
});
}
}
export const syncQueueDb = new SyncQueueDatabase();
2. 变更动作压入队列与批量上报实现
export async function enqueueMutation(type: "CREATE" | "UPDATE" | "DELETE", entityId: string, payload: any) {
// 1. 写入本地待办队列
await syncQueueDb.mutations.add({
type,
entityId,
payload,
timestamp: Date.now(),
retryCount: 0,
});
// 2. 尝试触发后台同步
if ("serviceWorker" in navigator && "SyncManager" in window) {
const reg = await navigator.serviceWorker.ready;
await (reg as any).sync.register("flush-todo-sync");
} else {
// 降级:若在线则立即直接触发重放
if (navigator.onLine) {
flushSyncQueue();
}
}
}
export async function flushSyncQueue() {
const pending = await syncQueueDb.mutations.toArray();
if (pending.length === 0) return;
console.log(`[Sync] 正在后台批量上报 ${pending.length} 条离线操作…`);
try {
const res = await fetch("/api/sync/batch", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mutations: pending }),
});
if (res.ok) {
// 成功上报,清空本地队列
const ids = pending.map((p) => p.id!);
await syncQueueDb.mutations.bulkDelete(ids);
console.log("[Sync] 离线队列同步成功,已完全清空");
}
} catch (err) {
console.warn("[Sync] 同步失败,等待下次网络恢复自动重试", err);
}
}
3. 全自动自愈
监听浏览器的 online 事件,当用户走出电梯、连上 4G 的那一瞬间,所有的离线操作在后台 0.2 秒内全部静默同步完毕。
把离线容错做到极致,才能让用户无论身处何地,都能安心托付自己的每一条记录。
