我是如何解决业务场景状态机流转及验证问题

745 阅读11分钟

话说在一次产品需求评审中,当讲到一个工单的业务流程中,讲到工单创建、工单挂起(一次挂起2小时,二次挂起12小时)、工单转存、处理完结,我当时听得是云里雾里,会后我又认真研读了 一下需求,33页产品需求,word文档,花费一天功夫我才看了一半,尽管如此,我依然吃透了需求,知道我们研发最终要实现什么。最终,经过不懈努力,前期做好技术方案设计,研发中认真设计代码, 开发完毕自己再认真的自测,自测完毕,再邀请业务同学帮忙过目,是不是实现其所期待的,一个月的努力,最终上线顺利成功。

一、业务概述

我们先来看一下产品方案中的流程图

流程图

然后摘抄产品需求方案中的解读说明

-(1)工单生成后,初始化状态均为订单待处理; -(2)工单接单后状态即转为1阶段审核中,1阶段审核中监控人员可以选择1次挂起、转存量或者处理完成; -(3)如果1阶段审核中,选择了1次挂起,则根据选择的时间进入等待中状态,等待时间到了后,转为下一个状态; -(4)1次挂起设定的时间到了后,监控人员接单后,进入到2阶段审核中,2阶段审核中状态可以转为2次挂起、转存量,或者处理完成; -(5)2次审核中如果选择了2次挂起,则根据选择的时间进入等待中状态,等待时间到了以后,转为下一个状态(3阶段审核); -(6)2次挂起的时间到了,工单领单后,状态转为3次审核中; -(7)转存量,当监控人员选择转存量后,根据对应时间,进入转存量等待中,转存量时间到了后,重新进入工单队列中,接单后,从第1阶段审核中开始; -(8)当监控人员认为此订单已经完成后,在审核详情页中选择处理完成,则此订单完结,本次流程不再进入工单池,但是如果订单转为处理完成后,第二天跑规则时,仍然触碰XX规则,则继续从初始化时的“待处理”状态开始。

相信你看到上面一大段话,你也会琢磨即便,脑子中构建一下流程图。经过一遍遍研读,我画了如下一个主状态+子状态的一个状态机流程图。

状态机流程图

上述图可以解读如下

  • 工单流程流转主状态路径:待处理 ==> 处理中 ==> [等待中] ==> 处理完结。(等待中用于挂起操作,可以跳过此状态)
  • 挂起操作 进入 [等待中],根据工单选择类型,子状态进入 [一次挂起] 或者 [二次挂起],挂起n小时后,进入待处理。
  • 处理中状态,可以选择[转存]操作,转存n天后,进入待处理,系统派单后,重新进入 处理中状态(子状态 一次处理中)。
  • 挂起操作,后续流程根据挂起次数,处理中的子状态显示 1阶段处理中、2阶段处理中、3阶段处理中。
  • 工单的终态就是 处理完结,整个工单流程结束。

二、技术方案

流程流转的核心,就是状态机,什么样状态可以允许进入当前状态,当前状态结束后,应该进入什么样状态,或许我们代码实现中,都有各自的思路,但是哪种思路实现最简单,后续最容易扩展维护,我们 有没有认真思考这个问题。借此方案,我来介绍一下我的方案设计。

AttentionEvent 定义一个状态机接口

/**
 * @description: 工单状态流转接口
 * @Date : 2020/7/19 上午9:34
 * @Author : 石冬冬-Seig Heil
 */
public interface AttentionEvent extends EnumDesc {
    /**
     * 前置状态
     * @return
     */
    EnumValue[] preventStatus();

    /**
     * 后置状态
     * @return
     */
    EnumValue[] nextStatus();

    /**
     * 是否终态
     * @return
     */
    boolean isEnd();

    /**
     * 校验状态
     * @param currentStatus 当前状态
     * @return
     */
    boolean checkCurrentStatus(int currentStatus);
    /**
     * 校验状态
     * @param expectStatus 期望状态
     * @return
     */
    boolean checkExpectStatus(int expectStatus);
}


public interface EnumDesc extends EnumValue {
    /**
     * 获取描述
     * @return
     */
    String getDesc();
}

public interface EnumValue {
    /**
     * 获取枚举索引
     * @return
     */
    int getIndex();

    /**
     * 获取枚举名称
     * @return
     */
    String getName();

    /**
     * 获取枚举类指定index对应的枚举成员
     * @param index index
     * @param clazz 枚举类
     * @param <E> 枚举类
     * @return 对的
     */
    static <E extends Enum<E> & EnumValue> E getByIndex(Integer index,Class<E> clazz) {
        return EnumSet.allOf(clazz).stream().filter(e -> e.getIndex() == index).findFirst().orElse(null);
    }

    /**
     * 获取枚举类指定index对应的枚举成员名称
     * @param index
     * @param clazz
     * @return
     */
    static <E extends Enum<E> & EnumValue> String getNameByIndex(Integer index, Class<E> clazz){
        EnumValue e = getByIndex(index,clazz);
        return null == e ? "" : e.getName();
    }
}

AttentionEventEnum 定义一个枚举实现该接口

public enum AttentionEventEnum implements AttentionEvent {
	INIT_ORDER(1,"初始化","第一次创建工单",
			new EnumValue[]{
			},
			new EnumValue[]{
					SubStatusEnum.PENDING,
			},
			false),
	ACCEPT_ORDER(2,"领单","审核专员领单",
			new EnumValue[]{
					SubStatusEnum.PENDING,
					SubStatusEnum.STORED,
			},
			new EnumValue[]{
					SubStatusEnum.PROCESSING_1_PHASE,
					SubStatusEnum.PROCESSING_2_PHASE,
					SubStatusEnum.PROCESSING_3_PHASE
			},
			false),
	STORE_ORDER(3,"转存","审核专员转存订单",
			new EnumValue[]{
					SubStatusEnum.PROCESSING_1_PHASE,
					SubStatusEnum.PROCESSING_2_PHASE,
					SubStatusEnum.PROCESSING_3_PHASE
			},
			new EnumValue[]{
					SubStatusEnum.STORED,
			},
			false),
	SUSPENDED_AT_ONCE(4,"一次挂起","审核专员一次挂起订单",
			new EnumValue[]{
					SubStatusEnum.PROCESSING_1_PHASE
			},
			new EnumValue[]{
					SubStatusEnum.SUSPENDED_AT_ONCE
			},
			false),
	SUSPENDED_AT_TWICE(5,"二次挂起","审核专员二次挂起订单",
			new EnumValue[]{
					SubStatusEnum.PROCESSING_2_PHASE
			},
			new EnumValue[]{
					SubStatusEnum.SUSPENDED_AT_TWICE
			},
			false),
	DELAYED_SCHEDULED(6,"延迟既定时间处理工单","挂起|转存的延迟队列处理",
			new EnumValue[]{
					SubStatusEnum.SUSPENDED_AT_ONCE,
					SubStatusEnum.SUSPENDED_AT_TWICE,
					SubStatusEnum.STORED
			},
			new EnumValue[]{
					SubStatusEnum.PENDING,
			},
			false),
	ASSIGN_ORDER(7,"指派订单","持有工单的人当前不坐席或者离职,需要主管指派给坐席专员处理",
			new EnumValue[]{
					SubStatusEnum.PENDING,
					SubStatusEnum.PROCESSING_1_PHASE,
					SubStatusEnum.PROCESSING_2_PHASE,
					SubStatusEnum.PROCESSING_3_PHASE
			},
			new EnumValue[]{
					SubStatusEnum.PROCESSING_1_PHASE,
					SubStatusEnum.PROCESSING_2_PHASE,
					SubStatusEnum.PROCESSING_3_PHASE
			},
			false),
	FINISH_ORDER(8,"处理完结","审核专员处理完结",
			new EnumValue[]{
					SubStatusEnum.PROCESSING_1_PHASE,
					SubStatusEnum.PROCESSING_2_PHASE,
					SubStatusEnum.PROCESSING_3_PHASE
			},
			new EnumValue[]{
					SubStatusEnum.FINISHED,
			},
			true),
	;

	/**
	 * 索引
	 */
	private int index;
	/**
	 * 名称
	 */
	private String name;
	/**
	 * 名称
	 */
	private String desc;
	/**
	 * 前置状态
	 * @return
	 */
	private EnumValue[] preventStatus;
	/**
	 * 后置状态
	 * @return
	 */
	private EnumValue[] nextStatus;
	/**
	 * 是否终态
	 * @return
	 */
	private boolean isEnd;


	AttentionEventEnum(int index, String name, String desc,
					   EnumValue[] preventStatus,
					   EnumValue[] nextStatus,
					   boolean isEnd){
		this.index = index;
		this.name = name;
		this.desc = desc;
		this.preventStatus = preventStatus;
		this.nextStatus = nextStatus;
		this.isEnd = isEnd;
	}


	@Override
	public EnumValue[] preventStatus() {
		return preventStatus;
	}

	@Override
	public EnumValue[] nextStatus() {
		return nextStatus;
	}

	@Override
	public boolean isEnd() {
		return isEnd;
	}

	@Override
	public String getDesc() {
		return desc;
	}

	@Override
	public int getIndex() {
		return index;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public boolean checkCurrentStatus(int currentStatus){
		SubStatusEnum currentStatusEnum = SubStatusEnum.getByIndex(currentStatus);
		return Arrays.asList(this.preventStatus()).contains(currentStatusEnum);
	}

	@Override
	public boolean checkExpectStatus(int expectStatus){
		SubStatusEnum expectStatusEnum = SubStatusEnum.getByIndex(expectStatus);
		return Arrays.asList(this.nextStatus()).contains(expectStatusEnum);
	}

}

解读说明

  • 枚举成员即我们流程流转的事件,通过构造方法,构造该事件触发的前置状态,以及事件结束的后置流转状态。
  • 枚举提供两个静态方法,提供外部调用对状态的合法校验。

WorkOrderStatusEnum 工单状态

public enum WorkOrderStatusEnum implements EnumValue {

	PENDING(200,"待处理"),
	PROCESSING(400,"处理中"),
	WAITING(600,"等待中"),
	FINISHED(800,"处理完结"),
	;


	private int index;
	private String value;

	WorkOrderStatusEnum(int index, String value ){
		this.value = value;
		this.index = index;
	}

	@Override
	public int getIndex() {
		return index;
	}

	@Override
	public String getName() {
		return value;
	}

	/**
	 * 子状态枚举
	 * @Date : 2020/7/10 下午4:04
	 * @Author : 石冬冬-Seig Heil
	 * 子状态(2XX-待处理[200-待处理;210-转存量];4XX-处理中[410-1阶段处理;420-2阶段处理;430-3阶段处理];6XX-等待中[610-1次挂起;620-2次挂起];8XX-处理完结[800-处理完成])
	 */
	public enum SubStatusEnum implements EnumValue {

		PENDING(200,"待处理",WorkOrderStatusEnum.PENDING),
		STORED(210,"转存量",WorkOrderStatusEnum.PENDING),

		PROCESSING_1_PHASE(410,"1阶段处理",WorkOrderStatusEnum.PROCESSING),
		PROCESSING_2_PHASE(420,"2阶段处理",WorkOrderStatusEnum.PROCESSING),
		PROCESSING_3_PHASE(430,"3阶段处理",WorkOrderStatusEnum.PROCESSING),

		SUSPENDED_AT_ONCE(610,"1次挂起",WorkOrderStatusEnum.WAITING),
		SUSPENDED_AT_TWICE(620,"2次挂起",WorkOrderStatusEnum.WAITING),

		FINISHED(800,"处理完成",WorkOrderStatusEnum.FINISHED),
		;


		private int index;
		private String value;
		private EnumValue primaryStatus;

		SubStatusEnum(int index, String value,EnumValue primaryStatus){
			this.value = value;
			this.index = index;
			this.primaryStatus = primaryStatus;
		}

		@Override
		public int getIndex() {
			return index;
		}

		@Override
		public String getName() {
			return value;
		}

		public EnumValue getPrimaryStatus() {
			return primaryStatus;
		}

		/**
		 * 根据索引获取对象
		 * @param index
		 * @return
		 */
		public static SubStatusEnum getByIndex(int index){
			return Stream.of(SubStatusEnum.values()).filter(each -> each.getIndex() == index).findFirst().get();
		}
		/**
		 * 根据索引获取名称
		 * @param index
		 * @return
		 */
		public static String getNameByIndex(int index){
			SubStatusEnum find = Stream.of(SubStatusEnum.values()).filter(each -> each.getIndex() == index).findFirst().get();
			return null == find ? "" : find.getName();
		}
	}

	/**
	 * 根据索引获取对象
	 * @param index
	 * @return
	 */
	public static WorkOrderStatusEnum getByIndex(int index){
		return Stream.of(WorkOrderStatusEnum.values()).filter(each -> each.getIndex() == index).findFirst().get();
	}
	/**
	 * 根据索引获取名称
	 * @param index
	 * @return
	 */
	public static String getNameByIndex(int index){
		WorkOrderStatusEnum find = Stream.of(WorkOrderStatusEnum.values()).filter(each -> each.getIndex() == index).findFirst().get();
		return null == find ? "" : find.getName();
	}

	/**
	 * 根据一级状态获取二级状态
	 * @param mainStatus
	 * @return
	 */
	public static List<EnumValue> getSubStatus(int mainStatus){
		return Stream.of(SubStatusEnum.values()).filter(each -> each.getPrimaryStatus().getIndex() == mainStatus).collect(toList());
	}
}
  • 工单流程操作

类图

类图解读

  • 通过策略模式实现各个流程操作的实现,抽象类实现公共抽象骨架的封装,譬如通过Redis分布式锁实现工单的获取锁、释放锁,防止多人操作一个工单。
  • 通过OperateStrategyManager,提供对策略类实例的创建,对外提供策略类的调用。

OperateStrategy(工单操作策略类接口)

/**
 * @description: 工单操作策略接口
 * 调用顺序:{@link OperateStrategy#prepare(OperateContext)} -> {@link OperateStrategy#paramCheck(OperateContext)}
 *  -> {@link OperateStrategy#operationCheck(OperateContext)} -> {@link OperateStrategy#operation(OperateContext)}
 * @Date : 2020/7/15 下午4:11
 * @Author : 石冬冬-Seig Heil
 */
public interface OperateStrategy {
    /**
     * 上下文相关初始化
     * 子类可以完成对context相关成员变量的城市化,以及子类的成员变量
     * @param context
     */
    void prepare(OperateContext context);
    /**
     * context成员变量参数校验
     * @param context
     * @return
     */
    boolean paramCheck(OperateContext context);
    /**
     * 操作校验,是否可以执行操作
     * @param context
     * @return
     */
    boolean operationCheck(OperateContext context);
    /**
     * 执行操作,包括入库更新操作若干业务处理
     * @param context
     */
    void operation(OperateContext context);
}

DelayedOperate(延迟操作接口)

/**
 * @description: 工单挂起或转存,延迟操作
 * @Date : 2020/7/15 下午4:11
 * @Author : 石冬冬-Seig Heil
 */
public interface DelayedOperate {
    /**
     * 为上下文对象设置延迟的时间
     * 场景1:挂起操作,当前系统时间+n个小时
     * 场景2:转存操作,当前系统时间+n天
     * @param context
     * @return
     */
    void setDelayedTime(OperateContext context);
}

AbstractOperateStrategy(抽象策略类)

@Slf4j
public abstract class AbstractOperateStrategy implements OperateStrategy {
    /**
     * 事件
     */
    final static ThreadLocal<AttentionEvent> ATTENTION_EVENT_CONTEXT = new NamedThreadLocal<>("ATTENTION_EVENT_CONTEXT");

    @Autowired
    RedisService redisService;

    @Autowired
    Validator validator;

    /**
     * 执行
     * @param context
     */
    public void execute(OperateContext context){
        // 1、上下文依赖初始化
        prepare(context);
        // 2. 参数校验
        if(!paramCheck(context)) {
            return;
        }
        String workCode = context.getOperateParam().getWorkCode();
        log.info("ATTENTION_EVENT_CONTEXT={}",ATTENTION_EVENT_CONTEXT.toString());
        if(null == ATTENTION_EVENT_CONTEXT.get()){
            ATTENTION_EVENT_CONTEXT.set(context.getAttentionEvent());
        }
        String redisKey = redisService.getKey(MessageFormat.format(CarthageConst.AuditMemberKey.OPERATE_LOCK,workCode));
        try {
            // 3. 获取锁
            String lock = redisService.get(redisKey,String.class);
            if(!Objects.isNull(lock)){
                context.buildExecuteResultWithFailure("亲:工单[{" + workCode + "}]正在处理中,请稍后操作!");
                return;
            }
            if(!redisService.lock(redisKey)) {
                context.buildExecuteResultWithFailure("亲:工单[{" + workCode + "}]正在处理中,请稍后操作!");
                return;
            }
            // 4. 操作校验
            if(!operationCheck(context)) {
                return;
            }
            // 5. 本地操作
            ((AbstractOperateStrategy) AopContext.currentProxy()).operation(context);
            log.info("operate success attentionEvent={}, workCode={}", ATTENTION_EVENT_CONTEXT.get(), workCode);
        } catch (Exception e) {
            log.error("operate error attentionEvent={}, context={}", ATTENTION_EVENT_CONTEXT.get(), JSON.toJSONString(context), e);
            context.buildExecuteResultWithFailure("系统异常,操作失败!");
        } finally {
            ATTENTION_EVENT_CONTEXT.remove();
            redisService.unlock(redisKey);
        }
    }

    /**
     * 执行操作(扩展部分),常用语redis处理
     * @param context
     */
    abstract void operationExtend(OperateContext context);

    @Override
    public boolean paramCheck(OperateContext context) {
        String message;
        List<String> messages = validateInOval(context);
        if(CollectionsTools.isNotEmpty(messages)){
            message = MessageFormat.format("非法[workOrder={0}],[attentionEvent={1}],context实例对象参数校验不通过:{2}"
                    ,context.getOperateParam().getWorkCode(),ATTENTION_EVENT_CONTEXT.get(),messages.get(0));
            context.buildExecuteResultWithFailure(message);
        }
        return true;
    }

    @Override
    public boolean operationCheck(OperateContext context) {
        AttentionEvent attentionEvent = ATTENTION_EVENT_CONTEXT.get();
        WorkOrder workOrder = context.getWorkOrder();
        String message;
        if(Const.isYes(workOrder.getIsFinished())){
            message = MessageFormat.format("工单[workOrder={0}]已处理完结,禁止操作![attentionEvent={1}]"
                    ,context.getOperateParam().getWorkCode(),attentionEvent);
            context.buildExecuteResultWithFailure(message);
            return false;
        }
        if(Objects.isNull(workOrder)){
            message = MessageFormat.format("非法[workOrder={0}]不存在,禁止操作![attentionEvent={1}]"
                    ,context.getOperateParam().getWorkCode(),attentionEvent);
            context.buildExecuteResultWithFailure(message);
            return false;
        }
        boolean passCheck = context.getAttentionEvent().checkCurrentStatus(workOrder.getSubStatus());
        if(!passCheck){
            message = MessageFormat.format("[workCode={0}]工单状态={2}不符合操作场景![attentionEvent={1}]"
                    ,workOrder.getWorkCode(),workOrder.getSubStatus(),attentionEvent);
            context.buildExecuteResultWithFailure(message);
        }
        return passCheck;
    }

    /**
     * 基于Oval校验实体对象
     * @param object
     * @return
     */
    List<String> validateInOval(Object object){
        List<String> messages = Lists.newArrayList();
        List<ConstraintViolation> violationList = validator.validate(object);
        if(CollectionsTools.isNotEmpty(violationList)){
            violationList.forEach(each -> messages.add(each.getMessage()));
        }
        return messages;
    }
}

AbstractSubmitOperateStrategy(抽象提交操作类)

@Slf4j
public abstract class AbstractSubmitOperateStrategy extends AbstractOperateStrategy implements DelayedOperate {

    @Autowired
    DisposeOrderService disposeOrderService;

    @Autowired
    WorkOrderService workOrderService;

    @Autowired
    SurveyResultService surveyResultService;

    @Autowired
    MemberQueueManager memberQueueManager;

    @Autowired
    WorkOrderQueueManager workOrderQueueManager;

    @Autowired
    WorkOrderCacheManager workOrderCacheManager;

    /**
     * 构建工单对象,用于更新工单相关字段
     * @param context
     * @return WorkOrder
     */
    WorkOrder buildWorkOrder(OperateContext context){
        OperateContext.OperateParam operateParam = context.getOperateParam();
        Date currentTime = TimeTools.createNowTime();
        String workCode = context.getWorkOrder().getWorkCode();
        WorkOrder workOrder = WorkOrder.builder()
                .id(context.getSurveyResult().getRelationId())
                .workCode(workCode)
                .handledTime(currentTime)
                .handlerCode(operateParam.getHandlerCode())
                .handlerName(operateParam.getHandlerName())
                .modified(currentTime)
                .build();
        return workOrder;
    }


    @Override
    public void prepare(OperateContext context) {
        WorkOrder workOrder = workOrderService.queryRecord(context.getSurveyResult().getRelationId());
        context.getOperateParam().setWorkCode(workOrder.getWorkCode());
        context.setWorkOrder(workOrder);
        // 初始化延迟时间
        setDelayedTime(context);
    }

    @Override
    public boolean paramCheck(OperateContext context) {
        if(super.paramCheck(context)){
            return true;
        }
        if(Objects.isNull(context.getSurveyResult())){
            context.buildExecuteResultWithFailure("[SurveyResult]为空!");
            return false;
        }
        if(Objects.isNull(context.getAttentionEvent())){
            context.buildExecuteResultWithFailure("[AttentionEvent]为空,上下文未初始化!");
            return false;
        }
        List<String> messages = validateInOval(context);
        if(CollectionsTools.isNotEmpty(messages)){
            context.buildExecuteResultWithFailure("[surveyResult]参数校验不通过:" + messages.get(0));
            return false;
        }
        return true;
    }


    @Override
    @Transactional(rollbackFor = Exception.class,timeout = 5000)
    public void operation(OperateContext context) {
        Date currentTime = TimeTools.createNowTime();
        WorkOrder originWorkOrder = context.getWorkOrder();
        String workCode = originWorkOrder.getWorkCode();

        //1、更新GPS工单相关字段
        WorkOrder workOrder = buildWorkOrder(context);
        boolean passCheck = context.getAttentionEvent().checkExpectStatus(workOrder.getSubStatus());
        if(!passCheck){
            context.buildExecuteResultWithFailure(MessageFormat.format("workCode={0},当前工单状态{1}==>{2}不符合操作!"
                    ,workOrder.getWorkCode(),workOrder.getSubStatus(),originWorkOrder.getSubStatus()));
            return;
        }
        log.info("执行[workOrder]更新操作,workCode={},workOrder={}",workCode, JSONObject.toJSONString(workOrder));
        workOrderService.updateByPrimaryKeySelective(workOrder);

        SurveyResult surveyResult = context.getSurveyResult();
        String appCode = surveyResult.getAppCode();
        MoveToEnum moveToEnum = MoveToEnum.getByIndex(surveyResult.getMoveTo());

        //选择【处理完结】处理完结操作
        if(moveToEnum == MoveToEnum.FINISHED){
            DisposeOrder disposeOrder = DisposeOrder.builder()
                    .id(surveyResult.getRelationId())
                    .appCode(appCode)
                    .isAllow(Const.NON_INDEX)
                    .isFinished(Const.YES_INDEX)
                    .finishedTime(currentTime)
                    .modified(currentTime)
                    .build();
            log.info("执行[disposeOrder]更新操作,appCode={},disposeOrder={}",appCode,JSONObject.toJSONString(disposeOrder));
            disposeOrderService.updateByPrimaryKeySelective(disposeOrder);
        }
        //新增调查记录
        log.info("执行[surveyResult]新增,workCode={},surveyResult={}",workCode, JSONObject.toJSONString(surveyResult));
        surveyResultService.insertRecord(surveyResult);
        // 外部操作
        operationExtend(context);
    }

    @Override
    void operationExtend(OperateContext context) {
        // 审核专员持有单量 -1
        String handlerCode = context.getWorkOrder().getHandlerCode();
        String currentDay = TimeTools.format4YYYYMMDD(TimeTools.createNowTime());
        //1.1、更新 holdingCount 中当前人持有单量
        memberQueueManager.incrementHoldingCountOfThisHandler(handlerCode,-1);
        //1.2、更新人员队列中的持有单量
        memberQueueManager.updateAcceptUserVoToUsersQueue(Boolean.FALSE,currentDay,users -> users.stream()
                .filter(each -> each.getUserId().equals(handlerCode)).forEach(each -> {
                    Integer originCount = Optional.ofNullable(each.getHoldingCount()).orElse(0);
                    each.setHoldingCount(originCount.intValue() == 0 ? 0 : -- originCount );
                }));
    }
}

SubmitWithSuspendOperateStrategy(挂起操作)

@Slf4j
@Component
public class SubmitWithSuspendOperateStrategy extends AbstractSubmitOperateStrategy{

    static final Map<MoveToEnum,AttentionEventEnum> suspend_to_attention_event_map = new HashMap<>();

    static final Map<MoveToEnum,WorkOrderStatusEnum.SubStatusEnum> suspend_to_sub_status_map = new HashMap<>();

    static final Map<MoveToEnum,Integer> suspend_count_map = new HashMap<>();

    static {

        suspend_to_attention_event_map.put(MoveToEnum.SUSPENDED_AT_ONCE,AttentionEventEnum.SUSPENDED_AT_ONCE);
        suspend_to_attention_event_map.put(MoveToEnum.SUSPENDED_AT_TWICE,AttentionEventEnum.SUSPENDED_AT_TWICE);

        suspend_to_sub_status_map.put(MoveToEnum.SUSPENDED_AT_ONCE,WorkOrderStatusEnum.SubStatusEnum.SUSPENDED_AT_ONCE);
        suspend_to_sub_status_map.put(MoveToEnum.SUSPENDED_AT_TWICE,WorkOrderStatusEnum.SubStatusEnum.SUSPENDED_AT_TWICE);

        suspend_count_map.put(MoveToEnum.SUSPENDED_AT_ONCE,1);
        suspend_count_map.put(MoveToEnum.SUSPENDED_AT_TWICE,2);

        log.info("init... suspend_to_attention_event_map={}",suspend_to_attention_event_map.toString());
        log.info("init... suspend_to_sub_status_map={}",suspend_to_sub_status_map.toString());
        log.info("init... suspend_count_map={}",suspend_count_map.toString());
    }

    @Autowired
    DiamondConfigProxy diamondConfigProxy;

    @Override
    public void prepare(OperateContext context) {
        super.prepare(context);
        SurveyResult surveyResult = context.getSurveyResult();
        MoveToEnum moveToEnum = MoveToEnum.getByIndex(surveyResult.getMoveTo());
        AttentionEvent attentionEvent = suspend_to_attention_event_map.getOrDefault(moveToEnum,null);
        ATTENTION_EVENT_CONTEXT.set(attentionEvent);
        context.setAttentionEvent(attentionEvent);
    }

    @Override
    WorkOrder buildWorkOrder(OperateContext context){
        SurveyResult surveyResult = context.getSurveyResult();
        MoveToEnum moveToEnum = MoveToEnum.getByIndex(surveyResult.getMoveTo());
        WorkOrder workOrder = super.buildWorkOrder(context);
        workOrder.setSuspendedCount(suspend_count_map.getOrDefault(moveToEnum,0).intValue());
        workOrder.setMainStatus(WorkOrderStatusEnum.WAITING.getIndex());
        workOrder.setSubStatus(suspend_to_sub_status_map.get(moveToEnum).getIndex());
        workOrder.setIsFinished(Const.NON_INDEX);
        workOrder.setIsStore(Const.NON_INDEX);
        workOrder.setDelayedTime(context.getOperateParam().getDelayedTime());
        return workOrder;
    }

    @Override
    void operationExtend(OperateContext context) {
        long delayedTime = context.getOperateParam().getDelayedTime().getTime();
        int delayedSeconds = context.getOperateParam().getDelayedSeconds();
        WorkOrder workOrder = context.getWorkOrder();
        WorkOrderContext cxt = WorkOrderContext.buildSuspended(workOrder.getWorkCode(),workOrder.getCasePriority(),delayedTime);
        workOrderQueueManager.leftPush(cxt);

        WorkOrderCacheManager.CacheValue cacheValue = WorkOrderCacheManager.CacheValue.
                buildSuspended(workOrder.getWorkCode(),workOrder.getCasePriority(),delayedTime,delayedSeconds);
        workOrderCacheManager.setCacheInExpire(cacheValue);
        super.operationExtend(context);
    }

    @Override
    public void setDelayedTime(OperateContext context) {
        SurveyResult surveyResult = context.getSurveyResult();
        MoveToEnum moveToEnum = MoveToEnum.getByIndex(surveyResult.getMoveTo());
        DiamondConfig.SuspendOrderConfig suspendOrderConfig = diamondConfigProxy.suspendOrderConfig();
        Date delayedTime = TimeTools.createNowTime();
        int timeUnit = Calendar.HOUR_OF_DAY;
        int delayedSeconds = 0;
        int value = suspendOrderConfig.getConfig().getOrDefault(moveToEnum.name(),0);
        switch (suspendOrderConfig.getTimeUnit()){
            case "DAY":
                timeUnit = Calendar.DAY_OF_YEAR;
                delayedSeconds = value * 24 * 3600;
                break;
            case "HOUR":
                timeUnit = Calendar.HOUR_OF_DAY;
                delayedSeconds = value * 3600;
                break;
            case "MINUTE":
                timeUnit = Calendar.MINUTE;
                delayedSeconds = value * 60;
                break;
            case "SECOND":
                timeUnit = Calendar.SECOND;
                delayedSeconds = value;
                break;
            default:
                break;
        }

        TimeTools.addTimeField(delayedTime, timeUnit,value);
        context.getOperateParam().setDelayedTime(delayedTime);
        context.getOperateParam().setDelayedSeconds(delayedSeconds);
    }
}

SubmitWithStoreOperateStrategy(转存操作)

@Slf4j
@Component
public class SubmitWithStoreOperateStrategy extends AbstractSubmitOperateStrategy{
    /**
     * 转存天数 换算 秒数
     */
    static final int DAY_TO_SECONDS = 24 * 60 * 60;

    @Override
    public void prepare(OperateContext context) {
        ATTENTION_EVENT_CONTEXT.set(AttentionEventEnum.STORE_ORDER);
        context.setAttentionEvent(AttentionEventEnum.STORE_ORDER);
        super.prepare(context);
    }

    @Override
    public boolean paramCheck(OperateContext context) {
        if(Objects.isNull(context.getSurveyResult().getDelayedDays())){
            context.buildExecuteResultWithFailure("[surveyResult.delayedDays]为空!");
        }
        if(context.getSurveyResult().getDelayedDays() == 0){
            context.buildExecuteResultWithFailure("等待天数[delayedDays]必须大于0!");
        }
        return super.paramCheck(context);
    }

    @Override
    WorkOrder buildWorkOrder(OperateContext context){
        WorkOrder workOrder = super.buildWorkOrder(context);
        workOrder.setMainStatus(WorkOrderStatusEnum.PENDING.getIndex());
        workOrder.setSubStatus(WorkOrderStatusEnum.SubStatusEnum.STORED.getIndex());
        workOrder.setIsFinished(Const.NON_INDEX);
        workOrder.setIsStore(Const.YES_INDEX);
        //setSuspendedCount 这里需要重置为0,转存后派单流程状态依赖该字段
        workOrder.setSuspendedCount(0);
        workOrder.setDelayedTime(context.getOperateParam().getDelayedTime());
        return workOrder;
    }

    @Override
    void operationExtend(OperateContext context) {
        long delayedTime = context.getOperateParam().getDelayedTime().getTime();
        int delayedSeconds = context.getOperateParam().getDelayedSeconds();
        WorkOrder workOrder = context.getWorkOrder();
        WorkOrderContext cxt = WorkOrderContext.buildStored(workOrder.getWorkCode(),workOrder.getCasePriority(),delayedTime);
        workOrderQueueManager.leftPush(cxt);

        WorkOrderCacheManager.CacheValue cacheValue = WorkOrderCacheManager.CacheValue.
                buildStored(workOrder.getWorkCode(),workOrder.getCasePriority(),delayedTime,delayedSeconds);
        workOrderCacheManager.setCacheInExpire(cacheValue);

        super.operationExtend(context);
    }

    @Override
    public void setDelayedTime(OperateContext context) {
        int delayedDays = context.getSurveyResult().getDelayedDays();
        Date delayedTime = TimeTools.createNowTime();
        TimeTools.addTimeField(delayedTime, Calendar.DAY_OF_YEAR,delayedDays);
        context.getOperateParam().setDelayedTime(delayedTime);
        context.getOperateParam().setDelayedSeconds(delayedDays * DAY_TO_SECONDS);
    }
}

三、总结

  • 通过枚举WorkOrderStatusEnum定义主状态和子状态。
  • 通过枚举AttentionEventEnum,并实现接口AttentionEvent,以维护状态机的前置状态和后置状态。
  • 通过策略模式实现各种操作场景的业务实现,如AbstractOperateStrategy方法boolean operationCheck(OperateContext context)提供统一的前置状态校验。
  • 策略实现类,实现boolean operationCheck(OperateContext context),在工单入库修改操作前实现对状态校验。

下面的是我的公众号二维码图片,欢迎关注,或公众号搜索【秋夜无霜】。 秋夜无霜