跨境电商独立站国际运费计算引擎设计:策略模式处理复杂计费规则-阿里云开发者社区
一、问题背景
国际运费计算是跨境电商独立站中最复杂的业务模块之一。不同物流渠道的计费方式完全不同——有的按实际重量,有的按体积重(长×宽×高÷5000),还有的首重续重阶梯不同、不同国家价格不同。如果将这些规则硬编码在业务逻辑中,每次新增渠道都需要修改代码。本文以 Taocarts 的运费计算引擎为例,讲解如何使用策略模式实现灵活可扩展的计费方案。
二、策略模式实现计费算法
java
// 计费策略接口
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);
}
}
三、运费模板配置与策略工厂
运费规则存储在数据库中,通过管理后台动态配置。新增渠道时只需插入一条配置记录,无需修改代码。
java
@Entity
@Table(name = "freight_rule")
public class FreightRule {
private String channel; // 物流渠道: yunexpress, ems, dhl
private String destinationCountry;
private Double firstWeight;
private BigDecimal firstPrice;
private Double additionalUnit;
private BigDecimal additionalPrice;
private String calculatorType; // actual / volumetric
}
@Component
public class FreightCalculatorFactory {
private final Map calculatorMap;
public FreightCalculatorFactory(List
this.calculatorMap = calculators.stream()
.collect(Collectors.toMap(
c -> c.getClass().getSimpleName().replace("Calculator", "").toLowerCase(),
Function.identity()
));
}
}
四、合并发货与运费分摊
多个订单合并成一个包裹时,汇总总重量并计算总运费,然后按每个订单的重量比例分摊。
java
@Service
public class CombinedFreightService {
public CombinedFreightResult calculate(List orderIds, String channel, String country) {
List orders = orderMapper.selectBatchIds(orderIds);
double totalActualWeight = orders.stream().mapToDouble(Order::getTotalWeight).sum();
FreightRule rule = freightRuleMapper.selectByChannelAndCountry(channel, country);
FreightCalculator calculator = calculatorFactory.getCalculator(rule.getCalculatorType());
BigDecimal totalFreight = calculator.calculate(new ShippingPackage(totalActualWeight), rule);
List
for (Order order : orders) {
double ratio = order.getTotalWeight() / totalActualWeight;
BigDecimal share = totalFreight.multiply(BigDecimal.valueOf(ratio));
shares.add(new FreightShare(order.getId(), order.getOrderNo(), share));
}
return new CombinedFreightResult(totalFreight, shares);
}
}
五、性能优化
运费规则变更不频繁,可缓存到 Redis 避免每次查询数据库:
java
@Component
public class FreightRuleCache {
public FreightRule getRule(String channel, String country) {
String key = "freight:rule:" + channel + ":" + country;
FreightRule rule = redisTemplate.opsForValue().get(key);
if (rule == null) {
rule = ruleMapper.selectByChannelAndCountry(channel, country);
if (rule != null) {
redisTemplate.opsForValue().set(key, rule, Duration.ofHours(2));
}
}
return rule;
}
}
六、总结
Taocarts 的运费计算引擎通过策略模式将不同计费算法解耦,通过数据库配置实现规则的动态管理,已适配全球数十种物流渠道和上百个国家的差异化定价,准确率达 99.9% 以上。