Java生鲜电商平台-订单模块状态机架构设计
Java生鲜电商平台-订单模块状态机架构设计
说明:在Java生鲜电商平台中订单的状态流转业务
我们知道 一个订单会有很多种状态:临时单、已下单、待支付、待收货、待评价、已完成,退货中等等。每一种状态都和其扭转前的状态、在扭转前状态所执行的操作有关。
一 实例说明
举例一个过程:用户将商品加入购物车,在后台生成了一个所谓的“临时单”,这个订单实际上还没有正式生成,因为用户仍然没有点击下单。只有当用户下单后,这个“临时单”才可以转化为一个“待支付的订单”。那么这个过程中:只有将一个处于“临时单”状态的订单执行下单操作,才能得到一个状态为“待支付”的订单。 即--一个前置状态+一个恰当的操作,才能扭转订单的状态。在这个过程中,如果是硬编码,那么我们需要一系列的 if...else 语句来检查订单的当前状态、可执行操作以及这两个的组合得到的下一个应该被流转的状态值。如果订单的状态流转很复杂的话,写出来的逻辑就会很复杂,并且可读性很低。后期的维护就是一个坑。
二 状态设计模式与订单状态流转
处理这个问题,我们可以使用 状态机设计模式 来处理。对应到实践,就是状态机。
关于状态机设计模式的具体内容,可以自行百度。这里用简单的一句话来概括的话:对象的内部状态随外部执行条件的变化而变化。再映射到订单状态的流转上:订单的状态,随订单当前状态和目前执行操作的组合而变化。
三 编码前的抽象与设计

图示模拟一个订单状态的流转流程。从一个临时订单开始,每当订单处于某一个已知的状态的时候,要想让这个订单改变状态,就需要我们去执行对应的操作。
从状态机角度来说,我们先将各种信息进行抽象和处理
3.1 代码抽象
编写对应订单状态枚举类
public enum OrderStatusEnum { CREATE_EVENT(1, "创建订单"),
FORMAL_EVENT(2, "正式订单"),
NEED_PAY(3, "待支付"),
PAY_DONE(4, "已支付"),
ORDER_FINISHED(5, "订单已完成"), ORDER_CANCEL(6, "订单已取消"); OrderStatusEnum(int status, String desc) {
this.status = status;
this.desc = desc;
} public int status; public String desc;
}
枚举类中先准备好需要用的状态信息。
先用一张图来描述整个工作机制:

然后是需要的核心代码部分:一个管理订单状态的中转站manager类,一组用于扭转订单状态的operator类,一组扭转完订单状态后执行后续逻辑操作的processor类。
manager类需要根据对应传入的当前订单状态、要对该订单执行操作来得到这个订单的结果状态(依靠对应的opertor类),然后执行一系列需要的业务逻辑操作(编写对应的processor类)。这样的好处就是将订单状态流转和对应的业务处理解耦。并且也不会再有一堆繁杂的 if...else 操作。每当需要新的订单状态流转操作的时候,可以去编写对应的一套operator和processor组件来完成,和已有业务的分离度很高。
接下来贴代码举例
/**
* 订单状态流转管理器--状态机核心组件
* @author Java生鲜电商平台
*
**/
@Component
public class OrderStateManager { Map<Integer, AbstractOrderOperator> orderOperatorMaps = new HashMap<Integer, AbstractOrderOperator>(); Map<Integer, AbstractOrderProcessor> orderProcessorMaps = new HashMap<Integer, AbstractOrderProcessor>(); public OrderStateManager() { } /**
* 状态流转方法
* @param orderId 订单id
* @param event 流转的订单操作事件
* @param status 当前订单状态
* @return 扭转后的订单状态
*/
public int handleEvent(final String orderId, OrderStatusEnum event, final int status) {
if (this.isFinalStatus(status)) {
throw new IllegalArgumentException("handle event can't process final state order.");
}
// 获取对应处理器,根据入参状态和时间获取订单流转的结果状态
AbstractOrderOperator abstractOrderOperator = this.getStateOperator(event);
int resState = abstractOrderOperator.handleEvent(status, event);
// 得到结果状态,在对应的processor中处理订单数据及其相关信息
AbstractOrderProcessor orderProcessor = this.getOrderProcessor(event);
if (!orderProcessor.process(orderId, resState)) {
throw new IllegalStateException(String.format("订单状态流转失败,订单id:%s", orderId));
}
return resState;
} /**
* 根据入参状态枚举实例获取对应的状态处理器
* @param event event
* @return
*/
private AbstractOrderOperator getStateOperator(OrderStatusEnum event) {
AbstractOrderOperator operator = null;
for (Map.Entry<Integer, AbstractOrderOperator> entry: orderOperatorMaps.entrySet()) {
if (event.status == entry.getKey()) {
operator = entry.getValue();
}
}
if (null == operator) {
throw new IllegalArgumentException(String.format("can't find proper operator. The parameter state :%s", event.toString()));
}
return operator;
} /**
* 根据入参状态枚举实例获取对应的状态后处理器
* @param event event
* @return
*/
private AbstractOrderProcessor getOrderProcessor(OrderStatusEnum event) {
AbstractOrderProcessor processor = null;
for (Map.Entry<Integer, AbstractOrderProcessor> entry : orderProcessorMaps.entrySet()) {
if (event.status == entry.getKey()) {
processor = entry.getValue();
}
}
if (null == processor) {
throw new IllegalArgumentException(String.format("can't find proper processor. The parameter state :%s", event.toString()));
}
return processor;
} /**
* 判断是不是已完成订单
* @param status 订单状态码
* @return
*/
private boolean isFinalStatus(int status) {
return OrderStatusEnum.ORDER_FINISHED.status == status;
} }
核心的代码就是类中的 handleEvent 方法。
对应的获取到的组件处理类示例:
/**
* @author Java生鲜电商平台-订单模块状态机架构设计
**/
@Data
public abstract class AbstractOrderOperator { int status; public abstract int handleEvent(int orderStatus, OrderStatusEnum orderStatusEnum);
}
===================================
/**
* 创建订单操作状态流转
* Java生鲜电商平台-订单模块状态机架构设计
**/
@Component
@OrderOperator
public class CreateOrderOperator extends AbstractOrderOperator { public CreateOrderOperator() {
super.setStatus(1);
} @Override
public int handleEvent(int orderStatus, OrderStatusEnum orderStatusEnum) {
if (orderStatus != OrderStatusEnum.CREATE_EVENT.status && orderStatus != OrderStatusEnum.ORDER_CANCEL.status) {
throw new IllegalArgumentException(String.format("create operation can't handle the status: %s", orderStatus));
}
System.out.println("进入创建订单状态扭转处理器...");
switch (orderStatusEnum) {
case CREATE_EVENT:
return OrderStatusEnum.FORMAL_EVENT.status;
case ORDER_CANCEL:
return OrderStatusEnum.ORDER_CANCEL.status;
default:
return getStatus();
}
}
}
后处理器
/**
* 订单处理器
**/
@Data
public abstract class AbstractOrderProcessor { int status; public abstract boolean process(String orderId, Object... params);
}
@Component
@OrderProcessor
public class CreateOrderProcessor extends AbstractOrderProcessor{ public CreateOrderProcessor() {
super.setStatus(1);
} @Override
public boolean process(String orderId, Object... params) {
// TODO 创建/取消订单对应的数据库修改,mq发送等操作,可以在此处process方法中完成
System.out.println("进入创建订单后处理器...");
return true;
}
}
这些组件类都是依赖于spring的组件扫描注入。如果要定制化地处理自己的组件类。可以用一些其他的技巧来处理。比如此处使用到了自定义注解,通过自定义注解+自定义状态机初始化类来完成对应组件类的筛选与初始化。将这个初始化类加载完毕,状态机就可以正常使用了。
/**
* 状态机前置激活类,在spring中扫描配置此类 <br/>
* 使用自定义注解标记对应的状态处理器和后置处理器并在初始化操作中完成对应处理器的初始化。
**/
@Component
public class Initialization implements BeanPostProcessor { @Resource
OrderStateManager manager; @Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AbstractOrderOperator && bean.getClass().isAnnotationPresent(OrderOperator.class) ) {
AbstractOrderOperator orderState = (AbstractOrderOperator) bean;
manager.orderOperatorMaps.put(orderState.getStatus(), orderState);
}
if (bean instanceof AbstractOrderProcessor && bean.getClass().isAnnotationPresent(OrderProcessor.class) ) {
AbstractOrderProcessor orderProcessor = (AbstractOrderProcessor) bean;
manager.orderProcessorMaps.put(orderProcessor.getStatus(), orderProcessor);
}
return bean;
}
}
这里有一个问题就是在正式开发环境中,依赖于项目的spring环境,需要在状态机正式运行前将对应的状态扭转组件类(operator和processor)注入到环境中。
联系QQ:137071249
QQ群:793305035
Java生鲜电商平台-订单模块状态机架构设计的更多相关文章
- Java生鲜电商平台-订单中心服务架构与异常订单逻辑
Java生鲜电商平台-订单中心服务架构与异常订单逻辑 订单架构实战中阐述了订单系统的重要性,并从订单系统的信息架构和流程上对订单系统有了总体认知,同时还穿插着一些常见的订单业务规则和逻辑.上文写到订单 ...
- Java生鲜电商平台-优惠券系统的架构设计与源码解析
Java生鲜电商平台-优惠券系统的架构设计与源码解析 电商后台:实例解读促销系统 电商后台系统包括商品管理系统.采购系统.仓储系统.订单系统.促销系统.维权系统.财务系统.会员系统.权限系统等,各系统 ...
- Java生鲜电商平台-促销系统的架构设计与源码解析
Java生鲜电商平台-促销系统的架构设计与源码解析 说明:本文重点讲解现在流行的促销方案以及源码解析,让大家对促销,纳新有一个深入的了解与学习过程. 促销系统是电商系统另外一个比较大,也是比较复杂的系 ...
- Java生鲜电商平台-商品基础业务架构设计-商品设计
Java生鲜电商平台-商品基础业务架构设计-商品设计 在生鲜电商的商品中心,在电子商务公司一般是后台管理商品的地方.在前端而言,是商家为了展示商品信息给用户的地方,它是承担了商品的数据,订单,营销活动 ...
- 点菜网---Java开源生鲜电商平台-商品基础业务架构设计-商品分类(源码可下载)
点菜网---Java开源生鲜电商平台-商品基础业务架构设计-商品分类 (源码可下载) 说明:我们搞过电商的人都可以体会到,搞生鲜电商是最复杂的,为什么复杂呢?我总结了有以下几个业务特性决定的: 1. ...
- Java生鲜电商平台-订单配送模块的架构与设计
Java生鲜电商平台-订单配送模块的架构与设计 生鲜电商系统最终的目的还是用户下单支付购买, 所以订单管理系统是电商系统中最为复杂的系统,其作为中枢决定着整个商城的运转, 本文将对于生鲜类电商平台的订 ...
- Java生鲜电商平台-订单架构实战
Java生鲜电商平台-订单架构实战 生鲜电商中订单中心是一个电商后台系统的枢纽,在这订单这一环节上需要读取多个模块的数据和信息进行加工处理,并流向下一环节:因此订单模块对一电商系统来说,重要性不言而喻 ...
- Java生鲜电商平台-供应链模块的设计与架构
Java生鲜电商平台-供应链模块的设计与架构 说明:Java开源生鲜电商平台中供应链模块属于卖家的行为,也就是卖家如何管理他们自己的供应商,包括结算方式,压款方式,结算周期等等,超出了我这个B2B平台 ...
- Java生鲜电商平台-提现模块的设计与架构
Java生鲜电商平台-提现模块的设计与架构 补充说明:生鲜电商平台-提现模块的设计与架构,提现功能指的卖家把在平台挣的钱提现到自己的支付宝或者银行卡的一个过程. 功能相对而言不算复杂,有以下几个功能需 ...
随机推荐
- 【玩转SpringBoot】让错误处理重新由web服务器接管
其实web服务器是会处理错误的 在web.xml还是随处可见的年代时(确实有点老黄历了),下面的这些配置应该都不陌生. 根据错误代码处理错误,如下图01: 根据异常类型处理错误,如下图02: 不过我们 ...
- df,dh 命令
原文内容来自于LZ(楼主)的印象笔记,如出现排版异常或图片丢失等问题,可查看当前链接:https://app.yinxiang.com/shard/s17/nl/19391737/df2f05c4-b ...
- CURL命令学习二
-a, --append 用于上传文件时,如果服务器上该文件不存在则创建,如果存在则追加到源文件. -K, --config <file> 指定从某个文件读取curl参数.如果指定-为文件 ...
- python内置模块-datetime
摘录 廖雪峰网站 datetime是python内置的处理日期和时间的标准库 获取当前日期和时间 >>> from datetime import datetime >> ...
- 使用ASP.NET Core 3.x 构建 RESTful API - 3.4 内容协商
现在,当谈论起 RESTful Web API 的时候,人们总会想到 JSON.但是实际上,JSON 和 RESTful API 没有半毛钱关系,只不过 JSON 恰好是RESTful API 结果的 ...
- wpf键盘
一.键盘类和键盘事件 WPF提供了基础的键盘类(System.Input.Keyboard类),该类提供与键盘相关的事件.方法和属性,这些事件.方法和属性提供有关键盘状态的信息.Keyboard的事件 ...
- 网易极客战记官方攻略-地牢-Kithgard 地牢
关卡连接: https://codecombat.163.com/play/level/dungeons-of-kithgard 夺取宝石,逃出地牢--注意不要触碰其他东西.在这个关卡里,你会学习编写 ...
- 欧洲杯在即英超yabo055红单介绍!沃特福德vs曼彻斯特联 沃特福德雪上加霜
北京时间12月22日22:00,2019-20赛季英超联赛第18轮打响,沃特福德主场迎战曼彻斯特联.本场曼联作客一步步得到支持,球队有望客场赢下比赛. [基本面分析] 1.2019-20赛季英超联赛第 ...
- latex 对中文字体设置的一些解决
latex 对中文字体设置的一些解决 直接使用Xelatex编译带中文的文件时,会出现无法识别的错误,这是因为latex默认的环境不支持中文,这时可以使用CTex 宏集.ctex 宏包或xeCJK 宏 ...
- weblogic的linux静默搭建
前言: Weblogic与Tomcat都是java应用的容器,而这两者有什么大的不同呢?Tomcat是Apache基金会提供的Servlet容器,它支持JSP, Servlet和JDBC等J2EE关键 ...