从 JDK 8 跨到 Java 21:EDEN-MACE 新一代 dist-admin 如何守住分润精度

2026-08-18 38 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

开源分销管理系统 EDEN-MACE 于 2018 年上线,核心思路是把分润规则、等级计算放到后台配置,并通过松耦合 API 对接业务系统。六年后,新一代 dist-admin 完成重构并开源:分销引擎与旧版逐行对齐,金额计算保持一致,技术底座则从 Spring Boot 1.5、JDK 8 升级到 Spring Boot 3.2.5 和 Java 21。

这类升级的难点并不只是把依赖版本改高。分销系统直接处理资金权益,真正的交付标准是:旧数据能解释、旧规则能复现、每一分钱都有归属,同时新架构还要具备清晰的权限边界和可持续维护能力。

业务引擎保持不动,运行底座彻底更新

dist-admin 的重构体现了一条适合资金类系统的迁移原则:业务结果与技术实现分开验收。

技术层可以重新设计,包括 Java 版本、Spring Boot 版本、权限模型、模块划分和 API 接入方式;分润结果却不能因为重构发生漂移。对同一笔订单、同一组会员关系和同一套等级规则,新旧系统应输出完全相同的受益人、金额和计算依据。

从 Spring Boot 1.5 跨到 3.2.5,还意味着需要处理一系列生态变化:

  • Java 基线从 JDK 8 提升到 Java 21。
  • Spring Boot 3 使用 Jakarta EE 命名空间,旧代码中的 javax.* 通常需要迁移到 jakarta.*
  • 老旧安全框架与后台脚手架需要重新评估,权限控制应落到明确的 RBAC 资源和操作上。
  • 依赖版本、序列化行为、数据库驱动及日期时间处理都需要回归验证。

这些变化不能只靠“项目能够启动”来验收。启动成功只能证明容器完成装配,不能证明一笔 99.99 元订单仍然按照旧规则准确分配。

一分钱不差,需要可解释的金额模型

分润计算最危险的实现是直接使用 double。二进制浮点数无法精确表示许多十进制小数,连续计算、舍入和汇总后可能出现一分钱的差异。

可以这样实践:在引擎边界统一使用“分”作为最小单位,比例计算使用 BigDecimal,并明确尾差归属。下面是一个可直接用 Java 21 运行的最小示例。它不代表 dist-admin 的内部实现,而是演示重构时如何建立确定性的金额规则。

将代码保存为 DistDemo.java,然后执行 java DistDemo.java

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.LinkedHashMap;
import java.util.Map;

public class DistDemo {
    public static void main(String[] args) {
        long orderAmountFen = 9_999; // 99.99 元

        Map<String, BigDecimal> rates = new LinkedHashMap<>();
        rates.put("direct_promoter", new BigDecimal("0.10"));
        rates.put("team_leader", new BigDecimal("0.03"));
        rates.put("platform", new BigDecimal("0.87"));

        Map<String, Long> result = allocate(orderAmountFen, rates);
        System.out.println(result);
        System.out.println("total=" + result.values().stream().mapToLong(Long::longValue).sum());
    }

    static Map<String, Long> allocate(long totalFen, Map<String, BigDecimal> rates) {
        BigDecimal totalRate = rates.values().stream()
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        if (totalRate.compareTo(BigDecimal.ONE) != 0) {
            throw new IllegalArgumentException("Rates must add up to 1");
        }

        Map<String, Long> allocated = new LinkedHashMap<>();
        long usedFen = 0;
        int index = 0;

        for (var entry : rates.entrySet()) {
            index++;
            long amountFen;
            if (index == rates.size()) {
                amountFen = totalFen - usedFen; // 尾差归入最后一个账户
            } else {
                amountFen = BigDecimal.valueOf(totalFen)
                        .multiply(entry.getValue())
                        .setScale(0, RoundingMode.DOWN)
                        .longValueExact();
            }
            allocated.put(entry.getKey(), amountFen);
            usedFen += amountFen;
        }
        return allocated;
    }
}

示例输出的分配总额始终等于 9999 分。生产系统还需要明确退款、部分退款、跨级分润、冻结期、负金额冲正和规则版本等场景,不能只验证正常订单。

用双跑对账验证新旧引擎

“逐行对齐”最适合转化为自动化的差异测试。迁移期间可以让旧引擎继续承担正式计算,新引擎读取同一份脱敏输入,在旁路运行并生成对账记录,但不实际入账。

一条对账记录至少应包含:

{
  "case_id": "order-20240501-0001",
  "rule_version": "level-rule-v7",
  "legacy": {
    "direct_promoter": 1000,
    "team_leader": 299,
    "platform": 8700
  },
  "new_engine": {
    "direct_promoter": 1000,
    "team_leader": 299,
    "platform": 8700
  },
  "difference_fen": 0
}

回归数据不能只抽取近期订单。更稳妥的样本应覆盖规则边界:刚好达到等级门槛、跨月升级、关系链缺失、受益人被冻结、订单部分退款以及历史规则失效后的重算。

可以用下面的命令把对账文件中所有非零差异筛出来。运行前将 reconciliation.jsonl 替换为实际文件名:

jq -c 'select(.difference_fen != 0)' reconciliation.jsonl

若输出为空,只能说明当前样本没有差异。上线门槛还应包括样本数量、订单金额覆盖率、规则版本覆盖率,以及每一种边界场景的测试结果。

RBAC 应围绕资金操作建模

分销后台的权限不能只区分“管理员”和“普通用户”。等级规则发布、人工调整分润、导出结算明细、执行退款冲正,这些操作的风险完全不同。

来源摘要说明新版从旧后台与 Shiro 方案转向 RBAC 等新设计,但没有给出完整的安全框架细节。因此在接入时,应以项目实际代码和文档为准,不宜仅凭技术名称推断实现。

权限资源可以这样拆分:

  • distribution:rule:read:查看分润规则。
  • distribution:rule:publish:发布新规则版本。
  • distribution:ledger:read:查看分润流水。
  • distribution:ledger:adjust:执行人工调整。
  • distribution:settlement:export:导出结算数据。

对于高风险操作,还应增加审批、审计日志和幂等键。RBAC 解决的是“谁可以操作”,审计记录则回答“谁在何时基于什么原因改了哪笔钱”。两者缺一不可。

升级落地检查表

评估或迁移到新一代 dist-admin 时,可以按以下顺序推进:

  1. 固定旧系统规则、数据快照和计算结果,建立可重复的基线。
  2. 使用生产脱敏数据执行新旧引擎双跑,对比到受益人和分的粒度。
  3. 盘点 JDK 21、Jakarta 命名空间、数据库驱动和第三方依赖的兼容性。
  4. 按资金操作拆分 RBAC 权限,避免把高风险能力集中到单一管理员角色。
  5. 验证退款、冲正、冻结、重复请求和规则版本切换等异常路径。
  6. 上线初期保留对账、灰度和回退能力,不在同一次发布中同时更换规则与引擎。

这次重构最值得关注的并非版本数字本身,而是它把“现代化技术底座”和“旧业务结果完全一致”放在同一个交付目标中。对于分销、佣金、积分和结算系统,架构可以更新,账本语义不能含糊;只有把每一分钱都变成可测试、可追踪、可复算的数据,新技术栈才真正完成了接班。


相关推荐