核心是map分组+stream聚合+bigdecimal精确比对三步闭环,避开double隐式转换、空值崩溃、舍入偏差;建模用string/long构造bigdecimal,分组用groupingby+summingbigdecimal,比对基于全量key集并补zero,支持舍入、明细统计与并发安全。

核心是用 Map 分组 + Stream 聚合 + BigDecimal 精确比对 三步闭环,避开 double 隐式转换、空值崩溃、舍入偏差等常见坑,让对账逻辑既准确又可读。
数据建模:统一视图,严控精度源头
定义轻量对账实体类,只保留必需字段:id(如订单号)、amount(必须用 String 或 long 构造 BigDecimal)、source(标识来源系统)。
- 禁止用
new BigDecimal(129.9)—— 浮点字面量会带精度误差 - 数据库查出的 DECIMAL 字段,建议转成字符串再构造:
rs.getBigDecimal("amt").toString() - 空金额统一转为
BigDecimal.ZERO,不依赖业务层默认值
分组聚合:用 Map 建索引,Stream 做归约
把两套数据(如订单、支付)合并打标后,按业务主键(如 order_id)分组求和,避免嵌套遍历。
- 先合并流:
Stream.concat(orderItems.stream().map(...), payItems.stream().map(...)) - 再分组聚合:
.collect(groupingBy(ReconItem::getId, summingBigDecimal(ReconItem::getAmount))) -
summingBigDecimal是自定义收集器,内部用reduce(BigDecimal.ZERO, BigDecimal::add),不经过 double 中转
差异比对:Map 键集驱动,逐 key 安全比较
拿到两个 Map(orderSumMap 和 paySumMap)后,不遍历任一 Map,而是基于全量 key 集合做统一比对。
- 取并集:
Set<string> allIds = new HashSet(orderSumMap.keySet()); allIds.addAll(paySumMap.keySet());</string> - 对每个 id,分别取两边金额(缺失则补
BigDecimal.ZERO),用compareTo()判断是否相等 - 差异记录保留原始符号:
diff = orderAmt.subtract(payAmt),正数表示订单多记,负数表示支付少记
健壮性补充:覆盖空、舍入、明细输出
生产环境不能只看“是否相等”,还要应对真实场景细节。
- 金额比对前可统一做舍入(如
setScale(2, HALF_UP)),但需全局约定且记录规则 - 对账结果对象中,除差异值外,应包含原始明细条目数(如该订单在订单系统有 3 笔,在支付系统有 2 笔)
- 使用
ConcurrentHashMap支持并发对账任务,避免多线程写冲突











