设计模式之美-学习导读(1)

367 阅读10分钟

一.为什么要学习设计模式相关知识

有如下5点原因:

  1. 应对面试中设计模式相关的问题
  2. 告别写被人吐槽的烂代码
  3. 提高复杂代码的设计和开发能力
  4. 让读源码, 学框架事半功倍
  5. 为你的职场发展做铺垫

二. 聊一聊你对设计模式相关知识的重要性看法

在各位的工作过程中, 你们是否在代码中遇到过各种if else判断, 是否写过多个执行过程基本一致的方法?是否会觉得代码的可读性, 可维护性很差? 当产品基于线上的老功能, 提出新的想法或者新的策略时, 我们往往都需要去改大量的代码, 导致代码耦合太高和可维护性低呢?

如果大家都有以上的困扰, 那么我们首先就应该学习设计模式, 它可以让我们的代码更加优雅, 可读性高, 可扩展性变高, 可维护性变高, 更加解耦合.

三. 在过往的项目中, 有没有用到某种设计模式? 在什么场景下应用? 解决了哪些问题?

3.1 可视化排货

可视化排货是在2022年11月开始做的一个项目, 是为了满足运营每天针对点位进行排货的一个系统. 因为趣拿的机器分为几大系, 分别是:3系, 5系, 6系, 7系四大类机型, 每一个系列的机型排货的属性都不一致, 他们的货道数, 层数, 每一层支持的长宽高, 扩展宽度都不一样.

之前老的排货系统都是冗余在了一个service里, 通过if else, if else来区别不同的机系, 每段机系的代码你都会发现有相同的代码, 维护起来非常困难, 可读性也很差, 当有新的机系进来的时候改那service里的方法时一不小心就导致其他的机系也出现问题, 扩展性很低.

所以借助排货系统重构的这次机会, 我对排货的货道验算和sku批量上下架接入了设计模式, 极大的提高了代码的可扩展性, 可维护性, 以及可读性.

3.1.1 设计思路

我采用了工厂模式 + 模版方法模式 + 策略模式的组合模式

首先, 运营每次对排期做添加商品的操作, 都会走一遍货道验算接口, 来校验这个商品能否排在当前货道上 其次, 货道验算接口会构建所需的基础信息, 利用工厂模式来生成当前所排机系对应的策略, 比如: 3系, 5系, 6系, 7系 他们都有各自的货道验算策略, 这些策略由工厂模式统一生成

    package com.quna.midst.cms.manager.factory;  
  
import com.quna.midst.cms.commons.core.exception.QunaRuntimeException;  
import com.quna.midst.cms.commons.enums.QunaExCode;  
import com.quna.midst.cms.commons.enums.visual.MachineSeriesEnum;  
import com.quna.midst.cms.manager.strategy.*;  
import com.quna.midst.cms.manager.strategy.intf.IAisleVerifyStrategy;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  
  
/**  
* 货道验算策略工厂  
* @Author: Nisy  
* @Date: 2022/11/07/13:57  
*/  
@Slf4j  
@Component  
public class VerifyEachFacadeAisleFactory {  
  
/**  
* 3系(38)机型货道校验策略组件  
*/  
@Autowired  
private ThreeSeriesAisleVerifyStartegy threeSeriesAisleVerifyStartegy;  
  
/**  
* 5系(50/51)机型货道校验策略组件  
*/  
@Autowired  
private FiveSeriesAisleVerifyStartegy fiveSeriesAisleVerifyStartegy;  
  
/**  
* 6系(53/60)机型货道校验策略组件  
*/  
@Autowired  
private SixSeriesAisleVerifyStartegry sixSeriesAisleVerifyStartegry;  
  
/**  
* 7系(70)机型货道校验策略组件  
*/  
@Autowired  
private SevenSeriesAisleVerifyStartegy sevenSeriesAisleVerifyStartegy;  
  
  
/**  
* 依据机型生成对应的货道验算策略  
* @return 货道验算策略  
*/  
public IAisleVerifyStrategy generateAisleVerifyStrategy(String machineSeries){  
    if (MachineSeriesEnum.THREE_SERIES.getKey().equals(machineSeries) ||  MachineSeriesEnum.THREE_SERIES_C.getKey().equals(machineSeries)) {  
        //3系  
        return threeSeriesAisleVerifyStartegy;  
    }else if (MachineSeriesEnum.FIVE_SERIES.getKey().equals(machineSeries) || MachineSeriesEnum.FIVE_ONE_SERIER.getKey().equals(machineSeries)){  
        //5系(50/51)  
        return fiveSeriesAisleVerifyStartegy;  
    }else if (MachineSeriesEnum.SIX_SERIES.getKey().equals(machineSeries)){  
        //6系  
        return sixSeriesAisleVerifyStartegry;  
    }else if (MachineSeriesEnum.SEVEN_SERIES.getKey().equals(machineSeries)){  
        //7系  
        return sevenSeriesAisleVerifyStartegy;  
    }else {  
        log.error("[货道验算] | 未知机型{}", machineSeries);  
        throw new QunaRuntimeException(QunaExCode.VISUAL_MACHINE_UNKNOWN_SERIES);  
    }  
  }    
}

其次, 进入当前所排机系的策略中, 由于各个机系的策略前, 有一些公用的代码, 比如: 获取机系当前层的基础信息, 校验层数, 校验基础数据是否正确等, 所以这里用到了模版方法模式, 通过抽象出一个模版方法来统一执行这段公用的验算逻辑, 然后新建一个抽象的模版方法, 让各自机系的策略继承该模版方法类, 随后重写该抽象的模版方法, 进行各自的验算

模版方法模式:

package com.quna.midst.cms.manager.template;  
  
import com.quna.midst.cms.commons.core.exception.QunaRuntimeException;  
import com.quna.midst.cms.commons.enums.QunaExCode;  
import com.quna.midst.cms.commons.enums.visual.MachineSeriesEnum;  
import com.quna.midst.cms.commons.fields.visual.MachineCurrentPlieSloutField;  
import com.quna.midst.cms.entity.bo.visual.BasicSkuBO;  
import com.quna.midst.cms.entity.bo.visual.MachineSloutBO;  
import com.quna.midst.cms.entity.dto.visual.AisleItemField;  
import com.quna.midst.cms.entity.dto.visual.CurrentPlieAisleItemDTO;  
import com.quna.midst.cms.entity.dto.visual.CycleDetailPlieFiled;  
import com.quna.midst.cms.entity.vo.visual.AisleVerifyResultVO;  
import com.quna.midst.cms.manager.strategy.intf.IAisleVerifyStrategy;  
import com.quna.midst.cms.service.visual.BasicSkuService;  
import com.quna.midst.cms.service.visual.CitySkuIndexReferenceService;  
import com.quna.utils.json.JsonUtil;  
import lombok.extern.slf4j.Slf4j;  
import org.apache.commons.lang3.StringUtils;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  
import org.springframework.util.CollectionUtils;  
  
import java.math.BigDecimal;  
import java.util.*;  
import java.util.stream.Collectors;  
  
/**  
* 货道验算抽象模版方法  
* @Author: Nisy  
* @Date: 2022/11/07/17:51  
*/  
@Slf4j  
@Component  
public abstract class AbstractAisleVerifyTemplate implements IAisleVerifyStrategy {  
  
/**  
* 可视化排货: sku基础信息组件接口  
*/  
@Autowired  
private BasicSkuService basicSkuService;  
  
@Autowired  
private CitySkuIndexReferenceService citySkuIndexReferenceService;  
  
/**  
* 货道验算模版  
* @param currentPlieAisleItemDTO 当前层货道&货道验算基础详情DTO类  
* @return 货道验算结果vo类  
*/  
@Override  
public AisleVerifyResultVO verifyAisleTemplate(CurrentPlieAisleItemDTO currentPlieAisleItemDTO) {  
CycleDetailPlieFiled cycleDetailPlieFiled = currentPlieAisleItemDTO.getCycleDetailPlieFiled();  
  
//1.计算机型所在层的基础属性  
MachineCurrentPlieSloutField machineCurrentPlieSloutField = this.calculateCurrentPlieSlout(  
cycleDetailPlieFiled.getPlie(), currentPlieAisleItemDTO.getMachineSlout());  
currentPlieAisleItemDTO.setMachineCurrentPlieSloutField(machineCurrentPlieSloutField);  
currentPlieAisleItemDTO.setMachineSlout(null);  
  
//2.校验层数  
Integer currentPlie = Integer.valueOf(cycleDetailPlieFiled.getPlie());  
if (machineCurrentPlieSloutField.getLayers() < currentPlie) {  
throw new QunaRuntimeException(QunaExCode.VISUAL_MACHINE_AISLE_PLIE);  
}  
  
//3.校验基础数值是否合规(高度, 剩余宽度, 当前层宽度)  
if (!this.checkPlieHighAndWidth(cycleDetailPlieFiled)) {  
log.error("[货道验算] | {}层货道宽高基础数值错误,当层货道详情:{}", currentPlie, JsonUtil.toJson(cycleDetailPlieFiled));  
throw new QunaRuntimeException(QunaExCode.ILLEGAL_REQ_PARAM);  
}  
  
//4.非空商品ID集合  
List<AisleItemField> aisleItemList = cycleDetailPlieFiled.getAisleItemList();  
Set<String> aids = aisleItemList.stream().filter(t -> StringUtils.isNotBlank(t.getAid())).map(AisleItemField::getAid).collect(Collectors.toSet());  
  
//5.操作行为:下架商品&最后一件商品时  
if (CollectionUtils.isEmpty(aids) && (cycleDetailPlieFiled.getHighLowFlag()==null || !cycleDetailPlieFiled.getHighLowFlag()) ) {  
log.info("[货道验算] | 下架商品且最后一件");  
//重设剩余宽度, 高度, 总体宽度  
Integer heightDmm = BigDecimal.valueOf(machineCurrentPlieSloutField.getPlieHeight()).divide(BigDecimal.TEN).setScale(0, BigDecimal.ROUND_DOWN).intValue();  
cycleDetailPlieFiled.setHeight(heightDmm);  
  
Integer surplusWidthDmm = BigDecimal.valueOf(machineCurrentPlieSloutField.getCrosserSurplusWidth()).divide(BigDecimal.TEN).setScale(0, BigDecimal.ROUND_DOWN).intValue();  
cycleDetailPlieFiled.setRemainingWidth(surplusWidthDmm);  
  
Integer widthDmm = BigDecimal.valueOf(machineCurrentPlieSloutField.getCrosserSurplusWidth()).divide(BigDecimal.TEN).setScale(0, BigDecimal.ROUND_DOWN).intValue();  
cycleDetailPlieFiled.setWidth(widthDmm);  
  
AisleVerifyResultVO verifyResultVO = AisleVerifyResultVO.builder()  
//存在空货道  
.existEmptyAisle(true)  
.cycleDetailPlieFiled(cycleDetailPlieFiled)  
.build();  
  
return verifyResultVO;  
}  
if (CollectionUtils.isEmpty(aids)){  
//上架|更换时, 商品集合为空  
log.error("[货道验算] | 点位编号:{}上架或更换商品详情为空", currentPlieAisleItemDTO.getMno());  
throw new QunaRuntimeException(QunaExCode.ILLEGAL_REQ_PARAM);  
}  
//6.上架商品|更换商品|下架商品且不是最后一件商品  
Map<String, BasicSkuBO> skuMap = basicSkuService.buildMapByAids(aids);  
currentPlieAisleItemDTO.setSkuMap(skuMap);  
  
//7.货道验算  
AisleVerifyResultVO aisleVerifyResultVO = this.abstractVerifyAisle(currentPlieAisleItemDTO);  
  
//8.非保存操作,校验剩余宽度是否可放入商品,若不可放入商品,则过滤空货道  
this.checkExistEmptyAisle(currentPlieAisleItemDTO,aisleVerifyResultVO);  
return aisleVerifyResultVO;  
}  
  
/**  
* 校验剩余宽度是否可放入商品  
* @param currentPlieAisleItemDTO  
* @param aisleVerifyResultVO  
*/  
private void checkExistEmptyAisle(CurrentPlieAisleItemDTO currentPlieAisleItemDTO, AisleVerifyResultVO aisleVerifyResultVO){  
try {  
if(!currentPlieAisleItemDTO.isSave()) {  
citySkuIndexReferenceService.checkExistEmptyAisle(currentPlieAisleItemDTO, aisleVerifyResultVO);  
//不存在可放置商品  
if(!aisleVerifyResultVO.getExistEmptyAisle()){  
List<AisleItemField> aisleItemList = aisleVerifyResultVO.getCycleDetailPlieFiled().getAisleItemList();  
Iterator<AisleItemField> iterator = aisleItemList.iterator();  
while(iterator.hasNext()){  
AisleItemField next = iterator.next();  
//隔板去除空货道  
if(next.getAisleType()==2 && StringUtils.isBlank(next.getAid())){  
iterator.remove();  
}  
}  
}  
}  
}catch (Exception e){  
log.error("校验剩余宽度是否可放入商品 出现错误,{}",e.getMessage());  
}  
}  
  
/**  
* 校验每层货道数量是否超限  
* @param plie 当前层数  
* @param aisleNum 货道数量  
* @return 返回结果, true: 超限, false: 不超限  
*/  
protected abstract Boolean checkEachPlieAisleNum(String plie, Integer aisleNum);  
  
/**  
* 校验货道弹簧属性  
* @param sloutSpec 机器当层支持的弹簧规格  
* @param aisleCategory 货道类型  
* @param basicSku 基础商品属性  
* @return 返回结果: true: 未通过, fasle: 通过  
*/  
protected abstract void checkAisleSpring(Integer aisleCategory, BasicSkuBO basicSku, Set<String> goodErrorReasonSet, Boolean thirtyEight, Integer sloutSpec);  
  
/**  
* 校验商品是否支持摆放到当前层(仅隔条)  
* @param plie 当前层  
* @param basicSku 基础商品属性  
* @return 返回结果, true: 不支持, false: 支持  
*/  
protected abstract void checkPliePosition(String plie, BasicSkuBO basicSku, Set<String> goodErrorReasonSet, Boolean thirtyEight);  
  
/**  
* 货道验算  
* @param currentPlieAisleItemDTO 当前层数货道详情DTO类  
* @return 货道验算结果vo类  
*/  
protected abstract AisleVerifyResultVO abstractVerifyAisle(CurrentPlieAisleItemDTO currentPlieAisleItemDTO);  
  
/**  
* 计算机型所在层的基础属性  
* @param plie 当前层  
* @param machineSlout 基础机型  
* @return (货道验算)机器当层基础信息field字段类  
*/  
private MachineCurrentPlieSloutField calculateCurrentPlieSlout(String plie, MachineSloutBO machineSlout){  
//计算单孔所占宽度 | 单位dmm | 计算公式: 单孔所占宽度 = holewidth(单孔宽度) + blockwidth(2孔间隙)  
Integer singleHoleWidth = machineSlout.getHolewidth() + machineSlout.getBlockwidth();  
//单件最大宽度 2100dmm(17个孔)  
Integer maxWidth = machineSlout.getMaxwidth();  
//单件最窄宽度 450dmm(占4个孔)  
Integer minWidth = machineSlout.getMinwidth();  
//总层数  
Integer layers = machineSlout.getLayers();  
//层高 | 单位: 丝米(dmm)  
Integer plieHeight = 0;  
//弹簧规格  
Integer spec = null;  
//扩展宽度 | 单位: 丝米(dmm)  
Integer ewidth = null;  
//当前层最大支持扫描宽度 | 单位: 丝米(dmm)  
Integer maxScanWidth = null;  
switch (plie){  
case "1":  
plieHeight = machineSlout.getHeigth1();  
spec = machineSlout.getEstype1();  
ewidth = machineSlout.getEwidth1();  
maxScanWidth = machineSlout.getSwidth1();  
break;  
case "2":  
plieHeight = machineSlout.getHeigth2();  
spec = machineSlout.getEstype2();  
ewidth = machineSlout.getEwidth2();  
maxScanWidth = machineSlout.getSwidth2();  
break;  
case "3":  
plieHeight = machineSlout.getHeigth3();  
spec = machineSlout.getEstype3();  
ewidth = machineSlout.getEwidth3();  
maxScanWidth = machineSlout.getSwidth3();  
break;  
case "4":  
plieHeight = machineSlout.getHeigth4();  
spec = machineSlout.getEstype4();  
ewidth = machineSlout.getEwidth4();  
maxScanWidth = machineSlout.getSwidth4();  
break;  
case "5":  
plieHeight = machineSlout.getHeigth5();  
spec = machineSlout.getEstype5();  
ewidth = machineSlout.getEwidth5();  
maxScanWidth = machineSlout.getSwidth5();  
break;  
case "6":  
plieHeight = machineSlout.getHeigth6();  
spec = machineSlout.getEstype6();  
ewidth = machineSlout.getEwidth6();  
maxScanWidth = machineSlout.getSwidth6();  
break;  
default:  
//未知层数  
log.error("[货道验算] | 计算机型基础属性异常, 未知层数:{}", plie);  
throw new QunaRuntimeException(QunaExCode.VISUAL_MACHINE_AISLE_PLIE);  
}  
  
//当层隔条总宽度 | 单位丝米(dmm)  
int crosserSurplusWidth = maxScanWidth;  
if (null != ewidth){  
crosserSurplusWidth = crosserSurplusWidth + ewidth;  
}  
  
//3系需要区分: 38/38C  
Boolean thirtyEightBoo = null;  
if (machineSlout.getMtype().equals(MachineSeriesEnum.THREE_SERIES.getKey())) {  
thirtyEightBoo = true;  
}else if (machineSlout.getMtype().equals(MachineSeriesEnum.THREE_SERIES_C.getKey())){  
thirtyEightBoo = false;  
}  
  
return MachineCurrentPlieSloutField.builder()  
//单孔所占宽度  
.singleHoleWidth(singleHoleWidth)  
//单件最窄宽度  
.minWidth(minWidth)  
//单件最宽宽度  
.maxWidth(maxWidth)  
//当前层高  
.plieHeight(plieHeight)  
//当前层隔条总宽度, 单位丝米|mm  
.crosserSurplusWidth(crosserSurplusWidth)  
//当前层支持的最大扫描宽度  
.maxScanWidth(maxScanWidth)  
//当前层支持的弹簧规格  
.spec(spec)  
//当前层的扩展宽度  
.ewidth(ewidth)  
//机器总层数  
.layers(layers)  
//是否是38||38c  
.thirtyEight(thirtyEightBoo)  
.build();  
}  
  
  
/**  
* 基础数值是否合规(高度, 剩余宽度, 当前层宽度)  
* @param cycleDetailPlieFiled 当前层数的货道详情  
* @return  
*/  
private Boolean checkPlieHighAndWidth(CycleDetailPlieFiled cycleDetailPlieFiled){  
Boolean boo = true;  
if (cycleDetailPlieFiled.getWidth() != null && cycleDetailPlieFiled.getWidth() < 0){  
//当前层宽度 < 0  
boo = false;  
}  
  
if (cycleDetailPlieFiled.getHeight() < 0){  
//当前层高度 < 0  
boo = false;  
}  
  
if (cycleDetailPlieFiled.getRemainingWidth() != null && cycleDetailPlieFiled.getRemainingWidth() < 0){  
//剩余宽度 < 0  
boo = false;  
}  
return boo;  
}  
}

某个机系的策略模式:

package com.quna.midst.cms.manager.strategy;  
  
import com.alibaba.druid.sql.visitor.functions.If;  
import com.quna.midst.cms.commons.core.exception.QunaRuntimeException;  
import com.quna.midst.cms.commons.enums.QunaExCode;  
import com.quna.midst.cms.commons.enums.visual.AisleStatusEnum;  
import com.quna.midst.cms.commons.enums.visual.AisleTypeEnum;  
import com.quna.midst.cms.commons.fields.visual.MachineCurrentPlieSloutField;  
import com.quna.midst.cms.entity.bo.visual.BasicSkuBO;  
import com.quna.midst.cms.entity.dto.visual.AisleItemField;  
import com.quna.midst.cms.entity.dto.visual.CurrentPlieAisleItemDTO;  
import com.quna.midst.cms.entity.dto.visual.CycleDetailPlieFiled;  
import com.quna.midst.cms.entity.vo.visual.AisleVerifyResultVO;  
import com.quna.midst.cms.manager.strategy.intf.IAisleVerifyStrategy;  
import com.quna.midst.cms.manager.template.AbstractAisleVerifyTemplate;  
import lombok.extern.slf4j.Slf4j;  
import org.apache.commons.lang3.StringUtils;  
import org.springframework.stereotype.Component;  
import org.springframework.util.CollectionUtils;  
import java.math.BigDecimal;  
import java.util.*;  
  
/**  
* 5系(50/51)机型货道校验策略模式  
* @Author: Nisy  
* @Date: 2022/11/07/14:13  
* @DESC: 5系包括: 50, 51; 全系没弹簧货道  
*/  
@Slf4j  
@Component  
public class FiveSeriesAisleVerifyStartegy extends AbstractAisleVerifyTemplate implements IAisleVerifyStrategy {  
  
/**  
* 校验每层货道数量是否超限  
* @param plie 当前层数  
* @param aisleNum 货道数量  
* @return 返回结果, true: 未通过, fasle: 通过  
*/  
@Override  
protected Boolean checkEachPlieAisleNum(String plie, Integer aisleNum) {  
Boolean boo = false;  
switch (plie){  
case "1":  
if (aisleNum > 12) {  
boo = true;  
}  
break;  
case "2":  
if (aisleNum > 12){  
boo = true;  
}  
break;  
case "3":  
if (aisleNum > 11){  
boo = true;  
}  
break;  
case "4":  
if (aisleNum > 11){  
boo = true;  
}  
break;  
case "5":  
if (aisleNum > 11){  
boo = true;  
}  
break;  
}  
return boo;  
}  
  
  
/**  
* 校验货道弹簧属性(5系没有弹簧)  
* @param sloutSpec 机器当层支持的弹簧规格  
* @param aisleCategory 货道类型  
* @param basicSku 基础商品属性  
* @return 返回结果: true: 未通过, fasle: 通过  
*/  
@Override  
protected void checkAisleSpring(Integer aisleCategory, BasicSkuBO basicSku, Set<String> listGoodErrorReason, Boolean thirtyEight, Integer sloutSpec) {  
}  
  
/**  
* 校验商品是否支持摆放到当前层(仅隔条)  
* @param plie 当前层  
* @param basicSku 基础商品属性  
* @return 返回结果, true: 不支持, false: 支持  
*/  
@Override  
protected void checkPliePosition(String plie, BasicSkuBO basicSku, Set<String> goodErrorReasonSet, Boolean thirtyEight) {  
if (null == basicSku.getFiftyBaffle() || 0 == basicSku.getFiftyBaffle()) {  
String format = String.format("[5系货道验算] | 商品不支持5系隔板");  
goodErrorReasonSet.add(format);  
}else {  
String fiftySeriesFloors = basicSku.getFiftySeriesFloors();  
if (StringUtils.isNotBlank(fiftySeriesFloors) && !fiftySeriesFloors.contains(plie)) {  
String format = String.format("[5系货道验算] | 商品支持%s层, 不支持摆放%s层", fiftySeriesFloors, plie);  
goodErrorReasonSet.add(format);  
}  
}  
}  
  
/**  
* 执行5系机型货道校验  
* @param currentPlieAisleItemDTO 当前层数货道详情DTO类  
*/  
@Override  
protected AisleVerifyResultVO abstractVerifyAisle(CurrentPlieAisleItemDTO currentPlieAisleItemDTO) {  
CycleDetailPlieFiled cycleDetailPlieFiled = currentPlieAisleItemDTO.getCycleDetailPlieFiled();  
List<AisleItemField> aisleItemList = cycleDetailPlieFiled.getAisleItemList();  
  
//1.校验每层货道数量是否超限  
Boolean aisleNumBoo = this.checkEachPlieAisleNum(cycleDetailPlieFiled.getPlie(), aisleItemList.size());  
if (aisleNumBoo){  
log.error("[5系货道验算] | 货道数量校验失败");  
throw new QunaRuntimeException(QunaExCode.VISUAL_MACHINE_AISLE_NUM);  
}  
//5系计算货道信息  
return this.calculateAisle(currentPlieAisleItemDTO);  
}  
  
  
/**  
* 5系计算货道信息  
* @param plie 层数  
* @param aisleItemFields 当层货道详情集合  
* @param sloutField (货道验算)机器当层基础信息field字段类  
* @desc 计算公式: minWidth + singleHoleWidth * x = 商品宽度  
*/  
private AisleVerifyResultVO calculateAisle(CurrentPlieAisleItemDTO currentPlieAisleItemDTO) {  
boolean operator = currentPlieAisleItemDTO.isSave();  
Map<String, BasicSkuBO> skuMap = currentPlieAisleItemDTO.getSkuMap();  
CycleDetailPlieFiled cycleDetailPlieFiled = currentPlieAisleItemDTO.getCycleDetailPlieFiled();  
MachineCurrentPlieSloutField sloutField = currentPlieAisleItemDTO.getMachineCurrentPlieSloutField();  
List<AisleItemField> aisleItemFields = cycleDetailPlieFiled.getAisleItemList();  
Map<String, Set<String>> errorAisleItemMap = currentPlieAisleItemDTO.getErrorAisleItemMap();  
  
//当前层  
String plie = cycleDetailPlieFiled.getPlie();  
//当层隔条总宽度 | 单位: 丝米(dmm)  
int crosserSurplusWidth = sloutField.getCrosserSurplusWidth();  
  
//当层总孔位数(不算扩展宽度)  
int holeTotal = 0;  
if ("3".equals(plie) || "4".equals(plie)) {  
holeTotal = FIVE_SIX_SERIES_THREE_FOUR_PLIE_TOTAL_HOLE;  
}else {  
holeTotal = FIVE_SIX_SERIES_TOTAL_HOLE;  
}  
  
//是否存在空货道  
Boolean existEmptyAisle = true;  
Set<String> goodErrorReasonSet = null;  
//遍历  
for (AisleItemField aisleItemField : aisleItemFields) {  
//隔板最小宽度占4个孔, 外加一个隔条的孔  
int holeNum = MIN_AISLE_HOLE_NUM;  
  
//商品ID  
String aid = aisleItemField.getAid();  
//空货道 & 编辑操作  
if (StringUtils.isBlank(aid)  
&& !AisleStatusEnum.NOT_EMPTY_AISLE.getValue().equals(aisleItemField.getAisleStatus())  
&& !operator) {  
continue;  
}  
  
//货道类型  
Integer aisleType = aisleItemField.getAisleType();  
if (!AisleTypeEnum.CROSSER_AISLE_TYPE.getValue().equals(aisleType)) {  
log.error("[5系货道验算] | 不支持弹簧货道:{}", aisleType);  
throw new QunaRuntimeException(QunaExCode.VISUAL_FIVE_SERIES_NON_SUPPORT_SPRING_AISLE);  
}  
  
//商品所占隔条宽度 | 单位丝米  
Integer initRemainWidth = 0;  
//商品宽度 | 单位丝米  
Integer goodWidth = 0;  
//商品高度 | 单位丝米  
Integer goodHeight = 0;  
  
//空货道 & 保存  
if (operator && StringUtils.isBlank(aid) && AisleStatusEnum.EMPTY_AISLE.getValue().equals(aisleItemField.getAisleStatus())){  
//操作保存, 需要计算空货道所占最小孔数  
holeTotal = holeTotal - holeNum;  
initRemainWidth = sloutField.getMinWidth();  
goodWidth = initRemainWidth;  
}else if (StringUtils.isNotBlank(aid)){  
//非空货道  
BasicSkuBO basicSku = skuMap.get(aid);  
if (null == basicSku) {  
log.error("[5系货道验算] | skuMap:{}, 不存在该商品:{}", skuMap, aid);  
throw new QunaRuntimeException(QunaExCode.ILLEGAL_REQ_PARAM);  
}  
goodErrorReasonSet = new HashSet<>();  
if (basicSku.getStatus() != null && 1 != basicSku.getStatus()) {  
String format = String.format("[5系货道验算] | 商品%s审核状态异常", aid);  
goodErrorReasonSet.add(format);  
}  
  
//1.商品高度 > 层高 | 单位: 丝米(dmm)  
goodHeight = basicSku.getLength().multiply(BigDecimal.TEN.multiply(BigDecimal.TEN)).intValue();  
if (goodHeight > sloutField.getPlieHeight()) {  
String format = String.format("[5系货道验算] | 商品高度%.2f厘米大于当前层高%.2f厘米", BigDecimal.valueOf(goodHeight).divide(BigDecimal.valueOf(100)).setScale(2), BigDecimal.valueOf(sloutField.getPlieHeight()).divide(BigDecimal.valueOf(100)).setScale(2));  
goodErrorReasonSet.add(format);  
}  
  
//2.商品宽度 > 层高 | 单位: 丝米(dmm)  
goodWidth = basicSku.getWidth().multiply(BigDecimal.TEN.multiply(BigDecimal.TEN)).intValue();  
if (goodWidth > sloutField.getMaxWidth()) {  
String format = String.format("[5系货道验算] | 商品宽度%.2f厘米大于当前层最大宽度%.2f厘米", BigDecimal.valueOf(goodWidth).divide(BigDecimal.valueOf(100)).setScale(2), BigDecimal.valueOf(sloutField.getMaxWidth()).divide(BigDecimal.valueOf(100)).setScale(2));  
goodErrorReasonSet.add(format);  
}  
  
//3.校验商品是否支持摆放到当前层(弹簧 + 隔条都需要判断)  
this.checkPliePosition(plie, basicSku, goodErrorReasonSet, null);  
  
//4.计算商品所占隔条宽度  
initRemainWidth = goodWidth - sloutField.getMinWidth();  
  
//5.计算商品所占孔数  
if (initRemainWidth >= 0) {  
//孔数  
int ceil = (int) Math.ceil((double) initRemainWidth / sloutField.getSingleHoleWidth());  
holeNum = holeNum + ceil;  
}  
//剩余孔数  
holeTotal = holeTotal - holeNum;  
log.info("[5系货道验算] | 商品:{}占用孔数:{}个, 剩余:{}个", aid, holeNum, holeTotal);  
}else {  
continue;  
}  
  
//剩余隔条宽度 < 0  
if (crosserSurplusWidth <= 0) {  
String format = String.format("[5系货道验算] | 隔条剩余宽度不足%.2f厘米", BigDecimal.valueOf(crosserSurplusWidth).divide(BigDecimal.valueOf(100)).setScale(2));  
goodErrorReasonSet.add(format);  
  
String key = StringUtils.join(aisleItemField.getAname(), "_", aisleItemField.getGcid());  
//当前商品错误信息不为空  
if (null == errorAisleItemMap.get(key)) {  
errorAisleItemMap.put(key, goodErrorReasonSet);  
}else {  
Set<String> resSet = new HashSet<>();  
resSet.addAll(errorAisleItemMap.get(key));  
resSet.addAll(goodErrorReasonSet);  
errorAisleItemMap.put(key, resSet);  
}  
continue;  
}  
  
if (holeTotal < 0){  
  
//孔位不够, 先看高度是否符合要求  
if (LAST_CROSSER_MAX__HEIGHT < goodHeight && ("5").equals(plie)) {  
String format = String.format("[5系货道验算] | 商品高度%.2f厘米大于5系第五层最后一个隔板货道17.7厘米", BigDecimal.valueOf(goodHeight).divide(BigDecimal.valueOf(100)).setScale(2));  
goodErrorReasonSet.add(format);  
}  
  
//孔位不够, 需兼容查看扩展宽度  
if (sloutField.getEwidth() != null){  
//查看剩余宽度是否大于商品宽度  
if (crosserSurplusWidth < goodWidth) {  
String format = String.format("[5系货道验算] | 商品宽度%.2f厘米大于剩余隔条支持宽度%.2f厘米", BigDecimal.valueOf(goodWidth).divide(BigDecimal.valueOf(100)).setScale(2), BigDecimal.valueOf(crosserSurplusWidth).divide(BigDecimal.valueOf(100)).setScale(2));  
goodErrorReasonSet.add(format);  
}else {  
//需要计算最后的商品占用了多少拓展宽度  
int takeExtensionWidth = goodWidth -(crosserSurplusWidth - sloutField.getEwidth());  
aisleItemField.setTakeExtensionWidth(takeExtensionWidth);  
  
//最后剩余宽度为0  
crosserSurplusWidth = 0;  
existEmptyAisle = false;  
}  
}else {  
//没有拓展宽度, 直接报错  
String format = String.format("[5系货道验算] | 孔位不够且%s层无拓展宽度", plie);  
goodErrorReasonSet.add(format);  
}  
} else if (holeTotal == 0){  
//恰巧最后一个孔位用完  
//最后剩余宽度为0  
crosserSurplusWidth = 0;  
existEmptyAisle = false;  
} else {  
//孔位还有剩余  
existEmptyAisle = true;  
crosserSurplusWidth = crosserSurplusWidth - holeNum * sloutField.getSingleHoleWidth();  
if (crosserSurplusWidth < 0) {  
existEmptyAisle = false;  
String format = String.format("[5系货道验算] | 剩余宽度%.2f厘米小于0", BigDecimal.valueOf(crosserSurplusWidth).divide(BigDecimal.valueOf(100)).setScale(2));  
goodErrorReasonSet.add(format);  
}  
}  
if (!CollectionUtils.isEmpty(goodErrorReasonSet)) {  
String key = StringUtils.join(aisleItemField.getAname(), "_", aisleItemField.getGcid());  
//当前商品错误信息不为空  
if (null == errorAisleItemMap.get(key)) {  
errorAisleItemMap.put(key, goodErrorReasonSet);  
}else {  
Set<String> resSet = new HashSet<>();  
resSet.addAll(errorAisleItemMap.get(key));  
resSet.addAll(goodErrorReasonSet);  
errorAisleItemMap.put(key, resSet);  
}  
}  
}  
  
//计算后的商品剩余宽度 | 单位毫米  
Integer surplusWidthDmm = BigDecimal.valueOf(crosserSurplusWidth).divide(BigDecimal.TEN).setScale(0, BigDecimal.ROUND_DOWN).intValue();  
if (cycleDetailPlieFiled.getRemainingWidthFlag()){  
cycleDetailPlieFiled.setRemainingWidth(surplusWidthDmm);  
}  
//层高 | 单位毫米  
Integer plieHeightDmm = BigDecimal.valueOf(sloutField.getPlieHeight()).divide(BigDecimal.TEN).setScale(0, BigDecimal.ROUND_DOWN).intValue();  
cycleDetailPlieFiled.setHeight(plieHeightDmm);  
//层宽 | 单位毫米  
Integer widthDmm = null;  
Integer widthMm = null;  
if (null != sloutField.getEwidth()) {  
widthDmm = sloutField.getMaxScanWidth() + sloutField.getEwidth();  
widthMm = BigDecimal.valueOf(widthDmm).divide(BigDecimal.TEN).setScale(0, BigDecimal.ROUND_DOWN).intValue();  
}else {  
widthDmm = sloutField.getMaxScanWidth();  
widthMm = BigDecimal.valueOf(sloutField.getMaxScanWidth()).divide(BigDecimal.TEN).setScale(0, BigDecimal.ROUND_DOWN).intValue();  
}  
cycleDetailPlieFiled.setWidth(widthMm);  
  
//当层全部商品所占用的宽度 | 单位丝米  
Integer useWidth = widthDmm - crosserSurplusWidth;  
if (useWidth > 0) {  
cycleDetailPlieFiled.setUseWidth(useWidth);  
}  
if (!CollectionUtils.isEmpty(errorAisleItemMap)) {  
//只展示错误信息  
return AisleVerifyResultVO.builder().errorAisleItemMap(errorAisleItemMap)  
.existEmptyAisle(existEmptyAisle)  
.cycleDetailPlieFiled(cycleDetailPlieFiled)  
.build();  
}else {  
return AisleVerifyResultVO.builder()  
.existEmptyAisle(existEmptyAisle)  
.cycleDetailPlieFiled(cycleDetailPlieFiled)  
.build();  
}  
}  
}