package com.zy.core.thread.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.core.common.Cools; import com.core.common.DateUtils; import com.core.common.SpringUtils; import com.zy.asrs.domain.vo.StationCycleCapacityVo; import com.zy.asrs.domain.vo.StationCycleLoopVo; import com.zy.asrs.entity.BasDevp; import com.zy.asrs.entity.BasStationOpt; import com.zy.asrs.entity.DeviceConfig; import com.zy.asrs.entity.DeviceDataLog; import com.zy.asrs.service.BasDevpService; import com.zy.asrs.service.BasStationOptService; import com.zy.asrs.service.StationCycleCapacityService; import com.zy.asrs.utils.Utils; import com.zy.common.model.NavigateNode; import com.zy.common.utils.NavigateUtils; import com.zy.common.utils.RedisUtil; import com.zy.core.cache.MessageQueue; import com.zy.core.cache.OutputQueue; import com.zy.core.enums.RedisKeyType; import com.zy.core.enums.SlaveType; import com.zy.core.enums.StationCommandType; import com.zy.core.model.CommandResponse; import com.zy.core.model.Task; import com.zy.core.model.command.StationCommand; import com.zy.core.model.protocol.StationProtocol; import com.zy.core.network.DeviceConnectPool; import com.zy.core.network.ZyStationConnectDriver; import com.zy.core.network.entity.ZyStationStatusEntity; import com.zy.core.thread.impl.v5.StationV5SegmentExecutor; import com.zy.core.utils.DeviceLogRedisKeyBuilder; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Data @Slf4j public class ZyStationV5Thread implements Runnable, com.zy.core.thread.StationThread { private static final int RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS = 60 * 60 * 24; private static final int SHORT_PATH_REPEAT_AVOID_THRESHOLD = 2; private static final int SMALL_LOOP_REPEAT_AVOID_THRESHOLD = 2; private List statusList = new ArrayList<>(); private DeviceConfig deviceConfig; private RedisUtil redisUtil; private ZyStationConnectDriver zyStationConnectDriver; private int deviceLogCollectTime = 200; private boolean initStatus = false; private long deviceDataLogTime = System.currentTimeMillis(); private ExecutorService executor = Executors.newFixedThreadPool(9999); private StationV5SegmentExecutor segmentExecutor; public ZyStationV5Thread(DeviceConfig deviceConfig, RedisUtil redisUtil) { this.deviceConfig = deviceConfig; this.redisUtil = redisUtil; this.segmentExecutor = new StationV5SegmentExecutor(deviceConfig, redisUtil, this::sendCommand); } @Override @SuppressWarnings("InfiniteLoopStatement") public void run() { this.connect(); deviceLogCollectTime = Utils.getDeviceLogCollectTime(); Thread readThread = new Thread(() -> { while (true) { try { if (initStatus) { deviceLogCollectTime = Utils.getDeviceLogCollectTime(); } readStatus(); Thread.sleep(100); } catch (Exception e) { log.error("StationV5Thread Fail", e); } } }, "DevpRead-" + deviceConfig.getDeviceNo()); readThread.start(); Thread processThread = new Thread(() -> { while (true) { try { int step = 1; Task task = MessageQueue.poll(SlaveType.Devp, deviceConfig.getDeviceNo()); if (task != null) { step = task.getStep(); } if (step == 2) { StationCommand cmd = (StationCommand) task.getData(); executor.submit(() -> segmentExecutor.execute(cmd)); } Thread.sleep(100); } catch (Exception e) { log.error("StationV5Process Fail", e); } } }, "DevpProcess-" + deviceConfig.getDeviceNo()); processThread.start(); } private void readStatus() { if (zyStationConnectDriver == null) { return; } if (statusList.isEmpty()) { BasDevpService basDevpService = null; try { basDevpService = SpringUtils.getBean(BasDevpService.class); } catch (Exception ignore) { } if (basDevpService == null) { return; } BasDevp basDevp = basDevpService .getOne(new QueryWrapper().eq("devp_no", deviceConfig.getDeviceNo())); if (basDevp == null) { return; } List list = JSONObject.parseArray(basDevp.getStationList(), ZyStationStatusEntity.class); for (ZyStationStatusEntity entity : list) { StationProtocol stationProtocol = new StationProtocol(); stationProtocol.setStationId(entity.getStationId()); statusList.add(stationProtocol); } initStatus = true; } List zyStationStatusEntities = zyStationConnectDriver.getStatus(); for (ZyStationStatusEntity statusEntity : zyStationStatusEntities) { for (StationProtocol stationProtocol : statusList) { if (stationProtocol.getStationId().equals(statusEntity.getStationId())) { stationProtocol.setTaskNo(statusEntity.getTaskNo()); stationProtocol.setTargetStaNo(statusEntity.getTargetStaNo()); stationProtocol.setAutoing(statusEntity.isAutoing()); stationProtocol.setLoading(statusEntity.isLoading()); stationProtocol.setInEnable(statusEntity.isInEnable()); stationProtocol.setOutEnable(statusEntity.isOutEnable()); stationProtocol.setEmptyMk(statusEntity.isEmptyMk()); stationProtocol.setFullPlt(statusEntity.isFullPlt()); stationProtocol.setPalletHeight(statusEntity.getPalletHeight()); stationProtocol.setError(statusEntity.getError()); stationProtocol.setErrorMsg(statusEntity.getErrorMsg()); stationProtocol.setBarcode(statusEntity.getBarcode()); stationProtocol.setRunBlock(statusEntity.isRunBlock()); stationProtocol.setEnableIn(statusEntity.isEnableIn()); stationProtocol.setWeight(statusEntity.getWeight()); stationProtocol.setTaskWriteIdx(statusEntity.getTaskWriteIdx()); } if (!Cools.isEmpty(stationProtocol.getSystemWarning())) { if (stationProtocol.isAutoing() && !stationProtocol.isLoading()) { stationProtocol.setSystemWarning(""); } } } } OutputQueue.DEVP.offer(MessageFormat.format("【{0}】[id:{1}] <<<<< 实时数据更新成功", DateUtils.convert(new Date()), deviceConfig.getDeviceNo())); if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) { DeviceDataLog deviceDataLog = new DeviceDataLog(); deviceDataLog.setOriginData(JSON.toJSONString(zyStationStatusEntities)); deviceDataLog.setWcsData(JSON.toJSONString(statusList)); deviceDataLog.setType(String.valueOf(SlaveType.Devp)); deviceDataLog.setDeviceNo(deviceConfig.getDeviceNo()); deviceDataLog.setCreateTime(new Date()); redisUtil.set(DeviceLogRedisKeyBuilder.build(deviceDataLog), deviceDataLog, 60 * 60 * 24); deviceDataLogTime = System.currentTimeMillis(); } } @Override public boolean connect() { zyStationConnectDriver = new ZyStationConnectDriver(deviceConfig, redisUtil); zyStationConnectDriver.start(); DeviceConnectPool.put(SlaveType.Devp, deviceConfig.getDeviceNo(), zyStationConnectDriver); return true; } @Override public void close() { if (zyStationConnectDriver != null) { zyStationConnectDriver.close(); } if (executor != null) { try { executor.shutdownNow(); } catch (Exception ignore) { } } } @Override public List getStatus() { return statusList; } @Override public Map getStatusMap() { Map map = new HashMap<>(); for (StationProtocol stationProtocol : statusList) { map.put(stationProtocol.getStationId(), stationProtocol); } return map; } @Override public StationCommand getCommand(StationCommandType commandType, Integer taskNo, Integer stationId, Integer targetStationId, Integer palletSize) { StationCommand stationCommand = new StationCommand(); stationCommand.setTaskNo(taskNo); stationCommand.setStationId(stationId); stationCommand.setTargetStaNo(targetStationId); stationCommand.setPalletSize(palletSize); stationCommand.setCommandType(commandType); if (commandType == StationCommandType.MOVE && !stationId.equals(targetStationId)) { List nodes = calcPathNavigateNodes(taskNo, stationId, targetStationId); return fillMoveCommandPath(stationCommand, nodes, taskNo, stationId, targetStationId); } return stationCommand; } @Override public synchronized StationCommand getRunBlockRerouteCommand(Integer taskNo, Integer stationId, Integer targetStationId, Integer palletSize) { if (taskNo == null || taskNo <= 0 || stationId == null || targetStationId == null) { return null; } if (Objects.equals(stationId, targetStationId)) { return getCommand(StationCommandType.MOVE, taskNo, stationId, targetStationId, palletSize); } RunBlockRerouteState rerouteState = loadRunBlockRerouteState(taskNo, stationId); TaskLoopRerouteState taskLoopRerouteState = loadTaskLoopRerouteState(taskNo); LoopIdentity currentLoopIdentity = resolveStationLoopIdentity(stationId); rerouteState.setTaskNo(taskNo); rerouteState.setBlockStationId(stationId); rerouteState.setLastTargetStationId(targetStationId); rerouteState.setPlanCount((rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount()) + 1); rerouteState.setLastPlanTime(System.currentTimeMillis()); taskLoopRerouteState.setTaskNo(taskNo); List> candidatePathList = calcCandidatePathNavigateNodes(taskNo, stationId, targetStationId); if (candidatePathList.isEmpty()) { saveRunBlockRerouteState(rerouteState); log.warn("输送线堵塞重规划失败,候选路径为空,taskNo={}, planCount={}, stationId={}, targetStationId={}", taskNo, rerouteState.getPlanCount(), stationId, targetStationId); return null; } StationCommand rerouteCommand = selectAvailableRerouteCommand( rerouteState, taskLoopRerouteState, currentLoopIdentity, candidatePathList, taskNo, stationId, targetStationId, palletSize ); if (rerouteCommand == null) { log.info("输送线堵塞重规划候选路线已全部试过,重置路线历史后重新开始,taskNo={}, planCount={}, stationId={}, targetStationId={}", taskNo, rerouteState.getPlanCount(), stationId, targetStationId); rerouteState.resetIssuedRoutes(); rerouteCommand = selectAvailableRerouteCommand( rerouteState, taskLoopRerouteState, currentLoopIdentity, candidatePathList, taskNo, stationId, targetStationId, palletSize ); } if (rerouteCommand != null) { saveRunBlockRerouteState(rerouteState); touchTaskLoopRerouteState(taskLoopRerouteState, currentLoopIdentity); saveTaskLoopRerouteState(taskLoopRerouteState); log.info("输送线堵塞重规划选中候选路线,taskNo={}, planCount={}, stationId={}, targetStationId={}, route={}", taskNo, rerouteState.getPlanCount(), stationId, targetStationId, JSON.toJSONString(rerouteCommand.getNavigatePath())); return rerouteCommand; } saveRunBlockRerouteState(rerouteState); log.warn("输送线堵塞重规划未找到可下发路线,taskNo={}, planCount={}, stationId={}, targetStationId={}, triedRoutes={}", taskNo, rerouteState.getPlanCount(), stationId, targetStationId, JSON.toJSONString(rerouteState.getIssuedRoutePathList())); return null; } @Override public CommandResponse sendCommand(StationCommand command) { CommandResponse commandResponse = null; try { commandResponse = zyStationConnectDriver.sendCommand(command); } catch (Exception e) { e.printStackTrace(); } finally { BasStationOptService optService = SpringUtils.getBean(BasStationOptService.class); List statusListEntity = zyStationConnectDriver.getStatus(); ZyStationStatusEntity matched = null; if (statusListEntity != null) { for (ZyStationStatusEntity entity : statusListEntity) { if (entity.getStationId() != null && entity.getStationId().equals(command.getStationId())) { matched = entity; break; } } } BasStationOpt basStationOpt = new BasStationOpt( command.getTaskNo(), command.getStationId(), new Date(), String.valueOf(command.getCommandType()), command.getStationId(), command.getTargetStaNo(), null, null, null, JSON.toJSONString(command), JSON.toJSONString(matched), 1, JSON.toJSONString(commandResponse) ); if (optService != null) { optService.save(basStationOpt); } } return commandResponse; } @Override public CommandResponse sendOriginCommand(String address, short[] data) { return zyStationConnectDriver.sendOriginCommand(address, data); } @Override public byte[] readOriginCommand(String address, int length) { return zyStationConnectDriver.readOriginCommand(address, length); } private List calcPathNavigateNodes(Integer taskNo, Integer startStationId, Integer targetStationId) { NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class); if (navigateUtils == null) { return new ArrayList<>(); } return navigateUtils.calcByStationId(startStationId, targetStationId, taskNo); } private List> calcCandidatePathNavigateNodes(Integer taskNo, Integer startStationId, Integer targetStationId) { NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class); if (navigateUtils == null) { return new ArrayList<>(); } return navigateUtils.calcCandidatePathByStationId(startStationId, targetStationId, taskNo); } private StationCommand buildMoveCommand(Integer taskNo, Integer stationId, Integer targetStationId, Integer palletSize, List nodes) { StationCommand stationCommand = new StationCommand(); stationCommand.setTaskNo(taskNo); stationCommand.setStationId(stationId); stationCommand.setTargetStaNo(targetStationId); stationCommand.setPalletSize(palletSize); stationCommand.setCommandType(StationCommandType.MOVE); return fillMoveCommandPath(stationCommand, nodes, taskNo, stationId, targetStationId); } private StationCommand fillMoveCommandPath(StationCommand stationCommand, List nodes, Integer taskNo, Integer stationId, Integer targetStationId) { List path = new ArrayList<>(); List liftTransferPath = new ArrayList<>(); for (NavigateNode node : nodes) { JSONObject valueObject; try { valueObject = JSONObject.parseObject(node.getNodeValue()); } catch (Exception ignore) { continue; } if (valueObject == null) { continue; } Integer stationNo = valueObject.getInteger("stationId"); if (stationNo == null) { continue; } path.add(stationNo); if (Boolean.TRUE.equals(node.getIsLiftTransferPoint())) { liftTransferPath.add(stationNo); } } if (path.isEmpty()) { log.warn("输送线命令生成失败,路径为空,taskNo={}, stationId={}, targetStationId={}", taskNo, stationId, targetStationId); return null; } stationCommand.setNavigatePath(path); stationCommand.setLiftTransferPath(liftTransferPath); stationCommand.setTargetStaNo(path.get(path.size() - 1)); return stationCommand; } private StationCommand selectAvailableRerouteCommand(RunBlockRerouteState rerouteState, TaskLoopRerouteState taskLoopRerouteState, LoopIdentity currentLoopIdentity, List> candidatePathList, Integer taskNo, Integer stationId, Integer targetStationId, Integer palletSize) { if (rerouteState == null || candidatePathList == null || candidatePathList.isEmpty()) { return null; } Set issuedRouteSignatureSet = rerouteState.getIssuedRouteSignatureSet(); int currentLoopIssuedCount = resolveCurrentLoopIssuedCount(taskLoopRerouteState, currentLoopIdentity); List candidateCommandList = new ArrayList<>(); for (List candidatePath : candidatePathList) { StationCommand rerouteCommand = buildMoveCommand(taskNo, stationId, targetStationId, palletSize, candidatePath); if (rerouteCommand == null || rerouteCommand.getNavigatePath() == null || rerouteCommand.getNavigatePath().isEmpty()) { continue; } String routeSignature = buildPathSignature(rerouteCommand.getNavigatePath()); if (Cools.isEmpty(routeSignature)) { continue; } RerouteCandidateCommand candidateCommand = new RerouteCandidateCommand(); candidateCommand.setCommand(rerouteCommand); candidateCommand.setRouteSignature(routeSignature); candidateCommand.setPathLength(rerouteCommand.getNavigatePath().size()); candidateCommand.setIssuedCount(rerouteState.getRouteIssueCountMap().getOrDefault(routeSignature, 0)); candidateCommand.setLoopFingerprint(currentLoopIdentity.getLoopFingerprint()); candidateCommand.setLoopIssuedCount(currentLoopIssuedCount); candidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit(rerouteCommand.getNavigatePath(), currentLoopIdentity.getStationIdSet())); candidateCommandList.add(candidateCommand); } if (candidateCommandList.isEmpty()) { return null; } List orderedCandidateCommandList = reorderCandidateCommandsForLoopRelease(candidateCommandList); for (RerouteCandidateCommand candidateCommand : orderedCandidateCommandList) { if (candidateCommand == null || candidateCommand.getCommand() == null) { continue; } if (issuedRouteSignatureSet.contains(candidateCommand.getRouteSignature())) { continue; } StationCommand rerouteCommand = candidateCommand.getCommand(); issuedRouteSignatureSet.add(candidateCommand.getRouteSignature()); rerouteState.getIssuedRoutePathList().add(new ArrayList<>(rerouteCommand.getNavigatePath())); rerouteState.setLastSelectedRoute(new ArrayList<>(rerouteCommand.getNavigatePath())); rerouteState.getRouteIssueCountMap().put( candidateCommand.getRouteSignature(), rerouteState.getRouteIssueCountMap().getOrDefault(candidateCommand.getRouteSignature(), 0) + 1 ); return rerouteCommand; } return null; } private List reorderCandidateCommandsForLoopRelease(List candidateCommandList) { if (candidateCommandList == null || candidateCommandList.isEmpty()) { return new ArrayList<>(); } int shortestPathLength = Integer.MAX_VALUE; int shortestPathLoopHitCount = Integer.MAX_VALUE; boolean shortestPathOverused = false; boolean currentLoopOverused = false; boolean hasLongerCandidate = false; for (RerouteCandidateCommand candidateCommand : candidateCommandList) { if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) { continue; } shortestPathLength = Math.min(shortestPathLength, candidateCommand.getPathLength()); } if (shortestPathLength == Integer.MAX_VALUE) { return candidateCommandList; } for (RerouteCandidateCommand candidateCommand : candidateCommandList) { if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) { continue; } if (candidateCommand.getPathLength() == shortestPathLength) { shortestPathLoopHitCount = Math.min(shortestPathLoopHitCount, safeInt(candidateCommand.getCurrentLoopHitCount())); } if (candidateCommand.getPathLength() > shortestPathLength) { hasLongerCandidate = true; } if (candidateCommand.getPathLength() == shortestPathLength && candidateCommand.getIssuedCount() != null && candidateCommand.getIssuedCount() >= SHORT_PATH_REPEAT_AVOID_THRESHOLD) { shortestPathOverused = true; } if (!Cools.isEmpty(candidateCommand.getLoopFingerprint()) && candidateCommand.getLoopIssuedCount() != null && candidateCommand.getLoopIssuedCount() >= SMALL_LOOP_REPEAT_AVOID_THRESHOLD) { currentLoopOverused = true; } } if (!shortestPathOverused && !currentLoopOverused) { return candidateCommandList; } if (shortestPathLoopHitCount == Integer.MAX_VALUE) { shortestPathLoopHitCount = 0; } boolean hasLoopExitCandidate = false; for (RerouteCandidateCommand candidateCommand : candidateCommandList) { if (candidateCommand == null) { continue; } if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) { hasLoopExitCandidate = true; break; } } if (!hasLongerCandidate && !hasLoopExitCandidate) { return candidateCommandList; } List reorderedList = new ArrayList<>(); if (currentLoopOverused && hasLoopExitCandidate) { for (RerouteCandidateCommand candidateCommand : candidateCommandList) { if (candidateCommand == null) { continue; } if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) { appendCandidateIfAbsent(reorderedList, candidateCommand); } } } for (RerouteCandidateCommand candidateCommand : candidateCommandList) { if (candidateCommand != null && candidateCommand.getPathLength() != null && candidateCommand.getPathLength() > shortestPathLength) { appendCandidateIfAbsent(reorderedList, candidateCommand); } } for (RerouteCandidateCommand candidateCommand : candidateCommandList) { if (candidateCommand == null || candidateCommand.getPathLength() == null) { continue; } appendCandidateIfAbsent(reorderedList, candidateCommand); } return reorderedList; } private void appendCandidateIfAbsent(List reorderedList, RerouteCandidateCommand candidateCommand) { if (reorderedList == null || candidateCommand == null) { return; } if (!reorderedList.contains(candidateCommand)) { reorderedList.add(candidateCommand); } } private RunBlockRerouteState loadRunBlockRerouteState(Integer taskNo, Integer blockStationId) { if (redisUtil == null || taskNo == null || taskNo <= 0 || blockStationId == null || blockStationId <= 0) { return new RunBlockRerouteState(); } Object stateObj = redisUtil.get(buildRunBlockRerouteStateKey(taskNo, blockStationId)); if (stateObj == null) { return new RunBlockRerouteState(); } try { RunBlockRerouteState state = JSON.parseObject(String.valueOf(stateObj), RunBlockRerouteState.class); return state == null ? new RunBlockRerouteState() : state.normalize(); } catch (Exception ignore) { return new RunBlockRerouteState(); } } private void saveRunBlockRerouteState(RunBlockRerouteState rerouteState) { if (redisUtil == null || rerouteState == null || rerouteState.getTaskNo() == null || rerouteState.getTaskNo() <= 0 || rerouteState.getBlockStationId() == null || rerouteState.getBlockStationId() <= 0) { return; } rerouteState.normalize(); redisUtil.set( buildRunBlockRerouteStateKey(rerouteState.getTaskNo(), rerouteState.getBlockStationId()), JSON.toJSONString(rerouteState), RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS ); } private TaskLoopRerouteState loadTaskLoopRerouteState(Integer taskNo) { if (redisUtil == null || taskNo == null || taskNo <= 0) { return new TaskLoopRerouteState(); } Object stateObj = redisUtil.get(RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskNo); if (stateObj == null) { return new TaskLoopRerouteState(); } try { TaskLoopRerouteState state = JSON.parseObject(String.valueOf(stateObj), TaskLoopRerouteState.class); return state == null ? new TaskLoopRerouteState() : state.normalize(); } catch (Exception ignore) { return new TaskLoopRerouteState(); } } private void saveTaskLoopRerouteState(TaskLoopRerouteState taskLoopRerouteState) { if (redisUtil == null || taskLoopRerouteState == null || taskLoopRerouteState.getTaskNo() == null || taskLoopRerouteState.getTaskNo() <= 0) { return; } taskLoopRerouteState.normalize(); redisUtil.set( RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskLoopRerouteState.getTaskNo(), JSON.toJSONString(taskLoopRerouteState), RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS ); } private void touchTaskLoopRerouteState(TaskLoopRerouteState taskLoopRerouteState, LoopIdentity currentLoopIdentity) { if (taskLoopRerouteState == null || currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) { return; } taskLoopRerouteState.getLoopIssueCountMap().put( currentLoopIdentity.getLoopFingerprint(), taskLoopRerouteState.getLoopIssueCountMap().getOrDefault(currentLoopIdentity.getLoopFingerprint(), 0) + 1 ); taskLoopRerouteState.setLastLoopFingerprint(currentLoopIdentity.getLoopFingerprint()); taskLoopRerouteState.setLastIssueTime(System.currentTimeMillis()); } private int resolveCurrentLoopIssuedCount(TaskLoopRerouteState taskLoopRerouteState, LoopIdentity currentLoopIdentity) { if (taskLoopRerouteState == null || currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) { return 0; } return taskLoopRerouteState.getLoopIssueCountMap().getOrDefault(currentLoopIdentity.getLoopFingerprint(), 0); } private int countCurrentLoopStationHit(List path, Set currentLoopStationIdSet) { if (path == null || path.isEmpty() || currentLoopStationIdSet == null || currentLoopStationIdSet.isEmpty()) { return 0; } int hitCount = 0; for (Integer stationId : path) { if (stationId != null && currentLoopStationIdSet.contains(stationId)) { hitCount++; } } return hitCount; } private String buildPathSignature(List path) { if (path == null || path.isEmpty()) { return ""; } StringBuilder builder = new StringBuilder(); for (Integer stationNo : path) { if (stationNo == null) { continue; } if (builder.length() > 0) { builder.append("->"); } builder.append(stationNo); } return builder.toString(); } private String buildRunBlockRerouteStateKey(Integer taskNo, Integer blockStationId) { return RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + taskNo + "_" + blockStationId; } private LoopIdentity resolveStationLoopIdentity(Integer stationId) { if (stationId == null || stationId <= 0) { return LoopIdentity.empty(); } try { StationCycleCapacityService stationCycleCapacityService = SpringUtils.getBean(StationCycleCapacityService.class); if (stationCycleCapacityService == null) { return LoopIdentity.empty(); } StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot(); if (capacityVo == null || capacityVo.getLoopList() == null || capacityVo.getLoopList().isEmpty()) { return LoopIdentity.empty(); } for (StationCycleLoopVo loopVo : capacityVo.getLoopList()) { List loopStationIdList = normalizeLoopStationIdList(loopVo == null ? null : loopVo.getStationIdList()); if (loopStationIdList.isEmpty() || !loopStationIdList.contains(stationId)) { continue; } return new LoopIdentity(buildLoopFingerprint(loopStationIdList), new HashSet<>(loopStationIdList)); } } catch (Exception ignore) { } return LoopIdentity.empty(); } private List normalizeLoopStationIdList(List stationIdList) { if (stationIdList == null || stationIdList.isEmpty()) { return new ArrayList<>(); } List normalizedList = new ArrayList<>(); Set seenStationIdSet = new HashSet<>(); for (Integer stationId : stationIdList) { if (stationId == null || stationId <= 0 || !seenStationIdSet.add(stationId)) { continue; } normalizedList.add(stationId); } Collections.sort(normalizedList); return normalizedList; } private String buildLoopFingerprint(List stationIdList) { if (stationIdList == null || stationIdList.isEmpty()) { return ""; } StringBuilder builder = new StringBuilder(); for (Integer stationId : stationIdList) { if (stationId == null) { continue; } if (builder.length() > 0) { builder.append("|"); } builder.append(stationId); } return builder.toString(); } private int safeInt(Integer value) { return value == null ? 0 : value; } @Data private static class RunBlockRerouteState { private Integer taskNo; private Integer blockStationId; private Integer planCount = 0; private Integer lastTargetStationId; private Long lastPlanTime; private List> issuedRoutePathList = new ArrayList<>(); private List lastSelectedRoute = new ArrayList<>(); private Set issuedRouteSignatureSet = new LinkedHashSet<>(); private Map routeIssueCountMap = new HashMap<>(); private RunBlockRerouteState normalize() { if (planCount == null || planCount < 0) { planCount = 0; } if (issuedRoutePathList == null) { issuedRoutePathList = new ArrayList<>(); } if (lastSelectedRoute == null) { lastSelectedRoute = new ArrayList<>(); } if (issuedRouteSignatureSet == null) { issuedRouteSignatureSet = new LinkedHashSet<>(); } if (routeIssueCountMap == null) { routeIssueCountMap = new HashMap<>(); } for (List routePath : issuedRoutePathList) { if (routePath == null || routePath.isEmpty()) { continue; } String pathSignature = buildPathSignatureText(routePath); if (!Cools.isEmpty(pathSignature)) { issuedRouteSignatureSet.add(pathSignature); routeIssueCountMap.putIfAbsent(pathSignature, 1); } } return this; } private void resetIssuedRoutes() { this.issuedRoutePathList = new ArrayList<>(); this.lastSelectedRoute = new ArrayList<>(); this.issuedRouteSignatureSet = new LinkedHashSet<>(); } private static String buildPathSignatureText(List routePath) { if (routePath == null || routePath.isEmpty()) { return ""; } StringBuilder builder = new StringBuilder(); for (Integer stationId : routePath) { if (stationId == null) { continue; } if (builder.length() > 0) { builder.append("->"); } builder.append(stationId); } return builder.toString(); } } @Data private static class RerouteCandidateCommand { private StationCommand command; private String routeSignature; private Integer pathLength; private Integer issuedCount; private String loopFingerprint; private Integer loopIssuedCount; private Integer currentLoopHitCount; } @Data private static class TaskLoopRerouteState { private Integer taskNo; private String lastLoopFingerprint; private Long lastIssueTime; private Map loopIssueCountMap = new HashMap<>(); private TaskLoopRerouteState normalize() { if (loopIssueCountMap == null) { loopIssueCountMap = new HashMap<>(); } return this; } } @Data private static class LoopIdentity { private String loopFingerprint; private Set stationIdSet = new HashSet<>(); private LoopIdentity(String loopFingerprint, Set stationIdSet) { this.loopFingerprint = loopFingerprint; this.stationIdSet = stationIdSet == null ? new HashSet<>() : stationIdSet; } private static LoopIdentity empty() { return new LoopIdentity("", new HashSet<>()); } } }