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生鲜电商平台-订单模块状态机架构设计的更多相关文章

  1. Java生鲜电商平台-订单中心服务架构与异常订单逻辑

    Java生鲜电商平台-订单中心服务架构与异常订单逻辑 订单架构实战中阐述了订单系统的重要性,并从订单系统的信息架构和流程上对订单系统有了总体认知,同时还穿插着一些常见的订单业务规则和逻辑.上文写到订单 ...

  2. Java生鲜电商平台-优惠券系统的架构设计与源码解析

    Java生鲜电商平台-优惠券系统的架构设计与源码解析 电商后台:实例解读促销系统 电商后台系统包括商品管理系统.采购系统.仓储系统.订单系统.促销系统.维权系统.财务系统.会员系统.权限系统等,各系统 ...

  3. Java生鲜电商平台-促销系统的架构设计与源码解析

    Java生鲜电商平台-促销系统的架构设计与源码解析 说明:本文重点讲解现在流行的促销方案以及源码解析,让大家对促销,纳新有一个深入的了解与学习过程. 促销系统是电商系统另外一个比较大,也是比较复杂的系 ...

  4. Java生鲜电商平台-商品基础业务架构设计-商品设计

    Java生鲜电商平台-商品基础业务架构设计-商品设计 在生鲜电商的商品中心,在电子商务公司一般是后台管理商品的地方.在前端而言,是商家为了展示商品信息给用户的地方,它是承担了商品的数据,订单,营销活动 ...

  5. 点菜网---Java开源生鲜电商平台-商品基础业务架构设计-商品分类(源码可下载)

    点菜网---Java开源生鲜电商平台-商品基础业务架构设计-商品分类 (源码可下载) 说明:我们搞过电商的人都可以体会到,搞生鲜电商是最复杂的,为什么复杂呢?我总结了有以下几个业务特性决定的: 1. ...

  6. Java生鲜电商平台-订单配送模块的架构与设计

    Java生鲜电商平台-订单配送模块的架构与设计 生鲜电商系统最终的目的还是用户下单支付购买, 所以订单管理系统是电商系统中最为复杂的系统,其作为中枢决定着整个商城的运转, 本文将对于生鲜类电商平台的订 ...

  7. Java生鲜电商平台-订单架构实战

    Java生鲜电商平台-订单架构实战 生鲜电商中订单中心是一个电商后台系统的枢纽,在这订单这一环节上需要读取多个模块的数据和信息进行加工处理,并流向下一环节:因此订单模块对一电商系统来说,重要性不言而喻 ...

  8. Java生鲜电商平台-供应链模块的设计与架构

    Java生鲜电商平台-供应链模块的设计与架构 说明:Java开源生鲜电商平台中供应链模块属于卖家的行为,也就是卖家如何管理他们自己的供应商,包括结算方式,压款方式,结算周期等等,超出了我这个B2B平台 ...

  9. Java生鲜电商平台-提现模块的设计与架构

    Java生鲜电商平台-提现模块的设计与架构 补充说明:生鲜电商平台-提现模块的设计与架构,提现功能指的卖家把在平台挣的钱提现到自己的支付宝或者银行卡的一个过程. 功能相对而言不算复杂,有以下几个功能需 ...

随机推荐

  1. C# WPF有趣的登录加载窗体

    时间如流水,只能流去不流回! 点赞再看,养成习惯,这是您给我创作的动力! 本文 Dotnet9 https://dotnet9.com 已收录,站长乐于分享dotnet相关技术,比如Winform.W ...

  2. Django3.0 异步通信初体验(小结)

    2019年12月2日,Django终于正式发布了3.0版本.怀着无比的期待,我们来尝试一下吧! (附ASGI官方文档地址:https://asgi.readthedocs.io/en/latest/e ...

  3. 2019最新EI源刊目录

    2D Materials Journal3D Printing and Additive Manufacturing Journal3D Research Journal3DTV-Conference ...

  4. abp模块生命周期设计思路剖析

    abp中将生命周期事件抽象为4个接口: //预初始化 public interface IOnPreApplicationInitialization { void OnPreApplicationI ...

  5. Node.js+Express+MongoDB数据库实现网页注册登入功能

    通过 Node.js + Express + MongoDB 实现网页注册账号 和 登入账号的功能 项目准备: 1: 事先准备好项目的页面 (首页页面 index.html)(登入页面 login.h ...

  6. Linux 实验 [Day 01]

    目录 1. Linux 简介(略过) 2. Linux 基本概念及操作:命令.快捷键与通配符 2.1 基础命令 2.2 终端快捷键 2.3 通配符 2.4 帮助命令 3. 用户及文件权限管理 3.1 ...

  7. mysql 查询存在A表中而不存在B表中的数据

    有两张表,学生信息表infolist: 学生姓名表namelist: 现要查询出,存在infolist中,而不存在namelist中的学生,语句如下: select * from infolist w ...

  8. [转]UiPath Invoke Code

    本文转自:https://dotnetbasic.com/2019/08/uipath-invoke-code.html We will learn step by step tutorial for ...

  9. Vue 中的keep-alive 什么用处?

    keep-alive keep-alive是Vue提供的一个抽象组件,用来对组件进行缓存,从而节省性能,由于是一个抽象组件,所以在v页面渲染完毕后不会被渲染成一个DOM元素 <keep-aliv ...

  10. 基于Redis扩展模块的布隆过滤器使用

    什么是布隆过滤器?它实际上是一个很长的二进制向量和一系列随机映射函数.把一个目标元素通过多个hash函数的计算,将多个随机计算出的结果映射到不同的二进制向量的位中,以此来间接标记一个元素是否存在于一个 ...