小蔡学Java

项目二总结:(三)运费的计算 责任链模式重构代码

2024-02-15 22:41 835 0 项目 责任链模式

需求分析

运费的计算是分不同地区的,比如:同城、省内、跨省,计算规则是不一样的,所以针对不同的类型需要设置不同的运费规则,这其实就是所谓的模板。

模板列表

表结构设计:

轻抛系数名称解释: 在计算运费时,包裹有两个维度,体积和重量,二者谁大取谁进行计算,但是体积和重量不是一个单位怎么比较呢?一般的做法就是将体积转化成重量,公式:体积 / 轻抛系数 = 重量,这样就可以比较了。 也就是说,相同的体积,轻抛系数越大计算出的重量就越小,反之就越大。

计费规则

重量计算方法: 取重量和体积两者间较大的数值,体积计算方法:长(cm)×宽(cm)×高(cm) / 轻抛系数

普快: 同城互寄:12000 省内寄件:8000 跨省寄件:12000 经济区互寄(京津翼、江浙沪):6000 经济区互寄(黑吉辽):12000 经济区互寄(川渝):8000

模板不可重复设置,需确保唯一值。 如已设置同城寄、跨省寄、省内寄,则只可修改,不可再新增 如已设置经济区互寄某个城市,下次添加不可再关联此经济区城市

运费模板有4种类型,分别为: 同城寄:同城寄件运费计算模板,全国统一定价 省内寄:省内寄件运费计算模板,全国统一定价 跨省寄:不同省份间的运费计算模板,全国统一定价 经济区互寄:4个经济区(京津翼、江沪浙皖、川渝、黑吉辽),经济区间寄件可设置优惠价格

运费计算

说明: ● 运费模板优先级:同城>省内>经济区互寄>跨省 ● 将体积转化成重量,与重量比较,取大值

代码实现

@Override
    public CarriageDTO compute(WaybillDTO waybillDTO) {
        //根据参数查找运费模板
        CarriageEntity carriage = this.findCarriage(waybillDTO);
        //计算重量,最小重量为1kg
        double computeWeight = this.getComputeWeight(waybillDTO, carriage);
        //计算运费,首重 + 续重
        double expense = carriage.getFirstWeight() + ((computeWeight - 1) * carriage.getContinuousWeight());
        //保留一位小数
        expense = NumberUtil.round(expense, 1).doubleValue();
        //封装运费和计算重量到DTO,并返回
        CarriageDTO carriageDTO = CarriageUtils.toDTO(carriage);
        carriageDTO.setExpense(expense);
        carriageDTO.setComputeWeight(computeWeight);
        return carriageDTO;
    }
    /**
     * 根据体积参数与实际重量计算计费重量
     *
     * @param waybillDTO 运费计算对象
     * @param carriage   运费模板
     * @return 计费重量
     */
    private double getComputeWeight(WaybillDTO waybillDTO, CarriageEntity carriage) {
        //计算体积,如果传入体积不需要计算
        Integer volume = waybillDTO.getVolume();
        if (ObjectUtil.isEmpty(volume)) {
            try {
                //长*宽*高计算体积
                volume = waybillDTO.getMeasureLong() * waybillDTO.getMeasureWidth() * waybillDTO.getMeasureHigh();
            } catch (Exception e) {
                //计算出错设置体积为0
                volume = 0;
            }
        }
        // 计算体积重量,体积 / 轻抛系数
        BigDecimal volumeWeight = NumberUtil.div(volume, carriage.getLightThrowingCoefficient(), 1);
        //取大值
        double computeWeight = NumberUtil.max(volumeWeight.doubleValue(), NumberUtil.round(waybillDTO.getWeight(), 1).doubleValue());
        //计算续重,规则:不满1kg,按1kg计费;10kg以下续重以0.1kg计量保留1位小数;10-100kg续重以0.5kg计量保留1位小数;100kg以上四舍五入取整
        if (computeWeight <= 1) {
            return 1;
        }
        if (computeWeight <= 10) {
            return computeWeight;
        }
        // 举例:
        // 108.4kg按照108kg收费
        // 108.5kg按照109kg收费
        // 108.6kg按照109kg收费
        if (computeWeight >= 100) {
            return NumberUtil.round(computeWeight, 0).doubleValue();
        }
        //0.5为一个计算单位,举例:
        // 18.8kg按照19收费,
        // 18.4kg按照18.5kg收费
        // 18.1kg按照18.5kg收费
        // 18.6kg按照19收费
        int integer = NumberUtil.round(computeWeight, 0, RoundingMode.DOWN).intValue();
        
        if (NumberUtil.sub(computeWeight, integer) == 0) {
            return integer;
        }
        
        if (NumberUtil.sub(computeWeight, integer) <= 0.5) {
            return NumberUtil.add(integer, 0.5);
        }
        return NumberUtil.add(integer, 1);
    }
    /**
     * 根据参数查找运费模板
     *
     * @param waybillDTO 参数
     * @return 运费模板
     */
    private CarriageEntity findCarriage(WaybillDTO waybillDTO) {
        //运费模板优先级:同城>省内>经济区互寄>跨省
        if (ObjectUtil.equals(waybillDTO.getReceiverCityId(), waybillDTO.getSenderCityId())) {
            //同城
            CarriageEntity carriage = this.findByTemplateType(CarriageConstant.SAME_CITY);
            if (ObjectUtil.isNotEmpty(carriage)) {
                return carriage;
            }
        }
        // 获取收寄件地址省份id
        Long receiverProvinceId = this.areaFeign.get(waybillDTO.getReceiverCityId()).getParentId();
        Long senderProvinceId = this.areaFeign.get(waybillDTO.getSenderCityId()).getParentId();
        if (ObjectUtil.equal(receiverProvinceId, senderProvinceId)) {
            //省内
            CarriageEntity carriage = this.findByTemplateType(CarriageConstant.SAME_PROVINCE);
            if (ObjectUtil.isNotEmpty(carriage)) {
                return carriage;
            }
        }
        //经济区互寄
        CarriageEntity carriage = this.findEconomicCarriage(receiverProvinceId, senderProvinceId);
        if (ObjectUtil.isNotEmpty(carriage)) {
            return carriage;
        }
        //跨省
        carriage = this.findByTemplateType(CarriageConstant.TRANS_PROVINCE);
        if (ObjectUtil.isNotEmpty(carriage)) {
            return carriage;
        }
        throw new SLException(CarriageExceptionEnum.NOT_FOUND);
    }
    private CarriageEntity findEconomicCarriage(Long receiverProvinceId, Long senderProvinceId) {
        //获取经济区城市配置枚举
        LinkedHashMap<String, EconomicRegionEnum> EconomicRegionMap = EnumUtil.getEnumMap(EconomicRegionEnum.class);
        EconomicRegionEnum economicRegionEnum = null;
        for (EconomicRegionEnum regionEnum : EconomicRegionMap.values()) {
            //该经济区是否全部包含收发件省id
            boolean result = ArrayUtil.containsAll(regionEnum.getValue(), receiverProvinceId, senderProvinceId);
            if (result) {
                economicRegionEnum = regionEnum;
                break;
            }
        }
        if (null == economicRegionEnum) {
            //没有找到对应的经济区
            return null;
        }
        //根据类型编码查询
        LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers.lambdaQuery(CarriageEntity.class)
                .eq(CarriageEntity::getTemplateType, CarriageConstant.ECONOMIC_ZONE)
                .eq(CarriageEntity::getTransportType, CarriageConstant.REGULAR_FAST)
                .like(CarriageEntity::getAssociatedCity, economicRegionEnum.getCode());
        return super.getOne(queryWrapper);
    }
    /**
     * 根据模板类型查询模板
     *
     * @param templateType 模板类型:1-同城寄,2-省内寄,3-经济区互寄,4-跨省
     * @return 运费模板
     */
    private CarriageEntity findByTemplateType(Integer templateType) {
        LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers.lambdaQuery(CarriageEntity.class)
                .eq(CarriageEntity::getTemplateType, templateType)
                .eq(CarriageEntity::getTransportType, CarriageConstant.REGULAR_FAST);
        return super.getOne(queryWrapper);
    }

责任链模式重构代码

的流程图中可以看出,计算运费的第一步逻辑就是需要查找到对应的运费模板。我们这里采用【责任链】模式进行代码编写。之所以采用【责任链】模式,是因为在查找模板时,不同的模板处理逻辑不同,并且这些逻辑组成了一条处理链,有开头有结尾,只要能找到符合条件的模板即结束。

责任链模式(chain of reaponsiblity Pattern):是将链中的每一个节点看作是一个对象,每一个节点对请求的处理不同(或者处理不同的请求),并且内部维护着下一个节点对象;一个请求进来,会从责任链的首部开始向下传递,直到有节点处理请求或者是走完整个链路;

优点:

1、将请求与处理进行解耦; 2、链路中的节点只需要处理自己关心的请求,对于自己不关心的请求放给下一个节点进行处理; 3、请求不需要知道链路结构,只需要等待链路处理的结果就行了; 4、链路的结构比较多灵活,可以更改链路的结构,动态的新增删除责任节点,符合开闭原则;

责任链模式涉及到的角色如下所示:

  • 抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。Handler类的聚合关系给出了具体子类对下家的引用,抽象方法handleRequest()规范了子类处理请求的操作。
  • 具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。

定义运费模板处理链,这是一个抽象类:


/**
 * 运费模板处理链的抽象定义
 */
public abstract class AbstractCarriageChainHandler {

    private AbstractCarriageChainHandler nextHandler;

    /**
     * 执行过滤方法,通过输入参数查找运费模板
     *
     * @param waybillDTO 输入参数
     * @return 运费模板
     */
    public abstract CarriageEntity doHandler(WaybillDTO waybillDTO);

    /**
     * 执行下一个处理器
     *
     * @param waybillDTO     输入参数
     * @param carriageEntity 上个handler处理得到的对象
     * @return
     */
    protected CarriageEntity doNextHandler(WaybillDTO waybillDTO, CarriageEntity carriageEntity) {
        if (nextHandler == null || carriageEntity != null) {
            //如果下游Handler为空 或 上个Handler已经找到运费模板就返回
            return carriageEntity;
        }
        return nextHandler.doHandler(waybillDTO);
    }

    /**
     * 设置下游Handler
     *
     * @param nextHandler 下游Handler
     */
    public void setNextHandler(AbstractCarriageChainHandler nextHandler) {
        this.nextHandler = nextHandler;
    }
}

下面针对不同的模板策略编写具体的实现类,同城寄:


/**
 * 同城寄
 */
@Order(100) //定义顺序
@Component
public class SameCityChainHandler extends AbstractCarriageChainHandler {

    @Resource
    private CarriageService carriageService;

    @Override
    public CarriageEntity doHandler(WaybillDTO waybillDTO) {
        CarriageEntity carriageEntity = null;
        if (ObjectUtil.equals(waybillDTO.getReceiverCityId(), waybillDTO.getSenderCityId())) {
            //同城
            carriageEntity = this.carriageService.findByTemplateType(CarriageConstant.SAME_CITY);
        }
        return doNextHandler(waybillDTO, carriageEntity);
    }
}

省内寄:

/**
 * 省内寄
 */
@Order(200) //定义顺序
@Component
public class SameProvinceChainHandler extends AbstractCarriageChainHandler {

    @Resource
    private CarriageService carriageService;
    @Resource
    private AreaFeign areaFeign;

    @Override
    public CarriageEntity doHandler(WaybillDTO waybillDTO) {
        CarriageEntity carriageEntity = null;
        // 获取收寄件地址省份id
        Long receiverProvinceId = this.areaFeign.get(waybillDTO.getReceiverCityId()).getParentId();
        Long senderProvinceId = this.areaFeign.get(waybillDTO.getSenderCityId()).getParentId();
        if (ObjectUtil.equal(receiverProvinceId, senderProvinceId)) {
            //省内
            carriageEntity = this.carriageService.findByTemplateType(CarriageConstant.SAME_PROVINCE);
        }
        return doNextHandler(waybillDTO, carriageEntity);
    }
}

经济区互寄:


/**
 * 经济区互寄
 */
@Order(300) //定义顺序
@Component
public class EconomicZoneChainHandler extends AbstractCarriageChainHandler {

    @Resource
    private CarriageService carriageService;
    @Resource
    private AreaFeign areaFeign;

    @Override
    public CarriageEntity doHandler(WaybillDTO waybillDTO) {
        CarriageEntity carriageEntity = null;

        // 获取收寄件地址省份id
        Long receiverProvinceId = this.areaFeign.get(waybillDTO.getReceiverCityId()).getParentId();
        Long senderProvinceId = this.areaFeign.get(waybillDTO.getSenderCityId()).getParentId();

        //获取经济区城市配置枚举
        LinkedHashMap<String, EconomicRegionEnum> EconomicRegionMap = EnumUtil.getEnumMap(EconomicRegionEnum.class);
        EconomicRegionEnum economicRegionEnum = null;
        for (EconomicRegionEnum regionEnum : EconomicRegionMap.values()) {
            //该经济区是否全部包含收发件省id
            boolean result = ArrayUtil.containsAll(regionEnum.getValue(), receiverProvinceId, senderProvinceId);
            if (result) {
                economicRegionEnum = regionEnum;
                break;
            }
        }

        if (ObjectUtil.isNotEmpty(economicRegionEnum)) {
            //根据类型编码查询
            LambdaQueryWrapper<CarriageEntity> queryWrapper = Wrappers.lambdaQuery(CarriageEntity.class)
                    .eq(CarriageEntity::getTemplateType, CarriageConstant.ECONOMIC_ZONE)
                    .eq(CarriageEntity::getTransportType, CarriageConstant.REGULAR_FAST)
                    .like(CarriageEntity::getAssociatedCity, economicRegionEnum.getCode());
            carriageEntity = this.carriageService.getOne(queryWrapper);
        }

        return doNextHandler(waybillDTO, carriageEntity);
    }
}

跨省寄

/**
 * 跨省
 */
@Order(400) //定义顺序
@Component
public class TransProvinceChainHandler extends AbstractCarriageChainHandler {

    @Resource
    private CarriageService carriageService;

    @Override
    public CarriageEntity doHandler(WaybillDTO waybillDTO) {
        CarriageEntity carriageEntity = this.carriageService.findByTemplateType(CarriageConstant.TRANS_PROVINCE);
        return doNextHandler(waybillDTO, carriageEntity);
    }
}

组装处理链,按照@Order注解中的值,由小到大排序。

/**
 * 查找运费模板处理链 @Order注解
 */
@Component
public class CarriageChainHandler {

    /**
     * 利用Spring注入特性,按照 @Order 从小到达排序注入到集合中
     */
    @Resource
    private List<AbstractCarriageChainHandler> chainHandlers;

    private AbstractCarriageChainHandler firstHandler;

    /**
     * 组装处理链
     */
    @PostConstruct
    private void constructChain() {
        if (CollUtil.isEmpty(chainHandlers)) {
            throw new SLException("not found carriage chain handler!");
        }
        //处理链中第一个节点
        firstHandler = chainHandlers.get(0);
        for (int i = 0; i < chainHandlers.size(); i++) {
            if (i == chainHandlers.size() - 1) {
                //最后一个处理链节点
                chainHandlers.get(i).setNextHandler(null);
            } else {
                //设置下游节点
                chainHandlers.get(i).setNextHandler(chainHandlers.get(i + 1));
            }
        }
    }

    public CarriageEntity findCarriage(WaybillDTO waybillDTO) {
        //从第一个节点开始处理
        return firstHandler.doHandler(waybillDTO);
    }

}

代码改造

注入的处理链集合对象:

测试结果,查询到同经济区的模板,结果符合预期:

模拟面试

● 你们的运费是怎么计算的?体积和重量怎么计算,到底以哪个为准?

● 轻抛系数的作用是什么?如何设置的?

● 详细聊聊你们的运费模板是做什么的?

● 开发中是否使用过设计模式?场景是怎样的?

● 有没有针对运费计算做什么优化?

● 计算运费时,城市信息是存储在哪里的?

评论( 0 )

  • 博主 Mr Cai
  • 坐标 河南 信阳
  • 标签 Java、SpringBoot、消息中间件、Web、Code爱好者

文章目录