阅读 1120

Spring StateMachine状态机引擎在项目中的应用(三)-状态及事件设计

背景

这期说下状态以及对应事件的相关设计,这部分内容是后续状态机相关的配置的基础,其中有些设计在实现的时候来回修改了几版,还是挺考验细节设计的。

状态变迁

状态机是为了解决订单的状态变迁问题,为了方便理解,就需要有一个具体的状态变化图,下面是之前处理过的一个案例,先上其对应的状态变迁图:

复杂订单状态机.png

说明:

  1. 这个状态变化比较复杂,也比较奇葩,将部分不应该由订单来维护的状态都统一放到了订单中,比如待实名认证、待业务审核、待用户资料不全、待上传影像这种,其实都是开户过程中应该做的,但是业务方抽象能力比较弱,又比较难沟通,且如果希望可以尽快落地,只能由订单系统吞掉这部分逻辑,所以整个流程就变成了上图所示,一个很复杂的状态节点变更流程。
  2. 用户在创建订单时,创建只是一个瞬时状态,只会有待实名及待借款两种情况,所以在这里,create状态设计成了一个choice;统一的初始入口比较容易处理、理解。
  3. 其实还有一些终结状态,比如各种审核失败、风控失败等等,这里为了简化处理,统一设置为close状态。
状态枚举

根据上图所示的状态,设计其对应的状态枚举如下:

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Optional;

@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum BizOrderStatusEnum {

    CREATE("1", "创建"),

    WYD_INITIAL_JUMP("2", "初始创建时可能为待实名校验,也可能为待发起借款,需要用guard判断"),

    WAIT_REAL_NAME_AUTH("3", "待实名认证"),

    WAIT_BORROW("4", "待发起借款"),

    CANCEL("999", "用户取消"),

    CLOSE("996", "订单关闭"),

    SUCCESS("888", "成功,指已销账,订单完全终结"),

    /**
     * 审核相关状态定义-start
     */
    AUDITING("100", "审核中"), 

    WAIT_BIZ_AUDIT("101", "待业务审核"),

    BIZ_APPROVED("102", "业务审核通过"),

    WAIT_COMPLEMENT("103", "待补全审核资料"), 

    CHECK_COMPLEMENT("104", "补全资料检查"),// --流程内部使用

    WAIT_UPLOAD_IMG("105", "待上传影像资料"), 

    CHECK_UPLOAD("106", "上传影像检查"),//  --- 流程内部使用

    WAIT_BEF_DEAL_RISK_AUDIT("111", "待贷前额度评估"),

    IN_DEAL_RISK_AUDITING("121", "贷中风控审核"),

    WAIT_AF_DEAL_RISK_AUDIT("131", "待贷后审核"),  

    AF_DEAL_RISK_APPROVED("132", "贷后审核通过"), 

    APPROVED("198", "审核通过"),

    /**
     * 审核相关状态定义--end
     */
    WAIT_SIGN("200", "待签约"),

    SIGNED("288", "签约完成"),

    LOANING("300", "放款中"),

    LOANED("388", "放款完成"),

    REFUNDING("401", "退款中"), // 对于消费贷,存在退款情况

    REFUNDED("488", "退款完成"), // 对于消费贷,存在退款情况

    BILL_GEN("501", "生成账单"),

    REPAYING("600", "还款中"),  // 内部使用的中间瞬时状态,外部传入时不要使用,部分还款采用PART_REPAID类型

    PART_REPAID("601", "部分还款"),

    REPAID("688", "已还清"),

    OVERDUE("700", "已逾期"),;


    private String status;

    private String desc;

    /**
     * status是否合法
     *
     * @param status
     * @return
     */
    public static boolean isIn(String status) {
        return Arrays.asList(BizOrderStatusEnum.values()).parallelStream().
                anyMatch(value -> StringUtils.equals(value.getStatus(), status));

    }

    /**
     * 判断status是否相等
     *
     * @param status
     * @param statusEnum
     * @return
     */
    public static boolean equals(String status, BizOrderStatusEnum statusEnum) {
        return StringUtils.equalsIgnoreCase(status, statusEnum.getStatus());

    }

    /**
     * status-->statusEnum
     *
     * @param status
     * @return
     */
    public static BizOrderStatusEnum getByStatus(String status) {
        Optional<BizOrderStatusEnum> statusEnumOptional = Arrays.asList(BizOrderStatusEnum.values()).parallelStream()
                .filter(statusEnum -> StringUtils.equalsIgnoreCase(status, statusEnum.getStatus())).findAny();

        if (statusEnumOptional.isPresent()) {
            return statusEnumOptional.get();
        }

        return null;

    }

    /**
     * 判断status是否合法
     *
     * @param status
     * @param statusEnums
     * @return
     */
    public static boolean isIn(String status, BizOrderStatusEnum... statusEnums) {
        return Arrays.asList(statusEnums).parallelStream().
                anyMatch(value -> StringUtils.equals(value.getStatus(), status));

    }

    /**
     * 判断是否订单已终结,取消、关闭、成功、拒绝都属于终结状态
     *
     * @param status
     * @return
     */
    public static boolean isFinish(String status) {
        return isIn(status, SUCCESS, CANCEL, CLOSE);
    }

    /**
     * 判断订单是否是初始创建状态
     * 对于: WAIT_REAL_NAME_AUTH, WAIT_BORROW 都可能是初始状态
     * 对于其他:暂时为CREATE状态
     *
     * @param status
     * @return
     */
    public static boolean isInitialStatus(String status) {
        return isIn(status, CREATE, WAIT_REAL_NAME_AUTH, WAIT_BORROW);
    }

}复制代码

需要注意的是,这里面有些状态在一些业务场景是用不到的,比如退款,在当前业务场景并没有用,这里的状态是全集。

事件枚举

事件会导致订单的状态发生变化(当然,不是绝对,有些事件是内部事件,并不会导致状态变化,这时就需要用withInternal来串联,详见下节配置),下面是本次的事件枚举

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Optional;

@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum BizOrderStatusChangeEventEnum {

    EVT_CREATE("evt_create"),

    EVT_CANCEL("evt_cancel"), // 取消

    EVT_NAME_AUTH("evt_name_auth"), // 实名

    EVT_SYS_CLOSE("evt_sys_close"), // 系统关单,如超时或风控关单等

    EVT_AUDIT("evt_audit"), // 审核

    EVT_COMPLEMENT("evt_complement"), // 补全材料

    EVT_UPLOAD_IMG("evt_upload_img"), // 上传影像

    EVT_APPROVED("evt_approved"), // 批准

    EVT_REFUSE("evt_refuse"), // 拒绝

    EVT_SIGN("evt_sign"), // 签约

    EVT_LOAN("evt_loan"), // 放款

    EVT_LOAN_FAILED("evt_loan_failed"),

    EVT_REFUND("evt_refund"), // 退款

    EVT_REPAY("evt_repay"), // 还款

    EVT_GEN_BILL("evt_gen_bill"), // 生成账单

    EVT_TOSUCCESS("evt_tosuccess"), // 销账

    EVT_RETRY("evt_retry"), // 重试

    EVT_OVERDUE("evt_overdue")  // 逾期,用户无动作,由系统定时任务发起
    ,
    EVT_LOAN_SUCC("evt_loan_succ"),

    EVT_NEED_NAME_AUTH("evt_need_name_auth");

    String event;

    /**
     * 判断
     * @param eventName
     * @return
     */
    public static BizOrderStatusChangeEventEnum getEvent(String eventName) {
        if (StringUtils.isBlank(eventName)) {
            return null;
        }

        Optional<BizOrderStatusChangeEventEnum> resultOptional = Arrays.asList(BizOrderStatusChangeEventEnum.values()).parallelStream().filter(eventEnum ->
                StringUtils.equals(eventName, eventEnum.getEvent())).findAny();

        if (resultOptional.isPresent()) {
            return resultOptional.get();
        }
        return null;
    }
}复制代码

这里的状态就是StateMachine中的S,事件就是对应的E。