package com.zy.core.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.core.common.Cools; import com.core.exception.CoolException; import com.zy.asrs.domain.enums.NotifyMsgType; import com.zy.asrs.domain.path.StationPathResolvedPolicy; import com.zy.asrs.domain.vo.StationCycleCapacityVo; import com.zy.asrs.domain.vo.StationCycleLoopVo; import com.zy.asrs.entity.*; import com.zy.asrs.service.*; import com.zy.asrs.utils.NotifyUtils; import com.zy.common.entity.FindCrnNoResult; import com.zy.common.model.NavigateNode; import com.zy.common.model.StartupDto; import com.zy.common.service.CommonService; import com.zy.common.utils.NavigateUtils; import com.zy.common.utils.RedisUtil; import com.zy.core.move.StationMoveCoordinator; import com.zy.core.move.StationMoveSession; import com.zy.core.News; import com.zy.core.cache.MessageQueue; import com.zy.core.cache.SlaveConnection; import com.zy.core.enums.*; import com.zy.core.model.StationObjModel; import com.zy.core.model.Task; import com.zy.core.model.command.StationCommand; import com.zy.core.model.protocol.StationProtocol; import com.zy.core.model.protocol.StationTaskBufferItem; import com.zy.core.service.StationTaskLoopService; import com.zy.core.thread.StationThread; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.*; @Component public class StationOperateProcessUtils { private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120; private static final int OUT_ORDER_DISPATCH_LIMIT_SECONDS = 2; private static final int STATION_COMMAND_DISPATCH_DEDUP_SECONDS = 10; private static final int STATION_IDLE_RECOVER_SECONDS = 10; private static final int STATION_IDLE_RECOVER_LIMIT_SECONDS = 30; private static final int STATION_IDLE_TRACK_EXPIRE_SECONDS = 60 * 60; private static final long STATION_MOVE_RESET_WAIT_MS = 1000L; private static final String IDLE_RECOVER_CLEARED_MEMO = "idleRecoverRerouteCleared"; @Autowired private BasDevpService basDevpService; @Autowired private WrkMastService wrkMastService; @Autowired private CommonService commonService; @Autowired private RedisUtil redisUtil; @Autowired private LocMastService locMastService; @Autowired private WmsOperateUtils wmsOperateUtils; @Autowired private NotifyUtils notifyUtils; @Autowired private NavigateUtils navigateUtils; @Autowired private BasStationService basStationService; @Autowired private StationCycleCapacityService stationCycleCapacityService; @Autowired private StationPathPolicyService stationPathPolicyService; @Autowired private BasStationOptService basStationOptService; @Autowired private StationTaskLoopService stationTaskLoopService; @Autowired private WrkAnalysisService wrkAnalysisService; @Autowired private StationMoveCoordinator stationMoveCoordinator; //执行输送站点入库任务 public synchronized void stationInExecute() { try { DispatchLimitConfig baseLimitConfig = getDispatchLimitConfig(null, null); int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()}; LoadGuardState loadGuardState = buildLoadGuardState(baseLimitConfig); List basDevps = basDevpService.list(new QueryWrapper<>()); for (BasDevp basDevp : basDevps) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); if (stationThread == null) { continue; } Map stationMap = stationThread.getStatusMap(); List list = basDevp.getBarcodeStationList$(); for (StationObjModel entity : list) { Integer stationId = entity.getStationId(); if (!stationMap.containsKey(stationId)) { continue; } StationProtocol stationProtocol = stationMap.get(stationId); if (stationProtocol == null) { continue; } Object lock = redisUtil.get(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId); if (lock != null) { continue; } //满足自动、有物、有工作号 if (stationProtocol.isAutoing() && stationProtocol.isLoading() && stationProtocol.getTaskNo() > 0 ) { //检测任务是否生成 WrkMast wrkMast = wrkMastService.getOne(new QueryWrapper().eq("barcode", stationProtocol.getBarcode())); if (wrkMast == null) { continue; } if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.NEW_INBOUND.sts)) { continue; } String locNo = wrkMast.getLocNo(); FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo); if (findCrnNoResult == null) { News.taskInfo(wrkMast.getWrkNo(), "{}工作,未匹配到堆垛机", wrkMast.getWrkNo()); continue; } Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationId); if (targetStationId == null) { News.taskInfo(wrkMast.getWrkNo(), "{}站点,搜索入库站点失败", stationId); continue; } DispatchLimitConfig limitConfig = getDispatchLimitConfig(stationProtocol.getStationId(), targetStationId); LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), targetStationId, loadGuardState); if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) { return; } StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0); if (command == null) { News.taskInfo(wrkMast.getWrkNo(), "{}工作,获取输送线命令失败", wrkMast.getWrkNo()); continue; } Date now = new Date(); wrkMast.setWrkSts(WrkStsType.INBOUND_STATION_RUN.sts); wrkMast.setSourceStaNo(stationProtocol.getStationId()); wrkMast.setStaNo(targetStationId); wrkMast.setSystemMsg(""); wrkMast.setIoTime(now); wrkMast.setModiTime(now); if (wrkMastService.updateById(wrkMast)) { wrkAnalysisService.markInboundStationStart(wrkMast, now); offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "stationInExecute"); News.info("输送站点入库命令下发成功,站点号={},工作号={},命令数据={}", stationId, wrkMast.getWrkNo(), JSON.toJSONString(command)); redisUtil.set(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId, "lock", 5); loadGuardState.reserveLoopTask(loopHitResult.getLoopNo()); saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult); } } } } } catch (Exception e) { e.printStackTrace(); } } //执行堆垛机输送站点出库任务 public synchronized void crnStationOutExecute() { try { DispatchLimitConfig baseLimitConfig = getDispatchLimitConfig(null, null); int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()}; LoadGuardState loadGuardState = buildLoadGuardState(baseLimitConfig); List wrkMasts = wrkMastService.list(new QueryWrapper() .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts) .isNotNull("crn_no") ); List outOrderList = getAllOutOrderList(); for (WrkMast wrkMast : wrkMasts) { Object infoObj = redisUtil.get(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo()); if (infoObj == null) { News.info("出库任务{}数据缓存不存在", wrkMast.getWrkNo()); continue; } StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class); StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo()); if (stationThread == null) { continue; } Map stationMap = stationThread.getStatusMap(); StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId()); if (stationProtocol == null) { continue; } Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId()); if (lock != null) { continue; } //满足自动、有物、工作号0 if (stationProtocol.isAutoing() && stationProtocol.isLoading() && stationProtocol.getTaskNo() == 0 ) { Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision( stationProtocol.getStationId(), wrkMast, outOrderList, pathLenFactor ); Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId(); if (moveStaNo == null) { continue; } DispatchLimitConfig limitConfig = getDispatchLimitConfig(stationProtocol.getStationId(), moveStaNo); LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), moveStaNo, loadGuardState, wrkMast, pathLenFactor); if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) { return; } StationCommand command = buildOutboundMoveCommand( stationThread, wrkMast, stationProtocol.getStationId(), moveStaNo, pathLenFactor ); if (command == null) { News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败"); continue; } Date now = new Date(); wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); wrkMast.setSystemMsg(""); wrkMast.setIoTime(now); wrkMast.setModiTime(now); if (wrkMastService.updateById(wrkMast)) { wrkAnalysisService.markOutboundStationStart(wrkMast, now); boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute"); if (offered && stationMoveCoordinator != null) { stationMoveCoordinator.recordDispatch( wrkMast.getWrkNo(), stationProtocol.getStationId(), "crnStationOutExecute", command, false ); } News.info("输送站点出库命令下发成功,站点号={},工作号={},命令数据={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command)); redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5); redisUtil.del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo()); currentStationTaskCountRef[0]++; loadGuardState.reserveLoopTask(loopHitResult.getLoopNo()); saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult); } } } } catch (Exception e) { e.printStackTrace(); } } //执行双工位堆垛机输送站点出库任务 public synchronized void dualCrnStationOutExecute() { try { List wrkMasts = wrkMastService.list(new QueryWrapper() .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts) .isNotNull("dual_crn_no") ); for (WrkMast wrkMast : wrkMasts) { Object infoObj = redisUtil.get(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo()); if (infoObj == null) { News.info("出库任务{}数据缓存不存在", wrkMast.getWrkNo()); continue; } StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class); StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo()); if (stationThread == null) { continue; } Map stationMap = stationThread.getStatusMap(); StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId()); if (stationProtocol == null) { continue; } Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId()); if (lock != null) { continue; } //满足自动、有物、工作号0 if (stationProtocol.isAutoing() && stationProtocol.isLoading() && stationProtocol.getTaskNo() == 0 ) { Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); StationCommand command = buildOutboundMoveCommand( stationThread, wrkMast, stationProtocol.getStationId(), wrkMast.getStaNo(), pathLenFactor ); if (command == null) { News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败"); continue; } wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts); wrkMast.setSystemMsg(""); wrkMast.setIoTime(new Date()); if (wrkMastService.updateById(wrkMast)) { offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "dualCrnStationOutExecute"); notifyUtils.notify(String.valueOf(SlaveType.Devp), stationObjModel.getDeviceNo(), String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN, null); News.info("输送站点出库命令下发成功,站点号={},工作号={},命令数据={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command)); redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5); redisUtil.del(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo()); } } } } catch (Exception e) { e.printStackTrace(); } } //检测输送站点出库任务执行完成 public synchronized void stationOutExecuteFinish() { try { List wrkMasts = wrkMastService.list(new QueryWrapper().eq("wrk_sts", WrkStsType.STATION_RUN.sts)); for (WrkMast wrkMast : wrkMasts) { Integer wrkNo = wrkMast.getWrkNo(); Integer targetStaNo = wrkMast.getStaNo(); if (wrkNo == null || targetStaNo == null) { continue; } boolean complete = false; Integer targetDeviceNo = null; BasStation basStation = basStationService.getOne(new QueryWrapper().eq("station_id", targetStaNo)); if (basStation != null) { targetDeviceNo = basStation.getDeviceNo(); StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); if (stationThread != null) { Map statusMap = stationThread.getStatusMap(); StationProtocol stationProtocol = statusMap.get(basStation.getStationId()); if (stationProtocol != null && wrkNo.equals(stationProtocol.getTaskNo())) { complete = true; } } } if (complete) { completeStationRunTask(wrkMast, targetDeviceNo); } } } catch (Exception e) { e.printStackTrace(); } } private void completeStationRunTask(WrkMast wrkMast, Integer deviceNo) { if (wrkMast == null || wrkMast.getWrkNo() == null) { return; } if (stationMoveCoordinator != null) { stationMoveCoordinator.finishSession(wrkMast.getWrkNo()); } Date now = new Date(); wrkMast.setWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts); wrkMast.setIoTime(now); wrkMast.setModiTime(now); wrkMastService.updateById(wrkMast); wrkAnalysisService.markOutboundStationComplete(wrkMast, now); if (deviceNo != null) { notifyUtils.notify(String.valueOf(SlaveType.Devp), deviceNo, String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN_COMPLETE, null); } redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkMast.getWrkNo(), "lock", 60); } // 检测任务转完成 public synchronized void checkTaskToComplete() { try { List wrkMasts = wrkMastService.list(new QueryWrapper().eq("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts)); for (WrkMast wrkMast : wrkMasts) { Integer wrkNo = wrkMast.getWrkNo(); Integer targetStaNo = wrkMast.getStaNo(); Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkNo); if (lock != null) { continue; } boolean complete = false; BasStation basStation = basStationService.getOne(new QueryWrapper().eq("station_id", targetStaNo)); if (basStation == null) { continue; } StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); if (stationThread == null) { continue; } Map statusMap = stationThread.getStatusMap(); StationProtocol stationProtocol = statusMap.get(basStation.getStationId()); if (stationProtocol == null) { continue; } if (!stationProtocol.getTaskNo().equals(wrkNo)) { complete = true; } if (complete) { if (stationMoveCoordinator != null) { stationMoveCoordinator.finishSession(wrkNo); } wrkMast.setWrkSts(WrkStsType.COMPLETE_OUTBOUND.sts); wrkMast.setIoTime(new Date()); wrkMastService.updateById(wrkMast); } } } catch (Exception e) { e.printStackTrace(); } } //检测输送站点是否运行堵塞 public synchronized void checkStationRunBlock() { try { List basDevps = basDevpService.list(new QueryWrapper<>()); for (BasDevp basDevp : basDevps) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); if (stationThread == null) { continue; } List runBlockReassignLocStationList = new ArrayList<>(); for (StationObjModel stationObjModel : basDevp.getRunBlockReassignLocStationList$()) { runBlockReassignLocStationList.add(stationObjModel.getStationId()); } List outOrderStationIds = basDevp.getOutOrderIntList(); List list = stationThread.getStatus(); for (StationProtocol stationProtocol : list) { if (stationProtocol.isAutoing() && stationProtocol.isLoading() && stationProtocol.getTaskNo() > 0 && stationProtocol.isRunBlock() ) { WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); if (wrkMast == null) { News.info("输送站点号={} 运行阻塞,但无法找到对应任务,工作号={}", stationProtocol.getStationId(), stationProtocol.getTaskNo()); continue; } Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo()); if (lock != null) { continue; } redisUtil.set(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo(), "lock", 15); if (shouldUseRunBlockDirectReassign(wrkMast, stationProtocol.getStationId(), runBlockReassignLocStationList)) { executeRunBlockDirectReassign(basDevp, stationThread, stationProtocol, wrkMast); continue; } Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); RerouteContext context = RerouteContext.create( RerouteSceneType.RUN_BLOCK_REROUTE, basDevp, stationThread, stationProtocol, wrkMast, outOrderStationIds, pathLenFactor, "checkStationRunBlock_reroute" ).withRunBlockCommand() .withCancelSessionBeforeDispatch() .withResetSegmentCommandsBeforeDispatch(); executeSharedReroute(context); } } } } catch (Exception e) { e.printStackTrace(); } } //检测输送站点任务停留超时后重新计算路径 public synchronized void checkStationIdleRecover() { try { List basDevps = basDevpService.list(new QueryWrapper<>()); for (BasDevp basDevp : basDevps) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); if (stationThread == null) { continue; } List list = stationThread.getStatus(); for (StationProtocol stationProtocol : list) { if (stationProtocol.isAutoing() && stationProtocol.isLoading() && stationProtocol.getTaskNo() > 0 && !stationProtocol.isRunBlock() ) { checkStationIdleRecover(basDevp, stationThread, stationProtocol, basDevp.getOutOrderIntList()); } } } } catch (Exception e) { e.printStackTrace(); } } //获取输送线任务数量 public synchronized int getCurrentStationTaskCount() { return countCurrentStationTask(); } public synchronized int getCurrentOutboundTaskCountByTargetStation(Integer stationId) { if (stationId == null) { return 0; } return (int) wrkMastService.count(new QueryWrapper() .eq("io_type", WrkIoType.OUT.id) .eq("sta_no", stationId) .in("wrk_sts", WrkStsType.OUTBOUND_RUN.sts, WrkStsType.OUTBOUND_RUN_COMPLETE.sts, WrkStsType.STATION_RUN.sts)); } // 检测出库排序 public synchronized void checkStationOutOrder() { List basDevps = basDevpService.list(new QueryWrapper()); for (BasDevp basDevp : basDevps) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); if (stationThread == null) { continue; } Map statusMap = stationThread.getStatusMap(); List orderList = basDevp.getOutOrderList$(); List outOrderStationIds = basDevp.getOutOrderIntList(); for (StationObjModel stationObjModel : orderList) { StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId()); if (stationProtocol == null) { continue; } if (!stationProtocol.isAutoing()) { continue; } if (!stationProtocol.isLoading()) { continue; } if (stationProtocol.getTaskNo() <= 0) { continue; } if (!stationProtocol.getStationId().equals(stationProtocol.getTargetStaNo())) { continue; } WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); if (wrkMast == null) { continue; } if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)) { continue; } if (Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) { continue; } Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); RerouteContext context = RerouteContext.create( RerouteSceneType.OUT_ORDER, basDevp, stationThread, stationProtocol, wrkMast, outOrderStationIds, pathLenFactor, "checkStationOutOrder" ).withDispatchDeviceNo(stationObjModel.getDeviceNo()) .withSuppressDispatchGuard() .withOutOrderDispatchLock() .withResetSegmentCommandsBeforeDispatch(); executeSharedReroute(context); } } } // 监控绕圈站点 public synchronized void watchCircleStation() { List basDevps = basDevpService.list(new QueryWrapper()); for (BasDevp basDevp : basDevps) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); if (stationThread == null) { continue; } List outOrderList = basDevp.getOutOrderIntList(); for (StationProtocol stationProtocol : stationThread.getStatus()) { if (!stationProtocol.isAutoing()) { continue; } if (!stationProtocol.isLoading()) { continue; } if (stationProtocol.getTaskNo() <= 0) { continue; } StationCommand circleCommand = getWatchCircleCommand(stationProtocol.getTaskNo()); if (circleCommand == null) { continue; } if (!stationProtocol.getStationId().equals(circleCommand.getTargetStaNo())) { continue; } WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); if (wrkMast == null) { continue; } if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)) { continue; } if (Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) { continue; } Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); RerouteContext context = RerouteContext.create( RerouteSceneType.WATCH_CIRCLE, basDevp, stationThread, stationProtocol, wrkMast, outOrderList, pathLenFactor, "watchCircleStation" ).withSuppressDispatchGuard() .withOutOrderDispatchLock() .withResetSegmentCommandsBeforeDispatch(); executeSharedReroute(context); } } } private StationCommand buildOutboundMoveCommand(StationThread stationThread, WrkMast wrkMast, Integer stationId, Integer targetStationId, Double pathLenFactor) { if (stationThread == null || wrkMast == null) { return null; } return stationThread.getCommand( StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0, normalizePathLenFactor(pathLenFactor) ); } RerouteCommandPlan buildRerouteCommandPlan(RerouteContext context, RerouteDecision decision) { if (context == null) { return RerouteCommandPlan.skip("missing-context"); } if (decision == null) { return RerouteCommandPlan.skip("missing-decision"); } if (decision.skip()) { return RerouteCommandPlan.skip(decision.skipReason()); } if (context.stationThread() == null || context.stationProtocol() == null || context.wrkMast() == null) { return RerouteCommandPlan.skip("missing-runtime-dependency"); } Integer currentStationId = context.stationProtocol().getStationId(); Integer targetStationId = decision.targetStationId(); if (currentStationId == null || targetStationId == null) { return RerouteCommandPlan.skip("missing-target-station"); } if (Objects.equals(currentStationId, targetStationId)) { return RerouteCommandPlan.skip("same-station"); } StationCommand command = context.useRunBlockCommand() ? context.stationThread().getRunBlockRerouteCommand( context.wrkMast().getWrkNo(), currentStationId, targetStationId, 0, context.pathLenFactor() ) : buildOutboundMoveCommand( context.stationThread(), context.wrkMast(), currentStationId, targetStationId, context.pathLenFactor() ); if (command == null) { if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) { News.taskInfo(context.wrkMast().getWrkNo(), "输送站点堵塞重规划未找到可下发路线,当前站点={},目标站点={}", currentStationId, targetStationId); } else if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) { News.taskInfo(context.wrkMast().getWrkNo(), "站点任务停留超时后重算路径失败,当前站点={},目标站点={}", currentStationId, targetStationId); } else { News.taskInfo(context.wrkMast().getWrkNo(), "获取输送线命令失败"); } return RerouteCommandPlan.skip("missing-command"); } return RerouteCommandPlan.dispatch(command, decision, context.dispatchScene()); } RerouteExecutionResult executeReroutePlan(RerouteContext context, RerouteCommandPlan plan) { if (context == null) { return RerouteExecutionResult.skip("missing-context"); } if (plan == null) { return RerouteExecutionResult.skip("missing-plan"); } if (plan.skip()) { return RerouteExecutionResult.skip(plan.skipReason()); } StationProtocol stationProtocol = context.stationProtocol(); if (stationProtocol == null) { return RerouteExecutionResult.skip("missing-station-protocol"); } Integer taskNo = stationProtocol.getTaskNo(); Integer stationId = stationProtocol.getStationId(); if (taskNo == null || taskNo <= 0 || stationId == null) { return RerouteExecutionResult.skip("invalid-station-task"); } if (context.checkRecentDispatch() && shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId)) { return RerouteExecutionResult.skip("recent-dispatch"); } if (countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), taskNo) > 0) { if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) { News.info("输送站点任务停留超时,但缓存区仍存在当前任务命令,已跳过重算。站点号={},工作号={},当前任务命令数={}", stationId, taskNo, countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), taskNo)); } return RerouteExecutionResult.skip("buffer-has-current-task"); } if (context.checkSuppressDispatch() && stationMoveCoordinator != null && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command())) { return RerouteExecutionResult.skip("dispatch-suppressed"); } if (context.requireOutOrderDispatchLock() && !tryAcquireOutOrderDispatchLock(taskNo, stationId)) { return RerouteExecutionResult.skip("out-order-lock"); } if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) { stationMoveCoordinator.cancelSession(taskNo); } if (!isBlank(context.executionLockKey())) { Object lock = redisUtil.get(context.executionLockKey()); if (lock != null) { return RerouteExecutionResult.skip("scene-lock"); } redisUtil.set(context.executionLockKey(), "lock", context.executionLockSeconds()); } if (context.resetSegmentCommandsBeforeDispatch()) { resetSegmentMoveCommandsBeforeReroute(taskNo); } int clearedCommandCount = 0; if (context.clearIdleIssuedCommands()) { clearedCommandCount = clearIssuedMoveCommandsDuringIdleStay(context.idleTrack(), taskNo, stationId); } boolean offered = offerDevpCommandWithDedup(context.dispatchDeviceNo(), plan.command(), plan.dispatchScene()); if (!offered) { return RerouteExecutionResult.skip("dispatch-dedup"); } applyRerouteDispatchEffects(context, plan, clearedCommandCount); return RerouteExecutionResult.dispatched(plan.command(), clearedCommandCount); } RerouteDecision resolveSharedRerouteDecision(RerouteContext context) { if (context == null || context.wrkMast() == null || context.stationProtocol() == null) { return RerouteDecision.skip("missing-runtime-dependency"); } Integer currentStationId = context.stationProtocol().getStationId(); if (currentStationId == null) { return RerouteDecision.skip("missing-current-station"); } if (context.sceneType() == RerouteSceneType.IDLE_RECOVER && !Objects.equals(context.wrkMast().getWrkSts(), WrkStsType.STATION_RUN.sts)) { Integer targetStationId = context.wrkMast().getStaNo(); return targetStationId == null || Objects.equals(targetStationId, currentStationId) ? RerouteDecision.skip("same-station") : RerouteDecision.proceed(targetStationId); } OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision( currentStationId, context.wrkMast(), context.outOrderStationIds(), context.pathLenFactor() ); Integer targetStationId = dispatchDecision == null ? null : dispatchDecision.getTargetStationId(); if (targetStationId == null || Objects.equals(targetStationId, currentStationId)) { return RerouteDecision.skip("same-station"); } return RerouteDecision.proceed(targetStationId, dispatchDecision); } private RerouteExecutionResult executeSharedReroute(RerouteContext context) { RerouteDecision decision = resolveSharedRerouteDecision(context); if (decision.skip()) { return RerouteExecutionResult.skip(decision.skipReason()); } RerouteCommandPlan plan = buildRerouteCommandPlan(context, decision); return executeReroutePlan(context, plan); } private void applyRerouteDispatchEffects(RerouteContext context, RerouteCommandPlan plan, int clearedCommandCount) { if (context == null || plan == null || plan.command() == null || context.wrkMast() == null || context.stationProtocol() == null) { return; } WrkMast wrkMast = context.wrkMast(); StationProtocol stationProtocol = context.stationProtocol(); OutOrderDispatchDecision dispatchDecision = plan.decision() == null ? null : plan.decision().dispatchDecision(); syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), context.outOrderStationIds(), dispatchDecision, plan.command()); if (stationMoveCoordinator != null) { stationMoveCoordinator.recordDispatch( wrkMast.getWrkNo(), stationProtocol.getStationId(), plan.dispatchScene(), plan.command(), dispatchDecision != null && dispatchDecision.isCircle() ); } if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) { saveStationTaskIdleTrack(new StationTaskIdleTrack(wrkMast.getWrkNo(), stationProtocol.getStationId(), System.currentTimeMillis())); News.info("输送站点任务停留{}秒未运行,已重新计算路径并重启运行,站点号={},目标站={},工作号={},清理旧分段命令数={},命令数据={}", STATION_IDLE_RECOVER_SECONDS, stationProtocol.getStationId(), plan.command().getTargetStaNo(), wrkMast.getWrkNo(), clearedCommandCount, JSON.toJSONString(plan.command())); return; } if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) { News.info("输送站点堵塞后重新计算路径命令下发成功,站点号={},工作号={},命令数据={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(plan.command())); return; } if (context.sceneType() == RerouteSceneType.OUT_ORDER) { News.info(dispatchDecision != null && dispatchDecision.isCircle() ? "{}任务进行绕圈" : "{}任务直接去目标点", wrkMast.getWrkNo()); } } private List calcOutboundNavigatePath(WrkMast wrkMast, Integer sourceStationId, Integer targetStationId, Double pathLenFactor) { Double normalizedFactor = normalizePathLenFactor(pathLenFactor); Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo(); if (currentTaskNo == null) { return navigateUtils.calcByStationId(sourceStationId, targetStationId, normalizedFactor); } return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor); } private Double resolveOutboundPathLenFactor(WrkMast wrkMast) { if (!isBatchOutboundTaskWithSeq(wrkMast)) { return 0.0d; } List activeBatchTaskList = loadActiveBatchTaskList(wrkMast.getBatch()); if (activeBatchTaskList.size() <= 1) { return 0.0d; } int activeTaskCount = 0; int predecessorCount = 0; for (WrkMast item : activeBatchTaskList) { if (!isFactorCandidateTask(item)) { continue; } activeTaskCount++; if (item.getBatchSeq() < wrkMast.getBatchSeq()) { predecessorCount++; } } if (activeTaskCount <= 1 || predecessorCount <= 0) { return 0.0d; } return normalizePathLenFactor((double) predecessorCount / (double) (activeTaskCount - 1)); } private boolean isBatchOutboundTaskWithSeq(WrkMast wrkMast) { return wrkMast != null && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) && !Cools.isEmpty(wrkMast.getBatch()) && wrkMast.getBatchSeq() != null && wrkMast.getWrkNo() != null; } private List loadActiveBatchTaskList(String batch) { if (Cools.isEmpty(batch)) { return Collections.emptyList(); } return wrkMastService.list(new QueryWrapper() .eq("io_type", WrkIoType.OUT.id) .eq("batch", batch) .notIn("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts, WrkStsType.COMPLETE_OUTBOUND.sts, WrkStsType.SETTLE_OUTBOUND.sts)); } private boolean isFactorCandidateTask(WrkMast wrkMast) { return wrkMast != null && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) && wrkMast.getBatchSeq() != null && !"taskCancel".equals(wrkMast.getMk()); } public List getAllOutOrderList() { List list = new ArrayList<>(); List basDevps = basDevpService.list(new QueryWrapper()); for (BasDevp basDevp : basDevps) { List orderList = basDevp.getOutOrderIntList(); list.addAll(orderList); } return list; } private OutOrderDispatchDecision resolveOutboundDispatchDecision(Integer currentStationId, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor) { if (wrkMast == null || wrkMast.getStaNo() == null) { return null; } if (!shouldApplyOutOrder(wrkMast, outOrderStationIds)) { return new OutOrderDispatchDecision(wrkMast.getStaNo(), false); } Integer dispatchStationId = resolveDispatchOutOrderTarget( wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), outOrderStationIds, pathLenFactor ); if (dispatchStationId == null) { return null; } if (isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) { return resolveCurrentOutOrderDispatchDecision(currentStationId, wrkMast, outOrderStationIds, pathLenFactor); } if (!Objects.equals(dispatchStationId, wrkMast.getStaNo()) && isCurrentOutOrderStation(currentStationId, outOrderStationIds) && isWatchingCircleArrival(wrkMast.getWrkNo(), currentStationId)) { return new OutOrderDispatchDecision(dispatchStationId, true, null, false); } return new OutOrderDispatchDecision(dispatchStationId, false); } private OutOrderDispatchDecision resolveCurrentOutOrderDispatchDecision(Integer currentStationId, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor) { if (!isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) { return null; } List batchWrkList = wrkMastService.list(new QueryWrapper() .eq("io_type", WrkIoType.OUT.id) .notIn("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts, WrkStsType.COMPLETE_OUTBOUND.sts, WrkStsType.SETTLE_OUTBOUND.sts) .eq("batch", wrkMast.getBatch()) .orderByAsc("batch_seq") .orderByAsc("wrk_no")); if (batchWrkList.isEmpty()) { return new OutOrderDispatchDecision(wrkMast.getStaNo(), false); } WrkMast firstWrkMast = batchWrkList.get(0); Integer currentBatchSeq = firstWrkMast.getBatchSeq(); if (currentBatchSeq == null) { News.taskInfo(wrkMast.getWrkNo(), "批次:{} 首个未完成任务缺少批次序号,当前任务暂不放行", wrkMast.getBatch()); return null; } List initPath; try { initPath = calcOutboundNavigatePath(wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), pathLenFactor); } catch (Exception e) { News.taskInfo(wrkMast.getWrkNo(), "批次:{} 计算排序路径失败,当前站点={}", wrkMast.getBatch(), currentStationId); return null; } Integer seq = getOutStationBatchSeq(initPath, currentStationId, wrkMast.getBatch()); boolean toTarget; if (seq == null) { toTarget = currentBatchSeq.equals(wrkMast.getBatchSeq()); } else { toTarget = Integer.valueOf(seq + 1).equals(wrkMast.getBatchSeq()); } if (toTarget) { if (hasReachableOutReleaseSlot(wrkMast, currentStationId, wrkMast.getStaNo(), pathLenFactor)) { return new OutOrderDispatchDecision(wrkMast.getStaNo(), false); } StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( wrkMast.getWrkNo(), currentStationId, outOrderStationIds ); Integer circleTarget = resolveNextCircleOrderTarget( wrkMast, currentStationId, outOrderStationIds, loopEvaluation.getExpectedLoopIssueCount(), pathLenFactor ); if (circleTarget == null) { News.taskInfo(wrkMast.getWrkNo(), "目标站当前不可进,且未找到可执行的下一排序检测点,当前站点={}", currentStationId); return null; } return new OutOrderDispatchDecision(circleTarget, true, loopEvaluation, true); } StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( wrkMast.getWrkNo(), currentStationId, outOrderStationIds ); Integer circleTarget = resolveNextCircleOrderTarget( wrkMast, currentStationId, outOrderStationIds, loopEvaluation.getExpectedLoopIssueCount(), pathLenFactor ); if (circleTarget == null) { News.taskInfo(wrkMast.getWrkNo(), "未找到可执行的下一排序检测点,当前站点={}", currentStationId); return null; } return new OutOrderDispatchDecision(circleTarget, true, loopEvaluation, true); } private boolean shouldApplyOutOrder(WrkMast wrkMast, List outOrderStationIds) { return wrkMast != null && wrkMast.getStaNo() != null && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) && !Cools.isEmpty(wrkMast.getBatch()) && wrkMast.getBatchSeq() != null && outOrderStationIds != null && !outOrderStationIds.isEmpty(); } private boolean isCurrentOutOrderDispatchStation(Integer currentStationId, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor) { if (!shouldApplyOutOrder(wrkMast, outOrderStationIds) || currentStationId == null) { return false; } Integer dispatchStationId = resolveDispatchOutOrderTarget( wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), outOrderStationIds, pathLenFactor ); return dispatchStationId != null && !Objects.equals(dispatchStationId, wrkMast.getStaNo()) && Objects.equals(currentStationId, dispatchStationId); } private boolean isCurrentOutOrderStation(Integer currentStationId, List outOrderStationIds) { return currentStationId != null && outOrderStationIds != null && outOrderStationIds.contains(currentStationId); } private void syncOutOrderWatchState(WrkMast wrkMast, Integer currentStationId, List outOrderStationIds, OutOrderDispatchDecision dispatchDecision, StationCommand command) { if (dispatchDecision == null || command == null || !shouldApplyOutOrder(wrkMast, outOrderStationIds)) { return; } if (dispatchDecision.isCircle()) { saveWatchCircleCommand(wrkMast.getWrkNo(), command); if (dispatchDecision.shouldCountLoopIssue() && stationTaskLoopService != null && dispatchDecision.getLoopEvaluation() != null) { stationTaskLoopService.recordLoopIssue(dispatchDecision.getLoopEvaluation(), "OUT_ORDER_CIRCLE"); } } else { clearWatchCircleCommand(wrkMast.getWrkNo()); } } private StationTaskLoopService.LoopEvaluation evaluateOutOrderLoop(Integer taskNo, Integer currentStationId, List outOrderStationIds) { if (stationTaskLoopService == null) { return new StationTaskLoopService.LoopEvaluation( taskNo, currentStationId, StationTaskLoopService.LoopIdentitySnapshot.empty(), 0, 0, false ); } return stationTaskLoopService.evaluateLoop( taskNo, currentStationId, true, outOrderStationIds, "outOrderCircle" ); } private Integer resolveDispatchOutOrderTarget(WrkMast wrkMast, Integer sourceStationId, Integer finalTargetStationId, List outOrderList, Double pathLenFactor) { if (finalTargetStationId == null) { return null; } if (sourceStationId == null || outOrderList == null || outOrderList.isEmpty()) { return finalTargetStationId; } try { List nodes = calcOutboundNavigatePath(wrkMast, sourceStationId, finalTargetStationId, pathLenFactor); for (int i = nodes.size() - 1; i >= 0; i--) { Integer stationId = getStationIdFromNode(nodes.get(i)); if (stationId == null) { continue; } if (Objects.equals(stationId, finalTargetStationId)) { continue; } if (outOrderList.contains(stationId)) { return stationId; } } } catch (Exception ignore) {} return finalTargetStationId; } private boolean hasReachableOutReleaseSlot(WrkMast wrkMast, Integer currentStationId, Integer finalTargetStationId, Double pathLenFactor) { if (currentStationId == null || finalTargetStationId == null) { return true; } try { List nodes = calcOutboundNavigatePath(wrkMast, currentStationId, finalTargetStationId, pathLenFactor); if (nodes == null || nodes.isEmpty()) { return true; } for (NavigateNode node : nodes) { Integer stationId = getStationIdFromNode(node); if (stationId == null || Objects.equals(stationId, currentStationId)) { continue; } if (!isPathStationBlocked(stationId)) { return true; } } return false; } catch (Exception ignore) { return true; } } private boolean isPathStationBlocked(Integer stationId) { if (stationId == null) { return true; } BasStation basStation = basStationService.getOne(new QueryWrapper().eq("station_id", stationId)); if (basStation == null) { return true; } StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); if (stationThread == null) { return true; } StationProtocol stationProtocol = stationThread.getStatusMap().get(stationId); if (stationProtocol == null) { return true; } return !stationProtocol.isAutoing() || stationProtocol.isLoading() || (stationProtocol.getTaskNo() != null && stationProtocol.getTaskNo() > 0); } private Integer resolveNextCircleOrderTarget(WrkMast wrkMast, Integer currentStationId, List orderedOutStationList, Integer expectedLoopIssueCount, Double pathLenFactor) { if (currentStationId == null || orderedOutStationList == null || orderedOutStationList.size() <= 1) { return null; } int startIndex = orderedOutStationList.indexOf(currentStationId); int total = orderedOutStationList.size(); List candidateList = new ArrayList<>(); for (int offset = 1; offset < total; offset++) { int candidateIndex = (startIndex + offset + total) % total; Integer candidateStationId = orderedOutStationList.get(candidateIndex); if (candidateStationId == null || currentStationId.equals(candidateStationId)) { continue; } try { List path = calcOutboundNavigatePath(wrkMast, currentStationId, candidateStationId, pathLenFactor); if (path != null && !path.isEmpty()) { candidateList.add(new CircleTargetCandidate(candidateStationId, path.size(), offset)); } } catch (Exception ignore) {} } if (candidateList.isEmpty()) { return null; } candidateList.sort(new Comparator() { @Override public int compare(CircleTargetCandidate left, CircleTargetCandidate right) { if (left == right) { return 0; } if (left == null) { return 1; } if (right == null) { return -1; } int pathCompare = Integer.compare(left.getPathLength(), right.getPathLength()); if (pathCompare != 0) { return pathCompare; } return Integer.compare(left.getOffset(), right.getOffset()); } }); return resolveGradualCircleTargetByPathLength(expectedLoopIssueCount, candidateList, pathLenFactor); } private Integer resolveGradualCircleTargetByPathLength(Integer expectedLoopIssueCount, List candidateList, Double pathLenFactor) { if (candidateList == null || candidateList.isEmpty()) { return null; } List tierList = new ArrayList<>(); Integer lastPathLength = null; for (CircleTargetCandidate candidate : candidateList) { if (candidate == null) { continue; } if (lastPathLength == null || !Objects.equals(lastPathLength, candidate.getPathLength())) { tierList.add(candidate); lastPathLength = candidate.getPathLength(); } } if (tierList.isEmpty()) { return candidateList.get(0).getStationId(); } int defaultTierIndex = expectedLoopIssueCount == null || expectedLoopIssueCount <= 2 ? 0 : Math.min(expectedLoopIssueCount - 2, tierList.size() - 1); int factorTierIndex = (int) Math.round(normalizePathLenFactor(pathLenFactor) * (tierList.size() - 1)); int tierIndex = Math.max(defaultTierIndex, factorTierIndex); return tierList.get(tierIndex).getStationId(); } private boolean tryAcquireOutOrderDispatchLock(Integer wrkNo, Integer stationId) { if (wrkNo == null || wrkNo <= 0 || stationId == null) { return true; } String key = RedisKeyType.STATION_OUT_ORDER_DISPATCH_LIMIT_.key + wrkNo + "_" + stationId; Object lock = redisUtil.get(key); if (lock != null) { return false; } redisUtil.set(key, "lock", OUT_ORDER_DISPATCH_LIMIT_SECONDS); return true; } private boolean isWatchingCircleArrival(Integer wrkNo, Integer stationId) { if (stationMoveCoordinator != null) { StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); if (session != null && session.isActive() && stationId != null) { if (stationId.equals(session.getNextDecisionStationId())) { return true; } if (session.containsStation(stationId)) { return false; } } } StationCommand command = getWatchCircleCommand(wrkNo); return command != null && stationId != null && stationId.equals(command.getTargetStaNo()); } private boolean isWatchingCircleTransit(Integer wrkNo, Integer stationId) { if (stationMoveCoordinator != null) { StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); if (session != null && session.isActive() && stationId != null) { if (stationId.equals(session.getNextDecisionStationId())) { return false; } if (session.containsStation(stationId)) { return true; } } } StationCommand command = getWatchCircleCommand(wrkNo); if (command == null || stationId == null || Objects.equals(stationId, command.getTargetStaNo())) { return false; } List navigatePath = command.getNavigatePath(); return navigatePath != null && navigatePath.contains(stationId); } private StationCommand getWatchCircleCommand(Integer wrkNo) { if (wrkNo == null || wrkNo <= 0) { return null; } Object circleObj = redisUtil.get(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo); if (circleObj == null) { return null; } try { return JSON.parseObject(circleObj.toString(), StationCommand.class); } catch (Exception ignore) { return null; } } private void saveWatchCircleCommand(Integer wrkNo, StationCommand command) { if (wrkNo == null || wrkNo <= 0 || command == null) { return; } redisUtil.set(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo, JSON.toJSONString(command, SerializerFeature.DisableCircularReferenceDetect), 60 * 60 * 24); } private void clearWatchCircleCommand(Integer wrkNo) { if (wrkNo == null || wrkNo <= 0) { return; } redisUtil.del(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo); } private void checkStationIdleRecover(BasDevp basDevp, StationThread stationThread, StationProtocol stationProtocol, List outOrderList) { if (stationProtocol == null || stationProtocol.getTaskNo() == null || stationProtocol.getTaskNo() <= 0) { return; } if (!Objects.equals(stationProtocol.getStationId(), stationProtocol.getTargetStaNo())) { return; } StationTaskIdleTrack idleTrack = touchStationTaskIdleTrack(stationProtocol.getTaskNo(), stationProtocol.getStationId()); if (shouldSkipIdleRecoverForRecentDispatch(stationProtocol.getTaskNo(), stationProtocol.getStationId())) { return; } if (idleTrack == null || !idleTrack.isTimeout(STATION_IDLE_RECOVER_SECONDS)) { return; } WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo()); if (!canRecoverIdleStationTask(wrkMast, stationProtocol.getStationId())) { return; } Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo()); if (lock != null) { return; } Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast); RerouteContext context = RerouteContext.create( RerouteSceneType.IDLE_RECOVER, basDevp, stationThread, stationProtocol, wrkMast, outOrderList, pathLenFactor, "checkStationIdleRecover" ).withCancelSessionBeforeDispatch() .withExecutionLock(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo(), STATION_IDLE_RECOVER_LIMIT_SECONDS) .withResetSegmentCommandsBeforeDispatch() .clearIdleIssuedCommands(idleTrack); executeSharedReroute(context); } boolean shouldUseRunBlockDirectReassign(WrkMast wrkMast, Integer stationId, List runBlockReassignLocStationList) { return wrkMast != null && Objects.equals(wrkMast.getIoType(), WrkIoType.IN.id) && stationId != null && runBlockReassignLocStationList != null && runBlockReassignLocStationList.contains(stationId); } private void executeRunBlockDirectReassign(BasDevp basDevp, StationThread stationThread, StationProtocol stationProtocol, WrkMast wrkMast) { if (basDevp == null || stationThread == null || stationProtocol == null || wrkMast == null) { return; } int currentTaskBufferCommandCount = countCurrentTaskBufferCommands( stationProtocol.getTaskBufferItems(), stationProtocol.getTaskNo() ); if (currentTaskBufferCommandCount > 0) { News.info("输送站点运行堵塞重分配已跳过,缓存区仍存在当前任务命令。站点号={},工作号={},当前任务命令数={}", stationProtocol.getStationId(), stationProtocol.getTaskNo(), currentTaskBufferCommandCount); return; } if (stationMoveCoordinator != null) { stationMoveCoordinator.cancelSession(wrkMast.getWrkNo()); } String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId()); if (Cools.isEmpty(response)) { News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口失败,接口未响应!!!response:{}", response); return; } JSONObject jsonObject = JSON.parseObject(response); if (!jsonObject.getInteger("code").equals(200)) { News.error("请求WMS接口失败!!!response:{}", response); return; } StartupDto dto = jsonObject.getObject("data", StartupDto.class); String sourceLocNo = wrkMast.getLocNo(); String locNo = dto.getLocNo(); LocMast sourceLocMast = locMastService.queryByLoc(sourceLocNo); if (sourceLocMast == null) { News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 源库位信息不存在", sourceLocNo); return; } if (!sourceLocMast.getLocSts().equals("S")) { News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 源库位状态不处于入库预约", sourceLocNo); return; } LocMast locMast = locMastService.queryByLoc(locNo); if (locMast == null) { News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 目标库位信息不存在", locNo); return; } if (!locMast.getLocSts().equals("O")) { News.taskInfo(wrkMast.getWrkNo(), "库位号:{} 目标库位状态不处于空库位", locNo); return; } FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo); if (findCrnNoResult == null) { News.taskInfo(wrkMast.getWrkNo(), "{}工作,未匹配到堆垛机", wrkMast.getWrkNo()); return; } Integer crnNo = findCrnNoResult.getCrnNo(); Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationProtocol.getStationId()); if (targetStationId == null) { News.taskInfo(wrkMast.getWrkNo(), "{}站点,搜索入库站点失败", stationProtocol.getStationId()); return; } StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), targetStationId, 0); if (command == null) { News.taskInfo(wrkMast.getWrkNo(), "{}工作,获取输送线命令失败", wrkMast.getWrkNo()); return; } sourceLocMast.setLocSts("O"); sourceLocMast.setModiTime(new Date()); locMastService.updateById(sourceLocMast); locMast.setLocSts("S"); locMast.setModiTime(new Date()); locMastService.updateById(locMast); wrkMast.setLocNo(locNo); wrkMast.setStaNo(targetStationId); if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) { wrkMast.setCrnNo(crnNo); } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) { wrkMast.setDualCrnNo(crnNo); } else { throw new CoolException("未知设备类型"); } if (!wrkMastService.updateById(wrkMast)) { return; } boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct"); if (!offered) { return; } if (stationMoveCoordinator != null) { stationMoveCoordinator.recordDispatch( wrkMast.getWrkNo(), stationProtocol.getStationId(), "checkStationRunBlock_direct", command, false ); } } private boolean canRecoverIdleStationTask(WrkMast wrkMast, Integer currentStationId) { if (wrkMast == null || currentStationId == null || wrkMast.getStaNo() == null) { return false; } if (Objects.equals(currentStationId, wrkMast.getStaNo())) { return false; } return Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN.sts) || Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts); } private boolean shouldSkipIdleRecoverForRecentDispatch(Integer taskNo, Integer stationId) { if (stationMoveCoordinator == null || taskNo == null || taskNo <= 0 || stationId == null) { return false; } StationMoveSession session = stationMoveCoordinator.loadSession(taskNo); if (session == null || !session.isActive() || session.getLastIssuedAt() == null) { return false; } if (!Objects.equals(stationId, session.getCurrentStationId()) && !Objects.equals(stationId, session.getDispatchStationId())) { return false; } long elapsedMs = System.currentTimeMillis() - session.getLastIssuedAt(); long thresholdMs = STATION_IDLE_RECOVER_SECONDS * 1000L; if (elapsedMs >= thresholdMs) { return false; } saveStationTaskIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis())); News.info("输送站点任务刚完成命令下发,已跳过停留重算。站点号={},工作号={},距上次下发={}ms,routeVersion={}", stationId, taskNo, elapsedMs, session.getRouteVersion()); return true; } private void resetSegmentMoveCommandsBeforeReroute(Integer taskNo) { if (redisUtil == null || taskNo == null || taskNo <= 0) { return; } String key = RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo; redisUtil.set(key, "cancel", 3); try { Thread.sleep(STATION_MOVE_RESET_WAIT_MS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception ignore) { } redisUtil.del(key); } private int countCurrentTaskBufferCommands(List taskBufferItems, Integer currentTaskNo) { if (taskBufferItems == null || taskBufferItems.isEmpty() || currentTaskNo == null || currentTaskNo <= 0) { return 0; } int count = 0; for (StationTaskBufferItem item : taskBufferItems) { if (item == null || item.getTaskNo() == null) { continue; } if (currentTaskNo.equals(item.getTaskNo())) { count++; } } return count; } private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) { if (deviceNo == null || command == null) { return false; } String dedupKey = buildStationCommandDispatchDedupKey(deviceNo, command); if (redisUtil != null) { Object lock = redisUtil.get(dedupKey); if (lock != null) { News.info("输送站点命令短时重复派发,已跳过。scene={},deviceNo={},taskNo={},stationId={},targetStaNo={},commandType={}", scene, deviceNo, command.getTaskNo(), command.getStationId(), command.getTargetStaNo(), command.getCommandType()); return false; } redisUtil.set(dedupKey, "lock", STATION_COMMAND_DISPATCH_DEDUP_SECONDS); } boolean offered = MessageQueue.offer(SlaveType.Devp, deviceNo, new Task(2, command)); if (!offered && redisUtil != null) { redisUtil.del(dedupKey); } return offered; } private String buildStationCommandDispatchDedupKey(Integer deviceNo, StationCommand command) { return RedisKeyType.STATION_COMMAND_DISPATCH_DEDUP_.key + deviceNo + "_" + command.getTaskNo() + "_" + command.getStationId() + "_" + (stationMoveCoordinator == null ? Integer.toHexString(buildFallbackPathSignature(command).hashCode()) : stationMoveCoordinator.buildPathSignatureHash(command)); } private String buildFallbackPathSignature(StationCommand command) { if (command == null) { return ""; } return String.valueOf(command.getCommandType()) + "_" + command.getStationId() + "_" + command.getTargetStaNo() + "_" + command.getNavigatePath() + "_" + command.getLiftTransferPath() + "_" + command.getOriginalNavigatePath(); } private int clearIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack, Integer taskNo, Integer stationId) { if (basStationOptService == null) { return 0; } List optList; try { optList = listIssuedMoveCommandsDuringIdleStay(idleTrack, taskNo); } catch (Exception e) { return 0; } if (optList == null || optList.isEmpty()) { return 0; } Date now = new Date(); String cleanupMemo = buildIdleRecoverClearedMemo(stationId); int clearedCount = 0; for (BasStationOpt opt : optList) { if (opt == null || opt.getId() == null) { continue; } opt.setSend(0); opt.setUpdateTime(now); opt.setMemo(appendCleanupMemo(opt.getMemo(), cleanupMemo)); clearedCount++; } if (clearedCount > 0) { basStationOptService.updateBatchById(optList); } return clearedCount; } private List listIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack, Integer taskNo) { if (idleTrack == null || taskNo == null || taskNo <= 0 || idleTrack.firstSeenTime == null || basStationOptService == null) { return Collections.emptyList(); } List optList = basStationOptService.list(new QueryWrapper() .select("id", "task_no", "send_time", "target_station_id", "memo", "send") .eq("task_no", taskNo) .eq("mode", String.valueOf(StationCommandType.MOVE)) .eq("send", 1) .ge("send_time", new Date(idleTrack.firstSeenTime)) .orderByAsc("send_time")); if (optList == null || optList.isEmpty()) { return Collections.emptyList(); } return optList; } private String buildIdleRecoverClearedMemo(Integer stationId) { if (stationId == null) { return IDLE_RECOVER_CLEARED_MEMO; } return IDLE_RECOVER_CLEARED_MEMO + "(stationId=" + stationId + ")"; } private String appendCleanupMemo(String memo, String cleanupMemo) { if (Cools.isEmpty(cleanupMemo)) { return memo; } if (Cools.isEmpty(memo)) { return cleanupMemo; } if (memo.contains(cleanupMemo)) { return memo; } return memo + " | " + cleanupMemo; } private StationTaskIdleTrack touchStationTaskIdleTrack(Integer taskNo, Integer stationId) { if (taskNo == null || taskNo <= 0 || stationId == null) { return null; } long now = System.currentTimeMillis(); StationTaskIdleTrack idleTrack = getStationTaskIdleTrack(taskNo); if (idleTrack == null || !Objects.equals(idleTrack.stationId, stationId)) { idleTrack = new StationTaskIdleTrack(taskNo, stationId, now); saveStationTaskIdleTrack(idleTrack); } return idleTrack; } private StationTaskIdleTrack getStationTaskIdleTrack(Integer taskNo) { if (taskNo == null || taskNo <= 0) { return null; } Object obj = redisUtil.get(RedisKeyType.STATION_TASK_IDLE_TRACK_.key + taskNo); if (obj == null) { return null; } try { return JSON.parseObject(obj.toString(), StationTaskIdleTrack.class); } catch (Exception e) { return null; } } private void saveStationTaskIdleTrack(StationTaskIdleTrack idleTrack) { if (idleTrack == null || idleTrack.taskNo == null || idleTrack.taskNo <= 0) { return; } redisUtil.set( RedisKeyType.STATION_TASK_IDLE_TRACK_.key + idleTrack.taskNo, JSON.toJSONString(idleTrack, SerializerFeature.DisableCircularReferenceDetect), STATION_IDLE_TRACK_EXPIRE_SECONDS ); } public Integer getOutStationBatchSeq(List pathList, Integer searchStationId, String searchBatch) { if (pathList == null || pathList.isEmpty() || searchStationId == null || Cools.isEmpty(searchBatch)) { return null; } List checkList = new ArrayList<>(); for (int i = pathList.size() - 1; i >= 0; i--) { NavigateNode node = pathList.get(i); JSONObject v = JSONObject.parseObject(node.getNodeValue()); if (v != null) { Integer stationId = v.getInteger("stationId"); if (searchStationId.equals(stationId)) { break; } else { checkList.add(stationId); } } } HashMap batchMap = new HashMap<>(); for (Integer station : checkList) { BasStation basStation = basStationService.getOne(new QueryWrapper().eq("station_id", station)); if (basStation == null) { continue; } StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); if (stationThread == null) { continue; } Map statusMap = stationThread.getStatusMap(); StationProtocol checkStationProtocol = statusMap.get(station); if (checkStationProtocol == null) { continue; } if (checkStationProtocol.getTaskNo() > 0) { WrkMast checkWrkMast = wrkMastService.selectByWorkNo(checkStationProtocol.getTaskNo()); if (checkWrkMast == null) { continue; } if (!Cools.isEmpty(checkWrkMast.getBatch())) { batchMap.put(checkWrkMast.getBatch(), checkWrkMast.getBatchSeq()); } } } Integer seq = batchMap.get(searchBatch); return seq; } private int countCurrentStationTask() { int currentStationTaskCount = 0; List basDevps = basDevpService.list(new QueryWrapper()); for (BasDevp basDevp : basDevps) { StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo()); if (stationThread == null) { continue; } for (StationProtocol stationProtocol : stationThread.getStatus()) { if (stationProtocol.getTaskNo() > 0) { currentStationTaskCount++; } } } return currentStationTaskCount; } private boolean isDispatchBlocked(DispatchLimitConfig config, int currentStationTaskCount, LoadGuardState loadGuardState, boolean needReserveLoopLoad) { if (config.loopModeEnable) { double currentLoad = loadGuardState.currentLoad(); if (currentLoad >= config.circleMaxLoadLimit) { News.warn("当前承载量达到上限,已停止站点任务下发。当前承载量={},上限={}", formatPercent(currentLoad), formatPercent(config.circleMaxLoadLimit)); return true; } if (needReserveLoopLoad) { double reserveLoad = loadGuardState.loadAfterReserve(); if (reserveLoad >= config.circleMaxLoadLimit) { News.warn("预占后承载量达到上限,已停止站点任务下发。预占后承载量={},上限={}", formatPercent(reserveLoad), formatPercent(config.circleMaxLoadLimit)); return true; } } } return false; } private LoadGuardState buildLoadGuardState(DispatchLimitConfig config) { LoadGuardState state = new LoadGuardState(); if (!config.loopModeEnable) { return state; } StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot(); if (capacityVo == null) { return state; } state.totalStationCount = toNonNegative(capacityVo.getTotalStationCount()); Integer occupiedStationCount = capacityVo.getOccupiedStationCount(); state.projectedTaskStationCount = toNonNegative(occupiedStationCount != null ? occupiedStationCount : capacityVo.getTaskStationCount()); List loopList = capacityVo.getLoopList(); if (loopList != null) { for (StationCycleLoopVo loopVo : loopList) { if (loopVo == null || loopVo.getStationIdList() == null) { continue; } Integer loopNo = loopVo.getLoopNo(); for (Integer stationId : loopVo.getStationIdList()) { if (stationId != null) { if (loopNo != null) { state.stationLoopNoMap.put(stationId, loopNo); } } } } } return state; } private LoopHitResult findPathLoopHit(DispatchLimitConfig config, Integer sourceStationId, Integer targetStationId, LoadGuardState loadGuardState) { return findPathLoopHit(config, sourceStationId, targetStationId, loadGuardState, null, null); } private LoopHitResult findPathLoopHit(DispatchLimitConfig config, Integer sourceStationId, Integer targetStationId, LoadGuardState loadGuardState, WrkMast wrkMast, Double pathLenFactor) { if (!config.loopModeEnable) { return LoopHitResult.NO_HIT; } if (sourceStationId == null || targetStationId == null) { return LoopHitResult.NO_HIT; } if (loadGuardState.stationLoopNoMap.isEmpty()) { return LoopHitResult.NO_HIT; } try { List nodes = wrkMast == null ? navigateUtils.calcByStationId(sourceStationId, targetStationId) : calcOutboundNavigatePath(wrkMast, sourceStationId, targetStationId, pathLenFactor); if (nodes == null || nodes.isEmpty()) { return LoopHitResult.NO_HIT; } for (NavigateNode node : nodes) { Integer stationId = getStationIdFromNode(node); if (stationId == null) { continue; } Integer loopNo = loadGuardState.stationLoopNoMap.get(stationId); if (loopNo != null) { return new LoopHitResult(true, loopNo, stationId); } } } catch (Exception e) { return LoopHitResult.NO_HIT; } return LoopHitResult.NO_HIT; } private Integer getStationIdFromNode(NavigateNode node) { if (node == null || isBlank(node.getNodeValue())) { return null; } try { JSONObject v = JSONObject.parseObject(node.getNodeValue()); if (v == null) { return null; } return v.getInteger("stationId"); } catch (Exception e) { return null; } } private int toNonNegative(Integer value) { if (value == null || value < 0) { return 0; } return value; } private Double normalizePathLenFactor(Double pathLenFactor) { if (pathLenFactor == null || pathLenFactor < 0.0d) { return 0.0d; } if (pathLenFactor > 1.0d) { return 1.0d; } return pathLenFactor; } enum RerouteSceneType { RUN_BLOCK_REROUTE, IDLE_RECOVER, OUT_ORDER, WATCH_CIRCLE } static final class RerouteDecision { private final boolean skip; private final String skipReason; private final Integer targetStationId; private final OutOrderDispatchDecision dispatchDecision; private RerouteDecision(boolean skip, String skipReason, Integer targetStationId, OutOrderDispatchDecision dispatchDecision) { this.skip = skip; this.skipReason = skipReason; this.targetStationId = targetStationId; this.dispatchDecision = dispatchDecision; } static RerouteDecision skip(String reason) { return new RerouteDecision(true, reason, null, null); } static RerouteDecision proceed(Integer targetStationId) { return new RerouteDecision(false, null, targetStationId, null); } static RerouteDecision proceed(Integer targetStationId, OutOrderDispatchDecision dispatchDecision) { return new RerouteDecision(false, null, targetStationId, dispatchDecision); } boolean skip() { return skip; } String skipReason() { return skipReason; } Integer targetStationId() { return targetStationId; } OutOrderDispatchDecision dispatchDecision() { return dispatchDecision; } } static final class RerouteContext { private final RerouteSceneType sceneType; private final BasDevp basDevp; private final StationThread stationThread; private final StationProtocol stationProtocol; private final WrkMast wrkMast; private final List outOrderStationIds; private final Double pathLenFactor; private final String dispatchScene; private Integer dispatchDeviceNo; private boolean useRunBlockCommand; private boolean checkSuppressDispatch; private boolean requireOutOrderDispatchLock; private boolean cancelSessionBeforeDispatch; private boolean resetSegmentCommandsBeforeDispatch; private boolean clearIdleIssuedCommands; private boolean checkRecentDispatch; private String executionLockKey; private int executionLockSeconds; private StationTaskIdleTrack idleTrack; private RerouteContext(RerouteSceneType sceneType, BasDevp basDevp, StationThread stationThread, StationProtocol stationProtocol, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor, String dispatchScene) { this.sceneType = sceneType; this.basDevp = basDevp; this.stationThread = stationThread; this.stationProtocol = stationProtocol; this.wrkMast = wrkMast; this.outOrderStationIds = outOrderStationIds == null ? Collections.emptyList() : outOrderStationIds; this.pathLenFactor = pathLenFactor; this.dispatchScene = dispatchScene; this.dispatchDeviceNo = basDevp == null ? null : basDevp.getDevpNo(); } static RerouteContext create(RerouteSceneType sceneType, BasDevp basDevp, StationThread stationThread, StationProtocol stationProtocol, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor, String dispatchScene) { return new RerouteContext(sceneType, basDevp, stationThread, stationProtocol, wrkMast, outOrderStationIds, pathLenFactor, dispatchScene); } RerouteContext withDispatchDeviceNo(Integer dispatchDeviceNo) { this.dispatchDeviceNo = dispatchDeviceNo; return this; } RerouteContext withRunBlockCommand() { this.useRunBlockCommand = true; return this; } RerouteContext withSuppressDispatchGuard() { this.checkSuppressDispatch = true; return this; } RerouteContext withOutOrderDispatchLock() { this.requireOutOrderDispatchLock = true; return this; } RerouteContext withCancelSessionBeforeDispatch() { this.cancelSessionBeforeDispatch = true; return this; } RerouteContext withResetSegmentCommandsBeforeDispatch() { this.resetSegmentCommandsBeforeDispatch = true; return this; } RerouteContext clearIdleIssuedCommands(StationTaskIdleTrack idleTrack) { this.clearIdleIssuedCommands = true; this.idleTrack = idleTrack; return this; } RerouteContext withRecentDispatchGuard() { this.checkRecentDispatch = true; return this; } RerouteContext withExecutionLock(String executionLockKey, int executionLockSeconds) { this.executionLockKey = executionLockKey; this.executionLockSeconds = executionLockSeconds; return this; } RerouteSceneType sceneType() { return sceneType; } BasDevp basDevp() { return basDevp; } StationThread stationThread() { return stationThread; } StationProtocol stationProtocol() { return stationProtocol; } WrkMast wrkMast() { return wrkMast; } List outOrderStationIds() { return outOrderStationIds; } Double pathLenFactor() { return pathLenFactor; } String dispatchScene() { return dispatchScene; } Integer dispatchDeviceNo() { return dispatchDeviceNo; } boolean useRunBlockCommand() { return useRunBlockCommand; } boolean checkSuppressDispatch() { return checkSuppressDispatch; } boolean requireOutOrderDispatchLock() { return requireOutOrderDispatchLock; } boolean cancelSessionBeforeDispatch() { return cancelSessionBeforeDispatch; } boolean resetSegmentCommandsBeforeDispatch() { return resetSegmentCommandsBeforeDispatch; } boolean clearIdleIssuedCommands() { return clearIdleIssuedCommands; } boolean checkRecentDispatch() { return checkRecentDispatch; } String executionLockKey() { return executionLockKey; } int executionLockSeconds() { return executionLockSeconds; } StationTaskIdleTrack idleTrack() { return idleTrack; } } static final class RerouteCommandPlan { private final boolean skip; private final String skipReason; private final StationCommand command; private final RerouteDecision decision; private final String dispatchScene; private RerouteCommandPlan(boolean skip, String skipReason, StationCommand command, RerouteDecision decision, String dispatchScene) { this.skip = skip; this.skipReason = skipReason; this.command = command; this.decision = decision; this.dispatchScene = dispatchScene; } static RerouteCommandPlan skip(String reason) { return new RerouteCommandPlan(true, reason, null, null, null); } static RerouteCommandPlan dispatch(StationCommand command, RerouteDecision decision, String dispatchScene) { return new RerouteCommandPlan(false, null, command, decision, dispatchScene); } boolean skip() { return skip; } String skipReason() { return skipReason; } StationCommand command() { return command; } RerouteDecision decision() { return decision; } String dispatchScene() { return dispatchScene; } } static final class RerouteExecutionResult { private final boolean skipped; private final String skipReason; private final boolean dispatched; private final StationCommand command; private final int clearedCommandCount; private RerouteExecutionResult(boolean skipped, String skipReason, boolean dispatched, StationCommand command, int clearedCommandCount) { this.skipped = skipped; this.skipReason = skipReason; this.dispatched = dispatched; this.command = command; this.clearedCommandCount = clearedCommandCount; } static RerouteExecutionResult skip(String reason) { return new RerouteExecutionResult(true, reason, false, null, 0); } static RerouteExecutionResult dispatched(StationCommand command, int clearedCommandCount) { return new RerouteExecutionResult(false, null, true, command, clearedCommandCount); } boolean skipped() { return skipped; } String skipReason() { return skipReason; } boolean dispatched() { return dispatched; } StationCommand command() { return command; } int clearedCommandCount() { return clearedCommandCount; } } private static class OutOrderDispatchDecision { private final Integer targetStationId; private final boolean circle; private final StationTaskLoopService.LoopEvaluation loopEvaluation; private final boolean countLoopIssue; private OutOrderDispatchDecision(Integer targetStationId, boolean circle) { this(targetStationId, circle, null, false); } private OutOrderDispatchDecision(Integer targetStationId, boolean circle, StationTaskLoopService.LoopEvaluation loopEvaluation, boolean countLoopIssue) { this.targetStationId = targetStationId; this.circle = circle; this.loopEvaluation = loopEvaluation; this.countLoopIssue = countLoopIssue; } private Integer getTargetStationId() { return targetStationId; } private boolean isCircle() { return circle; } private StationTaskLoopService.LoopEvaluation getLoopEvaluation() { return loopEvaluation; } private boolean shouldCountLoopIssue() { return countLoopIssue; } } private static class CircleTargetCandidate { private final Integer stationId; private final Integer pathLength; private final Integer offset; private CircleTargetCandidate(Integer stationId, Integer pathLength, Integer offset) { this.stationId = stationId; this.pathLength = pathLength == null ? 0 : pathLength; this.offset = offset == null ? 0 : offset; } private Integer getStationId() { return stationId; } private Integer getPathLength() { return pathLength; } private Integer getOffset() { return offset; } } private void saveLoopLoadReserve(Integer wrkNo, LoopHitResult loopHitResult) { if (wrkNo == null || wrkNo <= 0 || loopHitResult == null || !loopHitResult.isThroughLoop()) { return; } JSONObject reserveJson = new JSONObject(); reserveJson.put("wrkNo", wrkNo); reserveJson.put("loopNo", loopHitResult.getLoopNo()); reserveJson.put("hitStationId", loopHitResult.getHitStationId()); reserveJson.put("createTime", System.currentTimeMillis()); redisUtil.hset(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, String.valueOf(wrkNo), reserveJson.toJSONString()); redisUtil.expire(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, LOOP_LOAD_RESERVE_EXPIRE_SECONDS); } private DispatchLimitConfig getDispatchLimitConfig(Integer startStationId, Integer endStationId) { DispatchLimitConfig config = new DispatchLimitConfig(); Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key); if (systemConfigMapObj instanceof Map) { Map systemConfigMap = (Map) systemConfigMapObj; config.circleMaxLoadLimit = parseLoadLimit(getConfigValue(systemConfigMap, "circleMaxLoadLimit"), config.circleMaxLoadLimit); String loopModeValue = getConfigValue(systemConfigMap, "circleLoopModeEnable"); if (isBlank(loopModeValue)) { loopModeValue = getConfigValue(systemConfigMap, "circleModeEnable"); } if (isBlank(loopModeValue)) { loopModeValue = getConfigValue(systemConfigMap, "isCircleMode"); } config.loopModeEnable = parseBoolean(loopModeValue, config.loopModeEnable); } if (stationPathPolicyService != null && startStationId != null && endStationId != null) { try { StationPathResolvedPolicy resolvedPolicy = stationPathPolicyService.resolvePolicy(startStationId, endStationId); if (resolvedPolicy != null && resolvedPolicy.getProfileConfig() != null) { config.circleMaxLoadLimit = parseLoadLimit(String.valueOf(resolvedPolicy.getProfileConfig().getCircleMaxLoadLimit()), config.circleMaxLoadLimit); } } catch (Exception ignore) { } } return config; } private String getConfigValue(Map configMap, String key) { Object value = configMap.get(key); if (value == null) { return null; } return String.valueOf(value).trim(); } private boolean parseBoolean(String value, boolean defaultValue) { if (isBlank(value)) { return defaultValue; } String lowValue = value.toLowerCase(Locale.ROOT); if ("y".equals(lowValue) || "yes".equals(lowValue) || "true".equals(lowValue) || "1".equals(lowValue) || "on".equals(lowValue)) { return true; } if ("n".equals(lowValue) || "no".equals(lowValue) || "false".equals(lowValue) || "0".equals(lowValue) || "off".equals(lowValue)) { return false; } return defaultValue; } private double parseLoadLimit(String value, double defaultValue) { if (isBlank(value)) { return defaultValue; } try { String normalized = value.replace("%", "").trim(); double parsed = Double.parseDouble(normalized); if (parsed > 1.0) { parsed = parsed / 100.0; } if (parsed < 0.0) { return 0.0; } if (parsed > 1.0) { return 1.0; } return parsed; } catch (Exception e) { return defaultValue; } } private int parseInt(String value, int defaultValue) { if (isBlank(value)) { return defaultValue; } try { int parsed = Integer.parseInt(value.trim()); return parsed < 0 ? defaultValue : parsed; } catch (Exception e) { return defaultValue; } } private String formatPercent(double value) { return String.format(Locale.ROOT, "%.1f%%", value * 100.0); } private boolean isBlank(String value) { return value == null || value.trim().isEmpty(); } private static class DispatchLimitConfig { // 圈最大承载能力,默认80% private double circleMaxLoadLimit = 0.8d; // 是否启用绕圈模式(仅启用时才生效承载限制) private boolean loopModeEnable = false; } private static class LoadGuardState { private int totalStationCount = 0; private int projectedTaskStationCount = 0; private final Map stationLoopNoMap = new HashMap<>(); private double currentLoad() { return calcLoad(this.projectedTaskStationCount, this.totalStationCount); } private double loadAfterReserve() { return calcLoad(this.projectedTaskStationCount + 1, this.totalStationCount); } private void reserveLoopTask(Integer loopNo) { if (loopNo == null || loopNo <= 0) { return; } if (this.totalStationCount <= 0) { return; } this.projectedTaskStationCount++; } private double calcLoad(int taskCount, int stationCount) { if (stationCount <= 0 || taskCount <= 0) { return 0.0; } double load = (double) taskCount / (double) stationCount; if (load < 0.0) { return 0.0; } if (load > 1.0) { return 1.0; } return load; } } private static class LoopHitResult { private static final LoopHitResult NO_HIT = new LoopHitResult(false, null, null); private final boolean throughLoop; private final Integer loopNo; private final Integer hitStationId; private LoopHitResult(boolean throughLoop, Integer loopNo, Integer hitStationId) { this.throughLoop = throughLoop; this.loopNo = loopNo; this.hitStationId = hitStationId; } private boolean isThroughLoop() { return throughLoop; } private Integer getLoopNo() { return loopNo; } private Integer getHitStationId() { return hitStationId; } } private static class StationTaskIdleTrack { private Integer taskNo; private Integer stationId; private Long firstSeenTime; private StationTaskIdleTrack() {} private StationTaskIdleTrack(Integer taskNo, Integer stationId, Long firstSeenTime) { this.taskNo = taskNo; this.stationId = stationId; this.firstSeenTime = firstSeenTime; } private boolean isTimeout(int seconds) { if (firstSeenTime == null) { return false; } return System.currentTimeMillis() - firstSeenTime >= seconds * 1000L; } public Integer getTaskNo() { return taskNo; } public void setTaskNo(Integer taskNo) { this.taskNo = taskNo; } public Integer getStationId() { return stationId; } public void setStationId(Integer stationId) { this.stationId = stationId; } public Long getFirstSeenTime() { return firstSeenTime; } public void setFirstSeenTime(Long firstSeenTime) { this.firstSeenTime = firstSeenTime; } } }