履约系统接单制作流程设计方案

阅读: 评论:0

履约系统接单制作流程设计方案

履约系统接单制作流程设计方案

目录

1.制作单创建

1.1消费MQ消息创建制作单流程

 1.1.1创建制作单入库

1.1.2 基于ZSET结构对订单拆分后的所有制作单按 时间先后排序 进行存储

1.1.3 基于HASH结构对每个制作单组合中的每个单产品制作单的详情进行存储

1.1.4 为订单创建一个或多个门店下制作单组合标识存储到SET结构的门店制作单池中

 1.2任务调度pull方案流程

2.制作单接单和制作中状态流转

2.1基于分片策略的任务调度实现自动接单和制作单状态流转

 2.2自动接单和制作中状态流转的实现

2.2.1遍历当前门店下的产品组合单号SET集合,获取【最近30s】内创建的单菜品制作单号集合

2.2.2获取系统预设的该类产品组合的单菜品的最大可同时制作数量

2.2.3获取该类产品组合下的所有单菜品制作单信息

2.2.4遍历该产品组合下的所有单菜品制作单集合信息 筛选出该产品组处于【制作中状态】的制作单待制作的菜品的数量

2.2.5.遍历产品组合编号下的所有制作单集合,筛选出可接单的制作单集合,可加入制作流转为制作中的制作单集合

2.2.6执行接单和制作中状态流转


履约系统上游是订单支付操作完成,履约流程从支付完成后创建制作单,接单制作,制作完成,出餐配送一系列流程节点,还包括制作单取消(制作单创建但未接单制作),退货等流程节点。

1.制作单创建

考虑高峰期存在订单量激增的情况,订单支付到履约单创建环节的通信使用MQ消息+任务调度pull做兜底方案实现支付后创建履约单的流程操作。

1.1消费MQ消息创建制作单流程

  1. 接收到MQ消息,创建制作单入库(状态与订单同步为待商家接单状态);
  2. 以门店维度按支付完成时间排序基于Redis的zset结构保存制作单到制作队列中
  3. 以门店维度基于Hash结构保存制作单号和制作单的详情信息。
  4. 设置门店订单标识用于后续节点流程处理。

制作单是单菜品粒度的制作信息,一个订单到达履约系统后会被拆分为一个或多个产品组合,一个或多个产品组合与原订单是一对多的逻辑关联关系;每个产品组合下包含一个或多个单菜品,这个菜品的制作信息即制作单。所以要定位到一个制作单,它的层级是某个门店下的某个产品组合下的一个元素。 

 1.1.1创建制作单入库

这一步就是根据订单拆分创建制作单集合,保存原订单基本信息和制作单列表集合。

1.1.2 基于ZSET结构对订单拆分后的所有制作单按 时间先后排序 进行存储

把订单拆分后单分组后的产品组单号ProductNo,以订单提交时间为对应的score放入ZSET中 。这里要说明一下,制作队列中数据是以一个制作单组合为原子粒度,也就是说制作队列中不区分订单,之所以这么存储,是因为这样能保证产品的制作在订单粒度是并行的。怎么理解呢?

举个例子:像一些现成的产品例如饮料不用等待上一个订单的全部产品制作单都完成后再进行制作,但是单产品的制作顺序还是按照订单时间顺序来制作的。

// KEY结构:"UNACCEPT_MAKEORDER_SHOPS_" +shopMdCode+'_'+productNopublic void tempSaveUnAcceptMakeOrderShop(final Long shopMdCode,List<MakeOrderItemDTO> makeOrders) {//用当前时间times作为scoreLong times = Instance().getTimeInMillis() / 1000;//把一个订单的制作单数据 按产品组合分类 几个产品为一个组合,每个组合对应一个ProductNoMap<String, List<MakeOrderItemDTO>> productSetNoMap = makeOrders.stream().upingBy(MakeOrderItemDTO::getProductSetNo));productSetNoMap.forEach( (productNo,productSetNoOrderList) -> {//加入门店制作单ZSET集合中Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>(16);productSetNoOrderList.forEach(m -> {typedTupleSet.add(new DefaultTypedTuple( m.getMakeOrderNo(), times));});//存储时以门店下的产品组合为最小粒度redisTemplate.opsForZSet().add("UNACCEPT_MAKEORDER_SHOPS_" +shopMdCode+'_'+productNo, typedTupleSet);log.info("存储新建状态的制作单"+getUnAcceptKey(shopMdCode,productNo) +",typedTupleSet"&#JSONString(typedTupleSet));});
}

1.1.3 基于HASH结构对每个制作单组合中的每个单产品制作单的详情进行存储

以制作单拆分后的产品组号为KEY,存储门店下某个制作单产品组下的各个产品的制作详情数据,在整个流程中,这个hash结构中存储已新建和制作中状态的详情信息。基于这种存储结构,在处理制作单时可以根据1步骤中设置在ZSET中的产品组号和产品制作单号 从当前存储结构中 获取对应的单产品的制作单的详情信息。 

"MAKE_POOL_"+shopCode+"_"+productNo orderNo oderItempublic void putMakeOrderList(final List<MakeOrderItemDTO> makeOrders) {if (makeOrders != null) {makeOrders.forEach((orderItemDTO)->{if (StringUtils.MakeOrderNo())) {throw new RuntimeException("制作单编号不能为空");}MakeOrderItemDTO makeOrderNew = new MakeOrderItemDTO();pyProperties(orderItemDTO, makeOrderNew);redisTemplate.opsForHash().put('MAKE_POOL_'&#ShopMdCode()+'_'&#ProductNo()), MakeOrderNo(), makeOrderNew);});}
}

1.1.4 为订单创建一个或多个门店下制作单组合标识存储到SET结构的门店制作单池中

池中的一个或多个标识对应一笔订单,后续流程以门店粒度去处理门店下的订单的制作单时,根据池中的一个或多个制作单组合标识(对应一个订单),去ZSET中获取到该门店下某一笔订单的所有的单产品制作单号,然后根据单号在HASH结构中获取每个单产品制作单的详情信息。

public void setShopMdCodeSetInRedis(List<MakeOrderItemDTO> makeOrderItemList) {makeOrderItemList.forEach(makeOrderItem ->{redisTemplate.opsForSet().add("ShopCode", ShopMdCode() + "_" + ProductNo());});
}

 1.2任务调度pull方案流程

任务调度是作为订单支付后发送创建制作单的MQ消息丢失的一个兜底方案。设置合理频率例如五分钟执行一次主动以RPC方式加分页限制拉取当前时间前十分钟前推两小时范围内处于提交履约单状态的订单号集合,然后查询DB中同时间范围内的制作单的订单号集合,两个集合取差集,剩下的可以认为是MQ未成功通知创建制作单的订单信息。对这些订单执行与上面MQ消费创建制作单相同操作。

2.制作单接单和制作中状态流转

制作单创建后出于创建(待商家接单)状态。后续使用任务调度框架使用按门店进行分片的方案进行自动接单和制作单后续状态流转处理。该方案分摊了每个服务节点的压力。

2.1基于分片策略的任务调度实现自动接单和制作单状态流转

从Redis中获取制作单池中所有的的订单产品组合标识的元素集合,遍历产品组合单号SET集合,根据分片逻辑筛选出当前服务节点要处理的门店下的所有订单的产品组合单号,按门店分类基于map存储,key是门店编号,value是产品组合单号的Set集合。

遍历门店单号,基于线程池提交异步执行每个门店下的制作单处理任务,即每个线程处理某一个门店下的所有产品组合下的所有单产品制作单的状态。

//key是门店编号,set集合存放所有该门店下的订单产品组合标识
Map<Long,Set<String>> shopMdCodeMap = new HashMap<>();
//获取创建制作单流程中保存的所有的订单的产品组合标识
Set<String> set = redisTemplate.opsForSet().members("SHOP_MD_CODE");
//遍历产品组合标识,根据分片逻辑筛选指定门店下的所有产品组合标识,以门店为单位分组
shopMdCodeSetRedis.forEach((strKey) -> {String[] strs = strKey.split("_");Long shopMdCode = Long.parseLong(strs[0]);Long remainder = shopMdCode % shardTotal;String(remainder).String(shardIndex))){Set<String> set = (shopMdCode);if(null == set){set = new HashSet<>();}set.add(strs[1]);shopMdCodeMap.put(shopMdCode,set);}
});
for(Map.Entry Set()){Long shopMdCode = (Long) Key();//门店编号Set<String> productSetNoSet = (Set<String>)Value();//某一门店下所有订单的所有产品组合编号的集合ExecutorUtils.submit(() -> {doMakeOrderStateFlow(shopMdCode,productSetNoSet);});
}

 2.2自动接单和制作中状态流转的实现

任务调度使用线程池实现异步处理每个门店下的制作单接单和状态流转。线程任务逻辑流程如下:

2.2.1遍历当前门店下的产品组合单号SET集合,获取【最近30s】内创建的单菜品制作单号集合

从Redis的制作队列中获取每个产品组合编号下的所有单产品制作单号,按产品组合编号分组;这一步其实是根据创建制作单流程中ZSET制作队列的存储设计,从ZSET中获取门店下的所有单菜品制作单号集合。

2.2.2获取系统预设的该类产品组合的单菜品的最大可同时制作数量

这个数量值决定了处于接单状态的制作单多久能流转为制作中状态进行制作。

2.2.3获取该类产品组合下的所有单菜品制作单信息

根据当前门店编号和当前的产品组合编号批量获取门店下该产品组合下的所有单菜品制作单信息,即得到一个单品制作单信息对象的集合。

2.2.4遍历该产品组合下的所有单菜品制作单集合信息 筛选出该产品组处于【制作中状态】的制作单待制作的菜品的数量

如果当前出于制作中的制作单小于最大可同时制作数量,则可以把一定数量的接单状态的制作单流转为制作中状态,制作单可以开始制作。

2.2.5.遍历产品组合编号下的所有制作单集合,筛选出可接单的制作单集合,可加入制作流转为制作中的制作单集合

此处判断如果4步骤中【待制作的菜品的数量 】小于【单菜品的最大可同时制作数量】,则筛选出超过等待时长可流转为接单状态的制作单(设置为接单状态)和已经出于接单状态的制作单,按制作单创建时间排序生成【接单状态的制作集合】,然后从集合取最大可同时制作数量与当前待制作数量之差 个制作单,生成新的【可加入制作的制作单集合】。

如果【待制作的菜品的数量 】大于【单菜品的最大可同时制作数量】,也就是此时无法将任何接单状态的制作单加入制作,所以只筛选出超过等待时长可流转为接单状态的制作单,设置为接单状态,生成【接单状态的制作单集合】

2.2.6执行接单和制作中状态流转

将步骤5中筛选好的【接单状态的制作单集合】和【可加入制作的制作单集合】两个集合分别进行接单状态流转处理和制作中状态流转处理。

public doMakeOrderStateFlow(shopMdCode,productSetNoSet){//key是产品组合编号,value是组合下所有的制作单号的set集合Map<String,Set<String>> makeOrderNoMap = new HashMap<>(16);productSetNoSet.forEach(productSetNo ->{// 从待结单队列中获取该门店最近30秒内的已创建的(待接单状态)制作单列表//【此处的扫描区间一定要大于后面判断是否接单逻辑中的接单等待时间,否则就会造成某些创建状态的制作单因扫描不到而不能接单的情况】Long times = (Instance().getTimeInMillis() - 30000L) / 1000;//取出score{times}秒前面的门店制作单数据Set<ZSetOperations.TypedTuple<String>> createdMakeOrderNoSetForProductNo =  (Set<ZSetOperations.TypedTuple<String>>) redisTemplate.opsForZSet().rangeByScoreWithScores("UNACCEPT_MAKEORDER_SHOPS_" +shopMdCode+'_'+productSetNo, 0, times);//如果为空直接返回if (CollectionUtils.isEmpty(createdMakeOrderNoSetForProductNo)) {return;}createdMakeOrderNoSetForProductNo.forEach(makeOrderNo -> {if (makeOrderNo == null || StringUtils.Value())) {return;}Set<String> makeOrderNoSet = (makeOrderNo);if(null == makeOrderNoSet){makeOrderNoSet = new HashSet<>();}makeOrderNoSet.Value());makeOrderNoMap.put(productSetNo,makeOrderNoSet);});});//根据系统后台配置的产品组合配置信息获取某单菜品可同时制作的数量上限int maxQuantity = 0;ProductSetDTO productSetDTO = ProductSetByNo(productSetNo);maxQuantity = MaxQuantity();//可进入制作中状态的制作单集合List<MakeOrderItemDTO> inMakingCandidateList = new ArrayList<>();//可进入接单状态和已经处于接单状态的制作单集合List<MakeOrderItemDTO> acceptCandidateList = new ArrayList<>();productSetNos.forEach(productSetNo -> {Set<String> makeOrderNoSet = (productSetNo);if(null == makeOrderNoSet){log.info("productSetNo制作单位空"+productSetNo);return;}//根据门店编号和产品组合编号定位到HASH结构的key,批量获取key中多个单菜品制作单号对应的详情信息,即制作单信息对象的集合。List<MakeOrderItemDTO>  makeOrders = new ArrayList<>();List<Object> list = redisTemplate.opsForHash().multiGet("MAKE_POOL_"+shopMdCode+"_"+productSetNo, makeOrderNoSet);list.forEach(obj -> {if(null != obj){makeOrders.add((MakeOrderItemDTO) obj);}});long timeInMillis = Instance().getTimeInMillis();if (CollectionUtils.isNotEmpty(makeOrders)) {MakeOrderPoolConfigPO poolConfig = CfcenterConfigs.MAKE_ORDER_();//遍历制作池中的单菜品制作单集合信息 筛选出处于制作中状态的制作单需要的菜品的数量long inMakingCount = makeOrders.stream().filter(m -> MakeOrderStateEnum.Code().MakeStatus())).mapToInt(MakeOrderItemDTO::getQuantity).sum();log.info("当前制作中的菜品数量inMakingCount"+inMakingCount);//如果制作中的菜品数没有达到菜品设置的同时制作上限if (inMakingCount < maxQuantity) {//筛选出已接单的制作单集合,同时把超过30s等待的创建状态的制作单也设置为已接单状态放进集合里筛选出来,按制作单创建时间排序。List<MakeOrderItemDTO> candidateList = makeOrders.stream().filter(makeOrderItemDTO -> {//已经过了30s等待期的处于创建状态的制作,可以作为候选制作单if (CreateTime().getTime() + 30000L < timeInMillis && Code().MakeStatus())) {log.info("进入已接单"&#OrderNo());makeOrderItemDTO.setMakeStatus(Code());acceptCandidateList.add(makeOrderItemDTO);return true;}log.info("当前制作getMakeStatus()"&#MakeStatus());return Code().MakeStatus());}).sorted(Comparatorparing(MakeOrderItemDTO::getCreateTime)).List());//如果接单状态的制作单集合不为空,按先后顺序选取接单状态的制作单设置为制作中,达到菜品制作上限为止。if (!CollectionUtils.isEmpty(candidateList)) {log.info("制作中candidateList"+ JSONString(candidateList));for (MakeOrderItemDTO m : candidateList) {if (inMakingCount < maxQuantity) {log.info("进入制作中"&#OrderNo()+"inMakingCount"+inMakingCount+",maxQuantity"+maxQuantity);//制作中的商品数量未达最大值,直接进入制作池开始制作,无需判断是否超出队列inMakingCount += m.getQuantity();m.setMakeStatus(MakeOrderStateEnum.Code());inMakingCandidateList.add(m);}}}} else {//制作池已满 仅仅筛选超过【10s】等待接单时间的创建状态的制作单流转为已接单状态,放入接单集合List<MakeOrderItemDTO> candidateList = makeOrders.stream().filter(m -> {//是否已经过了等待期if (m.getCreateTime().getTime() + Origin() + "") < timeInMillis && Code().MakeStatus())) {//变为已接单m.setMakeStatus(Code());return true;}return false;}).List());acceptCandidateList.addAll(candidateList);}}});//如果可进入接单状态和已经处于接单状态的制作单集合不为空,对这些制作单做【流转为接单状态操作】if (!CollectionUtils.isEmpty(acceptCandidateList)) {("门店{}触发制作单进入接单节点数据{}", shopMdCode, JSONString(makeOrderCandidate));MakeOrderContext makeOrderContext = new MakeOrderContext();makeOrderContext.setShopMdCode(shopMdCode);makeOrderContext.setLastState(MakeOrderStateEnum.CREATED);makeOrderContext.setCurState(MakeOrderStateEnum.ACCEPTED);makeOrderContext.AcceptCandidateList());makeOrderContext.setOperateSource(Code());makeOrderContext.setOperateEmp(Constant.SYSTEM_USER_ID);makeOrderContext.setOperateEmpName(Constant.SYSTEM_USER_NAME);makeOrderContext.setOperateTime(new Date());makeOrderFlowService.doAction(makeOrderContext);}//如果可进入制作中状态的制作单集合不为空,对这些制作单做【流转为制作中状态的操作】if (!CollectionUtils.isEmpty(inMakingCandidateList)) {("门店{}触发制作单进入制作中节点数据{}", shopMdCode, JSONString(makeOrderCandidate));MakeOrderContext makeOrderContext = new MakeOrderContext();makeOrderContext.setShopMdCode(shopMdCode);makeOrderContext.setLastState(null);makeOrderContext.setCurState(MakeOrderStateEnum.IN_MAKING);makeOrderContext.InmakingCandidateList());makeOrderContext.setOperateSource(Code());makeOrderContext.setOperateEmp(Constant.SYSTEM_USER_ID);makeOrderContext.setOperateEmpName(Constant.SYSTEM_USER_NAME);makeOrderContext.setOperateTime(new Date());makeOrderFlowService.doAction(makeOrderContext);}
}

本文发布于:2024-01-27 20:10:28,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/17063574302379.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:设计方案   流程   系统
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23