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<Integer> 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<String, Object> 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<Integer> resolveFullPathStationIds(StationCommand command) {
|
if (command == null) {
|
return new ArrayList<>();
|
}
|
List<Integer> source = command.getOriginalNavigatePath();
|
if (source == null || source.isEmpty()) {
|
source = command.getNavigatePath();
|
}
|
if (source != null && !source.isEmpty()) {
|
return new ArrayList<>(source);
|
}
|
List<Integer> 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<Integer> copyIntegerList(List<Integer> 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());
|
}
|
}
|
}
|