Skip to content

账单解析

账单导入采用「解析预览 → 确认导入」两阶段流程,解析阶段不落库,用户确认后才写入。

解析器设计

解析器采用策略模式:BillParser 接口声明 getBillType()parse(byte[]),各实现类对应一种账单类型。服务层构造时用 EnumMap<BillType, BillParser> 注册所有实现,按类型分发,便于扩展新来源。

解析器格式表头锚点去重键来源
AlipayBillParserCSV(UTF-8/GBK)「交易号」交易号
WechatBillParserxlsx(POI)「交易时间」交易单号
MeituanBillParserCSV「交易创建时间」交易单号
CmbBillParserCSV「交易日期」生成 CMB_日期_类型_金额_余额
CmbPdfParserPDF(PDFBox)「记账日期」同上(与 CSV 互通)
BocomBillParserxls(POI)「记账日期」生成 BOCOM_日期_时分秒_类型_金额

各解析器统一输出 ParsedBillRow:行号、externalNo、交易时间、类型、金额、交易对方、商品、交易状态、原始收/支方向、是否计入统计、是否可导入、是否重复、命中规则与建议分类/标签、rawData(保留原始列的 JSON)、错误信息。

解析要点

  • 编码识别:CSV 自动识别 UTF-8 / UTF-8 BOM / GBK。
  • 表头定位:以特定表头文字定位数据区,忽略元信息与统计行。
  • 银行 PDF:按字符坐标切列、按纵坐标合并折行还原表格;用带符号金额判断收支;按「联机余额」做连续性校验(误差 0.005),不连续的行标记为不可导入。
  • 时间兼容:微信账单兼容 Excel 日期序列号与文本时间;支付宝在付款时间缺失时回退交易创建时间。

压缩包解密

BillArchiveExtractor 处理 zip 压缩包:

  • 按魔数 PK\x03\x04 判断是否为 zip,并区分「xlsx 本身是 zip」与「账单压缩包」。
  • 支持 ZipCrypto 与 AES 加密;未提供密码返回错误码 4001,密码错误返回 4002
  • 限制条目数与总大小,防止 zip bomb;解析完成后清理临时文件。

两阶段流程

parse(不落库)

  1. 按账单类型分发到对应解析器;若为 zip 则先解密提取。
  2. 批量查询已存在的 externalNo,标记重复行并取消勾选。
  3. 调用规则引擎批量匹配,生成建议分类与标签(批量查询分类与标签,避免 N+1)。

confirm(落库,事务)

  1. 校验默认分类归属与类型,过滤默认标签。
  2. 再次批量运行规则匹配。
  3. 逐条校验类型、时间、金额;按 externalNo 去重(含本批次内去重)。
  4. 规则建议分类优先于面板默认分类;标签取「默认标签 ∪ 规则标签」并去重。
  5. 批量插入(每 500 条一批),批次失败时回退逐条插入;标签关联在批量插入未回填主键时按 externalNo 回查 ID。
  6. 返回成功 / 跳过 / 失败统计。

去重策略

  • 统一使用「用户 + externalNo」唯一约束 uk_user_external 去重,external_no 为 NULL 的手动记录不受影响。
  • 有交易号的账单直接使用交易号。
  • 银行流水无交易号,则用「日期 + 类型 + 金额 + 余额(或时分秒)」生成稳定键,保证同一账单重复导入不会重复;招商银行 CSV 与 PDF 生成规则一致,可混用。

相关文档

基于 Spring Boot 3 + Vue 3 的个人收支管理系统