Junjie
2026-04-14 f7d2eda120867b3c5a2d9001d5f5c9d0396c65bd
src/main/java/com/zy/core/utils/station/StationRerouteProcessor.java
@@ -2,18 +2,18 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.core.common.Cools;
import com.core.exception.CoolException;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.LocMast;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasDevpService;
import com.zy.asrs.service.LocMastService;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.entity.FindCrnNoResult;
import com.zy.common.model.NavigateNode;
import com.zy.common.model.StartupDto;
import com.zy.common.service.CommonService;
import com.zy.common.utils.NavigateUtils;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.SlaveConnection;
@@ -29,7 +29,6 @@
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.model.protocol.StationTaskBufferItem;
import com.zy.core.move.StationMoveCoordinator;
import com.zy.core.move.StationMoveSession;
import com.zy.core.thread.StationThread;
import com.zy.core.utils.station.model.OutOrderDispatchDecision;
import com.zy.core.utils.station.model.RerouteCommandPlan;
@@ -42,6 +41,7 @@
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -51,10 +51,9 @@
public class StationRerouteProcessor {
    private static final int OUT_ORDER_DISPATCH_LIMIT_SECONDS = 2;
    private static final long STATION_MOVE_RESET_WAIT_MS = 1000L;
    private static final int RUN_BLOCK_DIRECT_REASSIGN_LIMIT_SECONDS = 15 * 60;
    private static final int RUN_BLOCK_DIRECT_REASSIGN_LIMIT_SECONDS = 8 * 60;
    private static final int RUN_BLOCK_DIRECT_REASSIGN_NEAREST_CACHE_SECONDS = 60 * 60 * 24;
    @Autowired
    private BasDevpService basDevpService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
@@ -73,6 +72,8 @@
    private StationOutboundDecisionSupport stationOutboundDecisionSupport;
    @Autowired
    private StationDispatchRuntimeStateSupport stationDispatchRuntimeStateSupport;
    @Autowired
    private NavigateUtils navigateUtils;
    public void checkStationRunBlock(BasDevp basDevp, Integer stationId) {
        try {
@@ -316,11 +317,141 @@
    public boolean shouldUseRunBlockDirectReassign(WrkMast wrkMast,
                                                   Integer stationId,
                                                   List<Integer> runBlockReassignLocStationList) {
        return wrkMast != null
                && Objects.equals(wrkMast.getIoType(), WrkIoType.IN.id)
                && stationId != null
                && runBlockReassignLocStationList != null
                && runBlockReassignLocStationList.contains(stationId);
        if (wrkMast == null || stationId == null) {
            return false;
        }
        if (!Objects.equals(wrkMast.getIoType(), WrkIoType.IN.id)) {
            return false;
        }
        if (runBlockReassignLocStationList == null || !runBlockReassignLocStationList.contains(stationId)) {
            return false;
        }
        if (shouldForceRunBlockPathReroute(wrkMast, stationId, runBlockReassignLocStationList)) {
            return false;
        }
        return true;
    }
    private boolean shouldForceRunBlockPathReroute(WrkMast wrkMast,
                                                   Integer stationId,
                                                   List<Integer> runBlockReassignLocStationList) {
        if (wrkMast == null || stationId == null) {
            return false;
        }
        Integer nearestStationId = resolveNearestRunBlockDirectReassignStationId(wrkMast, runBlockReassignLocStationList);
        return nearestStationId != null && !Objects.equals(stationId, nearestStationId);
    }
    private Integer resolveNearestRunBlockDirectReassignStationId(WrkMast wrkMast,
                                                                  List<Integer> runBlockReassignLocStationList) {
        if (wrkMast == null
                || wrkMast.getStaNo() == null
                || navigateUtils == null
                || runBlockReassignLocStationList == null
                || runBlockReassignLocStationList.isEmpty()) {
            return null;
        }
        Integer targetStationId = wrkMast.getStaNo();
        Integer cachedStationId = loadCachedNearestRunBlockDirectReassignStationId(targetStationId, runBlockReassignLocStationList);
        if (cachedStationId != null) {
            return cachedStationId;
        }
        Integer nearestStationId = null;
        int nearestPathLen = Integer.MAX_VALUE;
        for (Integer candidateStationId : runBlockReassignLocStationList) {
            if (candidateStationId == null) {
                continue;
            }
            List<NavigateNode> path = navigateUtils.calcOptimalPathByStationId(candidateStationId, targetStationId, wrkMast.getWrkNo(), null);
            if (path == null || path.isEmpty()) {
                continue;
            }
            int pathLen = countStationNodes(path);
            if (pathLen <= 0) {
                continue;
            }
            if (pathLen < nearestPathLen
                    || (pathLen == nearestPathLen && nearestStationId != null && candidateStationId < nearestStationId)) {
                nearestStationId = candidateStationId;
                nearestPathLen = pathLen;
            }
        }
        cacheNearestRunBlockDirectReassignStationId(targetStationId, runBlockReassignLocStationList, nearestStationId);
        return nearestStationId;
    }
    private Integer loadCachedNearestRunBlockDirectReassignStationId(Integer targetStationId,
                                                                     List<Integer> runBlockReassignLocStationList) {
        String cacheKey = buildNearestRunBlockDirectReassignCacheKey(targetStationId, runBlockReassignLocStationList);
        if (cacheKey == null || redisUtil == null) {
            return null;
        }
        Object cacheValue = redisUtil.get(cacheKey);
        if (cacheValue == null) {
            return null;
        }
        try {
            Integer stationId = Integer.valueOf(String.valueOf(cacheValue));
            return runBlockReassignLocStationList.contains(stationId) ? stationId : null;
        } catch (Exception ignore) {
            return null;
        }
    }
    private void cacheNearestRunBlockDirectReassignStationId(Integer targetStationId,
                                                             List<Integer> runBlockReassignLocStationList,
                                                             Integer nearestStationId) {
        String cacheKey = buildNearestRunBlockDirectReassignCacheKey(targetStationId, runBlockReassignLocStationList);
        if (cacheKey == null || nearestStationId == null || redisUtil == null) {
            return;
        }
        redisUtil.set(cacheKey, nearestStationId, RUN_BLOCK_DIRECT_REASSIGN_NEAREST_CACHE_SECONDS);
    }
    private String buildNearestRunBlockDirectReassignCacheKey(Integer targetStationId,
                                                              List<Integer> runBlockReassignLocStationList) {
        if (targetStationId == null || runBlockReassignLocStationList == null || runBlockReassignLocStationList.isEmpty()) {
            return null;
        }
        List<Integer> normalizedStationIdList = new ArrayList<>();
        for (Integer stationId : runBlockReassignLocStationList) {
            if (stationId != null && !normalizedStationIdList.contains(stationId)) {
                normalizedStationIdList.add(stationId);
            }
        }
        if (normalizedStationIdList.isEmpty()) {
            return null;
        }
        Collections.sort(normalizedStationIdList);
        return RedisKeyType.STATION_RUN_BLOCK_DIRECT_REASSIGN_NEAREST_CACHE_.key
                + targetStationId
                + "_"
                + JSON.toJSONString(normalizedStationIdList);
    }
    private int countStationNodes(List<NavigateNode> path) {
        if (path == null || path.isEmpty()) {
            return 0;
        }
        int count = 0;
        for (NavigateNode node : path) {
            if (extractStationId(node) != null) {
                count++;
            }
        }
        return count;
    }
    private Integer extractStationId(NavigateNode node) {
        if (node == null || Cools.isEmpty(node.getNodeValue())) {
            return null;
        }
        try {
            JSONObject valueObject = JSONObject.parseObject(node.getNodeValue());
            return valueObject == null ? null : valueObject.getInteger("stationId");
        } catch (Exception ignore) {
            return null;
        }
    }
    private boolean shouldSkipRunBlockStation(BasDevp basDevp, Integer stationId) {
@@ -380,13 +511,16 @@
        int clearedCommandCount = 0;
        boolean offered = offerDevpCommandWithDedup(context.dispatchDeviceNo(), plan.command(), plan.dispatchScene());
        if (!offered) {
            return RerouteExecutionResult.skip("dispatch-dedup");
        }
        // 先取消旧 session 并记录新 session,再入队命令,避免消费线程在 session 写入 Redis 前取到命令导致路由校验失败。
        if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
            stationMoveCoordinator.markCancelPending(taskNo, "reroute_pending");
            stationMoveCoordinator.cancelSession(taskNo);
        }
        preRegisterDispatchSession(context, plan);
        boolean offered = offerDevpCommandWithDedup(context.dispatchDeviceNo(), plan.command(), plan.dispatchScene());
        if (!offered) {
            return RerouteExecutionResult.skip("dispatch-dedup");
        }
        applyRerouteDispatchEffects(context, plan, clearedCommandCount);
@@ -400,6 +534,24 @@
        }
        RerouteCommandPlan plan = buildRerouteCommandPlan(context, decision);
        return executeReroutePlan(context, plan);
    }
    private void preRegisterDispatchSession(RerouteContext context, RerouteCommandPlan plan) {
        if (context == null || plan == null || plan.command() == null || context.wrkMast() == null || context.stationProtocol() == null) {
            return;
        }
        if (stationMoveCoordinator == null) {
            return;
        }
        OutOrderDispatchDecision dispatchDecision =
                plan.decision() == null ? null : plan.decision().dispatchDecision();
        stationMoveCoordinator.recordDispatch(
                context.wrkMast().getWrkNo(),
                context.stationProtocol().getStationId(),
                plan.dispatchScene(),
                plan.command(),
                dispatchDecision != null && dispatchDecision.isCircle()
        );
    }
    private void applyRerouteDispatchEffects(RerouteContext context,
@@ -420,15 +572,6 @@
                dispatchDecision,
                plan.command()
        );
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.recordDispatch(
                    wrkMast.getWrkNo(),
                    stationProtocol.getStationId(),
                    plan.dispatchScene(),
                    plan.command(),
                    dispatchDecision != null && dispatchDecision.isCircle()
            );
        }
        if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) {
            News.info("输送站点堵塞后重新计算路径命令下发成功,站点号={},工作号={},命令数据={}",
                    stationProtocol.getStationId(),
@@ -458,11 +601,10 @@
                    stationProtocol.getTaskNo(),
                    currentTaskBufferCommandCount);
        }
        if (!stationDispatchRuntimeStateSupport.tryAcquireRunBlockDirectReassignLock(
        if (stationDispatchRuntimeStateSupport.hasRunBlockDirectReassignLimit(
                wrkMast.getWrkNo(),
                stationProtocol.getStationId(),
                RUN_BLOCK_DIRECT_REASSIGN_LIMIT_SECONDS)) {
            News.info("输送站点运行堵塞重分配已跳过,15分钟内不允许重复申请。站点号={},工作号={}",
                stationProtocol.getStationId())) {
            News.info("输送站点运行堵塞重分配已跳过,8分钟内不允许重复申请。站点号={},工作号={}",
                    stationProtocol.getStationId(),
                    wrkMast.getWrkNo());
            return;
@@ -543,11 +685,11 @@
        if (!wrkMastService.updateById(wrkMast)) {
            return;
        }
        stationDispatchRuntimeStateSupport.recordRunBlockDirectReassignLimit(
                wrkMast.getWrkNo(),
                stationProtocol.getStationId(),
                RUN_BLOCK_DIRECT_REASSIGN_LIMIT_SECONDS);
        stationDispatchRuntimeStateSupport.signalSegmentReset(wrkMast.getWrkNo(), STATION_MOVE_RESET_WAIT_MS);
        boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct");
        if (!offered) {
            return;
        }
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.markCancelPending(wrkMast.getWrkNo(), "reroute_pending");
            stationMoveCoordinator.cancelSession(wrkMast.getWrkNo());
@@ -559,6 +701,10 @@
                    false
            );
        }
        boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct");
        if (!offered) {
            News.warn("输送站点堵塞直派命令入队被拒绝(可能重复),站点号={},工作号={}", stationProtocol.getStationId(), wrkMast.getWrkNo());
        }
    }
    private int countCurrentTaskBufferCommands(List<StationTaskBufferItem> taskBufferItems, Integer currentTaskNo) {