package com.zy.core.move; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.zy.common.utils.RedisUtil; import com.zy.core.enums.RedisKeyType; import com.zy.core.model.command.StationCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @Component public class StationMoveCoordinator { private static final int SESSION_EXPIRE_SECONDS = 60 * 60 * 24; @Autowired private StationMoveSessionRegistry sessionRegistry; @Autowired private RedisUtil redisUtil; public StationMoveSession loadSession(Integer taskNo) { if (sessionRegistry == null) { return null; } return sessionRegistry.load(taskNo); } public boolean isActiveRoute(Integer taskNo, Integer routeVersion) { return sessionRegistry != null && sessionRegistry.isActiveRoute(taskNo, routeVersion); } public void markSegmentIssued(Integer taskNo, Integer routeVersion) { if (sessionRegistry != null) { sessionRegistry.markSegmentIssued(taskNo, routeVersion); } } public void updateCurrentStation(Integer taskNo, Integer routeVersion, Integer currentStationId) { if (sessionRegistry != null) { sessionRegistry.updateCurrentStation(taskNo, routeVersion, currentStationId); } } public void markCancelled(Integer taskNo, Integer routeVersion, Integer currentStationId, String cancelReason) { updateTerminal(taskNo, routeVersion, currentStationId, StationMoveSession.STATUS_CANCELLED, cancelReason); } public void markBlocked(Integer taskNo, Integer routeVersion, Integer currentStationId) { updateTerminal(taskNo, routeVersion, currentStationId, StationMoveSession.STATUS_BLOCKED, null); } public void markTimeout(Integer taskNo, Integer routeVersion, Integer currentStationId) { updateTerminal(taskNo, routeVersion, currentStationId, StationMoveSession.STATUS_TIMEOUT, null); } public void markFinished(Integer taskNo, Integer routeVersion, Integer currentStationId) { updateTerminal(taskNo, routeVersion, currentStationId, StationMoveSession.STATUS_FINISHED, null); } public void markCancelPending(Integer taskNo, String cancelReason) { StationMoveSession session = loadSession(taskNo); if (session == null || !session.isActive()) { return; } session.setStatus(StationMoveSession.STATUS_CANCEL_PENDING); session.setCancelReason(cancelReason); saveSession(session); } public boolean shouldSuppressDispatch(Integer taskNo, Integer currentStationId, StationCommand candidateCommand) { if (taskNo == null || taskNo <= 0 || currentStationId == null || candidateCommand == null) { return false; } StationMoveSession session = loadSession(taskNo); if (session == null || !session.isActive()) { return false; } String candidateSignature = buildPathSignature(candidateCommand); if (!isBlank(candidateSignature) && Objects.equals(candidateSignature, session.getPathSignature())) { return true; } if (Objects.equals(currentStationId, session.getNextDecisionStationId())) { return false; } return session.containsStation(currentStationId); } public StationMoveSession recordDispatch(Integer taskNo, Integer dispatchStationId, String triggerName, StationCommand command, boolean circleRoute) { if (taskNo == null || taskNo <= 0 || command == null) { return null; } StationMoveSession current = loadSession(taskNo); long now = System.currentTimeMillis(); String pathSignature = buildPathSignature(command); List fullPathStationIds = resolveFullPathStationIds(command); boolean reuseCurrent = current != null && current.isActive() && Objects.equals(current.getDispatchStationId(), dispatchStationId) && Objects.equals(current.getNextDecisionStationId(), command.getTargetStaNo()) && Objects.equals(current.getPathSignature(), pathSignature); StationMoveSession session = reuseCurrent ? current : new StationMoveSession(); if (!reuseCurrent) { session.setRouteVersion(current == null || current.getRouteVersion() == null ? 1 : current.getRouteVersion() + 1); session.setCreatedAt(now); } else if (session.getRouteVersion() == null) { session.setRouteVersion(1); } session.setTaskNo(taskNo); session.setThreadImpl(resolveThreadImpl(triggerName)); session.setCurrentStationId(dispatchStationId); session.setBusinessTargetStationId(command.getTargetStaNo()); session.setCurrentRouteTargetStationId(command.getTargetStaNo()); session.setTriggerType(resolveTriggerType(triggerName, circleRoute)); session.setDispatchMode(resolveDispatchMode(triggerName, circleRoute)); session.setStatus(StationMoveSession.STATUS_RUNNING); session.setDispatchStationId(dispatchStationId); session.setNextDecisionStationId(command.getTargetStaNo()); session.setFullPathStationIds(fullPathStationIds); session.setPathSignature(pathSignature); session.setCancelReason(null); session.setUpdatedAt(now); session.setLastIssuedAt(now); command.setRouteVersion(session.getRouteVersion()); saveSession(session); if (circleRoute) { saveLegacyCircleCommand(taskNo, command); } else { clearLegacyCircleCommand(taskNo); } return session; } public StationMoveSession cancelSession(Integer taskNo) { StationMoveSession session = loadSession(taskNo); if (session == null) { clearLegacyCircleCommand(taskNo); return null; } session.setStatus(StationMoveSession.STATUS_CANCELLED); session.setCancelReason("reroute_cancelled"); saveSession(session); clearLegacyCircleCommand(taskNo); return session; } public StationMoveSession finishSession(Integer taskNo) { StationMoveSession session = loadSession(taskNo); if (session == null) { clearLegacyCircleCommand(taskNo); return null; } session.setStatus(StationMoveSession.STATUS_FINISHED); saveSession(session); clearLegacyCircleCommand(taskNo); return session; } public String buildPathSignature(StationCommand command) { if (command == null) { return ""; } Map signature = new LinkedHashMap<>(); signature.put("commandType", command.getCommandType() == null ? null : command.getCommandType().name()); signature.put("stationId", command.getStationId()); signature.put("targetStaNo", command.getTargetStaNo()); signature.put("navigatePath", copyIntegerList(command.getNavigatePath())); signature.put("liftTransferPath", copyIntegerList(command.getLiftTransferPath())); signature.put("originalNavigatePath", copyIntegerList(command.getOriginalNavigatePath())); return JSON.toJSONString(signature, SerializerFeature.DisableCircularReferenceDetect); } public String buildPathSignatureHash(StationCommand command) { String signature = buildPathSignature(command); if (isBlank(signature)) { return ""; } return digest(signature); } private void updateTerminal(Integer taskNo, Integer routeVersion, Integer currentStationId, String status, String cancelReason) { if (sessionRegistry == null) { return; } StationMoveSession session = sessionRegistry.load(taskNo); if (session == null || !Objects.equals(session.getRouteVersion(), routeVersion)) { return; } session.setCurrentStationId(currentStationId); session.setStatus(status); session.setCancelReason(cancelReason); saveSession(session); if (StationMoveSession.STATUS_CANCELLED.equals(status) || StationMoveSession.STATUS_BLOCKED.equals(status) || StationMoveSession.STATUS_TIMEOUT.equals(status) || StationMoveSession.STATUS_FINISHED.equals(status)) { clearLegacyCircleCommand(taskNo); } } private void saveSession(StationMoveSession session) { if (sessionRegistry != null) { sessionRegistry.save(session); } } private void saveLegacyCircleCommand(Integer taskNo, StationCommand command) { if (redisUtil == null || taskNo == null || taskNo <= 0 || command == null) { return; } redisUtil.set(RedisKeyType.WATCH_CIRCLE_STATION_.key + taskNo, JSON.toJSONString(command, SerializerFeature.DisableCircularReferenceDetect), SESSION_EXPIRE_SECONDS); } private void clearLegacyCircleCommand(Integer taskNo) { if (redisUtil == null || taskNo == null || taskNo <= 0) { return; } redisUtil.del(RedisKeyType.WATCH_CIRCLE_STATION_.key + taskNo); } private List resolveFullPathStationIds(StationCommand command) { if (command == null) { return new ArrayList<>(); } List source = command.getOriginalNavigatePath(); if (source == null || source.isEmpty()) { source = command.getNavigatePath(); } if (source != null && !source.isEmpty()) { return new ArrayList<>(source); } List path = new ArrayList<>(); if (command.getStationId() != null) { path.add(command.getStationId()); } if (command.getTargetStaNo() != null && !Objects.equals(command.getTargetStaNo(), command.getStationId())) { path.add(command.getTargetStaNo()); } return path; } private List copyIntegerList(List source) { if (source == null || source.isEmpty()) { return Collections.emptyList(); } return new ArrayList<>(source); } private String resolveThreadImpl(String triggerName) { if (triggerName == null) { return null; } if (triggerName.contains("crn")) { return "crn"; } if (triggerName.contains("watchCircle")) { return "station"; } if (triggerName.contains("checkStationRunBlock")) { return "station"; } if (triggerName.contains("checkStationIdleRecover")) { return "station"; } if (triggerName.contains("checkStationOutOrder")) { return "station"; } return triggerName; } private StationMoveTriggerType resolveTriggerType(String triggerName, boolean circleRoute) { if (circleRoute) { return StationMoveTriggerType.WATCH_CIRCLE; } if (triggerName == null) { return StationMoveTriggerType.INITIAL_OUTBOUND; } if (triggerName.contains("checkStationOutOrder")) { return StationMoveTriggerType.OUT_ORDER; } if (triggerName.contains("watchCircle")) { return StationMoveTriggerType.WATCH_CIRCLE; } if (triggerName.contains("checkStationRunBlock")) { return StationMoveTriggerType.RUN_BLOCK; } if (triggerName.contains("checkStationIdleRecover")) { return StationMoveTriggerType.IDLE_RECOVER; } return StationMoveTriggerType.INITIAL_OUTBOUND; } private StationMoveDispatchMode resolveDispatchMode(String triggerName, boolean circleRoute) { if (circleRoute) { return StationMoveDispatchMode.CIRCLE; } if (triggerName != null && triggerName.contains("checkStationRunBlock")) { return StationMoveDispatchMode.RUN_BLOCK_REROUTE; } if (triggerName != null && triggerName.contains("checkStationIdleRecover")) { return StationMoveDispatchMode.IDLE_RECOVER_REROUTE; } return StationMoveDispatchMode.DIRECT; } private boolean isBlank(String value) { return value == null || value.trim().isEmpty(); } private String digest(String value) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] bytes = digest.digest(value.getBytes(StandardCharsets.UTF_8)); StringBuilder builder = new StringBuilder(bytes.length * 2); for (byte b : bytes) { String hex = Integer.toHexString(b & 0xff); if (hex.length() == 1) { builder.append('0'); } builder.append(hex); } return builder.toString(); } catch (Exception ignore) { return Integer.toHexString(value.hashCode()); } } }