设计模式-总览https://blog.csdn.net/it_lihongmin/article/details/122202507整理一下,之前在项目中基于需求使用了模板和策略模式、并且在其实使用到了线程池,还是先说一下项目背景,因为所有的设计模式都是基于业务的落地。
1、项目背景
批量将采购订单根据条件处理开成销售订单或者预销售订单的过程(即:批量采购订单=销售订单+预销售订单),首先是可以有多条路径发起转单流程,由于整个过程判断条件比较多,并且在很多地方处理条件根据发送路径不同,处理的方式也不同。考虑到又是批量转单流程又长中间需要查很多rpc,所有在中间使用到了线程池并行处理,将责任链拆单(这里使用责任链进行各种拆单逻辑处理,防止后续增加其他维度的拆单逻辑,只需要增加链条即可,这里就不展示了,责任链更多可以参考:责任链模式 - 项目中的使用)完的订单放入每个线程中并行。整个流程比较长将业务分成了三个阶段,预计准备阶段、采购处理阶段、订单处理阶段、预采购处理阶段。
由于业务需求是每种路径转单在每个阶段处理方式可能不能,所以在每个阶段中间使用策略(Map)进行链接选择下一个要执行的模板类。每个阶段处理时,有个需求执行业务1、2、3而有点需要执行 1,2,3,4,有的需要执行1,2,3,4,5所以将每个阶段使用模板进行处理。当每个转单路径到达策略处,就选择了下个流程需要执行的模板类层级,每个层级都是一个Spring的Bean【自己说一下,刚开始学习设计模式的时候都是搭建demo,都是new对象之类的,而现在的项目都是Spring bean,两者怎么进行结合之前一直疑惑,当自己学习完Spring源码后就都懂了,确实设计模式是一种思想,跟使用不使用Spring无关,只是自己的功力要够,就能得心应手】。
梳理了一下整个流程图,并且代码都是在这个流程图下完成的,如下:
2、实现大致代码
1)、顶层策略类
本来有三个顶层策略类,但是都大致类似,就写一个流程的策略类,即可,他们都都有公共的父类CommonArare。
@Slf4j
public class CommonAware implements ApplicationContextAware, BeanNameAware {
/** 唯一的Bean 名称 */
protected String beanName;
/** Spring IOC容器 */
protected ApplicationContext applicationContext;
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 每层都会调用该方法答应,查看是否正确初始化
* @param name bean名称
*/
@Override
public void setBeanName(@NonNull String name) {
beanName = name;
log.info("Bean: " + this.getClass().getName() + ", bean name is " + name);
}
}
@Slf4j
public class TransferStrategy extends CommonAware implements InitializingBean {
/**
* 策略
*/
private static Map<TransferEnum, PreTransferSimpleService> nextStepMap;
@Override
public void afterPropertiesSet() {
Map<TransferEnum, PreTransferSimpleService> temp = new HashMap<>(16);
PreTransferSimpleService preTransferSimpleService = (PreTransferSimpleService) applicationContext.getBean("preTransferSimpleService");
PreTransferService preTransferService = (PreTransferService) applicationContext.getBean("preTransferService");
PrePriorityTransferService preAutoTransferService = (PrePriorityTransferService) applicationContext.getBean("prePriorityTransferService");
temp.put(SHORTAGE, preTransferSimpleService);
temp.put(NORMAL, preTransferService);
temp.put(AUTO, preAutoTransferService);
temp.put(INDEPENDENT_AGENCY, preTransferService);
temp.put(TASK, preAutoTransferService);
// ImmutableMap
nextStepMap = Collections.unmodifiableMap(temp);
}
/**
* 统一按照策略调用 pre* 的{@code preTransfer} 方法
* @param purOrderBOList 采购转单业务实体
* @return 转单结果
* @see PoTransferTemplate#transferOrder(List) 中的CompletionService执行线程池任务
*/
protected TransferResultDTO doPreProcess(List<PurOrderBO> purOrderBOList) {
final PreTransferSimpleService preTransferSimpleService = nextStepMap.get(purOrderBOList.get(0).getTransferEnum());
// 前置处理
preTransferSimpleService.preTransferBefore(purOrderBOList);
// 执行转单Pre*流程
TransferResultDTO transferResultDTO = preTransferSimpleService.preTransfer(purOrderBOList);
// 后置处理
preTransferSimpleService.preTransferAfter(purOrderBOList, transferResultDTO);
return transferResultDTO;
}
}
2)、具体模板实现
这里体现出了模板的解耦能力,将本来可能需要写大量的if else,还有可能路径需要有的不需要的流程,直接在不同的模板类的方法实现是不同的,进行解耦。下面只写一个模板方法层级:
@Slf4j
@Manager
@AutoConfigureAfter({SoTransferService.class, SoTransferTemplate.class})
public class PoTransferTemplate extends PoStrategy {
@Resource
public SendPayCheckFeignService sendPayService;
@Resource
private CommonAdapt commonAdapt;
@TimeConsume
public TransferResultDTO transferOrder(List<PurOrderBO> transferRequest) {
// 初始化结果容器
final TransferResultDTO resultDTO = new TransferResultDTO(Boolean.TRUE);
// 批量处理任务,一个PO对应一个线程
final Executor ttlExecutor = TtlExecutors.getTtlExecutor(ThreadPoolInit.THREAD_POOL_EXECUTOR_MAP.get(TRANSFER_ORDER.name()));
CompletionService<PoResultDTO> service = new ExecutorCompletionService<>(ttlExecutor, new LinkedBlockingDeque<>(transferRequest.size()));
// 拆单之后检查是否通过(只有在整体都通过的情况下,那么 业务账号,经销商,送达方是重复的),这三个检查都比较轻,先不考虑优化
transferRequest.stream().collect(Collectors.toMap(purOrderBO -> purOrderBO,
purOrderBO -> service.submit(Objects.requireNonNull(TtlCallable.get(() -> {
// 初始化菜单后的返回结果
PoResultDTO poResultDTO = new PoResultDTO(purOrderBO);
StopWatch stopWatch = new StopWatch("poResultDTO");
stopWatch.start("checkPassAdapt&saleCreate");
final List<String> skuIdLockKeys = new ArrayList<>();
List<PurOrderSkuBO> skuList = purOrderBO.getSkuList();
for (PurOrderSkuBO purOrderSkuBO : skuList) {
String skuId = purOrderSkuBO.getPurOrderSkuDTO().getId();
String skuIdKey = "PO_TRANS_LOCK_KEY_"+skuId;
skuIdLockKeys.add(skuIdKey);
}
boolean isLocked = RedisLockUtil.lock(skuIdLockKeys);
if (!isLocked) {
log.error("PO:{},{}获取锁失败", purOrderBO.getPurOrderDTO().getPurOrderCode(), String.join(",", skuIdLockKeys));
poResultDTO.setPoErrorMessage("当前订单正在转单中,请稍后重试");
castAllSku(poResultDTO, purOrderBO, NOT_VALID_OTHER);
return poResultDTO;
}
try {
// 检查当前订单码是否检查通过(只有所有的sku都检查不通过则采认为是所有不通过,不需要往下走)
SendPayCheckResultDTO check = commonAdapt.checkPassAdapt(purOrderBO);
if (!check.getIsPassCheck() && CollectionUtil.isNotEmpty(check.getErrorMessage())) {
poResultDTO.setPoErrorMessage(String.join(";", check.getErrorMessage()));
return castAllSku(poResultDTO, purOrderBO, NOT_VALID_OTHER);
}
final PurOrderDTO purOrderDTO = purOrderBO.getPurOrderDTO();
SaleCreateDTO saleCreateDTO = sendPayService.saleCreate(purOrderDTO.getSendpayCode());
if (purOrderBO.getTransferEnum() == NORMAL && isCommerce(purOrderDTO.getPoType())) {
log.info("正常转单类型电商订单,将整单开单手动修改为非整单,订单号为:{} 订单码为:{}", purOrderDTO.getPurOrderCode(), purOrderDTO.getSendpayCode());
saleCreateDTO = new SaleCreateDTO(false, saleCreateDTO);
}
purOrderDTO.getSendpayCode(), JSON.toJSONString(saleCreateDTO));
purOrderBO.setSaleCreateDTO(saleCreateDTO);
final boolean notValidWhole = saleCreateDTO.isSingleOrderControl && CollectionUtil.isNotEmpty(check.getErrorCodeMap());
if (notValidWhole) {
return castWholeOrderSku(poResultDTO, purOrderBO, check.getErrorCodeMap(), WHOLE_ORDER_NOT_VALID_SKU);
}
PurOrderBO request = CollectionUtil.isNotEmpty(check.getErrorCodeMap()) ? castSku(poResultDTO, purOrderBO, NOT_VALID_SKU, check.getErrorCodeMap()) : purOrderBO;
stopWatch.stop();
log.info(stopWatch.shortSummary());
// 转单
if (CollectionUtil.isNotEmpty(request.getSkuList())) {
handlerPoBusiness(request, poResultDTO);
}
return poResultDTO;
} finally {
RedisLockUtil.unlock(skuIdLockKeys);
}
}))))).forEach((purOrderBO, value) -> {
try {
resultDTO.addPoResultDTO(service.take().get());
} catch (InterruptedException | ExecutionException e) {
// 执行线程被中断,可能是执行时间过程保护等, 需要稍后重试
log.error("addPoResultDTO source purOrderBO = " + JSON.toJSONString(purOrderBO) + e);
}
});
return resultDTO;
}
/**
* 判断是否电商订单
* @param
* @return
*/
public static Boolean isCommerce(String poTypeCode) {
PoTypeEnum poTypeEnum = PoTypeEnum.getByCode(poTypeCode);
if (poTypeEnum == null) {
throw new BusinessException("采购订单类型异常!");
}
return poTypeEnum == E_COMMERCE || poTypeEnum == O_COMMERCE;
}
/**
* 转单业务操作,扩展点
*/
protected void handlerPoBusiness(PurOrderBO purOrderBO, PoResultDTO poResultDTO) {
super.doSoProcess(purOrderBO, poResultDTO);
}
}
这里使用了线程池,使用了TransmittableThreadLocal处理线程池,并且解耦点就是父类的 handlerPoBusiness方法,不同的模板子类调用该方法后就实现了不同的业务块的动态加载或者切换。想这样的解耦点在项目中如果只有一两处时整个模板就有点别扭,但是如果解耦的有很多处时,整个模板就体现处其价值。并且主要是规定了整个流程,后续发送任何变动不需要改动主流程,而是额外的切入。后面业务调整过多次,但是都能轻松应对,个人这方面体会比较深。
@Slf4j
@Manager
@AutoConfigureAfter({SoTransferService.class, SoTransferTemplate.class})
public class PoTransferService extends PoTransferTemplate {
@Autowired
private VsoTransferService vsoTransferService;
@Autowired
private CommonAdapt commonAdapt;
@Override
@TimeConsume
protected void handlerPoBusiness(PurOrderBO purOrderBO, PoResultDTO poResultDTO) {
// 是否紧缺检查
if (!purOrderBO.getSaleCreateDTO().isSaleShortage) {
// 不检查直接走创建so路径
super.doSoProcess(purOrderBO, poResultDTO);
return;
}
// 是否在紧缺中,查询紧缺商品表
List<String> skuList = purOrderBO.getSkuList().stream().map(skuBO -> getTwoTypeCode(skuBO.getPurOrderSkuDTO())).collect(Collectors.toList());
List<String> skuShortage = commonAdapt.skuShortageList(purOrderBO.getDistributionCenter(), skuList);
// log.info("是否在紧缺中 distributionCenter = {}, sku list = {}, skuShortage = {}", purOrderBO.getDistributionCenter(), JSON.toJSONString(skuList), JSON.toJSON(skuShortage));
if (CollectionUtil.isEmpty(skuShortage)) {
super.doSoProcess(purOrderBO, poResultDTO);
return;
}
if (purOrderBO.getSkuList().size() == skuShortage.stream().distinct().count() || purOrderBO.getSaleCreateDTO().isSingleOrderControl) {
vsoTransferService.transferVso(purOrderBO, poResultDTO);
return;
}
PurOrderBO soBO = purOrderBO.newPoHead();
PurOrderBO vsoBO = purOrderBO.newPoHead();
purOrderBO.getSkuList().forEach(sku -> {
String code = getTwoTypeCode(sku.getPurOrderSkuDTO());
if (skuShortage.contains(code)) {
vsoBO.addPurOrderSkuBO(sku);
} else {
soBO.addPurOrderSkuBO(sku);
}
});
// 重新调用父类的公共方法,保证代码原子行
if (CollectionUtil.isNotEmpty(soBO.getSkuList())) {
super.doSoProcess(soBO, poResultDTO);
}
if (CollectionUtil.isNotEmpty(vsoBO.getSkuList())) {
vsoTransferService.transferVso(vsoBO, poResultDTO);
}
}
}
其中,子类部分又可以调用父类的公共方法,保证所有业务代码的原子性(后续原子性部分代码业务变动,修改后就整体生效,不用改多处)。
// 重新调用父类的公共方法,保证代码原子行
if (CollectionUtil.isNotEmpty(soBO.getSkuList())) {
super.doSoProcess(soBO, poResultDTO);
}
设计模式还是需要在项目上多用,慢慢体会,落地时看起来简单但是整个过程需要完全贴合业务..... 任重而道远......