话说在一次产品需求评审中,当讲到一个工单的业务流程中,讲到工单创建、工单挂起(一次挂起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规则,则继续从初始化时的“待处理”状态开始。
相信你看到上面一大段话,你也会琢磨即便,脑子中构建一下流程图。经过一遍遍研读,我画了如下一个主状态+子状态的一个状态机流程图。
上述图可以解读如下
流程流转的核心,就是状态机,什么样状态可以允许进入当前状态,当前状态结束后,应该进入什么样状态,或许我们代码实现中,都有各自的思路,但是哪种思路实现最简单,后续最容易扩展维护,我们
有没有认真思考这个问题。借此方案,我来介绍一下我的方案设计。
/**
* @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 枚举类
* @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();
}
}
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);
}
}
解读说明
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());
}
}
类图解读
OperateStrategyManager
,提供对策略类实例的创建,对外提供策略类的调用。/**
* @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);
}
/**
* @description: 工单挂起或转存,延迟操作
* @Date : 2020/7/15 下午4:11
* @Author : 石冬冬-Seig Heil
*/
public interface DelayedOperate {
/**
* 为上下文对象设置延迟的时间
* 场景1:挂起操作,当前系统时间+n个小时
* 场景2:转存操作,当前系统时间+n天
* @param context
* @return
*/
void setDelayedTime(OperateContext context);
}
@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;
}
}
@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 );
}));
}
}
@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);
}
}
@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)
,在工单入库修改操作前实现对状态校验。