背景
这期说下状态以及对应事件的相关设计,这部分内容是后续状态机相关的配置的基础,其中有些设计在实现的时候来回修改了几版,还是挺考验细节设计的。
状态变迁
状态机是为了解决订单的状态变迁问题,为了方便理解,就需要有一个具体的状态变化图,下面是之前处理过的一个案例,先上其对应的状态变迁图:
说明:
- 这个状态变化比较复杂,也比较奇葩,将部分不应该由订单来维护的状态都统一放到了订单中,比如待实名认证、待业务审核、待用户资料不全、待上传影像这种,其实都是开户过程中应该做的,但是业务方抽象能力比较弱,又比较难沟通,且如果希望可以尽快落地,只能由订单系统吞掉这部分逻辑,所以整个流程就变成了上图所示,一个很复杂的状态节点变更流程。
- 用户在创建订单时,创建只是一个瞬时状态,只会有待实名及待借款两种情况,所以在这里,create状态设计成了一个choice;统一的初始入口比较容易处理、理解。
- 其实还有一些终结状态,比如各种审核失败、风控失败等等,这里为了简化处理,统一设置为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。