package com.zy.core.thread.impl.v5; import com.alibaba.fastjson.JSON; import com.core.common.Cools; import com.zy.common.utils.RedisUtil; import com.zy.core.enums.RedisKeyType; import com.zy.core.model.command.StationCommand; import com.zy.core.service.StationTaskLoopService; import lombok.Data; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; public class StationV5RunBlockReroutePlanner { private static final int RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS = 60 * 60 * 24; private static final int SHORT_PATH_REPEAT_AVOID_THRESHOLD = 2; private final RedisUtil redisUtil; public StationV5RunBlockReroutePlanner(RedisUtil redisUtil) { this.redisUtil = redisUtil; } public PlanResult plan(Integer taskNo, Integer blockStationId, StationTaskLoopService.LoopEvaluation loopEvaluation, List candidateCommands) { RunBlockRerouteState rerouteState = loadRunBlockRerouteState(taskNo, blockStationId); rerouteState.setTaskNo(taskNo); rerouteState.setBlockStationId(blockStationId); rerouteState.setPlanCount((rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount()) + 1); StationCommand rerouteCommand = selectAvailableRerouteCommand(rerouteState, loopEvaluation, candidateCommands); if (rerouteCommand == null && candidateCommands != null && !candidateCommands.isEmpty()) { rerouteState.resetIssuedRoutes(); rerouteCommand = selectAvailableRerouteCommand(rerouteState, loopEvaluation, candidateCommands); } saveRunBlockRerouteState(rerouteState); return new PlanResult( rerouteCommand, rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount(), copyRoutePathList(rerouteState.getIssuedRoutePathList()) ); } private StationCommand selectAvailableRerouteCommand(RunBlockRerouteState rerouteState, StationTaskLoopService.LoopEvaluation loopEvaluation, List candidateCommands) { if (rerouteState == null || candidateCommands == null || candidateCommands.isEmpty()) { return null; } Set issuedRouteSignatureSet = rerouteState.getIssuedRouteSignatureSet(); StationTaskLoopService.LoopIdentitySnapshot loopIdentity = loopEvaluation == null ? StationTaskLoopService.LoopIdentitySnapshot.empty() : loopEvaluation.getLoopIdentity(); if (loopIdentity == null) { loopIdentity = StationTaskLoopService.LoopIdentitySnapshot.empty(); } List candidateCommandList = new ArrayList<>(); for (StationCommand candidateCommand : candidateCommands) { if (candidateCommand == null || candidateCommand.getNavigatePath() == null || candidateCommand.getNavigatePath().isEmpty()) { continue; } String routeSignature = buildPathSignature(candidateCommand.getNavigatePath()); if (Cools.isEmpty(routeSignature)) { continue; } RerouteCandidateCommand rerouteCandidateCommand = new RerouteCandidateCommand(); rerouteCandidateCommand.setCommand(candidateCommand); rerouteCandidateCommand.setRouteSignature(routeSignature); rerouteCandidateCommand.setPathLength(candidateCommand.getNavigatePath().size()); rerouteCandidateCommand.setIssuedCount(rerouteState.getRouteIssueCountMap().getOrDefault(routeSignature, 0)); rerouteCandidateCommand.setLoopFingerprint(loopIdentity.getLoopFingerprint()); rerouteCandidateCommand.setLoopTriggered(loopEvaluation != null && loopEvaluation.isLargeLoopTriggered()); rerouteCandidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit( candidateCommand.getNavigatePath(), loopIdentity.getStationIdSet() )); candidateCommandList.add(rerouteCandidateCommand); } 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()) && Boolean.TRUE.equals(candidateCommand.getLoopTriggered())) { 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 String buildRunBlockRerouteStateKey(Integer taskNo, Integer blockStationId) { return RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + taskNo + "_" + blockStationId; } 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 int safeInt(Integer value) { return value == null ? 0 : value; } private List> copyRoutePathList(List> source) { List> copy = new ArrayList<>(); if (source == null || source.isEmpty()) { return copy; } for (List route : source) { copy.add(route == null ? new ArrayList<>() : new ArrayList<>(route)); } return copy; } @Data public static class PlanResult { private final StationCommand command; private final int planCount; private final List> issuedRoutePathList; } @Data private static class RunBlockRerouteState { private Integer taskNo; private Integer blockStationId; private Integer planCount = 0; 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 Boolean loopTriggered; private Integer currentLoopHitCount; } }