现在的目标是:根据上文中的状态机组件实现一个完整的订单状态机。没有看上一节的同学可以点击链接先去看上一节Java状态机实战一:接口定义
一、编写状态机代码
1.1. 实现StatusDefine 状态接口
@Getter
@AllArgsConstructor
public enum OrderStatusEnum implements StatusDefine {
NO_PAY(0, "待支付", "NO_PAY"),
DISPATCHING(100, "派单中", "DISPATCHING"),
NO_SERVE(200, "待服务", "NO_SERVE"),
SERVING(300, "服务中", "SERVING"),
FINISHED(500, "已完成", "FINISHED"),
CANCELED(600, "已取消", "CANCELED"),
CLOSED(700, "已关闭", "CLOSED");
private final Integer status;
private final String desc;
private final String code;
/**
* 根据状态值获得对应枚举
*
* @param status 状态
* @return 状态对应枚举
*/
public static OrderStatusEnum codeOf(Integer status) {
for (OrderStatusEnum orderStatusEnum : values()) {
if (orderStatusEnum.status.equals(status)) {
return orderStatusEnum;
}
}
return null;
}
}
1.2. 实现状态枚举类
所有状态之间存在的变更都需要定义状态变更事件,它实现了StatusChangeEvent 状态变更事件接口,事件对应状态机四要素的事件。
@Getter
@AllArgsConstructor
public enum OrderStatusChangeEventEnum implements StatusChangeEvent {
PAYED(OrderStatusEnum.NO_PAY, OrderStatusEnum.DISPATCHING, "支付成功", "payed"),
DISPATCH(OrderStatusEnum.DISPATCHING, OrderStatusEnum.NO_SERVE, "接单/抢单成功", "dispatch"),
START_SERVE(OrderStatusEnum.NO_SERVE, OrderStatusEnum.SERVING, "开始服务", "start_serve"),
COMPLETE_SERVE(OrderStatusEnum.SERVING, OrderStatusEnum.FINISHED, "完成服务", "complete_serve"),
EVALUATE(OrderStatusEnum.NO_EVALUATION, OrderStatusEnum.FINISHED, "评价完成", "evaluate"),
CANCEL(OrderStatusEnum.NO_PAY, OrderStatusEnum.CANCELED, "取消订单", "cancel"),
SERVE_PROVIDER_CANCEL(OrderStatusEnum.NO_SERVE, OrderStatusEnum.DISPATCHING, "服务人员/机构取消订单", "serve_provider_cancel"),
CLOSE_DISPATCHING_ORDER(OrderStatusEnum.DISPATCHING, OrderStatusEnum.CLOSED, "派单中订单关闭", "close_dispatching_order"),
CLOSE_NO_SERVE_ORDER(OrderStatusEnum.NO_SERVE, OrderStatusEnum.CLOSED, "待服务订单关闭", "close_no_serve_order"),
CLOSE_SERVING_ORDER(OrderStatusEnum.SERVING, OrderStatusEnum.CLOSED, "服务中订单关闭", "close_serving_order"),
CLOSE_NO_EVALUATION_ORDER(OrderStatusEnum.NO_EVALUATION, OrderStatusEnum.CLOSED, "待评价订单关闭", "close_no_evaluation_order"),
CLOSE_FINISHED_ORDER(OrderStatusEnum.FINISHED, OrderStatusEnum.CLOSED, "已完成订单关闭", "close_finished_order");
/**
* 源状态
*/
private final OrderStatusEnum sourceStatus;
/**
* 目标状态
*/
private final OrderStatusEnum targetStatus;
/**
* 描述
*/
private final String desc;
/**
* 代码
*/
private final String code;
}
1.3. 定义订单快照类
快照是订单变化瞬间的状态及相关信息。
比如:001号订单创建成功此时记录它的快照信息(订单号、下单人、订单详细信息、订单状态等),当001号订单支付成功由待支付状态变化为派单中状态此时也会记录它的快照信息(订单号、下单人、支付状态、支付相关信息,订单状态等相关信息),由此可以看出订单快照可以追溯订单的历史变化信息,只要状态发生变化便会记录快照。
/**
* 订单快照
*
* @author itcast
* @create 2023/8/19 10:30
**/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderSnapshotDTO extends StateMachineSnapshot {
/**
* 订单id
*/
private Long id;
/**
* 订单所属人
*/
private Long userId;
/**
* 服务类型id
*/
private Long serveTypeId;
/**
* 服务类型名称
*/
private String serveTypeName;
/**
* 服务项id
*/
private Long serveItemId;
/**
* 服务项名称
*/
private String serveItemName;
/**
* 服务项图片
*/
private String serveItemImg;
/**
* 服务单位
*/
private Integer unit;
/**
* 服务id
*/
private Long serveId;
/**
* 订单状态,0:待支付,100:派单中,200:待服务,300:服务中,500:订单完成,600:订单取消,700已关闭
*/
private Integer ordersStatus;
/**
* 支付状态,2:待支付,4:支付成功
*/
private Integer payStatus;
/**
* 退款,0:发起退款,1:退款中,2:退款成功 3:退款失败
*/
private Integer refundStatus;
/**
* 单价
*/
private BigDecimal price;
/**
* 购买数量
*/
private Integer purNum;
/**
* 订单总金额
*/
private BigDecimal totalAmount;
/**
* 实际支付金额
*/
private BigDecimal realPayAmount;
/**
* 优惠金额
*/
private BigDecimal discountAmount;
/**
* 城市编码
*/
private String cityCode;
/**
* 服务详细地址
*/
private String serveAddress;
/**
* 联系人手机号
*/
private String contactsPhone;
/**
* 联系人姓名
*/
private String contactsName;
/**
* 服务开始时间
*/
private LocalDateTime serveStartTime;
/**
* 经度
*/
private String lon;
/**
* 纬度
*/
private String lat;
/**
* 支付时间
*/
private LocalDateTime payTime;
/**
* 评价时间
*/
private LocalDateTime evaluationTime;
/**
* 订单创建时间
*/
private LocalDateTime createTime;
/**
* 订单更新时间
*/
private LocalDateTime updateTime;
/**
* 支付服务交易单号
*/
private Long tradingOrderNo;
/**
* 支付服务退款单号
*/
private Long refundNo;
/**
* 支付渠道【支付宝、微信、现金、免单挂账】
*/
private String tradingChannel;
/**
* 三方流水,微信支付订单号或支付宝订单号
*/
private String thirdOrderId;
/**
* 退款三方流水,微信支付订单号或支付宝订单号
*/
private String thirdRefundOrderId;
/**
* 取消人id
*/
private Long cancellerId;
/**
* 取消人名称
*/
private String cancelerName;
/**
* 取消人类型
*/
private Integer cancellerType;
/**
* 取消时间
*/
private LocalDateTime cancelTime;
/**
* 取消原因
*/
private String cancelReason;
/**
* 实际服务完成时间
*/
private LocalDateTime realServeEndTime;
/**
* 评价状态
*/
private Integer evaluationStatus;
@Override
public String getSnapshotId() {
return String.valueOf(id);
}
@Override
public Integer getSnapshotStatus() {
return ordersStatus;
}
@Override
public void setSnapshotId(String snapshotId) {
this.id = Long.parseLong(snapshotId);
}
@Override
public void setSnapshotStatus(Integer snapshotStatus) {
this.ordersStatus = snapshotStatus;
}
}
1.4. 定义事件变更动作类
当执行状态变更事件会伴随着执行具体的动作,此部分对应状态机四要素中的动作。 定义订单支付成功动作类,实现StatusChangeHandler接口,泛型中指定快照类型。
/**
* 订单支付成功处理器
*
**/
@Slf4j
@Component("order_payed")
public class OrderPayedHandler implements StatusChangeHandler<OrderSnapshotDTO> {
@Resource
private IOrdersCommonService ordersService;
/**
* 订单支付处理逻辑
*
* @param bizId 业务id
* @param bizSnapshot 快照
*/
@Override
public void handler(String bizId, StatusChangeEvent statusChangeEventEnum, OrderSnapshotDTO bizSnapshot) {
log.info("支付成功事件处理逻辑开始,订单号:{}", bizId);
}
}
1.5. 定义订单状态机类
AbstractStateMachine状态机抽象类是状态机的核心类,是具体的状态机要继承的抽象类,比如我们实现订单状态机就需要继承AbstractStateMachine抽象类。
/**
* 订单状态机
*/
@Component
public class OrderStateMachine extends AbstractStateMachine<OrderSnapshotDTO> {
public OrderStateMachine(StateMachinePersister stateMachinePersister, BizSnapshotService bizSnapshotService, RedisTemplate redisTemplate) {
super(stateMachinePersister, bizSnapshotService, redisTemplate);
}
/**
* 设置状态机名称
*
* @return 状态机名称
*/
@Override
protected String getName() {
return "order";
}
@Override
protected void postProcessor(OrderSnapshotDTO orderSnapshotDTO) {
}
/**
* 设置状态机初始状态
*
* @return 状态机初始状态
*/
@Override
protected OrderStatusEnum getInitState() {
return OrderStatusEnum.NO_PAY;
}
}
二、测试状态机
2.1 下单
@Transactional(rollbackFor = Exception.class)
public void add(Orders orders) {
boolean save = this.save(orders);
if (!save) {
throw new DbRuntimeException("下单失败");
}
//构建快照对象
OrderSnapshotDTO orderSnapshotDTO = BeanUtil.toBean(baseMapper.selectById(orders.getId()), OrderSnapshotDTO.class);
//状态机启动
orderStateMachine.start(null,String.valueOf(orders.getId()),orderSnapshotDTO);
}
2.2 支付成功
需要先完善状态变更动作类
/**
* 订单支付成功处理器
*/
@Slf4j
@Component("order_payed")
public class OrderPayedHandler implements StatusChangeHandler<OrderSnapshotDTO> {
@Resource
private IOrdersCommonService ordersService;
/**
* 订单支付处理逻辑
*
* @param bizId 业务id
* @param statusChangeEventEnum 状态变更事件
* @param bizSnapshot 快照
*/
@Override
public void handler(String bizId, StatusChangeEvent statusChangeEventEnum, OrderSnapshotDTO bizSnapshot) {
log.info("支付成功事件处理逻辑开始,订单号:{}", bizId);
// 修改订单状态和支付状态
OrderUpdateStatusDTO orderUpdateStatusDTO = OrderUpdateStatusDTO.builder().id(Long.valueOf(bizId))
.originStatus(OrderStatusEnum.NO_PAY.getStatus())
.targetStatus(OrderStatusEnum.DISPATCHING.getStatus())
.payStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus())
.payTime(bizSnapshot.getPayTime())
.tradingOrderNo(bizSnapshot.getTradingOrderNo())
.transactionId(bizSnapshot.getThirdOrderId())
.tradingChannel(bizSnapshot.getTradingChannel())
.build();
int result = ordersService.updateStatus(orderUpdateStatusDTO);
if (result <= 0) {
throw new DbRuntimeException("支付事件处理失败");
}
}
}
@Transactional(rollbackFor = Exception.class)
public void paySuccess(TradeStatusMsg tradeStatusMsg) {
//查询订单
Orders orders = baseMapper.selectById(tradeStatusMsg.getProductOrderNo());
//2:待支付,4:支付成功
if (ObjectUtil.notEqual(OrderPayStatusEnum.NO_PAY.getStatus(), orders.getPayStatus())) {
log.info("当前订单:{},不是待支付状态", orders.getId());
return;
}
//第三方支付单号校验
if (ObjectUtil.isEmpty(tradeStatusMsg.getTransactionId())) {
throw new CommonException("支付成功通知缺少第三方支付单号");
}
// 修改订单状态和支付状态
OrderSnapshotDTO orderSnapshotDTO = OrderSnapshotDTO.builder()
.payTime(LocalDateTime.now())
.tradingOrderNo(tradeStatusMsg.getTradingOrderNo())
.tradingChannel(tradeStatusMsg.getTradingChannel())
.thirdOrderId(tradeStatusMsg.getTransactionId())
.build();
orderStateMachine.changeStatus( String.valueOf(orders.getId()), OrderStatusChangeEventEnum.PAYED, orderSnapshotDTO);
}