上一节的费用导入入口已经能把四条 CSV 明细分成三条有效记录和一条错误记录。真正让它适合长期维护的下一步,不是继续往 ImportPage.ets 塞功能,而是把解析能力从入口页面抽离出来。财务账单以后可能出现多行引号、不同编码和不同金额策略;如果页面直接引用解析器的内部文件结构,一次目录调整就能牵连整套导入页面。

这里把解析协议做成工程内的独立 HAR 模块,由 ohpm 以本地源码依赖方式接入应用。当前业务数据不变:TX001 是 12500 分,TX002 是 8600 分,TX003 的 12.5x 为格式错误,TX004 是 4300 分;读取四条、有效三条、错误一条、待入库三条。封装的目标是让调用方能够通过稳定公开端口拿到同样的解析语义,而不是为了增加一个包而改变财务规则。

本地 HAR 与公开解析接口封面

封装之前先划清模块责任

在工程里新增名为 csvparser 的 HAR 模块,它持有 CSV 字段解析器、结果契约和统一入口文件。应用模块 entry 继续持有文件选择、业务金额校验、错误行展示以及是否确认入库的交互。CSV 解析器并不应该知道“物料”“配件”这些分类,也不应该负责向应用数据库写数据。否则代码搬进 HAR 后只是换了目录,依赖边界仍然纠缠在一起。

调用链可以写成 ImportPage → CsvParserPort → CsvLineParser。页面依赖的只有解析端口及其结果类型;真正的解析实现由应用入口构造并交给页面或服务类。错误结果仍由 issue 表达,字段数组仍由 cells 表达,这与上篇的返回契约相同。将来换成另一套解析实现,只要遵守端口语义,财务页面便不用跟着替换异常展示逻辑。

华为开发者的添加依赖项明确区分了本地模块、本地 HAR 文件、远程包的接入方式;oh-package.json5 说明说明了模块入口、名称、版本与依赖字段。这里选用本地源码模块配置,让 HAR 源代码跟随仓库调整;它不等价于已经发布到远程仓库的安装包。

入口文件只暴露能长期承诺的类型

模块内部可以有很多辅助函数,例如对双引号转义的判断、读取下一字符的工具、错误位置映射。这些实现细节不适合全部从 Index.ets 导出。公开面越大,下游越容易绕开业务约束;下次修改某个内部帮助函数,也就更难判断是否破坏外部调用。第一版公开接口保持一个结果类型、一个解析端口和一个默认实现类即可。

代码仍采用上篇的单行协议,不在本次改变 CSV 引号的处理规则。CsvLineParser 保留原来 parse 的处理逻辑,公开入口引用它,而不是复制出另一个名字不同的解析器。issue 为非空时应当忽略该行 cells,不让页面依据部分字段继续生成费用明细。

// csvparser/src/main/ets/CsvParserPort.ets
export interface CsvLineResult {
  cells: string[];
  issue: string;
}

export interface CsvParserPort {
  parse(line: string): CsvLineResult;
}

// csvparser/src/main/ets/Index.ets
export { CsvParserPort, CsvLineResult } from './CsvParserPort';
export { CsvLineParser } from './CsvLineParser';

CsvLineResult 使用明确字段名而不是顺序数组,能避免一个危险的版本问题:假设后续将错误码直接插在数组第二项,调用方如果仍按旧顺序解构,就可能把报错当成正文。对象字段配合接口名称更容易审查版本差异。本阶段可给包标记 0.2.0,公开契约维持协议版本一;包的语义版本与业务导入批次不是同一个概念。

本地依赖声明写在哪里

HAR 自己的模块级 oh-package.json5 用于描述包名、版本和入口;应用模块也需要在自己的模块级依赖中引用它。两个配置放错位置是常见的集成问题:有时开发者在工程根目录添加依赖,某个子模块又使用了直接导入,最后依赖解析不符合预期。将生产期间需要的解析模块写入 entry 的 dependencies,路径指向真实的同级模块目录,会更容易维护。

下方是两个配置文件的核心内容,项目根部还应将 csvparser 注册为工程模块。示例以源码依赖为主;需要以构建后的 .har 文件集成时,file: 的值应改为相应文件路径,不能把源码目录和二进制包路径混为一谈。设置后通过 ohpm install 安装依赖,再从公开入口导入,不直接访问模块私有源文件。

// csvparser/oh-package.json5
{
  "name": "@ledgerdock/csvparser",
  "version": "0.2.0",
  "main": "Index.ets",
  "dependencies": {}
}

// entry/oh-package.json5 中的核心依赖项
{
  "dependencies": {
    "@ledgerdock/csvparser": "file:../csvparser"
  }
}

由于 oh-package.json5 采用 JSON5 格式,配置中能使用注释。实际工程会在原有入口配置上合并依赖字段,不能将示例片段直接覆盖整个配置文件。依赖的键建议与 HAR 的 name 保持一致,这样更利于后续审查锁文件和排查多版本冲突。正式构建时也要确认模块的产物类型为 HAR,而不是把普通 HAP 当作共享库。

页面依赖端口,而不是目录层级

导入页上一节能识别 TX003 的金额错误,依据的是业务映射器对整数分的要求;这项规则应继续留在应用侧。HAR 解析器把 TX003,2026-10-03,12.5x,差旅 分成四个字符串字段,并不会因为数值包含字母就抛出 CSV 语法错误。随后映射器检查金额字段,才把这条明细送入错误列表。两个失败层级在 UI 上可以合并展示,但内部来源应该能够区分。

应用入口从包的名称导入 CsvParserPort 与 CsvLineParser,把具体实现藏在返回端口的工厂后面。导入页拥有 ParseSummary 和行结果集合;此时对组件进行升级,不需要改变页面传递参数的形状,也不需要修改 TX001 到 TX004 的业务数据。这样的稳定端口,比“把所有文件放进 HAR”更有实际价值。

import { CsvLineParser, CsvParserPort, CsvLineResult } from '@ledgerdock/csvparser';

export interface ImportSummary {
  read: number;
  valid: number;
  invalid: number;
  pending: number;
}

export function parseLedgerLines(lines: string[], parser: CsvParserPort): ImportSummary {
  let valid: number = 0;
  let invalid: number = 0;
  for (let i: number = 1; i < lines.length; i++) {
    const result: CsvLineResult = parser.parse(lines[i]);
    if (result.issue.length > 0 || result.cells.length !== 4 ||
      !/^[0-9]+$/.test(result.cells[2])) {
      invalid++;
      continue;
    }
    valid++;
  }
  return { read: lines.length - 1, valid, invalid, pending: valid };
}

export function defaultParser(): CsvParserPort {
  return new CsvLineParser();
}

这里的循环从一开始,因为第零项约定为表头。更完整的映射器仍应检查表头名称、流水号是否重复、日期格式,以及整数分的安全范围;本段代码只突出稳定端口对接和四条既有样本的结果数量。CsvLineParser 本身保持上篇已经写过的引号处理算法,未在新的模块里重新发明字段切分逻辑。

HAR 的公开入口与依赖配置

处理解析器升级时最容易遗漏的两件事

第一件事是改变结果语义却不改变公开契约。比如新实现开始将空字段自动丢弃,旧代码却认为空字段仍占一个列位,那么原本第五列为空的单据就可能被错误挤成四列。面对这种升级,先固定契约样例,再判断是否需要增加新的端口版本或可选参数。对财务导入来说,错列比一次明确失败更难追踪。

第二件事是错误类型的归属。CSV 的引号不闭合属于解析错误,金额分含字母属于业务格式错误;如果 HAR 直接返回“金额错误”,就把模块限制死在费用业务上,未来导入库存 CSV 时还要拆掉它。可将领域错误编码留在映射层,HAR 返回字段和语法错误位置。下一篇处理跨行引号时,也就不会碰到金额规则和文件存储。

为了排查本地包接入,可以检查三条线索:entry 依赖键与模块名称是否匹配、公开入口是否导出了调用方所需类型、编译器是否误从内部目录导入旧实现。尤其不要在一部分页面使用 @ledgerdock/csvparser,另一部分页面继续使用 ../parser/CsvLineParser;即使现阶段表现一致,以后也会生成两条难以合并的维护路线。

生命周期与性能不应被封装层隐藏

解析器如果只是同步处理单行字符串,本身没有持久的文件句柄;文件选择和读取流仍由应用侧管理。页面退出时应停止继续更新已经离开的页面状态,大文件解析将来要拆成可调度的任务,并在取消后停止把结果推入列表。把解析器移入 HAR 不会自动获得线程隔离,若在 UI 事件中直接循环几十万行,依然会阻塞交互。这些都是边界设计必须留下的运行约束。

可以把四条固定样本作为端口回归输入:四条明细、三条有效、一条金额错误;TX003 仍停留在错误列表,待入库仍是三条。对于带逗号的备注、空字段及双引号位置错误,还需要分别核对 cells 与 issue,防止升级改变字段位置。记录版本与问题来源即可,没有必要把所有解析细节都打印到日志,尤其要避免输出原始财务备注中的敏感内容。

导入结果和解析接口信息

下一阶段保留的改动范围

本地 HAR 的价值已经比较清楚:界面关心文件、业务字段和用户确认;库关心 CSV 行如何形成字段数组。两个职责通过 CsvParserPort 连接,而 Index.ets 是唯一稳定的公开入口。迁移过程中对 UI 影响最小的办法,是保留原来的四条样本和错误语义,让结构调整与业务规则调整分开进行。

后面要处理含换行的引号字段,问题将从单行解析扩展到记录边界识别。这时会新增增量扫描器,但仍可以复用当前的错误结构与入口规则。模块化完成的标准并不是文件夹变多了,而是调用方在不接触内部解析细节的前提下,仍能准确知道哪些费用记录可以继续进入确认流程。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐