
项目SchemaGate页面ImportReviewPage任务SG-2908说明以下导入内容、状态及 HiLog 均为离线人工演示数据不代表真实企业数据、DevEco 编译结果或上架审核记录。一、一个版本号就能把“成功导入”变成错账同事给我四条离线维保单说只要能把 JSON 解析成功就算导入完成。我没有立即做列表展示而是先检查了四行数据里到底有哪些含义。第一条是旧版本没有紧急程度字段第二条使用当前版本第三条却写着尚未支持的版本3第四条版本正确但等级写了根本不存在的extreme。这四行都能被JSON.parse读成对象但只有两行符合业务约定。因此这次把 Demo 命名为SchemaGate入口页为ImportReviewPage任务号SG-2908。演示输入固定为四条R101的 v1“滤芯更换”R102的 v2“阀门复检”R103的 v3“密封检查”R104的 v2“压力巡检”。目标并不是让解析器尽量宽容而是明确允许哪些历史版本被降级处理拒绝哪些未来结构和非法枚举值。本例最后的结果应该非常具体通过R101、R102两条拒绝R103、R104两条接受率为50%状态为REVIEW_READY。拒绝原因分别为UNSUPPORTED_VERSION与INVALID_LEVEL。请注意这些英文大写状态属于本文自定义的业务标识不是 HarmonyOS SDK 的错误码也没有向后台服务器上报任何内容。二、先把导入契约写得比 UI 早一步如果导入入口直接声明JSON.parse(text) as Ticket后续组件很容易误以为拿到的已经是业务对象。实际上类型断言只影响开发期的类型认知不会替不可信输入做运行时检查。我更愿意把它看成一道分界线左侧是外来字符串右侧才是经过验证和重建的规范化记录中间不能省略字段判定。我先定义输出对象和样例输入。第一条 v1 没有level字段约定在接受时补成normal第二条 v2 明确为high。第三条版本号3不做猜测性兼容第四条即使写着v2也不能默认将extreme映射为high。展示字段只允许id、title、level其它来源字段不能随手展平进页面状态。type TicketLevel normal | high; interface Ticket { id: string; title: string; level: TicketLevel; } interface RawTicket { version?: number; id?: string; title?: string; level?: string; } const IMPORT_LINES: string[] [ {version:1,id:R101,title:滤芯更换}, {version:2,id:R102,title:阀门复检,level:high}, {version:3,id:R103,title:密封检查,level:normal}, {version:2,id:R104,title:压力巡检,level:extreme} ];这四个字符串是人工构造的最小复现实例没有文件权限、网络传输或系统账号依赖。若换成真实导入文件读取权限、输入大小和编码应由外层适配器处理不能让单条 JSON 验证函数承担整个文件生命周期。当前所有字段都只存在于内存并不写入偏好设置或关系型数据库。三、版本支持不是看到整数就全部放行接下来先处理单条记录解析失败要单独返回原因根值不是对象也要拒绝版本只接受1和2业务 ID 必须匹配R加三位数字标题不能为空且长度不能超过20v2 的等级严格在两值集合里选。这里选择重建一个新的Ticket对象不直接返回解析所得的原始对象避免随源数据附带不认识的字段混入后续渲染与持久化。interface CheckResult { ok: boolean; row?: Ticket; reason: string; } function validateLine(text: string): CheckResult { let raw: RawTicket; try { raw JSON.parse(text) as RawTicket; } catch (err) { return { ok: false, reason: BAD_JSON }; } if (!raw || typeof raw ! object || Array.isArray(raw)) { return { ok: false, reason: BAD_SHAPE }; } if (raw.version ! 1 raw.version ! 2) { return { ok: false, reason: UNSUPPORTED_VERSION }; } if (typeof raw.id ! string || !/^R\d{3}$/.test(raw.id) || typeof raw.title ! string || raw.title.trim().length 0 || raw.title.trim().length 20) { return { ok: false, reason: INVALID_FIELD }; } const level: TicketLevel raw.version 1 ? normal : (raw.level high ? high : normal); if (raw.version 2 raw.level ! high raw.level ! normal) { return { ok: false, reason: INVALID_LEVEL }; } return { ok: true, reason: OK, row: { id: raw.id, title: raw.title.trim(), level: level }}; }这段代码有两处刻意保守一是 v1 才允许使用normal作为缺省值v2 缺字段也要判为非法二是标题先做trim()再检查长度。未来如果有真实的版本迁移需要明确迁移配置和变更记录不能随意拿“向前兼容”四个字盖过去。异常路径只给出业务原因不把解析源全文写到日志里避免导入内容以后含有隐私信息时形成二次泄露。从生命周期看每次调用validateLine都只创建一份新对象没有持久连接与线程实例。JSON.parse的异常在函数内收口但内存分配失败、超长输入和系统资源紧张等情况并未通过这段短代码完全解决真实产品必须在读取前限定总文件字节数与单行长度。四、逐行接受与拒绝比一个布尔值有用单条合法并不等于整批导入已经可以提交。如果两条合法数据用了同一个 ID直接写入可能覆盖已有工单。因此批处理阶段还要保持原始行顺序禁止本批次重复 ID分离可展示的通过清单与待人工检查的失败清单。这里的“通过”仅代表结构验证通过不意味着工单真实性已被核验。下面的汇总函数一次性处理四行输出统一状态REVIEW_READY。它并不在循环中立即落库原因是批量导入时最需要保存的是每行的诊断信息待用户确认后才应该单独设计数据库事务及幂等提交操作。若页面被关闭本次内存报告可以直接丢弃下次重新读取来源而不是恢复一个可能已经过期的提交按钮状态。interface ImportReport { accepted: Ticket[]; rejected: string[]; status: string; } function reviewBatch(lines: string[]): ImportReport { const accepted: Ticket[] []; const rejected: string[] []; const seen: Setstring new Setstring(); for (let index 0; index lines.length; index) { const result: CheckResult validateLine(lines[index]); if (!result.ok || !result.row) { rejected.push(row${index 1} reason${result.reason}); continue; } if (seen.has(result.row.id)) { rejected.push(row${index 1} reasonDUPLICATE_ID); continue; } seen.add(result.row.id); accepted.push(result.row); } console.info(SG-2908 accepted${accepted.length} rejected${rejected.length} statusREVIEW_READY); return { accepted: accepted, rejected: rejected, status: REVIEW_READY }; }示意 DevEco 页面左侧是SchemaGate项目目录中间放着validateLine与reviewBatch的业务关键段右侧为ImportReviewPage的模拟布局底部 HiLog 使用同一条演示日志SG-2908 accepted2 rejected2 statusREVIEW_READY。这里没有“真实审核通过”这样的词因为我们做的只是四段静态输入的业务检查根本没有触达应用市场流程。五、拒绝项不等于整个页面报错以前做这类导入界面容易把某一行 JSON 解析失败直接变成“整批失败”。在离线运维现场这样会让维护人员看不出到底是第几行坏了也无法确定前面已识别的记录是否安全。更实用的画面是上半部分汇总结果下半部分把R103的未知版本和R104的非法等级并排列出明确告诉操作者这两行需要修复来源数据本次不会被悄悄接受。R101的normal是明确的旧版默认值并不是任何版本遇到陌生字段都可以默默归一的借口。R102的high来自 v2 的显式输入。有效两项与拒绝两项合计四项因此接受率固定为50%如果页面把拒绝行也算进“已提交”汇总文字就与真实业务语义冲突。REVIEW_READY表示可以进入人工复核页面不表示已经保存、同步或审批。手机演示界面保留完整状态栏显示任务SG-2908、输入4条、通过2条、拒绝2条、接受率50%下方逐项列出两条拒绝原因。此图是没有手机外壳的纯屏幕设计稿目的是建立文章数据、示例日志和可视结果的共同基线不能拿它证明真实手机完成了导入测试。六、真正上线时还要加三道边界第一道是资源预算。当前例子只处理四行短字符串真实导入时应限制总大小、单行长度和最大记录数并在解析前确定来源格式。一次读取巨大 JSON 字符串再交给JSON.parse即使逻辑完全正确也可能造成内存峰值和界面停顿。大文件应在外层按格式设计分批读取与用户取消路径不要把示例的小数组直接复制成生产处理器。第二道是提交边界。本篇代码没有执行持久化没有数据库事务也没有跨设备同步进入真实工作流时必须把本次验证报告与提交时的源文件版本绑定。否则用户先看到“通过2条”再替换来源文件点击提交却写入了另一批数据。建议提交前重新校验当前源版本并使用数据库唯一约束阻止跨批次重复 ID。第三道是生命周期与隐私。页面返回时及时丢弃大体积原始文本和临时报告引用若扩展为文件选择器记得释放读取资源并遵守 URI 授权边界。日志只写总数、行号及拒绝原因不记录维保内容本身。当前演示既没有真实句柄也没有后台任务因此无需捏造 close、terminate 或系统权限调用这恰恰是把示例能力与产品责任分开的做法。本轮的结论只有一句能被 JSON 解析不代表能被业务接受。先建立显式版本白名单、字段重建和逐行拒绝清单再考虑 UI 和落库才能在处理来源复杂的离线单据时留下可追溯的判断链。官方核对资料ArkTS 语言介绍与 HarmonyOS 开发文档中心。文中JSON.parse、正则、数组与集合是语言层能力字段契约、版本迁移和拒绝码完全属于业务自定义逻辑不是操作系统提供的导入校验 SDK。https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-overviewhttps://developer.huawei.com/consumer/cn/doc/