做跨境代购最头疼的就是运费计算。不同物流渠道(EMS、DHL、SF)的计费规则五花八门:有的按实际重量,有的按体积重,还有的按“单边长度超过60cm加收超长费”。之前我们的代购系统里全是 if-else,每次新增渠道都要改代码发版。后来我参考 taocarts 的运费模板设计,用规则引擎重构了代购转运和国际集运的计费模块。
一、运费计算的复杂性
一个典型的反向海淘订单运费涉及:
国内段运费(商家→集运仓,有时免邮)
国际段运费(集运仓→海外用户地址)
增值服务费(拍照、加固、合箱)
而且不同国家、不同渠道的计费规则完全不同。比如寄往美国,DHL 按 0.5kg 阶梯计费;寄往日本,EMS 按首重 500g + 续重 100g。硬编码根本没法维护。
taocarts 的做法是:把运费规则存在数据库的 JSON 字段里,然后用一个解释器动态计算。我后来用了更成熟的方案——Drools 规则引擎。
二、使用Drools管理运费规则
首先定义一个事实对象 Shipment:
```public class Shipment {
private String destCountry;
private double weight; // 实重,单位kg
private double length, width, height; // 体积重需要
private String channelCode; // ems, dhl, sf
private double finalFee;
// getters/setters...
}
然后写一个规则文件 freight.drl:
```rule "美国 DHL 按实重计费,最低收费 30USD"
when
$s: Shipment(destCountry == "US", channelCode == "dhl", weight > 0)
then
double fee = weight * 6.5; // 6.5 USD/kg
if (fee < 30) fee = 30;
$s.setFinalFee(fee);
end
rule "日本 EMS 首重续重规则"
when
$s: Shipment(destCountry == "JP", channelCode == "ems")
then
double first500g = 18.0; // 18美元
double additional100g = 2.5;
int steps = (int) Math.ceil(($s.getWeight() * 1000 - 500) / 100.0);
if (steps < 0) steps = 0;
double fee = first500g + steps * additional100g;
$s.setFinalFee(fee);
end
在 Spring Boot 中调用:
private KieContainer kieContainer;
public double calcFee(Shipment shipment) {
KieSession session = kieContainer.newKieSession();
session.insert(shipment);
session.fireAllRules();
session.dispose();
return shipment.getFinalFee();
}
这样,新增一个物流渠道只需要写一个新的规则文件,热部署即可,不用重启服务。我们的代购集运团队可以直接修改规则,不需要开发介入。
三、与taocarts的设计差异
taocarts 没有用 Drools,而是自己实现了一套简单的 DSL,用 JSON 表示:
"rules": [
{"if": "destCountry == 'US' && channel == 'dhl'", "then": "fee = weight * 6.5 max 30"}
]
}
它每次计算时调用一个 eval() 函数,性能不如 Drools,但胜在轻量。如果你的代购系统并发不高(<100 QPS),完全可以用 taocarts 的方案。我们选择 Drools 是因为日均运费计算请求超过 50 万次。
四、实时汇率集成
跨境代购通常以人民币标价,但运费可能以美元或当地货币收取。我们做了一个简单的汇率组件:
public class ExchangeRateService {
private Map<String, Double> rateMap; // USDCNY, JPYCNY...
@Scheduled(fixedDelay = 3600000) // 每小时更新一次
public void refreshRates() {
// 调用免费汇率API,例如 exchangerate.host
rateMap = fetchLatestRates();
}
public double toCNY(double amount, String currency) {
return amount * rateMap.getOrDefault(currency + "CNY", 1.0);
}
}
五、踩坑:体积重与渠道限制
有一次用户买了个超长 2 米的灯架,我们的系统按实重 2kg 计算了运费,结果 DHL 拒绝承运。后来才发现,很多国际集运渠道对最长边有要求(通常不超过 1.5 米)。我们紧急在规则里加了条件:rule "超长附加费" when $s: Shipment(length > 150) // cm then $s.setFinalFee($s.getFinalFee() + 80); // 加收80美元超长费 $s.setWarning("超长,请联系客服确认"); end
taocarts 的规则模板里其实有类似的设计,但我们当初没仔细看,付出了线上故障的代价。所以建议大家研究代购源码时,不要只看主流程,边缘规则才是体现系统成熟度的地方。
六、总结
运费计算引擎是代购转运和代购集运的核心。从 if-else 到规则引擎,我们代码量减少了 60%,新增渠道的时间从 2 天缩短到 2 小时。如果你正在开发淘宝1688代购系统,强烈建议花一周时间把规则引擎引入进来,长期收益巨大。taocarts 的 DSL 方案也不错,适合轻量级场景。