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>中的S,事件就是对应的E。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容