From 9d97384aaf54feb26028d498c9b5d43abbc43239 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 16 Oct 2026 15:46:51 +0800 Subject: [PATCH] =?UTF-8?q?sync:=20=E5=90=84=E7=AB=AF=E4=BA=92=E9=80=9A?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E5=B0=B1=E4=BD=8D=20=E2=80=94=E2=80=94=20wir?= =?UTF-8?q?e=20=E5=BA=94=E7=94=A8/=E6=8B=89=E5=8F=96=EF=BC=88LWW+FK=20uuid?= =?UTF-8?q?=20=E6=98=A0=E5=B0=84=EF=BC=89=E6=8E=A5=E5=85=A5=20/sync/push|p?= =?UTF-8?q?ull=EF=BC=8C=E6=A1=8C=E9=9D=A2=E4=B8=8E=E7=BD=91=E9=A1=B5?= =?UTF-8?q?=E5=90=8C=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/.wire-dbg/footprints.db | Bin 0 -> 217088 bytes app/.wire-test/footprints.db | Bin 0 -> 217088 bytes app/server/db.mjs | 188 ++++++++++++++++++++++++++++++++++- sync-server/src/index.mjs | 5 +- 4 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 app/.wire-dbg/footprints.db create mode 100644 app/.wire-test/footprints.db diff --git a/app/.wire-dbg/footprints.db b/app/.wire-dbg/footprints.db new file mode 100644 index 0000000000000000000000000000000000000000..c198797c033c863908ae4e4e9b14bd7819136f1d GIT binary patch literal 217088 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 literal 0 HcmV?d00001 diff --git a/app/server/db.mjs b/app/server/db.mjs index 5807e07..1604cdf 100644 --- a/app/server/db.mjs +++ b/app/server/db.mjs @@ -80,6 +80,35 @@ export function buildSearchText(title, description, result, tags, extras) { return [title, description || '', result || '', (tags || []).join(' '), ...extraFlat] .filter(Boolean).join('\n'); } + +// ---------- Wire 协议(各端互通:uuid 引用 FK,LWW 仲裁) ---------- + +function parseHlc(h) { + const m = /^([0-9]+):([0-9]+):(.+)$/.exec(String(h || '')); + return m ? { ms: Number(m[1]), cnt: Number(m[2]), dev: m[3] } : null; +} + +function hlcNewer(a, b) { + if (a.ms !== b.ms) return a.ms > b.ms; + if (a.cnt !== b.cnt) return a.cnt > b.cnt; + return a.dev > b.dev; +} + +// 表 → [列, FK引用目标表] +const WIRE_TABLES = { + footprint: { fks: { type_id: 'footprint_type', parent_id: 'footprint' } }, + footprint_type: { fks: {} }, + taxonomy: { fks: {} }, + dimension: { fks: {} }, + grade_band: { fks: {} }, + metric_def: { fks: {} }, + semester: { fks: {} }, + course_grade: { fks: { semester_id: 'semester' } }, + saved_view: { fks: {} }, + attachment: { fks: { owner_id: 'footprint' } }, + footprint_dimension: { fks: { footprint_id: 'footprint', dimension_id: 'dimension' } } +}; + // 老库迁移:补列 + 回填 uuid/hlc/origin(幂等) const SYNC_TABLES = ['footprint', 'footprint_type', 'taxonomy', 'dimension', 'attachment', 'semester', 'course_grade', 'metric_def', 'saved_view', 'grade_band']; @@ -977,13 +1006,168 @@ function listFootprints(d, query) { return { ok: true }; } + case 'sync_apply_wire': + return applyWireBatch(payload.changes || []); + + case 'sync_pull_wire': + return pullWire(payload.cursor, payload.limit); + default: throw new Error('未知命令:' + cmd); } } // 工厂闭合 - return { handleCommand, getDb }; + // ---------- Wire 协议:出站序列化 / 入站应用 ---------- + + const SKIP_COLS = ['id', 'uuid', 'hlc', 'origin', 'created_at', 'updated_at', 'search_text']; + + function uuidOf(table, id) { + if (id == null) return null; + const r = db.prepare('SELECT uuid FROM ' + table + ' WHERE id=?').get(id); + return r ? r.uuid : null; + } + + function idOfUuid(table, uuid, mayCreate) { + if (uuid == null) return null; + const r = db.prepare('SELECT id FROM ' + table + ' WHERE uuid=?').get(uuid); + if (r) return r.id; + if (!mayCreate) return null; + const h2 = hlcNext(db); + if (table === 'footprint_type') { + const rr = db.prepare("INSERT INTO footprint_type (name, icon, color, sort, form_template, is_builtin, uuid, hlc, origin) VALUES (?,?,?,?,?,0,?,?,?)") + .run('同步类型 ' + uuid.slice(0, 8), 'tag', '#64748b', 500, '[]', uuid, h2, 'sync'); + return Number(rr.lastInsertRowid); + } + if (table === 'dimension') { + const rr = db.prepare("INSERT INTO dimension (name, color, sort, uuid, hlc, origin) VALUES (?,?,?,?,?,?)") + .run('同步维度 ' + uuid.slice(0, 8), '#64748b', 500, uuid, h2, 'sync'); + return Number(rr.lastInsertRowid); + } + if (table === 'grade_band') { + const rr = db.prepare("INSERT INTO grade_band (label, color, min, max, sort, uuid, hlc, origin) VALUES (?,?,?,?,?,?,?,?)") + .run('同步梯度 ' + uuid.slice(0, 8), '#64748b', 60, 70, 500, uuid, h2, 'sync'); + return Number(rr.lastInsertRowid); + } + if (table === 'metric_def') { + const rr = db.prepare("INSERT INTO metric_def (key, label, unit, agg, section, uuid, hlc, origin) VALUES (?,?,?,?,?,?,?,?)") + .run('sync_' + uuid.slice(0, 8), '同步指标 ' + uuid.slice(0, 8), null, 'sum', null, uuid, h2, 'sync'); + return Number(rr.lastInsertRowid); + } + if (table === 'taxonomy') { + const rr = db.prepare("INSERT INTO taxonomy (kind, value, color, sort, uuid, hlc, origin) VALUES (?,?,?,0,?,?,?,?)") + .run('level', '同步条目 ' + uuid.slice(0, 8), '#64748b', uuid, h2, 'sync'); + return Number(rr.lastInsertRowid); + } + return null; + } + + function serializeRowOf(table) { + const spec = WIRE_TABLES[table]; + return (row) => { + const out = { uuid: row.uuid, hlc: row.hlc, deleted_at: row.deleted_at || null }; + for (const k of Object.keys(row)) { + if (SKIP_COLS.includes(k)) continue; + if (spec.fks[k] !== undefined) { out[k.replace('_id', '_uuid')] = uuidOf(spec.fks[k], row[k]); continue; } + out[k] = row[k]; + } + return out; + }; + } + + function softDeleteById(table, id) { + db.prepare('UPDATE ' + table + " SET deleted_at=datetime('now','localtime') WHERE id=?").run(id); + } + + function applyWire(change) { + const spec = WIRE_TABLES[change.table]; + if (!spec) return { accepted: 0, stale: 0, invalid: 1 }; + const rowUuid = String(change.row_uuid || ''); + const incoming = parseHlc(change.hlc); + if (!incoming || (change.op !== 'upsert' && change.op !== 'delete')) return { accepted: 0, stale: 0, invalid: 1 }; + const existing = db.prepare('SELECT id, hlc FROM ' + change.table + ' WHERE uuid=?').get(rowUuid); + if (existing) { + const e = parseHlc(existing.hlc); + if (e && !hlcNewer(incoming, e)) return { accepted: 0, stale: 1, invalid: 0 }; + } + let res; + try { + res = withTx(db, () => { + if (change.op === 'delete') { + if (existing) softDeleteById(change.table, existing.id); + touchOplog(db, change.table, rowUuid, 'delete', change.hlc); + return 1; + } + const data = change.data || {}; + const fv = {}; + for (const fk of Object.keys(spec.fks)) { + const uuidVal = data[fk.replace('_id', '_uuid')]; + fv[fk] = idOfUuid(spec.fks[fk], uuidVal, true); + } + const cols = []; + const vals = []; + for (const k of Object.keys(data)) { + if (['uuid', 'hlc', 'deleted_at'].includes(k)) continue; + if (k.endsWith('_uuid') && spec.fks[k.replace('_uuid', '_id')] !== undefined) continue; + cols.push(k); + vals.push(data[k]); + } + for (const fk of Object.keys(fv)) { cols.push(fk); vals.push(fv[fk]); } + cols.push('uuid', 'hlc', 'origin'); + vals.push(rowUuid, change.hlc, 'sync'); + if (change.table === 'footprint') { + cols.push('search_text'); + vals.push(buildSearchText(String(data.title || ''), String(data.description || ''), String(data.result || ''), data.tags || [], data.extras || {})); + } + if (existing) { + const setSql = cols.map(c => c + '=?').join(', '); + db.prepare('UPDATE ' + change.table + ' SET ' + setSql + ' WHERE id=?').run(...vals, existing.id); + } else { + db.prepare('INSERT INTO ' + change.table + ' (' + cols.join(',') + ') VALUES (' + cols.map(() => '?').join(',') + ')').run(...vals); + } + touchOplog(db, change.table, rowUuid, 'upsert', change.hlc); + return 1; + }); + } catch (ex) { + throw ex; + } + return res === 1 ? { accepted: 1, stale: 0, invalid: 0 } : { accepted: 0, stale: 0, invalid: 0 }; + } + + function applyWireBatch(changes) { + let accepted = 0; let stale = 0; let invalid = 0; + for (const ch of (changes || [])) { + const r = applyWire(ch); + accepted += r.accepted; stale += r.stale; invalid += r.invalid; + } + return { accepted, stale, invalid }; + } + + function pullWire(cursorRaw, limitRaw) { + const cursor = Math.max(0, Number(cursorRaw) || 0); + const limit = Math.min(500, Math.max(1, Number(limitRaw) || 200)); + const opRows = db.prepare('SELECT id, table_name, row_uuid, op, hlc FROM sync_oplog WHERE id>? ORDER BY id LIMIT ?').all(cursor, limit); + const changes = []; + for (const op of opRows) { + if (!WIRE_TABLES[op.table_name]) continue; + if (op.op === 'delete') { + changes.push({ seq: op.id, table: op.table_name, row_uuid: op.row_uuid, op: 'delete', hlc: op.hlc, data: null }); + continue; + } + const row = db.prepare('SELECT * FROM ' + op.table_name + ' WHERE uuid=?').get(op.row_uuid); + if (!row) continue; + changes.push({ seq: op.id, table: op.table_name, row_uuid: op.row_uuid, op: 'upsert', hlc: op.hlc, data: serializeRowOf(op.table_name)(row) }); + } + const maxSeq = db.prepare('SELECT COALESCE(MAX(id),0) AS m FROM sync_oplog').get().m; + const hasMore = opRows.length === limit; + return { + changes, + cursor: hasMore && opRows.length ? opRows[opRows.length - 1].id : Math.max(cursor, maxSeq), + has_more: hasMore + }; + } + + return { handleCommand, getDb, pullWire, applyWireBatch }; } // ---------- 默认单例(开发桥接 / seed 兼容) ---------- @@ -999,4 +1183,4 @@ export function getDb() { export function handleCommand(cmd, payload) { return getDefault().handleCommand(cmd, payload); -} +} \ No newline at end of file diff --git a/sync-server/src/index.mjs b/sync-server/src/index.mjs index 9a074dc..f4184d1 100644 --- a/sync-server/src/index.mjs +++ b/sync-server/src/index.mjs @@ -126,10 +126,11 @@ const server = http.createServer(async (req, res) => { if (req.method === 'POST' && route === '/sync/push') { const body = await readJson(req); - return send(200, true, push(auth.user_id, body.changes)); + // wire 变更直接应用到该用户档案库(网页版立即可见);generate_changes 由档案库 oplog 驱动 + return send(200, true, commandsFor(auth.user_id).applyWireBatch(body.changes)); } if (req.method === 'GET' && route === '/sync/pull') { - return send(200, true, pull(auth.user_id, url.searchParams.get('cursor'), url.searchParams.get('limit'))); + return send(200, true, commandsFor(auth.user_id).pullWire(url.searchParams.get('cursor'), url.searchParams.get('limit'))); } if (req.method === 'POST' && route === '/sync/attachment') { const body = await readJson(req);