网站首页 > java教程 正文
Java微服务实现订单状态机(业务,规划,设计,实现)
1 前言
电商平台所有模块中,订单系统作为比较核心的模块,它决定了整个流程能不能顺畅的执行,起着承上启下的作用(下单、支付、履约、售后、清结算、营销活动)。
订单系统的设计主要需要考虑订单字段、业务流程、状态机三大个方面,这些内容决定了订单系统稳定性与扩展性。
2 订单流程
订单流程指整个订单从产生到完成的整个流转过程,它包括正向流程和逆向的流程。
3 订单状态机
状态机表示了一笔订单的生命周期,按照一定的方向通过触发不同的事件产生数据流转的过程。
状态机v2.0
随着业务快速发展,我们的状态机也在逐步完善,在完善的过程中我们遇到两个问题
如何提供更多的订单状态来描述现实世界?
在提供更多的订单状态后,同时节省状态机维护成本?
为了解决这两个问题,我们采用大状态+小状态的设计。主、子订单上只维护大状态流转,抽象出物流、售后两个领域分别去维护各自的小状态。这样保证了主子订单上的状态不会特别多,同时又可以通过不同的领域中的小状态去满足业务需求。
主、子订单变更规则
正向状态是当全部子订单向前流转时,主订单才会向前走;
逆向状态是当子订单全部变成15(售后中) 主订单才变售后中. 子订单全部变成16(售后完成)主订单才变售后完成 子订单全部变成11(取消)主订单才变取消;
状态机在APP端的体现
团长C端提货界面
用户C端提货界面
思考与改进
1 目前物流领域的状态机只针对团长端开放,并没有针对C端用户开放。WMS和TMS作业都是按照团长+SKU纬度,团长收到货后承担这二次分拣的职责,由团长决定用户的货是否真的送达,所以WMS和TMS回传给订单的消息对用户是不准确的。
问题举例
小明在A团长下单5个苹果
小红在A团长下单5个苹果
23点仓库开始作业,发现一共只有5个苹果。
WMS TMS 下发出库、配送指令 A团长+5个苹果
OMS收到指令按照团长纬度更新订单状态,此时小明、小红的订单状态都被更新为司机送达
团长收到5个苹果后,选择把苹果全部给和他关系较好的小明。
此时小红没收到货、同时看到自己订单状态为司机送达就会很懵逼,这也是目前为什么履约状态只针对团长开放的原因。谁到货谁没到货团长线下决定的。
下面基于Java微服务来实现代码:
1. 有限状态机介绍
有限状态机,也称为 FSM (Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态。有限状态机成立的必要条件有:
- 对象有一组互斥的状态(或对象的生命周期),且这组状态可以涵盖对象的创建到消亡的整个过程。
- 当向对象传入一个信号(或事件)时,对象的状态能从当前状态转换成另一种状态,或者保持状态不变。
- 状态是有限的。
如图例所示,红绿灯在同一时间只能亮一个颜色,控制程序可以定义 3 种不同的事件,每个事件定义好红绿灯的起始颜色和目标颜色,我们不需要直接去操作红绿灯开关,只需要按照一定的顺序发送事件过去,我们就可以精确控制红绿灯的工作,红绿灯的工作控制其实就是一个标准的 FSM。
由上所述,FSM 一般需要以下 4 个部分组成:
- 对象状态枚举。
- 对象事件枚举,并指定事件的起始状态和目标状态。
- 事件逻辑体,用于处理状态变更引起的业务逻辑。
- 事件注册工厂类,FSM 的唯一入口。
2. 从需求开始分析 FSM
当我们拿到一个类似需求时应该怎么入手呢?下面我们以一个简单的商城订单的 FSM 实现为例,从需求分析到代码实现为大家讲解:
首先我们应该从需求中提炼出一个订单具体需要经过哪些状态(这里我只列举了一个简单订单的正向状态)。
订单状态:待支付、待发货、待收货、已取消、已完成
列举出所有状态之后,在作图工具中把状态全部画出来,每个状态进行分析是否能转换为其他状态,如分析待支付状态,用户可以从待支付状态进行付款事件,待支付状态将会转换为待发货,用户也可以从待支付状态取消支付,待支付将会转换为订单取消状态。按照这个思路使用单向箭头将所有事件列举出来,并给每个事件起名字。
订单事件:下单事件、支付事件、发货事件、支付取消事件、收货事件
最后形成如下图所示的状态流转图
3. FSM 的 java 实现
按照上面所说,我们将 FSM 的 4 个部分声明出来:
3.1. 对象状态枚举类
使用枚举的方式穷举出订单所有可能的状态。
public enum OrderStatusEnum {
Unpaid("Unpaid", "待支付"),
UnShipping("UnShipping", "待发货"),
UnReceiving("UnReceiving", "待收货"),
Canceled("Canceled", "已取消"),
Finished("Finished", "已完成");
private final String code;
private final String desc;
OrderStatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
3.2. 对象事件枚举类
为了省事,我在这里把事件和状态做了关联,也可以单独拿出来做一个配置封装。
public enum OrderEventEnum {
CreateOrder("CreateOrder", "下单事件", null, OrderStatusEnum.Unpaid),
Payment("Payment", "支付事件", OrderStatusEnum.Unpaid, OrderStatusEnum.UnShipping),
Shipping("Shipping", "发货事件", OrderStatusEnum.UnShipping, OrderStatusEnum.UnReceiving),
Receiving("Receiving", "收货事件", OrderStatusEnum.UnReceiving, OrderStatusEnum.Finished),
CancelPayment("CancelPayment", "支付取消事件", OrderStatusEnum.Unpaid, OrderStatusEnum.Canceled);
private final String code;
private final String desc;
private final OrderStatusEnum sourceOrderStatus;
private final OrderStatusEnum targetOrderStatus;
OrderEventEnum(String code, String desc, OrderStatusEnum sourceOrderStatus, OrderStatusEnum targetOrderStatus) {
this.code = code;
this.desc = desc;
this.sourceOrderStatus = sourceOrderStatus;
this.targetOrderStatus = targetOrderStatus;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
public OrderStatusEnum getSourceOrderStatus() {
return sourceOrderStatus;
}
public OrderStatusEnum getTargetOrderStatus() {
return targetOrderStatus;
}
}
3.3. 事件逻辑体
首先声明一个逻辑体基类,基类里面定义了事件过程数据缓存对象、事件条件校验方法、事件后处理方法、事件业务逻辑处理虚方法(需要由业务自己实现)。
@Slf4j
public abstract class BaseOrderFsmProcessor {
private static final Map<Long, Object> FSM_DATA_MAP = new ConcurrentHashMap<>();
/**
* 执行业务逻辑
*
* @param orderId 订单ID
* @param event 事件类型
* @return 成功or失败
*/
public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
log.info("OrderFSM_开始FSM事件:orderId={}", orderId);
if (!checkRule(orderId, event)) {
log.warn("OrderFSM_不满足条件,拒绝执行FSM事件:orderId={}", orderId);
return true;
}
boolean b = process(orderId, event);
log.info("OrderFSM_结束FSM事件:orderId={}", orderId);
postHandler(orderId, event);
return b;
}
/**
* 业务逻辑实现类
*
* @param orderId 订单ID
* @param event 事件类型
* @return 成功or失败
* @throws Exception ex
*/
public abstract boolean process(Long orderId, OrderEventEnum event) throws Exception;
/**
* 校验是否满足条件执行当亲process
*
* @param orderId 订单ID
* @param event 事件类型
* @return 成功or失败
*/
public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
return true;
}
/**
* 后置处理逻辑
*
* @param orderId 订单ID
* @param event 事件类型
*/
public void postHandler(Long orderId, OrderEventEnum event) throws Exception {
}
/**
* 根据流程ID获取事件过程数据
*
* @param orderId 订单ID
* @return 事件过程数据
*/
public static Object getFsmData(Long orderId) {
return FSM_DATA_MAP.remove(orderId);
}
/**
* 根据流程ID设置事件过程数据
*
* @param orderId 订单ID
* @param obj 事件过程数据
*/
public static void setFsmData(Long orderId, Object obj) {
FSM_DATA_MAP.put(orderId, obj);
}
}
再跟进基类派生出不同事件对应的处理方法,一个事件要声明一个对应的方法:
@Slf4j
public class CreateOrderProcessor extends BaseOrderFsmProcessor {
@Override
public boolean process(Long orderId, OrderEventEnum event) throws Exception {
log.info("业务逻辑执行中:className={},event={}", getClass().getSimpleName(), event.name());
//TODO 模拟业务逻辑
TimeUnit.MILLISECONDS.sleep(1000);
setFsmData(orderId, Thread.currentThread().getName());
log.info("业务逻辑执行完成");
return true;
}
@Override
public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
log.info("执行条件检查通过");
return true;
}
@Override
public void postHandler(Long orderId, OrderEventEnum event) throws Exception {
log.info("执行后置处理逻辑");
// TODO 将orderId的状态改为 event.getTargetOrderStatus()
}
}
3.4. 事件注册工厂类
工厂类里封装了单例 FSM 执行的唯一入口和查询设置流程中间数据的入口。
@Slf4j
public class OrderFsmManager {
private final Map<OrderEventEnum, BaseOrderFsmProcessor> orderProcessorMap = new HashMap<>();
private volatile static OrderFsmManager orderFsmManager;
private OrderFsmManager() {
orderProcessorMap.put(OrderEventEnum.CreateOrder, new CreateOrderProcessor());
orderProcessorMap.put(OrderEventEnum.Payment, new PaymentFsmProcessor());
}
/**
* 获取fsm实例
*/
public static OrderFsmManager getInstance() {
if (orderFsmManager == null) {
synchronized (OrderFsmManager.class) {
if (orderFsmManager == null) {
orderFsmManager = new OrderFsmManager();
}
}
}
return orderFsmManager;
}
/**
* 开始执行fsm事件
*
* @param orderId 订单ID
* @param event 事件类型
* @return 成功or失败
*/
public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
if (!orderProcessorMap.containsKey(event)) {
throw new Exception(String.format("MediaProcessFSM没有匹配到事件:orderId=%s,currentOrderEvent=%s"
, orderId, event));
}
return orderProcessorMap.get(event).fireProcess(orderId, event);
}
/**
* 根据流程ID获取事件过程数据
*
* @param orderId 订单ID
* @return 事件过程数据
*/
public Object getFsmData(Long orderId) {
return BaseOrderFsmProcessor.getFsmData(orderId);
}
/**
* 根据流程ID设置事件过程数据
*
* @param orderId 订单ID
* @param obj 事件过程数据
*/
public void setFsmData(Long orderId, Object obj) {
BaseOrderFsmProcessor.setFsmData(orderId, obj);
}
}
3.5. 简单做个测试
public static void main(String[] args) throws Exception {
OrderFsmManager orderFsmManager = OrderFsmManager.getInstance();
// boolean b1 = orderFsmManager.fireProcess(1L, OrderEventEnum.CreateOrder);
// boolean b2 = orderFsmManager.fireProcess(2L, OrderEventEnum.Payment);
// System.out.println(String.format("orderId=%s,data=%s",1, orderFsmManager.getFsmData(1L)));
// System.out.println(String.format("orderId=%s,data=%s",2, orderFsmManager.getFsmData(2L)));
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
String threadName = "thread-" + finalI;
Thread.currentThread().setName(threadName);
try {
if (finalI%2==0){
boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.CreateOrder);
}else {
boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.Payment);
}
System.out.println(String.format("threadName=%s,data=%s",threadName, orderFsmManager.getFsmData((long) finalI)));
} catch (Exception exception) {
exception.printStackTrace();
}
}).start();
}
}
11:17:39.213 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=2
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行条件检查通过
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.213 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=1
11:17:39.213 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=0
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行条件检查通过
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 执行条件检查通过
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 业务逻辑执行中:className=PaymentFsmProcessor,event=Payment
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 业务逻辑执行完成
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=1
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 执行后置处理逻辑
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行完成
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=2
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行后置处理逻辑
threadName=thread-1,data=thread-1
threadName=thread-2,data=thread-2
11:17:40.246 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行完成
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=0
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行后置处理逻辑
threadName=thread-0,data=thread-0
4. 结语
到这里 FSM 的封装就完成了,其中有一些设计可以根据自身技术选择进行调整,如工厂类的单例实现可以使用 spring 的 bean 注入方式,事件和状态的绑定关系可以外挂出来作为配置项等。
猜你喜欢
- 2024-11-26 Java线程调度&状态
- 2024-11-26 JAVA线程状态及实现
- 2024-11-26 学Java很久了,但越学越“糊涂”,Java学到什么程度才叫精通呢?
- 2024-11-26 设计模式之状态模式
- 2024-11-26 java开发工具IntelliJ IDEA Git使用教程:检查项目状态
- 2024-11-26 java设计模式状态模式
- 2024-11-26 Java线程的6种状态及切换,透彻讲解
- 2024-11-26 计算机等级二考试辅导:Java IO机试题及解答(第2部分)
- 2024-11-26 清华教授,手把手带你深度解读Java枚举,理论与实战齐飞
- 2024-11-26 Java试题分享211111
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)