Java生鲜电商平台

Java生鲜电商平台-订单模块状态机架构设计

说明:在Java生鲜电商平台中订单的状态流转业务

我们知道 一个订单会有很多种状态:临时单、已下单、待支付、待收货、待评价、已完成,退货中等等。每一种状态都和其扭转前的状态、在扭转前状态所执行的操作有关。

一 实例说明

举例一个过程:用户将商品加入购物车,在后台生成了一个所谓的“临时单”,这个订单实际上还没有正式生成,因为用户仍然没有点击下单。只有当用户下单后,这个“临时单”才可以转化为一个“待支付的订单”。那么这个过程中:只有将一个处于“临时单”状态的订单执行下单操作,才能得到一个状态为“待支付”的订单。 即--一个前置状态+一个恰当的操作,才能扭转订单的状态。在这个过程中,如果是硬编码,那么我们需要一系列的 if...else 语句来检查订单的当前状态、可执行操作以及这两个的组合得到的下一个应该被流转的状态值。如果订单的状态流转很复杂的话,写出来的逻辑就会很复杂,并且可读性很低。后期的维护就是一个坑。

二 状态设计模式与订单状态流转

处理这个问题,我们可以使用 状态机设计模式 来处理。对应到实践,就是状态机。

关于状态机设计模式的具体内容,可以自行百度。这里用简单的一句话来概括的话:对象的内部状态随外部执行条件的变化而变化。再映射到订单状态的流转上:订单的状态,随订单当前状态和目前执行操作的组合而变化。

三 编码前的抽象与设计

图示模拟一个订单状态的流转流程。从一个临时订单开始,每当订单处于某一个已知的状态的时候,要想让这个订单改变状态,就需要我们去执行对应的操作。

从状态机角度来说,我们先将各种信息进行抽象和处理

3.1 代码抽象

编写对应订单状态枚举类

1public enum OrderStatusEnum { 2 3 CREATE_EVENT(1, "创建订单"), 4 FORMAL_EVENT(2, "正式订单"), 5 NEED_PAY(3, "待支付"), 6 PAY_DONE(4, "已支付"), 7 ORDER_FINISHED(5, "订单已完成"), 8 9 ORDER_CANCEL(6, "订单已取消"); 10 11 OrderStatusEnum(int status, String desc) { 12 this.status = status; 13 this.desc = desc; 14 } 15 16 public int status; 17 18 public String desc; 19}

枚举类中先准备好需要用的状态信息。

先用一张图来描述整个工作机制:

然后是需要的核心代码部分:一个管理订单状态的中转站manager类,一组用于扭转订单状态的operator类,一组扭转完订单状态后执行后续逻辑操作的processor类。

manager类需要根据对应传入的当前订单状态、要对该订单执行操作来得到这个订单的结果状态(依靠对应的opertor类),然后执行一系列需要的业务逻辑操作(编写对应的processor类)。这样的好处就是将订单状态流转和对应的业务处理解耦。并且也不会再有一堆繁杂的 if...else 操作。每当需要新的订单状态流转操作的时候,可以去编写对应的一套operator和processor组件来完成,和已有业务的分离度很高。

接下来贴代码举例

1/** 2 * 订单状态流转管理器--状态机核心组件 3 * @author Java生鲜电商平台 4 * 5 **/ 6 7@Component 8public class OrderStateManager { 9 10 Map<Integer, AbstractOrderOperator> orderOperatorMaps = new HashMap<Integer, AbstractOrderOperator>(); 11 12 Map<Integer, AbstractOrderProcessor> orderProcessorMaps = new HashMap<Integer, AbstractOrderProcessor>(); 13 14 public OrderStateManager() { } 15 16 /** 17 * 状态流转方法 18 * @param orderId 订单id 19 * @param event 流转的订单操作事件 20 * @param status 当前订单状态 21 * @return 扭转后的订单状态 22 */ 23 public int handleEvent(final String orderId, OrderStatusEnum event, final int status) { 24 if (this.isFinalStatus(status)) { 25 throw new IllegalArgumentException("handle event can't process final state order."); 26 } 27 // 获取对应处理器,根据入参状态和时间获取订单流转的结果状态 28 AbstractOrderOperator abstractOrderOperator = this.getStateOperator(event); 29 int resState = abstractOrderOperator.handleEvent(status, event); 30 // 得到结果状态,在对应的processor中处理订单数据及其相关信息 31 AbstractOrderProcessor orderProcessor = this.getOrderProcessor(event); 32 if (!orderProcessor.process(orderId, resState)) { 33 throw new IllegalStateException(String.format("订单状态流转失败,订单id:%s", orderId)); 34 } 35 return resState; 36 } 37 38 /** 39 * 根据入参状态枚举实例获取对应的状态处理器 40 * @param event event 41 * @return 42 */ 43 private AbstractOrderOperator getStateOperator(OrderStatusEnum event) { 44 AbstractOrderOperator operator = null; 45 for (Map.Entry<Integer, AbstractOrderOperator> entry: orderOperatorMaps.entrySet()) { 46 if (event.status == entry.getKey()) { 47 operator = entry.getValue(); 48 } 49 } 50 if (null == operator) { 51 throw new IllegalArgumentException(String.format("can't find proper operator. The parameter state :%s", event.toString())); 52 } 53 return operator; 54 } 55 56 /** 57 * 根据入参状态枚举实例获取对应的状态后处理器 58 * @param event event 59 * @return 60 */ 61 private AbstractOrderProcessor getOrderProcessor(OrderStatusEnum event) { 62 AbstractOrderProcessor processor = null; 63 for (Map.Entry<Integer, AbstractOrderProcessor> entry : orderProcessorMaps.entrySet()) { 64 if (event.status == entry.getKey()) { 65 processor = entry.getValue(); 66 } 67 } 68 if (null == processor) { 69 throw new IllegalArgumentException(String.format("can't find proper processor. The parameter state :%s", event.toString())); 70 } 71 return processor; 72 } 73 74 /** 75 * 判断是不是已完成订单 76 * @param status 订单状态码 77 * @return 78 */ 79 private boolean isFinalStatus(int status) { 80 return OrderStatusEnum.ORDER_FINISHED.status == status; 81 } 82 83}

核心的代码就是类中的 handleEvent 方法。

对应的获取到的组件处理类示例:

1/** 2 * @author Java生鲜电商平台-订单模块状态机架构设计 3 **/ 4@Data 5public abstract class AbstractOrderOperator { 6 7 int status; 8 9 public abstract int handleEvent(int orderStatus, OrderStatusEnum orderStatusEnum); 10} 11=================================== 12/** 13 * 创建订单操作状态流转 14 * Java生鲜电商平台-订单模块状态机架构设计 15 **/ 16@Component 17@OrderOperator 18public class CreateOrderOperator extends AbstractOrderOperator { 19 20 public CreateOrderOperator() { 21 super.setStatus(1); 22 } 23 24 @Override 25 public int handleEvent(int orderStatus, OrderStatusEnum orderStatusEnum) { 26 if (orderStatus != OrderStatusEnum.CREATE_EVENT.status && orderStatus != OrderStatusEnum.ORDER_CANCEL.status) { 27 throw new IllegalArgumentException(String.format("create operation can't handle the status: %s", orderStatus)); 28 } 29 System.out.println("进入创建订单状态扭转处理器..."); 30 switch (orderStatusEnum) { 31 case CREATE_EVENT: 32 return OrderStatusEnum.FORMAL_EVENT.status; 33 case ORDER_CANCEL: 34 return OrderStatusEnum.ORDER_CANCEL.status; 35 default: 36 return getStatus(); 37 } 38 } 39}

后处理器

1/** 2 * 订单处理器 3 **/ 4@Data 5public abstract class AbstractOrderProcessor { 6 7 int status; 8 9 public abstract boolean process(String orderId, Object... params); 10} 11@Component 12@OrderProcessor 13public class CreateOrderProcessor extends AbstractOrderProcessor{ 14 15 public CreateOrderProcessor() { 16 super.setStatus(1); 17 } 18 19 @Override 20 public boolean process(String orderId, Object... params) { 21 // TODO 创建/取消订单对应的数据库修改,mq发送等操作,可以在此处process方法中完成 22 System.out.println("进入创建订单后处理器..."); 23 return true; 24 } 25}

这些组件类都是依赖于spring的组件扫描注入。如果要定制化地处理自己的组件类。可以用一些其他的技巧来处理。比如此处使用到了自定义注解,通过自定义注解+自定义状态机初始化类来完成对应组件类的筛选与初始化。将这个初始化类加载完毕,状态机就可以正常使用了。

1/** 2 * 状态机前置激活类,在spring中扫描配置此类 <br/> 3 * 使用自定义注解标记对应的状态处理器和后置处理器并在初始化操作中完成对应处理器的初始化。 4 **/ 5@Component 6public class Initialization implements BeanPostProcessor { 7 8 @Resource 9 OrderStateManager manager; 10 11 @Nullable 12 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 13 if (bean instanceof AbstractOrderOperator && bean.getClass().isAnnotationPresent(OrderOperator.class) ) { 14 AbstractOrderOperator orderState = (AbstractOrderOperator) bean; 15 manager.orderOperatorMaps.put(orderState.getStatus(), orderState); 16 } 17 if (bean instanceof AbstractOrderProcessor && bean.getClass().isAnnotationPresent(OrderProcessor.class) ) { 18 AbstractOrderProcessor orderProcessor = (AbstractOrderProcessor) bean; 19 manager.orderProcessorMaps.put(orderProcessor.getStatus(), orderProcessor); 20 } 21 return bean; 22 } 23}

这里有一个问题就是在正式开发环境中,依赖于项目的spring环境,需要在状态机正式运行前将对应的状态扭转组件类(operator和processor)注入到环境中。

联系QQ:137071249

QQ群:793305035

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

thinkcmf+jsapi 实现微信支付

首先从小程序端接收订单号、金额等参数,然后后台进行统一下单,把微信支付的订单号返回,在把订单号发送给前台,前台拉起支付,返回参数后更改支付状态。。。回调publicfunctionnotify(){$wechatDb::name('wechat')where('status',1)find();

java 商城系统源码分享

目的snowflake是常见的id(编号)生成算法,由时间戳业务id机器id序列号组合而成,在电商系统(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.javamall.com.cn%2F)中,用于订单号的生成、支付单号的生成等等。本发号器主要解决在容器化的部署情