fix: 同步引擎重写——外键重映射、自然键去重、墓碑传播;服务端冲突比较修正(双设备测试 12/12)

This commit is contained in:
root
2026-10-16 14:54:29 +08:00
parent 3f7b072ac6
commit dba22765a6
9 changed files with 68796 additions and 112 deletions
+47 -3
View File
@@ -14,6 +14,15 @@ const ITER = 150000
const TABLES = ['project', 'work_log', 'log_item', 'attendance', 'weekly_note', 'setting', 'memo', 'credential', 'transaction']
// 自然键:同一逻辑记录在两台设备上独立创建时的去重依据
const NATURAL_KEYS = {
project: 'name',
work_log: 'date',
attendance: 'date',
weekly_note: 'week_start',
setting: 'key'
}
fs.mkdirSync(DATA_DIR, { recursive: true })
// ---------- 存储辅助 ----------
@@ -221,21 +230,56 @@ async function handle(req, res) {
if (!userData.tables) userData.tables = {}
if (!userData.tombstones) userData.tombstones = {}
// 应用推送的变更(新者胜)
// 应用推送的变更(新者胜 + 自然键去重)
const incomingTables = body.tables || {}
let changed = 0
const now = new Date().toISOString()
for (const t of TABLES) {
const rows = incomingTables[t] || []
userData.tables[t] = userData.tables[t] || {}
const nk = NATURAL_KEYS[t] || null
for (const row of rows) {
if (!row || !row.uuid) continue
const clean = {}
for (const k of Object.keys(row)) if (k !== 'id') clean[k] = row[k]
// 自然键冲突:另一 uuid 的记录已占用同一自然键
let conflictUuid = null
if (nk) {
const nkVal = String(clean[nk] || '')
if (nkVal) {
for (const u2 of Object.keys(userData.tables[t])) {
if (u2 === row.uuid) continue
if (String(userData.tables[t][u2][nk] || '') === nkVal) { conflictUuid = u2; break }
}
}
}
const existing = userData.tables[t][row.uuid]
if (!existing || String(clean.updated_at || '') >= String(existing.updated_at || '')) {
userData.tables[t][row.uuid] = clean
if (conflictUuid) {
// 与自然键冲突行比较新者(而非与同 uuid 旧行)
const conflictRow = userData.tables[t][conflictUuid]
const incomingNewer = String(clean.updated_at || '') >= String(conflictRow.updated_at || '')
if (!incomingNewer) {
// 本记录较旧:加墓碑让推送端删除它
userData.tombstones[t] = userData.tombstones[t] || {}
userData.tombstones[t][row.uuid] = now
changed++
continue
}
// 本记录较新:删除冲突副本并加墓碑通知其他端
if (t === 'work_log') {
// 冲突的工作日志被删时,其日志条目改挂到幸存者
for (const iu of Object.keys(userData.tables.log_item || {})) {
const it = userData.tables.log_item[iu]
if (String(it.wuuid || '') === conflictUuid) { it.wuuid = row.uuid; changed++ }
}
}
delete userData.tables[t][conflictUuid]
userData.tombstones[t] = userData.tombstones[t] || {}
userData.tombstones[t][conflictUuid] = now
changed++
}
userData.tables[t][row.uuid] = clean
changed++
}
}
// 应用墓碑