一.为什么要学习设计模式相关知识
有如下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();
}
}
}