| New file |
| | |
| | | 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<Integer> 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<String, Object> 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<Integer> customStationIdList, |
| | | String customScopeType) { |
| | | LoopIdentitySnapshot customLoopIdentity = buildCustomLoopIdentity(customStationIdList, customScopeType); |
| | | if (customLoopIdentity.hasFingerprint()) { |
| | | return customLoopIdentity; |
| | | } |
| | | return resolveStationLoopIdentity(stationId); |
| | | } |
| | | |
| | | private LoopIdentitySnapshot buildCustomLoopIdentity(List<Integer> customStationIdList, |
| | | String customScopeType) { |
| | | List<Integer> 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<Integer> loopStationIdList = normalizeStationIdList(loopVo == null ? null : loopVo.getStationIdList()); |
| | | if (loopStationIdList.isEmpty() || !loopStationIdList.contains(stationId)) { |
| | | continue; |
| | | } |
| | | Set<Integer> loopStationIdSet = new HashSet<>(loopStationIdList); |
| | | Map<Integer, Set<Integer>> stationGraph = loadUndirectedStationGraph(); |
| | | List<Integer> localCycleStationIdList = resolveLocalCycleStationIdList(stationId, loopStationIdSet, stationGraph); |
| | | if (localCycleStationIdList.size() >= 3) { |
| | | return buildLoopIdentity(localCycleStationIdList, loopStationIdList.size(), "localCycle"); |
| | | } |
| | | List<Integer> 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<Integer> stationIdList = new ArrayList<>(); |
| | | stationIdList.add(stationId); |
| | | return new LoopIdentitySnapshot( |
| | | "station:" + stationId, |
| | | new HashSet<>(stationIdList), |
| | | 1, |
| | | 1, |
| | | "stationFallback" |
| | | ); |
| | | } |
| | | |
| | | private LoopIdentitySnapshot buildLoopIdentity(List<Integer> stationIdList, |
| | | int sourceLoopStationCount, |
| | | String scopeType) { |
| | | List<Integer> normalizedStationIdList = normalizeStationIdList(stationIdList); |
| | | if (normalizedStationIdList.isEmpty()) { |
| | | return LoopIdentitySnapshot.empty(); |
| | | } |
| | | return new LoopIdentitySnapshot( |
| | | buildLoopFingerprint(normalizedStationIdList), |
| | | new HashSet<>(normalizedStationIdList), |
| | | sourceLoopStationCount, |
| | | normalizedStationIdList.size(), |
| | | scopeType |
| | | ); |
| | | } |
| | | |
| | | private List<Integer> resolveLocalCycleStationIdList(Integer stationId, |
| | | Set<Integer> loopStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph) { |
| | | if (stationId == null |
| | | || stationId <= 0 |
| | | || loopStationIdSet == null |
| | | || loopStationIdSet.isEmpty() |
| | | || stationGraph == null |
| | | || stationGraph.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | Set<Integer> localNeighborhoodStationIdSet = collectLoopNeighborhoodStationIdSet( |
| | | stationId, |
| | | loopStationIdSet, |
| | | stationGraph, |
| | | LOCAL_LOOP_NEIGHBOR_HOP |
| | | ); |
| | | if (localNeighborhoodStationIdSet.size() < 3) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | Set<Integer> directNeighborStationIdSet = filterLoopNeighborStationIdSet( |
| | | stationGraph.getOrDefault(stationId, Collections.emptySet()), |
| | | localNeighborhoodStationIdSet, |
| | | stationId |
| | | ); |
| | | if (directNeighborStationIdSet.size() < 2) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | List<Integer> bestCycleStationIdList = new ArrayList<>(); |
| | | List<Integer> 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<Integer> pathBetweenNeighbors = findShortestScopePath( |
| | | leftNeighborStationId, |
| | | rightNeighborStationId, |
| | | stationId, |
| | | localNeighborhoodStationIdSet, |
| | | stationGraph |
| | | ); |
| | | if (pathBetweenNeighbors.isEmpty()) { |
| | | continue; |
| | | } |
| | | List<Integer> 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<Integer> resolveLocalNeighborhoodStationIdList(Integer stationId, |
| | | Set<Integer> loopStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph) { |
| | | return normalizeStationIdList(new ArrayList<>(collectLoopNeighborhoodStationIdSet( |
| | | stationId, |
| | | loopStationIdSet, |
| | | stationGraph, |
| | | LOCAL_LOOP_NEIGHBOR_HOP |
| | | ))); |
| | | } |
| | | |
| | | private Set<Integer> collectLoopNeighborhoodStationIdSet(Integer stationId, |
| | | Set<Integer> loopStationIdSet, |
| | | Map<Integer, Set<Integer>> stationGraph, |
| | | int maxHop) { |
| | | Set<Integer> neighborhoodStationIdSet = new LinkedHashSet<>(); |
| | | if (stationId == null |
| | | || stationId <= 0 |
| | | || loopStationIdSet == null |
| | | || loopStationIdSet.isEmpty() |
| | | || !loopStationIdSet.contains(stationId) |
| | | || stationGraph == null |
| | | || stationGraph.isEmpty() |
| | | || maxHop < 0) { |
| | | return neighborhoodStationIdSet; |
| | | } |
| | | |
| | | Deque<StationHopNode> 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<Integer> 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<Integer> filterLoopNeighborStationIdSet(Set<Integer> candidateNeighborStationIdSet, |
| | | Set<Integer> allowedStationIdSet, |
| | | Integer excludedStationId) { |
| | | Set<Integer> 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<Integer> findShortestScopePath(Integer startStationId, |
| | | Integer endStationId, |
| | | Integer excludedStationId, |
| | | Set<Integer> allowedStationIdSet, |
| | | Map<Integer, Set<Integer>> 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<Integer> queue = new ArrayDeque<>(); |
| | | Map<Integer, Integer> parentMap = new HashMap<>(); |
| | | Set<Integer> 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<Integer> 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<Integer> 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<Integer, Set<Integer>> loadUndirectedStationGraph() { |
| | | if (navigateUtils == null) { |
| | | return new HashMap<>(); |
| | | } |
| | | Map<Integer, Set<Integer>> 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<Integer> normalizeStationIdList(List<Integer> stationIdList) { |
| | | if (stationIdList == null || stationIdList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | List<Integer> normalizedList = new ArrayList<>(); |
| | | Set<Integer> 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<Integer> 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<Integer> 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<String, Integer> 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; |
| | | } |
| | | } |