HarmonyOS合同离线核验记录导入导出与完整性核对【共创稿事节】
合同双人修订流程推进到线下会签环节后,真正需要传递的往往不是整份合同正文,而是一组经过定位的核验记录。前五篇已经建立 service-agreement 的稳定条款编号、r1与r2逐段对齐、金额日期规则,以及双人并行修订的冲突状态。当前的 CL-03 付款约定包含金额由120000.00元变为125000.00元、截止日期由2026-11-15变为2026-11-20两处字段变化;CL-02 验收期限则仍有待复核的并行修订。离线交接必须确保这些状态在导出、复制、导入后仍能辨认来源与归属。

把合同正文与核验记录包隔离
把整个合同版本复制到另一台设备,容易同时引入文档内容、临时草稿、浏览位置和多余附件。这里选择较小的核验包:包内包含文档稳定标识、目标修订版、核验规则版本、记录列表、生成时间与内容摘要。每条记录至少保存条款编号、字段路径、字段旧值与新值、核验状态、修订操作编号及记录人。原合同正文仍由原文档仓库管理;导入核验包不会修改 CL-03 的正式文本,也不会直接确认具有分歧的 CL-02。
核验包的标识应稳定且可重复计算。将字段按固定顺序序列化、统一日期格式、统一金额的小数位,能够减少不同客户端由于对象属性顺序造成的摘要差异。但规范化不能擅自改变原业务含义:金额125000.00不能被浮点数格式化成带有不确定误差的中间结果,合同日期也不宜在未明确时区规则时转成设备当前时区的时间戳。这里使用纯字符串字段表示已核对的金额和日期,仅对字段的存在性与格式做检查。
一份记录包可以通过以下代码生成。digest 覆盖正文以外的全部业务字段,使用SHA-256检查包内容在传输前后是否一致;该摘要是完整性线索,而不是对人员身份的认证。createdAt 用固定ISO格式表达,schemaVersion 留给后续格式迁移使用。
const assert = require('node:assert/strict');
const { createHash } = require('node:crypto');
const records = [
{ id: 'REV-CL03-AMOUNT', clauseId: 'CL-03', field: 'amount', before: '120000.00', after: '125000.00', status: 'review', actor: 'editor-a' },
{ id: 'REV-CL03-DATE', clauseId: 'CL-03', field: 'dueDate', before: '2026-11-15', after: '2026-11-20', status: 'review', actor: 'editor-b' }
];
function payloadOf(input) {
return { schemaVersion: 1, docId: input.docId, revision: input.revision,
rulesVersion: input.rulesVersion, createdAt: input.createdAt,
records: [...input.records].sort((a,b) => a.id.localeCompare(b.id)) };
}
function makeEnvelope(input) {
const payload = payloadOf(input);
const digest = createHash('sha256').update(JSON.stringify(payload), 'utf8').digest('hex');
return { payload, digest };
}
const envelope = makeEnvelope({ docId: 'service-agreement', revision: 'r2', rulesVersion: '2.1',
createdAt: '2026-10-28T10:00:00Z', records });
assert.equal(envelope.digest.length, 64);
assert.equal(envelope.payload.records.length, 2);
console.log(JSON.stringify(envelope, null, 2));
导入从结构校验开始,不从覆盖开始
离线文件进入应用时,第一步是解析格式和限制大小,第二步验证摘要,第三步检查文档标识与目标修订版,第四步才考虑本地状态合并。解析阶段要拒绝数组之外的记录、空条款编号、未知状态和格式错误的金额日期;包内若含有不认识的字段,可以选择提示升级格式,而不是直接忽略影响决策的字段。对存在差异的核验记录,导入结果应该明确区分“新增”“重复”“需复核”,绝不能将导入成功等同于合同校验通过。
校验摘要时需要注意:JSON格式的空格与对象属性顺序都可能改变字节。为此导出和导入应约定完全相同的规范化规则,并在摘要校验通过后再进行业务比对。下面的读取逻辑保留确定性字段顺序,同时对不合规状态和重复记录编号给出可理解的错误。单独的Node.js脚本可以检查样本包,但实际应用读取外部URI还需要遵循HarmonyOS文件访问机制。
const assert = require('node:assert/strict');
const { createHash } = require('node:crypto');
function verifyEnvelope(envelope, expectedDoc, expectedRevision) {
if (!envelope || !envelope.payload || typeof envelope.digest !== 'string') throw new Error('包结构缺失');
const p = envelope.payload;
if (p.schemaVersion !== 1 || p.docId !== expectedDoc || p.revision !== expectedRevision) throw new Error('版本不匹配');
if (!Array.isArray(p.records) || p.records.length > 1000) throw new Error('记录数量异常');
const keys = new Set();
for (const r of p.records) {
if (!r || typeof r.id !== 'string' || !/^CL-\d+$/.test(r.clauseId) ||
!['review', 'approved', 'rejected'].includes(r.status) || keys.has(r.id)) throw new Error('记录校验失败');
keys.add(r.id);
}
const data = JSON.stringify(p);
const digest = createHash('sha256').update(data, 'utf8').digest('hex');
if (digest !== envelope.digest) throw new Error('摘要不一致');
return { count: p.records.length, digest };
}
const payload = { schemaVersion: 1, docId: 'service-agreement', revision: 'r2',
rulesVersion: '2.1', createdAt: '2026-10-28T10:00:00Z', records: [
{ id:'REV-CL03-AMOUNT', clauseId:'CL-03', field:'amount', before:'120000.00', after:'125000.00', status:'review' }
] };
const digest = createHash('sha256').update(JSON.stringify(payload)).digest('hex');
assert.equal(verifyEnvelope({ payload, digest }, 'service-agreement', 'r2').count, 1);
assert.throws(() => verifyEnvelope({ payload, digest:'0'.repeat(64) }, 'service-agreement', 'r2'));
console.log('校验记录数量:1');
冲突记录保留双方证据
第五篇的双人冲突判定依赖基线修订和字段路径。导入时还需考虑同一条核验记录在两台设备上发生了状态变化:例如离线包中的 CL-03.amount 为“待复核”,本地已经记录为“同意变更”,不能仅比较导入时间就覆盖本地结论。应该以记录ID执行幂等识别,再检查两条记录的摘要和决策状态。如果内容完全一致,记为重复;如果编号一致而内容不同,保留本地记录并把外来内容放入冲突队列。不同编号但引用同一条款、同一字段的记录也应进入人工归并列表,避免出现相互矛盾的多个生效结论。
为了不让导入动作破坏此前的本地核验历史,合并结果可以先在内存快照上计算,再输出候选记录集与冲突集。只有用户明确确认并完成持久化写入后,才更新当前视图版本。下面的逻辑提供了基于记录编号的幂等合并,并将同一字段的不同决策标成待处理;它不会自动替业务人员选定有效条款。
const assert = require('node:assert/strict');
function mergeReviewRecords(local, incoming) {
const result = new Map(local.map(r => [r.id, { ...r }]));
const conflicts = [];
const fieldIndex = new Map(local.map(r => [`${r.clauseId}:${r.field}`, r.id]));
for (const item of incoming) {
const old = result.get(item.id);
if (old) {
if (JSON.stringify(old) !== JSON.stringify(item)) conflicts.push({ id:item.id, reason:'同编号内容差异' });
continue;
}
const fieldKey = `${item.clauseId}:${item.field}`;
if (fieldIndex.has(fieldKey)) { conflicts.push({ id:item.id, reason:'同字段另有记录' }); continue; }
result.set(item.id, { ...item });
fieldIndex.set(fieldKey, item.id);
}
return { records: [...result.values()], conflicts };
}
const local = [{ id:'REV-CL03-AMOUNT', clauseId:'CL-03', field:'amount', status:'approved' }];
const incoming = [{ id:'REV-CL03-AMOUNT', clauseId:'CL-03', field:'amount', status:'review' },
{ id:'REV-CL02-TERM', clauseId:'CL-02', field:'acceptanceTerm', status:'review' }];
const merged = mergeReviewRecords(local, incoming);
assert.equal(merged.records.length, 2);
assert.equal(merged.conflicts.length, 1);
assert.equal(merged.records[0].status, 'approved');
console.log(merged);

文件入口与持久化次序
在HarmonyOS应用中,选择外部文件时可通过系统文件选择流程取得用户选定的URI,再根据授权范围进行读取。读取前应限制文件大小,读取完成后立即校验JSON结构和摘要,避免把任意外部内容直接写入应用数据区。写入阶段建议先落到临时文件、完成解析和一致性校验,然后替换目标文件;如果应用存储采用数据库,还应把快照更新与导入记录写入同一事务。异常中断后只恢复已确认的版本,不读取半成品。包含个人信息的核验记录需要遵循最小字段原则,并由使用者决定分享范围。
华为开发者的文件共享接口介绍了URI访问授权及权限持久化条件,其中部分持久化操作要求额外权限和设备能力,普通导入流程不应假设所有URI都能长期访问。Preferences参考可用于少量应用设置与索引状态;它也明确提醒多进程并发访问的风险,因此整包业务记录仍宜使用适合事务管理的持久化方案。应用层校验规则本身可以先由上述纯函数单独检查,再与实际文件读写适配。

导出审阅与导入确认分离
业务界面建议将“生成核验包”“选择待导入文件”“查看差异”“确认导入”拆成互不混淆的步骤。导出页必须清楚列出文档 service-agreement、目标版 r2、记录总数和摘要前缀,让接收方知道自己收到的是什么;导入预览则重点列出相同编号的差异、同字段重复以及目标文档不匹配情况。对于CL-02这样仍存在并行意见的条款,状态应继续显示待复核,不因跨设备传输而变成已通过。
核查清单至少覆盖篡改摘要、空记录、重复编号、旧格式、文档标识不符、目标版不符、同条款不同字段同时变化、同字段多次导入、存储中断后的恢复以及隐私字段遗漏等情形。对于导入前的用户确认,还要显示原记录创建人与接收设备上的登录身份是否为同一概念,避免将文件中的操作人字段直接当成当前会话的授权依据。离线包也应携带规则版本,接收方遇到更高版本规则时先提醒人工核对字段含义,再决定是否迁移,而不是忽略新规则继续写入。检查结果要将语法错误和业务冲突分开:前者应阻断导入,后者可以生成可审阅的冲突队列。把冲突保留下来比静默覆盖更有价值,因为后续的文档版本迁移仍需知道每一项审阅意见关联哪一个稳定条款。
本篇完成的是核验记录的可携带结构、摘要核对和应用层幂等合并,而不是合同条款的自动裁定。通过稳定的文档编号、条款定位键与可复核的冲突记录,离线交接不会丢失此前的核验依据。下一篇迁移到更新的文档版本时,可沿用同一包结构,再对失效或更名的条款编号执行映射与差异核查。
更多推荐

所有评论(0)