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

2024-01-24 13:30

本文主要是介绍履约系统接单制作流程设计方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

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 = Calendar.getInstance().getTimeInMillis() / 1000;//把一个订单的制作单数据 按产品组合分类 几个产品为一个组合,每个组合对应一个ProductNoMap<String, List<MakeOrderItemDTO>> productSetNoMap = makeOrders.stream().collect(Collectors.groupingBy(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"+JSON.toJSONString(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.isBlank(orderItemDTO.getMakeOrderNo())) {throw new RuntimeException("制作单编号不能为空");}MakeOrderItemDTO makeOrderNew = new MakeOrderItemDTO();BeanUtils.copyProperties(orderItemDTO, makeOrderNew);redisTemplate.opsForHash().put('MAKE_POOL_'+makeOrderNew.getShopMdCode()+'_'+makeOrderNew.getProductNo()), makeOrderNew.getMakeOrderNo(), makeOrderNew);});}
}

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

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

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

 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;if(Long.toString(remainder).equals(Integer.toString(shardIndex))){Set<String> set = shopMdCodeMap.get(shopMdCode);if(null == set){set = new HashSet<>();}set.add(strs[1]);shopMdCodeMap.put(shopMdCode,set);}
});
for(Map.Entry entry:shopMdCodeMap.entrySet()){Long shopMdCode = (Long) entry.getKey();//门店编号Set<String> productSetNoSet = (Set<String>)entry.getValue();//某一门店下所有订单的所有产品组合编号的集合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 = (Calendar.getInstance().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.isEmpty(makeOrderNo.getValue())) {return;}Set<String> makeOrderNoSet = makeOrderNoMap.get(makeOrderNo);if(null == makeOrderNoSet){makeOrderNoSet = new HashSet<>();}makeOrderNoSet.add(makeOrderNo.getValue());makeOrderNoMap.put(productSetNo,makeOrderNoSet);});});//根据系统后台配置的产品组合配置信息获取某单菜品可同时制作的数量上限int maxQuantity = 0;ProductSetDTO productSetDTO = productSetService.getProductSetByNo(productSetNo);maxQuantity = productSetDTO.getMaxQuantity();//可进入制作中状态的制作单集合List<MakeOrderItemDTO> inMakingCandidateList = new ArrayList<>();//可进入接单状态和已经处于接单状态的制作单集合List<MakeOrderItemDTO> acceptCandidateList = new ArrayList<>();productSetNos.forEach(productSetNo -> {Set<String> makeOrderNoSet = makeOrderNoMap.get(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 = Calendar.getInstance().getTimeInMillis();if (CollectionUtils.isNotEmpty(makeOrders)) {MakeOrderPoolConfigPO poolConfig = CfcenterConfigs.MAKE_ORDER_POOL_CONFIG.get();//遍历制作池中的单菜品制作单集合信息 筛选出处于制作中状态的制作单需要的菜品的数量long inMakingCount = makeOrders.stream().filter(m -> MakeOrderStateEnum.IN_MAKING.getCode().equals(m.getMakeStatus())).mapToInt(MakeOrderItemDTO::getQuantity).sum();log.info("当前制作中的菜品数量inMakingCount"+inMakingCount);//如果制作中的菜品数没有达到菜品设置的同时制作上限if (inMakingCount < maxQuantity) {//筛选出已接单的制作单集合,同时把超过30s等待的创建状态的制作单也设置为已接单状态放进集合里筛选出来,按制作单创建时间排序。List<MakeOrderItemDTO> candidateList = makeOrders.stream().filter(makeOrderItemDTO -> {//已经过了30s等待期的处于创建状态的制作,可以作为候选制作单if (makeOrderItemDTO.getCreateTime().getTime() + 30000L < timeInMillis && MakeOrderStateEnum.CREATED.getCode().equals(makeOrderItemDTO.getMakeStatus())) {log.info("进入已接单"+makeOrderItemDTO.getOrderNo());makeOrderItemDTO.setMakeStatus(MakeOrderStateEnum.ACCEPTED.getCode());acceptCandidateList.add(makeOrderItemDTO);return true;}log.info("当前制作getMakeStatus()"+makeOrderItemDTO.getMakeStatus());return MakeOrderStateEnum.ACCEPTED.getCode().equals(makeOrderItemDTO.getMakeStatus());}).sorted(Comparator.comparing(MakeOrderItemDTO::getCreateTime)).collect(Collectors.toList());//如果接单状态的制作单集合不为空,按先后顺序选取接单状态的制作单设置为制作中,达到菜品制作上限为止。if (!CollectionUtils.isEmpty(candidateList)) {log.info("制作中candidateList"+ JSON.toJSONString(candidateList));for (MakeOrderItemDTO m : candidateList) {if (inMakingCount < maxQuantity) {log.info("进入制作中"+m.getOrderNo()+"inMakingCount"+inMakingCount+",maxQuantity"+maxQuantity);//制作中的商品数量未达最大值,直接进入制作池开始制作,无需判断是否超出队列inMakingCount += m.getQuantity();m.setMakeStatus(MakeOrderStateEnum.IN_MAKING.getCode());inMakingCandidateList.add(m);}}}} else {//制作池已满 仅仅筛选超过【10s】等待接单时间的创建状态的制作单流转为已接单状态,放入接单集合List<MakeOrderItemDTO> candidateList = makeOrders.stream().filter(m -> {//是否已经过了等待期if (m.getCreateTime().getTime() + poolConfig.getWaitAcceptTimeByType(m.getOrigin() + "") < timeInMillis && MakeOrderStateEnum.CREATED.getCode().equals(m.getMakeStatus())) {//变为已接单m.setMakeStatus(MakeOrderStateEnum.ACCEPTED.getCode());return true;}return false;}).collect(Collectors.toList());acceptCandidateList.addAll(candidateList);}}});//如果可进入接单状态和已经处于接单状态的制作单集合不为空,对这些制作单做【流转为接单状态操作】if (!CollectionUtils.isEmpty(acceptCandidateList)) {log.error("门店{}触发制作单进入接单节点数据{}", shopMdCode, JSONObject.toJSONString(makeOrderCandidate));MakeOrderContext makeOrderContext = new MakeOrderContext();makeOrderContext.setShopMdCode(shopMdCode);makeOrderContext.setLastState(MakeOrderStateEnum.CREATED);makeOrderContext.setCurState(MakeOrderStateEnum.ACCEPTED);makeOrderContext.setMakeOrderItemList(makeOrderCandidate.getAcceptCandidateList());makeOrderContext.setOperateSource(MakeOrderOperateSourceEnum.SYSTEM.getCode());makeOrderContext.setOperateEmp(Constant.SYSTEM_USER_ID);makeOrderContext.setOperateEmpName(Constant.SYSTEM_USER_NAME);makeOrderContext.setOperateTime(new Date());makeOrderFlowService.doAction(makeOrderContext);}//如果可进入制作中状态的制作单集合不为空,对这些制作单做【流转为制作中状态的操作】if (!CollectionUtils.isEmpty(inMakingCandidateList)) {log.error("门店{}触发制作单进入制作中节点数据{}", shopMdCode, JSONObject.toJSONString(makeOrderCandidate));MakeOrderContext makeOrderContext = new MakeOrderContext();makeOrderContext.setShopMdCode(shopMdCode);makeOrderContext.setLastState(null);makeOrderContext.setCurState(MakeOrderStateEnum.IN_MAKING);makeOrderContext.setMakeOrderItemList(makeOrderCandidate.getInmakingCandidateList());makeOrderContext.setOperateSource(MakeOrderOperateSourceEnum.SYSTEM.getCode());makeOrderContext.setOperateEmp(Constant.SYSTEM_USER_ID);makeOrderContext.setOperateEmpName(Constant.SYSTEM_USER_NAME);makeOrderContext.setOperateTime(new Date());makeOrderFlowService.doAction(makeOrderContext);}
}

这篇关于履约系统接单制作流程设计方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/639841

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识