国际集运计费方案:策略模式+规则引擎深度解析

2026-06-16阅读 0热度 0
其他

摘要:反向海淘运费计算核心依赖首重续重、体积重及不同国家与渠道的差异化计费规则。本文阐述如何基于Java策略模式与Drools规则引擎,构建一套灵活可配置的计费引擎,支持实时计算与批量试算。

国际集运计费引擎实现:策略模式+规则引擎处理复杂运费计算

运费规则需求建模

以下列举几类典型的国际运费规则:

  • 美国云途专线:首重0.5kg,85元;续重每0.5kg,25元,采用实际重量计费。
  • 美国EMS:首重0.5kg,110元;续重每0.5kg,35元,计费取实际重量与体积重量(长*宽*高/5000)中的较大值。
  • 加拿大专线:首重1kg,120元;续重每0.5kg,30元。

由此可见,核心变量仅两个:计费方式(实际重量与体积重)和费率结构(首重/续重)。面对频繁变动的业务需求,代码设计需高度灵活可配置。

基于策略模式的运费计算实现

首先定义运费计算策略接口:

public interface FreightCalculator {
    BigDecimal calculate(ShippingPackage pkg, FreightRule rule);
}

实际重量策略:

@Component
public class ActualWeightCalculator implements FreightCalculator {
    @Override
    public BigDecimal calculate(ShippingPackage pkg, FreightRule rule) {
        double weight = pkg.getActualWeightKg();
        if (weight <= rule.getFirstWeight()) {
            return rule.getFirstPrice();
        }
        double additional = weight - rule.getFirstWeight();
        int units = (int) Math.ceil(additional / rule.getAdditionalUnit());
        return rule.getFirstPrice().add(
            rule.getAdditionalPrice().multiply(BigDecimal.valueOf(units))
        );
    }
}

体积重策略:

@Component
public class VolumetricWeightCalculator implements FreightCalculator {
    @Override
    public BigDecimal calculate(ShippingPackage pkg, FreightRule rule) {
        double volumetric = pkg.getLengthCm() * pkg.getWidthCm() * pkg.getHeightCm() / 5000.0;
        double weight = Math.max(pkg.getActualWeightKg(), volumetric);
        // 复用实际重量计算逻辑,传入修正后的重量
        return actualWeightCalculator.calculate(new ShippingPackage(weight), rule);
    }
}

策略工厂,按类型定位对应计算器:

@Component
public class FreightCalculatorFactory {
    private final Map calculatorMap = new HashMap<>();

    public FreightCalculatorFactory() {
        calculatorMap.put("actual", new ActualWeightCalculator());
        calculatorMap.put("volumetric", new VolumetricWeightCalculator());
        // 可扩展更多策略
    }

    public FreightCalculator getCalculator(String type) {
        return calculatorMap.getOrDefault(type, calculatorMap.get("actual"));
    }
}

此时可能出现疑问:运费模板如何配置?难道每次规则调整都需要修改代码?
答案是利用规则引擎实现灵活配置。

基于Drools规则引擎配置运费模板

用Drools,我们把运费规则写成 .drl 文件:

// 运费规则定义文件 freight.drl
package com.taocarts.freight;

import com.taocarts.domain.ShippingRequest;
import com.taocarts.domain.FreightResult;

rule "US_YunExpress_Rule"
    when
        $req: ShippingRequest(destinationCountry == "US", channel == "YunExpress")
    then
        FreightRule rule = new FreightRule();
        rule.setFirstWeight(0.5);
        rule.setFirstPrice(new BigDecimal("85"));
        rule.setAdditionalUnit(0.5);
        rule.setAdditionalPrice(new BigDecimal("25"));
        rule.setCalculatorType("actual");
        $req.setMatchedRule(rule);
end

rule "US_EMS_Rule"
    when
        $req: ShippingRequest(destinationCountry == "US", channel == "EMS")
    then
        FreightRule rule = new FreightRule();
        rule.setFirstWeight(0.5);
        rule.setFirstPrice(new BigDecimal("110"));
        rule.setAdditionalUnit(0.5);
        rule.setAdditionalPrice(new BigDecimal("35"));
        rule.setCalculatorType("volumetric");
        $req.setMatchedRule(rule);
end

通过以上方式,新增物流渠道或调整费率仅需修改DRL文件,应用无需重启(结合规则热加载机制效果更佳)。

合并发货与拼单运费分摊

实际业务中,用户常将多笔订单合并发货,再按重量比例分摊总运费。

@Service
public class CombinedFreightService {
    public CombinedFreightResult combineAndCalculate(List orderIds, String channel) {
        // 第一步:汇总订单商品重量
        List orders = orderService.listByIds(orderIds);
        double totalActualWeight = orders.stream()
            .mapToDouble(Order::getTotalWeight).sum();
        double totalVolumetricWeight = orders.stream()
            .mapToDouble(o -> o.getLengthCm() * o.getWidthCm() * o.getHeightCm() / 5000.0)
            .max().orElse(0);
        double finalWeight = Math.max(totalActualWeight, totalVolumetricWeight);

        // 第二步:获取对应运费规则
        FreightRule rule = freightRuleMapper.selectByChannelAndCountry(
            channel, orders.get(0).getCountry());
        FreightCalculator calculator = calculatorFactory.getCalculator(rule.getCalculatorType());
        BigDecimal totalFreight = calculator.calculate(new ShippingPackage(finalWeight), rule);

        // 第三步:按实际重量比例分摊
        List shares = new ArrayList<>();
        for (Order order : orders) {
            double shareRatio = order.getTotalWeight() / totalActualWeight;
            BigDecimal shareFreight = totalFreight.multiply(BigDecimal.valueOf(shareRatio));
            shares.add(new FreightShare(order.getId(), shareFreight));
        }
        return new CombinedFreightResult(totalFreight, shares);
    }
}

关键点:体积重取各订单中的最大值(按整箱体积计算),实际重量累加,最终计费重量取二者较大值。分摊策略采用实际重量比例,相对简洁;若存在大件商品等特殊场景,需针对性细化分摊逻辑。

性能优化策略

高频计算场景下,常用优化手段:

  • 运费模板缓存至Redis,每小时刷新一次,降低数据库查询频率。
  • 体积重计算采用本地缓存,同一包裹重复计算时直接命中结果。
  • 批量计费场景利用CompletableFuture并行处理,充分提升多核利用率。

经压力测试,单节点可支撑500次/秒的运费计算请求,P99延迟低于50毫秒,满足生产环境性能需求。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策