package com.zy.core.service; import com.alibaba.fastjson.JSON; import com.core.common.Cools; import com.zy.asrs.domain.vo.StationCycleCapacityVo; import com.zy.asrs.domain.vo.StationCycleLoopVo; import com.zy.asrs.service.StationCycleCapacityService; import com.zy.common.utils.NavigateUtils; import com.zy.common.utils.RedisUtil; import com.zy.core.enums.RedisKeyType; import com.zy.core.trace.StationTaskTraceRegistry; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; 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; @Component public class StationTaskLoopService { private static final int LOOP_STATE_EXPIRE_SECONDS = 60 * 60 * 24; private static final int LOOP_REPEAT_TRIGGER_COUNT = 3; private static final int LOCAL_LOOP_NEIGHBOR_HOP = 3; private static final String OUT_ORDER_SCOPE_TYPE = "outOrderCircle"; @Autowired private RedisUtil redisUtil; @Autowired private NavigateUtils navigateUtils; @Autowired private StationCycleCapacityService stationCycleCapacityService; @Autowired private StationTaskTraceRegistry stationTaskTraceRegistry; public LoopEvaluation evaluateLoop(Integer taskNo, Integer stationId, boolean includePendingIssue) { return evaluateLoop(taskNo, stationId, includePendingIssue, null, null); } public LoopEvaluation evaluateLoop(Integer taskNo, Integer stationId, boolean includePendingIssue, List customStationIdList, String customScopeType) { LoopIdentitySnapshot loopIdentity = buildEffectiveLoopIdentity(stationId, customStationIdList, customScopeType); if (taskNo == null || taskNo <= 0) { return new LoopEvaluation(taskNo, stationId, loopIdentity, 0, 0, false); } TaskLoopState state = loadTaskLoopState(taskNo); int currentLoopIssueCount = resolveCurrentLoopIssueCount(state, loopIdentity); int expectedLoopIssueCount = includePendingIssue && loopIdentity.hasFingerprint() ? currentLoopIssueCount + 1 : currentLoopIssueCount; return new LoopEvaluation( taskNo, stationId, loopIdentity, currentLoopIssueCount, expectedLoopIssueCount, shouldTriggerLargeLoop(expectedLoopIssueCount) ); } public void recordLoopIssue(LoopEvaluation evaluation, String triggerSource) { if (evaluation == null || evaluation.getTaskNo() == null || evaluation.getTaskNo() <= 0 || evaluation.getLoopIdentity() == null || !evaluation.getLoopIdentity().hasFingerprint()) { return; } TaskLoopState state = loadTaskLoopState(evaluation.getTaskNo()); int nextLoopIssueCount = state.getLoopIssueCountMap().getOrDefault(evaluation.getLoopIdentity().getLoopFingerprint(), 0) + 1; state.getLoopIssueCountMap().put(evaluation.getLoopIdentity().getLoopFingerprint(), nextLoopIssueCount); state.setTaskNo(evaluation.getTaskNo()); state.setLastLoopFingerprint(evaluation.getLoopIdentity().getLoopFingerprint()); state.setLastIssueTime(System.currentTimeMillis()); saveTaskLoopState(state); syncTaskTraceLoopAlert(evaluation, nextLoopIssueCount, triggerSource); } public boolean shouldTriggerLargeLoop(int loopIssueCount) { return loopIssueCount >= LOOP_REPEAT_TRIGGER_COUNT; } private void syncTaskTraceLoopAlert(LoopEvaluation evaluation, int loopIssueCount, String triggerSource) { if (stationTaskTraceRegistry == null || evaluation == null || evaluation.getTaskNo() == null || evaluation.getTaskNo() <= 0) { return; } LoopIdentitySnapshot loopIdentity = evaluation.getLoopIdentity(); boolean active = loopIdentity != null && loopIdentity.hasFingerprint() && shouldTriggerLargeLoop(loopIssueCount); String loopAlertType = resolveLoopAlertType(loopIdentity); String loopAlertText = buildLoopAlertText(loopAlertType, loopIdentity, loopIssueCount); Map details = new HashMap<>(); details.put("stationId", evaluation.getStationId()); details.put("loopScopeType", loopIdentity == null ? "" : loopIdentity.getScopeType()); details.put("loopStationCount", loopIdentity == null ? 0 : loopIdentity.getLocalStationCount()); details.put("sourceLoopStationCount", loopIdentity == null ? 0 : loopIdentity.getSourceLoopStationCount()); details.put("loopRepeatCount", loopIssueCount); details.put("loopTriggerSource", triggerSource); stationTaskTraceRegistry.updateLoopHint( evaluation.getTaskNo(), active, loopAlertType, loopAlertText, loopIssueCount, details ); } private String resolveLoopAlertType(LoopIdentitySnapshot loopIdentity) { if (loopIdentity == null || !loopIdentity.hasFingerprint()) { return ""; } if (OUT_ORDER_SCOPE_TYPE.equals(loopIdentity.getScopeType())) { return "OUT_ORDER_CIRCLE"; } return "wholeLoop".equals(loopIdentity.getScopeType()) ? "LARGE_LOOP" : "SMALL_LOOP"; } private String buildLoopAlertText(String loopAlertType, LoopIdentitySnapshot loopIdentity, int loopIssueCount) { if (Cools.isEmpty(loopAlertType) || loopIdentity == null || !shouldTriggerLargeLoop(loopIssueCount)) { return ""; } String typeLabel; if ("OUT_ORDER_CIRCLE".equals(loopAlertType)) { typeLabel = "排序环线"; } else if ("LARGE_LOOP".equals(loopAlertType)) { typeLabel = "大环线"; } else { typeLabel = "小环线"; } return typeLabel + "绕圈预警,累计绕圈" + loopIssueCount + "次,已启用绕大圈,当前识别范围" + loopIdentity.getLocalStationCount() + "站"; } private LoopIdentitySnapshot buildEffectiveLoopIdentity(Integer stationId, List customStationIdList, String customScopeType) { LoopIdentitySnapshot customLoopIdentity = buildCustomLoopIdentity(customStationIdList, customScopeType); if (customLoopIdentity.hasFingerprint()) { return customLoopIdentity; } return resolveStationLoopIdentity(stationId); } private LoopIdentitySnapshot buildCustomLoopIdentity(List customStationIdList, String customScopeType) { List normalizedStationIdList = normalizeStationIdList(customStationIdList); if (normalizedStationIdList.isEmpty()) { return LoopIdentitySnapshot.empty(); } String scopeType = Cools.isEmpty(customScopeType) ? OUT_ORDER_SCOPE_TYPE : customScopeType; return new LoopIdentitySnapshot( buildLoopFingerprint(normalizedStationIdList), new HashSet<>(normalizedStationIdList), normalizedStationIdList.size(), normalizedStationIdList.size(), scopeType ); } public LoopIdentitySnapshot resolveStationLoopIdentity(Integer stationId) { if (stationId == null || stationId <= 0) { return LoopIdentitySnapshot.empty(); } try { if (stationCycleCapacityService != null) { StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot(); if (capacityVo != null && capacityVo.getLoopList() != null && !capacityVo.getLoopList().isEmpty()) { for (StationCycleLoopVo loopVo : capacityVo.getLoopList()) { List loopStationIdList = normalizeStationIdList(loopVo == null ? null : loopVo.getStationIdList()); if (loopStationIdList.isEmpty() || !loopStationIdList.contains(stationId)) { continue; } Set loopStationIdSet = new HashSet<>(loopStationIdList); Map> stationGraph = loadUndirectedStationGraph(); List localCycleStationIdList = resolveLocalCycleStationIdList(stationId, loopStationIdSet, stationGraph); if (localCycleStationIdList.size() >= 3) { return buildLoopIdentity(localCycleStationIdList, loopStationIdList.size(), "localCycle"); } List localNeighborhoodStationIdList = resolveLocalNeighborhoodStationIdList(stationId, loopStationIdSet, stationGraph); if (localNeighborhoodStationIdList.size() >= 3 && localNeighborhoodStationIdList.size() < loopStationIdList.size()) { return buildLoopIdentity(localNeighborhoodStationIdList, loopStationIdList.size(), "localNeighborhood"); } return buildLoopIdentity(loopStationIdList, loopStationIdList.size(), "wholeLoop"); } } } } catch (Exception ignore) { } return buildStationFallbackLoopIdentity(stationId); } private LoopIdentitySnapshot buildStationFallbackLoopIdentity(Integer stationId) { if (stationId == null || stationId <= 0) { return LoopIdentitySnapshot.empty(); } List stationIdList = new ArrayList<>(); stationIdList.add(stationId); return new LoopIdentitySnapshot( "station:" + stationId, new HashSet<>(stationIdList), 1, 1, "stationFallback" ); } private LoopIdentitySnapshot buildLoopIdentity(List stationIdList, int sourceLoopStationCount, String scopeType) { List normalizedStationIdList = normalizeStationIdList(stationIdList); if (normalizedStationIdList.isEmpty()) { return LoopIdentitySnapshot.empty(); } return new LoopIdentitySnapshot( buildLoopFingerprint(normalizedStationIdList), new HashSet<>(normalizedStationIdList), sourceLoopStationCount, normalizedStationIdList.size(), scopeType ); } private List resolveLocalCycleStationIdList(Integer stationId, Set loopStationIdSet, Map> stationGraph) { if (stationId == null || stationId <= 0 || loopStationIdSet == null || loopStationIdSet.isEmpty() || stationGraph == null || stationGraph.isEmpty()) { return new ArrayList<>(); } Set localNeighborhoodStationIdSet = collectLoopNeighborhoodStationIdSet( stationId, loopStationIdSet, stationGraph, LOCAL_LOOP_NEIGHBOR_HOP ); if (localNeighborhoodStationIdSet.size() < 3) { return new ArrayList<>(); } Set directNeighborStationIdSet = filterLoopNeighborStationIdSet( stationGraph.getOrDefault(stationId, Collections.emptySet()), localNeighborhoodStationIdSet, stationId ); if (directNeighborStationIdSet.size() < 2) { return new ArrayList<>(); } List bestCycleStationIdList = new ArrayList<>(); List neighborStationIdList = new ArrayList<>(directNeighborStationIdSet); for (int i = 0; i < neighborStationIdList.size(); i++) { Integer leftNeighborStationId = neighborStationIdList.get(i); if (leftNeighborStationId == null) { continue; } for (int j = i + 1; j < neighborStationIdList.size(); j++) { Integer rightNeighborStationId = neighborStationIdList.get(j); if (rightNeighborStationId == null) { continue; } List pathBetweenNeighbors = findShortestScopePath( leftNeighborStationId, rightNeighborStationId, stationId, localNeighborhoodStationIdSet, stationGraph ); if (pathBetweenNeighbors.isEmpty()) { continue; } List cycleStationIdList = new ArrayList<>(); cycleStationIdList.add(stationId); cycleStationIdList.addAll(pathBetweenNeighbors); cycleStationIdList = normalizeStationIdList(cycleStationIdList); if (cycleStationIdList.size() < 3) { continue; } if (bestCycleStationIdList.isEmpty() || cycleStationIdList.size() < bestCycleStationIdList.size()) { bestCycleStationIdList = cycleStationIdList; } } } return bestCycleStationIdList; } private List resolveLocalNeighborhoodStationIdList(Integer stationId, Set loopStationIdSet, Map> stationGraph) { return normalizeStationIdList(new ArrayList<>(collectLoopNeighborhoodStationIdSet( stationId, loopStationIdSet, stationGraph, LOCAL_LOOP_NEIGHBOR_HOP ))); } private Set collectLoopNeighborhoodStationIdSet(Integer stationId, Set loopStationIdSet, Map> stationGraph, int maxHop) { Set neighborhoodStationIdSet = new LinkedHashSet<>(); if (stationId == null || stationId <= 0 || loopStationIdSet == null || loopStationIdSet.isEmpty() || !loopStationIdSet.contains(stationId) || stationGraph == null || stationGraph.isEmpty() || maxHop < 0) { return neighborhoodStationIdSet; } Deque queue = new ArrayDeque<>(); queue.offer(new StationHopNode(stationId, 0)); neighborhoodStationIdSet.add(stationId); while (!queue.isEmpty()) { StationHopNode current = queue.poll(); if (current == null || current.getHop() >= maxHop) { continue; } Set neighborStationIdSet = filterLoopNeighborStationIdSet( stationGraph.getOrDefault(current.getStationId(), Collections.emptySet()), loopStationIdSet, null ); for (Integer neighborStationId : neighborStationIdSet) { if (neighborStationId == null || !neighborhoodStationIdSet.add(neighborStationId)) { continue; } queue.offer(new StationHopNode(neighborStationId, current.getHop() + 1)); } } return neighborhoodStationIdSet; } private Set filterLoopNeighborStationIdSet(Set candidateNeighborStationIdSet, Set allowedStationIdSet, Integer excludedStationId) { Set result = new LinkedHashSet<>(); if (candidateNeighborStationIdSet == null || candidateNeighborStationIdSet.isEmpty() || allowedStationIdSet == null || allowedStationIdSet.isEmpty()) { return result; } for (Integer stationId : candidateNeighborStationIdSet) { if (stationId == null || !allowedStationIdSet.contains(stationId) || (excludedStationId != null && excludedStationId.equals(stationId))) { continue; } result.add(stationId); } return result; } private List findShortestScopePath(Integer startStationId, Integer endStationId, Integer excludedStationId, Set allowedStationIdSet, Map> stationGraph) { if (startStationId == null || endStationId == null || Objects.equals(startStationId, excludedStationId) || Objects.equals(endStationId, excludedStationId) || allowedStationIdSet == null || !allowedStationIdSet.contains(startStationId) || !allowedStationIdSet.contains(endStationId) || stationGraph == null || stationGraph.isEmpty()) { return new ArrayList<>(); } Deque queue = new ArrayDeque<>(); Map parentMap = new HashMap<>(); Set visitedStationIdSet = new HashSet<>(); queue.offer(startStationId); visitedStationIdSet.add(startStationId); while (!queue.isEmpty()) { Integer currentStationId = queue.poll(); if (currentStationId == null) { continue; } if (Objects.equals(currentStationId, endStationId)) { break; } Set neighborStationIdSet = filterLoopNeighborStationIdSet( stationGraph.getOrDefault(currentStationId, Collections.emptySet()), allowedStationIdSet, excludedStationId ); for (Integer neighborStationId : neighborStationIdSet) { if (neighborStationId == null || !visitedStationIdSet.add(neighborStationId)) { continue; } parentMap.put(neighborStationId, currentStationId); queue.offer(neighborStationId); } } if (!visitedStationIdSet.contains(endStationId)) { return new ArrayList<>(); } List pathStationIdList = new ArrayList<>(); Integer cursorStationId = endStationId; while (cursorStationId != null) { pathStationIdList.add(cursorStationId); if (Objects.equals(cursorStationId, startStationId)) { break; } cursorStationId = parentMap.get(cursorStationId); } if (pathStationIdList.isEmpty() || !Objects.equals(pathStationIdList.get(pathStationIdList.size() - 1), startStationId)) { return new ArrayList<>(); } Collections.reverse(pathStationIdList); return pathStationIdList; } private Map> loadUndirectedStationGraph() { if (navigateUtils == null) { return new HashMap<>(); } Map> stationGraph = navigateUtils.loadUndirectedStationGraphSnapshot(); return stationGraph == null ? new HashMap<>() : stationGraph; } private TaskLoopState loadTaskLoopState(Integer taskNo) { if (redisUtil == null || taskNo == null || taskNo <= 0) { return new TaskLoopState(); } Object stateObj = redisUtil.get(RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskNo); if (stateObj == null) { return new TaskLoopState(); } try { TaskLoopState state = JSON.parseObject(String.valueOf(stateObj), TaskLoopState.class); return state == null ? new TaskLoopState() : state.normalize(); } catch (Exception ignore) { return new TaskLoopState(); } } private void saveTaskLoopState(TaskLoopState taskLoopState) { if (redisUtil == null || taskLoopState == null || taskLoopState.getTaskNo() == null || taskLoopState.getTaskNo() <= 0) { return; } taskLoopState.normalize(); redisUtil.set( RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskLoopState.getTaskNo(), JSON.toJSONString(taskLoopState), LOOP_STATE_EXPIRE_SECONDS ); } private int resolveCurrentLoopIssueCount(TaskLoopState taskLoopState, LoopIdentitySnapshot loopIdentity) { if (taskLoopState == null || loopIdentity == null || !loopIdentity.hasFingerprint()) { return 0; } return taskLoopState.getLoopIssueCountMap().getOrDefault(loopIdentity.getLoopFingerprint(), 0); } private List normalizeStationIdList(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(); } @Data public static class LoopEvaluation { private final Integer taskNo; private final Integer stationId; private final LoopIdentitySnapshot loopIdentity; private final int currentLoopIssueCount; private final int expectedLoopIssueCount; private final boolean largeLoopTriggered; } @Data public static class LoopIdentitySnapshot { private final String loopFingerprint; private final Set stationIdSet; private final int sourceLoopStationCount; private final int localStationCount; private final String scopeType; public boolean hasFingerprint() { return !Cools.isEmpty(loopFingerprint); } public static LoopIdentitySnapshot empty() { return new LoopIdentitySnapshot("", new HashSet<>(), 0, 0, "none"); } } @Data private static class TaskLoopState { private Integer taskNo; private String lastLoopFingerprint; private Long lastIssueTime; private Map loopIssueCountMap = new HashMap<>(); private TaskLoopState normalize() { if (loopIssueCountMap == null) { loopIssueCountMap = new HashMap<>(); } return this; } } @Data private static class StationHopNode { private final Integer stationId; private final int hop; } }