状态机配置
状态机配置有两种方式,
- 创建config类,实现StateMachineConfigurer(或者根据S\E的不同,直接继承其子类StateMachineConfigurerAdapter、EnumStateMachineConfigurerAdapter),然后分别重写其不同的configure方法,用于指定对应配置。
- 依然实现StateMachineConfigurer(或继承其子类),不过通过StateMachineBuilder.<States, Events>builder()来指定对应配置。
我更倾向于用第二种方式,可以自定义不同的builder,以服务于不同的业务场景,通过显式指定那个builder的方式来选择对应的业务状态机配置。
自定义接口
自定义了一个Builder接口,用于规范定义不同业务的状态机配置。
import org.springframework.beans.factory.BeanFactory;
import org.springframework.statemachine.StateMachine;
/**
* 状态机构造器定义
*/
public interface BizOrderStateMachineBuilder {
String getName();
StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> build(BeanFactory beanFactory) throws Exception;
// 业务一对应的builder name
String WYLOAN_BUILDER_NAME = "wyLoanStateMachineBuilder";
// 业务二对应的builder name
String VPAY_BUILDER_NAME = "vpayStateMachineBuilder";
}
对应的设置业务状态机配置Builder,注意这里不需要@EnableStateMachine注解,不是springboot环境:
@Component
@Slf4j
public class WYLoanBizOrderStateMachineBuilder extends EnumStateMachineConfigurerAdapter implements BizOrderStateMachineBuilder {
......
@Override
public String getName() {
return WYLOAN_BUILDER_NAME;
}
@Override
public StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> build(BeanFactory beanFactory) throws Exception {
StateMachineBuilder.Builder<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> builder = StateMachineBuilder.builder();
builder.configureConfiguration()
.withConfiguration()
.autoStartup(true)
.beanFactory(beanFactory)
.machineId("WYLoanStateMachineId");
// 初始化状态机,并指定状态集合
// 详细说明下,由于XXX将用户实名+申请额度的流程都统一承载在订单维度,所以这里有个分支流程处理相关数据
// 包括实名、业务审核、资料补全、证件上传、贷前额度审核等节点
// 这块流程与订单流程全都耦合在一起,拆出来工作量及代价都比较大,所以在订单系统中统一维护起来
// 故XXX的业务状态最多,也最复杂
builder.configureStates()
.withStates()
.initial(CREATE)
.choice(WYD_INITIAL_JUMP)
.choice(CHECK_COMPLEMENT)
.choice(CHECK_UPLOAD)
.choice(IN_DEAL_RISK_AUDITING)
.choice(REPAYING) // 还款ing 对应的不同结果
.end(CANCEL)
.end(CLOSE)
.end(SUCCESS)
.states(EnumSet.allOf(BizOrderStatusEnum.class)) // 所有状态,避免有遗漏
;
// 指定状态机有哪些节点,即迁移动作
builder.configureTransitions()
// XXX的创建,并不是CREATE状态,而是为待实名WAIT_REAL_NAME_AUTH或者待借款WAIT_BORROW状态,
// 所以需要虚拟节点,自己跳转
.withExternal()
.source(CREATE)
.target(WYD_INITIAL_JUMP)
.event(BizOrderStatusChangeEventEnum.EVT_CREATE)
.action(orderCreateAction, errorHandlerAction)
.and()
.withChoice()
.source(WYD_INITIAL_JUMP)
.first(WAIT_REAL_NAME_AUTH, needNameAuthGurad(), needNameAuthAction)
.then(WAIT_BORROW, toWaitBorrowGuard(), waitBorrowAction)
.last(CREATE)
/** 待实名WAIT_REAL_NAME_AUTH 可以到达的节点 **/
// cancel
.and().withExternal()
.source(WAIT_REAL_NAME_AUTH)
.target(CANCEL)
.event(EVT_CANCEL)
.action(cancelAction, errorHandlerAction)
// close
.and().withExternal()
.source(WAIT_REAL_NAME_AUTH)
.target(CLOSE)
.event(EVT_SYS_CLOSE)
.action(closeAction, errorHandlerAction)
// 实名,下一步是待hr审核
.and().withExternal()
.source(WAIT_REAL_NAME_AUTH)
.target(WAIT_BIZ_AUDIT)
.event(EVT_NAME_AUTH)
.action(nameAuthAction, errorHandlerAction)
/** 待BIZ审核可到达的节点 **/
// 关闭
.and().withExternal()
.source(WAIT_BIZ_AUDIT)
.target(CLOSE)
.event(EVT_REFUSE).guard(toCloseGuard())
.action(closeAction, errorHandlerAction)
// BIZ审核通过,到待补全资料
.and().withExternal()
.source(WAIT_BIZ_AUDIT)
.target(WAIT_COMPLEMENT)
.event(EVT_AUDIT)
.action(hrAuditAction, errorHandlerAction)
/** 补全资料可以到达的节点 **/
// 待上传证件
.and().withExternal()
.source(WAIT_COMPLEMENT)
.target(CHECK_COMPLEMENT)
.event(EVT_COMPLEMENT)
// choice
.and().withChoice()
.source(CHECK_COMPLEMENT)
.first(WAIT_COMPLEMENT, retryCompleteGuard(), retryCompleteAction)
.last(WAIT_UPLOAD_IMG, completeAction)
/** 上传证件可以到达的节点 **/
.and().withExternal()
.source(WAIT_UPLOAD_IMG)
.target(CHECK_UPLOAD)
.event(EVT_UPLOAD_IMG)
.and().withChoice()
.source(CHECK_UPLOAD)
.first(WAIT_UPLOAD_IMG, retryUploadGuard(), retryUploadAction)
.last(WAIT_BEF_DEAL_RISK_AUDIT, uploadAction)
/** 贷前审核可以到达的节点 **/
// 关单
.and().withExternal()
.source(WAIT_BEF_DEAL_RISK_AUDIT)
.target(CLOSE)
.event(EVT_AUDIT)
.guard(toCloseGuard())
.action(closeAction, errorHandlerAction)
// 跳转到待借款
.and().withExternal()
.source(WAIT_BEF_DEAL_RISK_AUDIT)
.target(WAIT_BORROW)
.event(EVT_AUDIT)
.guard(toWaitBorrowGuard())
.action(befDealRiskAction, errorHandlerAction)
/** 待借款可以到达的节点 **/
// 签约环节补充所有待完善数据,所以是从createService中发起此流程
.and().withExternal()
.source(WAIT_BORROW)
.target(SIGNED)
.event(EVT_SIGN)
.action(signAction, errorHandlerAction)
.and().withExternal()
.source(WAIT_BORROW)
.target(IN_DEAL_RISK_AUDITING)
.event(EVT_AUDIT)
.and().withChoice()
.source(IN_DEAL_RISK_AUDITING)
.first(CLOSE, toCloseGuard(), closeAction)
.last(WAIT_SIGN, toWaitSignAction)
.and().withExternal()
.source(WAIT_SIGN)
.target(SIGNED)
.event(EVT_SIGN)
.action(signAction, errorHandlerAction)// -- to be complete
/** 签约可以到达的节点 **/
.and().withExternal()
.source(SIGNED)
.target(LOANING)
.event(EVT_LOAN)
.action(loanAction, errorHandlerAction)
/* 需要外部触发,暂时不用choice了,无法自己内部决定
.and().withChoice()
.source(LOANING)
.first(CLOSE, loanFailGuard(), closeAction)
.last(LOANED, loanSuccGuard(), loanAction())*/
.and().withExternal()
.source(LOANING)
.target(CLOSE)
.event(EVT_LOAN_FAILED)
.action(closeAction, errorHandlerAction)
.and().withExternal()
.source(LOANING)
.target(LOANED)
.event(EVT_LOAN_SUCC)
.action(loanSuccAction, errorHandlerAction)
/** 放款成功可以到达的节点 **/
.and().withExternal()
.source(LOANED)
.target(BILL_GEN)
.event(EVT_GEN_BILL)
.action(genBillAction, errorHandlerAction)
/** 生成账单 到逾期/还款 **/
.and().withExternal()
.source(BILL_GEN)
.target(OVERDUE)
.event(EVT_OVERDUE)
.action(overdueAction, errorHandlerAction)
.and().withExternal()
.source(BILL_GEN)
.target(REPAYING)
.event(EVT_REPAY)
// OVERDUE可以到达的节点
.and().withExternal()
.source(OVERDUE)
.target(REPAYING)
.event(EVT_REPAY)
.and().withExternal()
.source(PART_REPAID)
.target(OVERDUE)
.event(EVT_OVERDUE)
.action(overdueAction, errorHandlerAction)
.and().withExternal()
.source(PART_REPAID)
.target(REPAYING)
.event(EVT_REPAY)
// 还款过程,repaying可以到达的节点
.and().withChoice()
.source(REPAYING)
.first(PART_REPAID, partRepayGuard(), partRepayAction) // 部分还款,到本身,状态不变
.last(REPAID, repaidAction) // 全部还款
// repayed 可以到达的节点-success 销账
.and().withExternal()
.source(REPAID)
.target(SUCCESS)
.event(EVT_TOSUCCESS)
.action(successAction, errorHandlerAction)
;
return builder.build();
}
......
}
这里先忽略对应的guard及action,主要关注每个节点的配置(withXX\source\target\event),对照上文中的状态变迁图,理解这套配置。
创建状态机引擎的工厂
这里封装了调用builder生成对应状态机的实现,如下所示
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.vipfins.finance.middleplatform.order.statemachine.BizOrderStateMachineBuilder.WYLOAN_BUILDER_NAME;
@Component
public class BizOrderStateMachineBuildFactory implements InitializingBean {
@Autowired
private List<BizOrderStateMachineBuilder> builders;
@Autowired
private BeanFactory beanFactory;
/**
* 用来存储builder-name及builder的map
*/
private Map<String, BizOrderStateMachineBuilder> builderMap = Maps.newConcurrentMap();
/**
* 用于存储bizType+subBizType 与 builder-name的集合
*/
private Map<String, String> bizTypeBuilderMap = Maps.newConcurrentMap();
/**
* State machine instantiation is a relatively expensive operation so it is better to try to pool instances
* instead of instantiating a new instance with every request
* <p>
* 不过目前先继续每次请求过来进行创建,后续再考虑池化操作
* <p>
* 创建statemachine
*
* @param bizType
* @param subBizType
* @return
*/
public StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> createStateMachine(String bizType, String subBizType) {
if (StringUtils.isBlank(subBizType)) {
subBizType = "";
}
String key = StringUtils.trim(bizType) + StringUtils.trim(subBizType);
String builderName = bizTypeBuilderMap.get(key);
if (StringUtils.isBlank(builderName)) {
throw new BusinessException(BizOrderErrorCode.NO_CORRESPONDING_STATEMACHINE_BUILDER, "当前业务没有对应的状态机配置,请检查");
}
return createStateMachine(builderName);
}
/**
* 创建stateMachine
* @param builderName
* @return
*/
public StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> createStateMachine(String builderName) {
BizOrderStateMachineBuilder builder = builderMap.get(builderName);
StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine = null;
try {
stateMachine = builder.build(beanFactory);
} catch (Exception e) {
e.printStackTrace();
throw new BusinessException(BizOrderErrorCode.ORDER_GENERIC_EXCEPTION, e.getLocalizedMessage());
}
return stateMachine;
}
/**
* Invoked by a BeanFactory after it has set all bean properties supplied
* (and satisfied BeanFactoryAware and ApplicationContextAware).
* <p>This method allows the bean instance to perform initialization only
* possible when all bean properties have been set and to throw an
* exception in the event of misconfiguration.
*
* @throws Exception in the event of misconfiguration (such
* as failure to set an essential property) or if initialization fails.
*/
@Override
public void afterPropertiesSet() throws Exception {
builderMap = builders.stream().collect(Collectors.toMap(
BizOrderStateMachineBuilder::getName,
Function.identity()
));
// 暂时将bizType和subBizType XXX-单笔授信作为key,绑定对应的XXX状态机,后续还需要绑定别的业务
bizTypeBuilderMap.put(BizOrderBizTypeEnum.EMPLOAN.getOrderBizType() + BizOrderSubTypeEnum.SINGLE_AUTH.getBizSubType(),
WYLOAN_BUILDER_NAME);
// XXX 不区分子业务类型
bizTypeBuilderMap.put(BizOrderBizTypeEnum.EMPLOAN.getOrderBizType(), WYLOAN_BUILDER_NAME);
}
}
外部调用时,只需要使用createStateMachine就可以创建出对应的状态机实例。
Guard相关实现
上文中,每次choice都有至少一个guard出现,但是其实在action之前也可以指定guard,如果不满足guard中运行的条件(guard返回false),就不会执行对应的action。这里忽略了这个配置。
上面配置中对应的guard实现(依然在WYLoanBizOrderStateMachineBuilder中):
/**
* 判断是否要到待借款状态
*
* @return 如果需要,返回true,否则返回false
*/
private Guard<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> toWaitBorrowGuard() {
return context -> {
String finalOrderStatus = (String) context.getMessageHeader(BizOrderConstants.FINAL_STATUS_KEY);
if (BizOrderStatusEnum.equals(finalOrderStatus, BizOrderStatusEnum.WAIT_BORROW)) {
log.debug("toWaitBorrowGurad return true");
return true;
}
log.debug("toWaitBorrowGurad return false");
return false;
};
}
/**
* 判断是否需要用户实名
*
* @return 如果需要,返回true,否则返回false
*/
private Guard<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> needNameAuthGurad() {
return context -> {
String finalOrderStatus = (String) context.getMessageHeader(BizOrderConstants.FINAL_STATUS_KEY);
if (BizOrderStatusEnum.equals(finalOrderStatus, BizOrderStatusEnum.WAIT_REAL_NAME_AUTH)) {
log.debug("needNameAuthGurad return true");
return true;
}
log.debug("needNameAuthGurad return false");
return false;
};
}
private Guard<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> retryUploadGuard() {
return context -> {
// 判断请求参数中的targetStatus是否为需要重试upload
String finalOrderStatus = (String) context.getMessageHeader(BizOrderConstants.FINAL_STATUS_KEY);
if (BizOrderStatusEnum.equals(finalOrderStatus, BizOrderStatusEnum.WAIT_UPLOAD_IMG)) {
log.debug("retryUploadGuard return true");
return true;
}
log.debug("retryUploadGuard return false");
return false;
};
}
/**
* 判断是否需要重试补全资料
*
* @return 结果
*/
private Guard<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> retryCompleteGuard() {
return context -> {
// 判断请求参数中的targetStatus是否为需要重试complete补全
String finalOrderStatus = (String) context.getMessageHeader(BizOrderConstants.FINAL_STATUS_KEY);
if (BizOrderStatusEnum.equals(finalOrderStatus, BizOrderStatusEnum.WAIT_COMPLEMENT)) {
log.debug("retryCompleteGuard return true");
return true;
}
log.debug("retryCompleteGuard return false");
return false;
};
}
private Guard<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> toCloseGuard() {
return context -> {
// 判断请求参数中的targetStatus是否为关单
String finalOrderStatus = (String) context.getMessageHeader(BizOrderConstants.FINAL_STATUS_KEY);
if (BizOrderStatusEnum.equals(finalOrderStatus, BizOrderStatusEnum.CLOSE)) {
log.debug("toCloseGuard return true");
return true;
}
log.debug("toCloseGuard return false");
return false;
};
}
private Guard<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> partRepayGuard() {
return context -> {
// 判断请求参数中的targetStatus是否为关单
String finalOrderStatus = (String) context.getMessageHeader(BizOrderConstants.FINAL_STATUS_KEY);
if (BizOrderStatusEnum.equals(finalOrderStatus, BizOrderStatusEnum.PART_REPAID)) {
log.debug("partRepayGuard return true");
return true;
}
log.debug("partRepayGuard return false");
return false;
};
}
注意,每个guard都从messageHeader中获取了FINAL_STATUS_KEY对应的值,这个数据是由外部系统传入,然后在每次调用状态机时设置到message中,外部调用如下:
Message<BizOrderStatusChangeEventEnum> eventMsg = MessageBuilder.withPayload(eventEnum)
// key 与 status change 时不同,对应的model也不同
.setHeader(BizOrderConstants.BIZORDER_CONTEXT_CREATE_KEY, bizOrderCreateRequest)
// 根据传递过来的订单状态决定后续choice跳转
.setHeader(BizOrderConstants.FINAL_STATUS_KEY, bizOrderCreateRequest.getBizOrderCreateModel().getOrderStatus())
.build();
sendResult = stateMachine.sendEvent(eventMsg);
这里这么实现的原因是,目前的订单系统没办法自己判断某个条件是否达成,只能依赖外部参数传入。但是如果订单系统中可以通过调用外部服务做最终判断,这里guard的实现就可以系统自己判断,而不是依赖外部参数传入。
Action注入
配置中除了guard之外,另一个跟业务实现紧密关联的就是Action了,下面将所有的Action注入的代码罗列如下(注入到WYLoanBizOrderStateMachineBuilder中)
@Autowired
@Qualifier("errorHandlerAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> errorHandlerAction;
@Resource(name = "orderCreateAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> orderCreateAction;
@Resource(name = "successAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> successAction;
@Resource(name = "cancelAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> cancelAction;
@Resource(name = "closeAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> closeAction;
@Resource(name = "nameAuthAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> nameAuthAction;
@Resource(name = "needNameAuthAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> needNameAuthAction;
@Resource(name = "waitBorrowAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> waitBorrowAction;
@Resource(name = "hrAuditAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> hrAuditAction;
@Resource(name = "retryCompleteAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> retryCompleteAction;
@Resource(name = "completeAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> completeAction;
@Resource(name = "retryUploadAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> retryUploadAction;
@Resource(name = "uploadAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> uploadAction;
@Resource(name = "befDealRiskAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> befDealRiskAction;
// sign时需要补充所有必需业务数据
@Resource(name = "signAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> signAction;
@Resource(name = "toWaitSignAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> toWaitSignAction;
@Resource(name = "loanAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> loanAction;
@Resource(name = "loanSuccAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> loanSuccAction;
@Resource(name = "genBillAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> genBillAction;
@Resource(name = "overdueAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> overdueAction;
@Resource(name = "partRepayAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> partRepayAction;
@Resource(name = "repaidAction")
private Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> repaidAction;
@Autowired默认注入byType,@Qualifier指定对应的beanName,所以二者结合起来等同于@Resource的作用。
Action实现
首先注意errorHandlerAction,这里并没有什么业务逻辑,只是封装了异常发生时的信息,实现如下:
/**
* 异常处理Action
*
* @return action对象
*/
@Bean(name = "errorHandlerAction", autowire = Autowire.BY_TYPE)
public Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> errorHandlerAction() {
return context -> {
RuntimeException exception = (RuntimeException) context.getException();
log.error("stateMachine execute error = ", exception);
context.getStateMachine()
.getExtendedState().getVariables()
.put(RuntimeException.class, exception);
};
}
这里将发生的异常信息记录在StateMachineContext中,在外部可以根据这个这个值是否存在来判断是否有异常发生。
其他的Action实现大同小异:
- 从context中获取状态机
- 从context中获取请求参数
- 打印日志,记录状态机信息、请求参数信息
- 通过注入的bizManager实现来处理具体的业务逻辑,关于bizManager,可以参考https://www.jianshu.com/p/ba744cfd3672文章中BaseBizManager的定义,以及其子类AbstractBizManagerImpl的实现
以订单创建和订单待关闭两个Action为例,其对应代码实现如下:
/**
* 创建订单
* @return
*/
@Bean(name = "orderCreateAction", autowire = Autowire.BY_TYPE)
public Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> orderCreateAction(){
return context -> {
// 订单创建相关请求
BizOrderCreateRequest createRequest = (BizOrderCreateRequest) context.getMessageHeader(BizOrderConstants.BIZORDER_CONTEXT_CREATE_KEY);
// 从context中获取状态机
StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine = context.getStateMachine();
log.info("order info={},stateMachine id={},uuid={},jump from {} to sign status",
createRequest,
stateMachine.getId(),
stateMachine.getUuid(),
stateMachine.getState().getId());
bizOrderCreateBizManager.process(createRequest);
};
}
/**
* 自动跳转到close的Action
* <p>
* 比如超时未处理,希望关单,可以使用此action
*
* @return action对象
*/
@Bean(name = "toCloseAction",autowire = Autowire.BY_TYPE)
public Action<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> toCloseAction() {
return context -> {
StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine = context.getStateMachine();
BizOrderStatusRequest statusRequest = (BizOrderStatusRequest) context.getMessageHeader(BizOrderConstants.BIZORDER_CONTEXT_KEY);
log.info("order info={},stateMachine id={},uuid={}, jump from {} to toClose status",
statusRequest,
stateMachine.getId(),
stateMachine.getUuid(),
stateMachine.getState().getId());
bizOrderToCloseBizManager.process(statusRequest);
};
}
相当于action只是一层粘连,而具体的实现则落地在bizManager中。
下一章节会展开BizManager的实现。