顺序校验
在真实的项目中,我们会遇到很多的校验问题,例如一个excel文件导入需要校验数据的合法性,且要按顺序校验,为此编码中会遇到很多的if else,部分复杂校验还需要嵌套多层循环,会出现很多n^3级别时间复杂度的代码,本文以博主实际案例,优化这样这一臃肿过程
场景描述
小张是某大型后台管理系统的业务操作员,需要对清货管理模块添加清货数据,为此需要建立清货单据,由于之前老系统的原因,需要先从老系统中导出excel清货数据,再把excel导入到新系统,导入过程中需要校验清货单内的货品是否已经存在新系统中,且清货编码是否重复,单据是否存在等,校验步骤如下:
以下介绍的实现方法,可能有些对象没有定义,博友可当成黑盒理解,不必计较其中内容,不妨碍理解本文。
实现方式
定义共用的抽象方法(未选择接口的原因:需要有具体的实现方法,不是默认方法) AbstractHandler.java
**
* 模板设计模式
* ps:划定责任链调用模板方法
*
* @author dzx
* @since 2020/10/26
*/
public abstract class AbstractHandler {
private AbstractHandler handlerBase;
/**
* 指定下一级校验handler
*
* @param handlerBase handlerBase
* @return HandlerBase
*/
public AbstractHandler setNextHandler(AbstractHandler handlerBase) {
this.handlerBase = handlerBase;
return handlerBase;
}
/**
* 获取下一个校验handler
*
* @return HandlerBase
*/
public AbstractHandler getNextHandler() {
return handlerBase;
}
/**
* 校验抽象方法
*
* @param event event 导入对象(含具体数据:单号-商品编码)
* @param clearanceChainDto clearanceChainDto 校验过程中产生的结果对象
* @param resultEvent resultEvent 校验成功后的反馈对象
* @return boolean boolean 校验结果 true校验通过 false校验不通过
*/
public abstract boolean doBulkFileTask(BulkFileTaskEvent.BulkFileTaskDetail event, DelistChainDTO clearanceChainDto,
BulkFileTaskResultCallbackEvent resultEvent);
}
校验是否存在该单据DelistCodeHandler.java
/**
* 校验是否存在该单据
*
* @author dzx
* @since 2020/10/26
*/
@Component
public class DelistCodeHandler extends AbstractHandler {
private static final String SUCCESS_MESSAGE = "校验成功,存在该单号";
@Autowired
private DelistOrderQuery delistQuery;
@Override
public boolean doBulkFileTask(BulkFileTaskEvent.BulkFileTaskDetail event,
DelistChainDTO delistImportDto,
BulkFileTaskResultCallbackEvent resultEvent) {
boolean flag = false;
//查询单号
DelistOrderVO delistVo = delistQuery.queryDelistOrder(delistImportDto.getDelistImportEvent().getCode());
if (Optional.ofNullable(delistVo).isPresent()) {
resultEvent.getTaskDetails().add(BulkFileTaskResultCallbackEvent.BulkFileTaskDetailResult.builder()
.taskDetailId(event.getId())
.bulkFileTaskDetailStatus(BulkFileTaskDetailStatus.SUCCESS)
.message(SUCCESS_MESSAGE)
.build());
//继续下一级校验 保留上一级校验的结果,以便后一级校验使用
delistImportDto.setDelistOrderVO(delistVo);
flag = getNextHandler().doBulkFileTask(event, delistImportDto, resultEvent);
} else {
resultEvent.getTaskDetails().add(BulkFileTaskResultCallbackEvent.BulkFileTaskDetailResult.builder()
.taskDetailId(event.getId())
.bulkFileTaskDetailStatus(BulkFileTaskDetailStatus.FAILURE)
.message(DelistOrderExceptionMsg.NO_EXIST_DELIST_CODE.getMsg())
.build());
}
return flag;
}
}
校验该单据状态是否为待提交DelistStatusHandler.java
/**
* 校验该单据状态是否为待提交
*
* @author dzx
* @since 2020/10/26
*/
@Component
public class DelistStatusHandler extends AbstractHandler {
private static final String SUCCESS_MESSAGE = "校验成功,该单号为待提交状态";
@Override
public boolean doBulkFileTask(BulkFileTaskEvent.BulkFileTaskDetail event, DelistChainDTO delistChainDto, BulkFileTaskResultCallbackEvent resultEvent) {
boolean flag = false;
resultEvent.getTaskDetails().clear();
if (UN_SUBMIT.name().equals(delistChainDto.getDelistOrderVO().getStatus())) {
resultEvent.getTaskDetails().add(BulkFileTaskResultCallbackEvent.BulkFileTaskDetailResult.builder()
.taskDetailId(event.getId())
.bulkFileTaskDetailStatus(BulkFileTaskDetailStatus.SUCCESS)
.message(SUCCESS_MESSAGE)
.build());
//继续下一级校验
flag = getNextHandler().doBulkFileTask(event, delistChainDto, resultEvent);
} else {
resultEvent.getTaskDetails().add(BulkFileTaskResultCallbackEvent.BulkFileTaskDetailResult.builder()
.taskDetailId(event.getId())
.bulkFileTaskDetailStatus(BulkFileTaskDetailStatus.FAILURE)
.message(DelistOrderExceptionMsg.DELIST_STATUS_NO_UN_SUBMIT.getMsg())
.build());
}
return flag;
}
}
校验该单据下模号商品主数据是否存在DelistGoodsExistHandler.java
/**
* 校验该单据下模号商品主数据是否存在
* @author dzx
* @since 2020/10/26
*/
@Component
public class DelistGoodsExistHandler extends AbstractHandler {
private static final String SUCCESS_MESSAGE = "校验成功,存在该模号";
@Autowired
private GoodsClient goodsClient;
@Override
public boolean doBulkFileTask(BulkFileTaskEvent.BulkFileTaskDetail event, DelistChainDTO delistChainDto, BulkFileTaskResultCallbackEvent resultEvent) {
boolean flag = false;
//查询该模号
List<SimpleSpuCTO> simpleSpus = goodsClient.getSimpleSpus(SpuParams.builder().codes(Collections.singletonList(delistChainDto.getDelistImportEvent().getSpuCode())).build());
resultEvent.getTaskDetails().clear();
if (CollectionUtils.isNotEmpty(simpleSpus)) {
resultEvent.getTaskDetails().add(BulkFileTaskResultCallbackEvent.BulkFileTaskDetailResult.builder()
.taskDetailId(event.getId())
.bulkFileTaskDetailStatus(BulkFileTaskDetailStatus.SUCCESS)
.message(SUCCESS_MESSAGE)
.build());
flag = true;
} else {
resultEvent.getTaskDetails().add(BulkFileTaskResultCallbackEvent.BulkFileTaskDetailResult.builder()
.taskDetailId(event.getId())
.bulkFileTaskDetailStatus(BulkFileTaskDetailStatus.FAILURE)
.message(DelistOrderExceptionMsg.SPU_NO_EXIST.getMsg())
.build());
}
return flag;
}
}
借用springBoot的启动对象ApplicationRunner 项目启动过程中就指定好校验流程 DelistCheckOutChain.java
/**
*
* 责任链模式
* 指定校验顺序
* @author dzx
* @since 2020/10/26
*/
@Component
public class DelistCheckOutChain implements ApplicationRunner {
/**
* 单号唯一性校验
*/
@Autowired
private DelistCodeHandler delistCodeHandler;
/**
* 单号状态校验
*/
@Autowired
private DelistStatusHandler delistStatusHandler;
/**
* 单号下货品是否存在
*/
@Autowired
private DelistGoodsExistHandler delistGoodsExistHandler;
@Override
public void run(ApplicationArguments args) {
//链式指定校验流程
delistCodeHandler.setNextHandler(delistStatusHandler)
.setNextHandler(delistGoodsExistHandler);
}
}
最后的调用校验过程
@Service
@Slf4j
public class DelistOrderApplication {
@Autowired
private DelistOrderEventBus delistOrderEventBus;
@Autowired
private DelistCodeHandler delistCodeHandler;
/**
* 保存导入信息
* @param event event
* @throws NotFoundException NotFoundException
*/
public void saveClearanceBulkFile(BulkFileTaskEvent event) throws NotFoundException {
//校验成功结果集合
List<DelistImportEvent> delistImportEventList = new ArrayList<>(event.getTaskDetails().size());
//
List<DelistChainDTO> results = new ArrayList<>();
//校验数据
for (BulkFileTaskEvent.BulkFileTaskDetail bulkFileTaskDetail : event.getTaskDetails()) {
//校验对象创建 ......
//bulkFileTaskDetail导入对象 delistChainDto 存储校验过程中的产生的对象(对下一级有用) resultEvent校验结果存储
//调用校验过程
boolean flag = delistCodeHandler.doBulkFileTask(bulkFileTaskDetail, delistChainDto,resultEvent);
delistOrderEventBus.sendDelistOrderOutput(resultEvent);
//校验成功加入集合中
if (flag){
delistImportEventList.add(delistImportEvent);
results.add(delistChainDto);
}
}
//处理成功校验的数据
orderService.doDelistBulkFile(delistImportEventList,results);
}
}
总结
使用模板加职责链模式可以指定校验顺序,有效减少if和else繁琐的嵌套问题,更加优雅的实现这一顺序校验的过程,使代码有更好的可读性和拓展性,本文使用的校验方式控制在n^2时间复杂度内,有另外一种实现方式为 n^3时间复杂度,其实现方式和上文最大的不同是校验过程中加入校验LinkedList,该LinkedList包含各种校验类,同时在项目启动时可以通过LinkedList的特性,保证插入顺序,来解决校验的顺序性问题,上文的实现方式空间复杂度会比这一种实现方式要高