引入
最近在做商城项目,负责订单模块的开发,在浏览微信支付官方文档的时候,在统一下单API文档中提到了“状态机”的概念,由此联想到我们的代码实现可以应用这个模式。
概念
状态模式是一种行为模式,在不同的状态下有不同的行为。状态模式的行为是平行的,不可替换的,比如电梯状态可以分为开门状态,关门状态,运行中状态。状态模式把对象的行为包装在不同的状态对象里,对象的行为取决于它的状态,当一个对象内部状态改变时,行为也随之改变。
意义
状态机可以对业务状态进行梳理,使项目结构更加清晰,提高代码可读性,同时可以轻松应对不断增加的业务场景。
实现(订单主流程)
在实现方面,把业务拆解成【状态枚举、事件枚举、状态机配置、状态机监听器】四个核心部分。
在状态枚举中列出订单的所有状态;
在事件枚举中列出触发订单状态切换的行为;
在状态机配置中配置业务流程中的种种行为和行为发生时的原始状态到目标状态的切换,我喜欢把每一个配置的触发称为“部件启动”;
状态机监听器负责监听状态机配置中的状态机部件启动,控制业务流程的走向。
业务流程
项目结构
States 状态枚举
public enum States {
/** 待提交 */
PENDING_SUBMIT,
/** 待支付 */
PENDING_PAYMENT,
/** 已支付 */
ALREADY_PAID,
/** 用户取消 */
USER_CANCEL,
/** 超时取消 */
TIMEOUT_CANCEL,
/** 退款 */
REFUND,
}
Events 事件枚举
public enum Events {
/** 预览 */
PREVIEW,
/** 提交 */
SUBMIT,
/** 用户取消 */
USER_CANCEL,
/** 超时取消 */
TIMEOUT_CANCEL,
/** 预支付 */
PRE_PAY,
/** 支付 */
PAY,
/** 退款 */
REFUND
}
EventConfig 状态机监听器 我这里使用SpringBoot的statemachine实现
<dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>1.2.0.RELEASE</version> </dependency>
@Slf4j
@WithStateMachine
public class EventConfig {
@OnTransition(target = "PENDING_SUBMIT")
public void preview() {
log.info("临时订单创建,待提交");
}
@OnTransition(source = "PENDING_SUBMIT", target = "PENDING_PAYMENT")
public void submit() {
log.info("订单提交,待支付");
}
@OnTransitionEnd(source = "PENDING_PAYMENT", target = "PENDING_PAYMENT")
public void prePay() {
log.info("预支付,等待用户支付: start");
}
@OnTransitionEnd(source = "PENDING_PAYMENT", target = "ALREADY_PAID")
public void pay() {
log.info("用户完成支付,订单完成: end");
}
@OnTransition(source = "PENDING_PAYMENT", target = "USER_CANCEL")
public void userCancel() {
log.info("订单用户取消,订单结束");
}
@OnTransitionStart(source = "PENDING_PAYMENT", target = "TIMEOUT_CANCEL")
public void timeoutCancel() {
log.info("订单超时取消,订单结束");
}
@OnTransition(source = "ALREADY_PAID", target = "REFUND")
public void refund() {
log.info("退款,订单结束");
}
}
StateMachineConfig 状态机配置 这里需要根据业务流程,配置事件触发后发生的状态转换,比如 .withExternal() .source(States.PENDING_SUBMIT) .target(States.PENDING_PAYMENT) .event(Events.SUBMIT) .and() 这样的一个配置,就会被状态机监听器EventConfig中的 @OnTransition(source = "PENDING_SUBMIT", target = "PENDING_PAYMENT") public void submit() { log.info("订单提交,待支付"); } 监听并处理
@Slf4j
@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.PENDING_SUBMIT)
.states(EnumSet.allOf(States.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.target(States.PENDING_SUBMIT)
.event(Events.PREVIEW)
.and()
.withExternal()
.source(States.PENDING_SUBMIT)
.target(States.PENDING_PAYMENT)
.event(Events.SUBMIT)
.and()
.withExternal()
.source(States.PENDING_PAYMENT)
.target(States.PENDING_PAYMENT)
.event(Events.PRE_PAY)
.and()
.withExternal()
.source(States.PENDING_PAYMENT)
.target(States.ALREADY_PAID)
.event(Events.PAY)
.and()
.withExternal()
.source(States.PENDING_PAYMENT)
.target(States.USER_CANCEL)
.event(Events.USER_CANCEL)
.and()
.withExternal()
.source(States.PENDING_PAYMENT)
.target(States.TIMEOUT_CANCEL)
.event(Events.TIMEOUT_CANCEL)
.and()
.withExternal()
.source(States.ALREADY_PAID)
.target(States.REFUND)
.event(Events.REFUND);
}
}
测试
@Test
public void test() {
stateMachine.start();
boolean preview = stateMachine.sendEvent(Events.PREVIEW);
System.out.println("getUuid=" + stateMachine.getUuid());
System.out.println("getId=" + stateMachine.getId());
System.out.println("isComplete=" + stateMachine.isComplete());
System.out.println("getExtendedState=" + stateMachine.getExtendedState());
System.out.println("getInitialState=" + stateMachine.getInitialState());
System.out.println("hasStateMachineError=" + stateMachine.hasStateMachineError());
System.out.println("=========================================================");
boolean submit = stateMachine.sendEvent(Events.SUBMIT);
System.out.println("getUuid=" + stateMachine.getUuid());
System.out.println("getId=" + stateMachine.getId());
System.out.println("isComplete=" + stateMachine.isComplete());
System.out.println("getExtendedState=" + stateMachine.getExtendedState());
System.out.println("getInitialState=" + stateMachine.getInitialState());
System.out.println("hasStateMachineError=" + stateMachine.hasStateMachineError());
System.out.println("=========================================================");
// boolean userCancel = stateMachine.sendEvent(Events.USER_CANCEL);
// boolean timeoutCancel = stateMachine.sendEvent(Events.TIMEOUT_CANCEL);
boolean prepay = stateMachine.sendEvent(Events.PRE_PAY);
System.out.println("getUuid=" + stateMachine.getUuid());
System.out.println("getId=" + stateMachine.getId());
System.out.println("isComplete=" + stateMachine.isComplete());
System.out.println("getExtendedState=" + stateMachine.getExtendedState());
System.out.println("getInitialState=" + stateMachine.getInitialState());
System.out.println("hasStateMachineError=" + stateMachine.hasStateMachineError());
System.out.println("=========================================================");
boolean pay = stateMachine.sendEvent(Events.PAY);
System.out.println("getUuid=" + stateMachine.getUuid());
System.out.println("getId=" + stateMachine.getId());
System.out.println("isComplete=" + stateMachine.isComplete());
System.out.println("getExtendedState=" + stateMachine.getExtendedState());
System.out.println("getInitialState=" + stateMachine.getInitialState());
System.out.println("hasStateMachineError=" + stateMachine.hasStateMachineError());
System.out.println("=========================================================");
boolean refund = stateMachine.sendEvent(Events.REFUND);
System.out.println("getUuid=" + stateMachine.getUuid());
System.out.println("getId=" + stateMachine.getId());
System.out.println("isComplete=" + stateMachine.isComplete());
System.out.println("getExtendedState=" + stateMachine.getExtendedState());
System.out.println("getInitialState=" + stateMachine.getInitialState());
System.out.println("hasStateMachineError=" + stateMachine.hasStateMachineError());
System.out.println("=========================================================");
}
2020-03-03 11:56:11.812 INFO 17160 --- [ main] c.n.o.c.statemachine.config.EventConfig : 临时订单创建,待提交
2020-03-03 11:56:11.813 INFO 17160 --- [ main] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@30f74e79
2020-03-03 11:56:11.814 INFO 17160 --- [ main] o.s.s.support.LifecycleObjectSupport : started PENDING_PAYMENT ALREADY_PAID PENDING_SUBMIT REFUND TIMEOUT_CANCEL USER_CANCEL / PENDING_SUBMIT / uuid=a5899ad4-02b2-4417-84d8-9f1034f9e466 / id=null
getUuid=a5899ad4-02b2-4417-84d8-9f1034f9e466
getId=null
isComplete=false
getExtendedState=DefaultExtendedState [variables={}]
getInitialState=ObjectState [getIds()=[PENDING_SUBMIT], getClass()=class org.springframework.statemachine.state.ObjectState, hashCode()=256522893, toString()=AbstractState [id=PENDING_SUBMIT, pseudoState=org.springframework.statemachine.state.DefaultPseudoState@8d8f754, deferred=[], entryActions=[], exitActions=[], stateActions=[], regions=[], submachine=null]]
hasStateMachineError=false
=========================================================
2020-03-03 11:56:11.818 INFO 17160 --- [ main] c.n.o.c.statemachine.config.EventConfig : 订单提交,待支付
getUuid=a5899ad4-02b2-4417-84d8-9f1034f9e466
getId=null
isComplete=false
getExtendedState=DefaultExtendedState [variables={}]
getInitialState=ObjectState [getIds()=[PENDING_SUBMIT], getClass()=class org.springframework.statemachine.state.ObjectState, hashCode()=256522893, toString()=AbstractState [id=PENDING_SUBMIT, pseudoState=org.springframework.statemachine.state.DefaultPseudoState@8d8f754, deferred=[], entryActions=[], exitActions=[], stateActions=[], regions=[], submachine=null]]
hasStateMachineError=false
=========================================================
2020-03-03 11:56:11.819 INFO 17160 --- [ main] c.n.o.c.statemachine.config.EventConfig : 预支付,等待用户支付: start
getUuid=a5899ad4-02b2-4417-84d8-9f1034f9e466
getId=null
isComplete=false
getExtendedState=DefaultExtendedState [variables={}]
getInitialState=ObjectState [getIds()=[PENDING_SUBMIT], getClass()=class org.springframework.statemachine.state.ObjectState, hashCode()=256522893, toString()=AbstractState [id=PENDING_SUBMIT, pseudoState=org.springframework.statemachine.state.DefaultPseudoState@8d8f754, deferred=[], entryActions=[], exitActions=[], stateActions=[], regions=[], submachine=null]]
hasStateMachineError=false
=========================================================
2020-03-03 11:56:11.820 INFO 17160 --- [ main] c.n.o.c.statemachine.config.EventConfig : 用户完成支付,订单完成: end
getUuid=a5899ad4-02b2-4417-84d8-9f1034f9e466
getId=null
isComplete=false
getExtendedState=DefaultExtendedState [variables={}]
getInitialState=ObjectState [getIds()=[PENDING_SUBMIT], getClass()=class org.springframework.statemachine.state.ObjectState, hashCode()=256522893, toString()=AbstractState [id=PENDING_SUBMIT, pseudoState=org.springframework.statemachine.state.DefaultPseudoState@8d8f754, deferred=[], entryActions=[], exitActions=[], stateActions=[], regions=[], submachine=null]]
hasStateMachineError=false
=========================================================
2020-03-03 11:56:11.821 INFO 17160 --- [ main] c.n.o.c.statemachine.config.EventConfig : 退款,订单结束
getUuid=a5899ad4-02b2-4417-84d8-9f1034f9e466
getId=null
isComplete=false
getExtendedState=DefaultExtendedState [variables={}]
getInitialState=ObjectState [getIds()=[PENDING_SUBMIT], getClass()=class org.springframework.statemachine.state.ObjectState, hashCode()=256522893, toString()=AbstractState [id=PENDING_SUBMIT, pseudoState=org.springframework.statemachine.state.DefaultPseudoState@8d8f754, deferred=[], entryActions=[], exitActions=[], stateActions=[], regions=[], submachine=null]]
hasStateMachineError=false
=========================================================
总结
至此,我们完成了状态机的简单实现,结合我上一篇文章的“设计模式——管道模式”,一个健壮成熟的订单业务就完成了。
业务总是在不变的基础上变化,开发者也需要在思想和实现方式上不断演进。
来源:oschina
链接:https://my.oschina.net/zbnb/blog/3186489