Junjie
17 小时以前 e22fca2d289dabb3151d5248bf9ce8d05d927615
refactor: add station reroute execution pipeline
2个文件已修改
605 ■■■■■ 已修改文件
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java 485 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/core/utils/StationOperateProcessUtilsReroutePipelineTest.java 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -909,6 +909,197 @@
        );
    }
    RerouteCommandPlan buildRerouteCommandPlan(RerouteContext context,
                                               RerouteDecision decision) {
        if (context == null) {
            return RerouteCommandPlan.skip("missing-context");
        }
        if (decision == null) {
            return RerouteCommandPlan.skip("missing-decision");
        }
        if (decision.skip()) {
            return RerouteCommandPlan.skip(decision.skipReason());
        }
        if (context.stationThread() == null || context.stationProtocol() == null || context.wrkMast() == null) {
            return RerouteCommandPlan.skip("missing-runtime-dependency");
        }
        Integer currentStationId = context.stationProtocol().getStationId();
        Integer targetStationId = decision.targetStationId();
        if (currentStationId == null || targetStationId == null) {
            return RerouteCommandPlan.skip("missing-target-station");
        }
        if (Objects.equals(currentStationId, targetStationId)) {
            return RerouteCommandPlan.skip("same-station");
        }
        StationCommand command = context.useRunBlockCommand()
                ? context.stationThread().getRunBlockRerouteCommand(
                context.wrkMast().getWrkNo(),
                currentStationId,
                targetStationId,
                0,
                context.pathLenFactor()
        )
                : buildOutboundMoveCommand(
                context.stationThread(),
                context.wrkMast(),
                currentStationId,
                targetStationId,
                context.pathLenFactor()
        );
        if (command == null) {
            return RerouteCommandPlan.skip("missing-command");
        }
        return RerouteCommandPlan.dispatch(command, decision, context.dispatchScene());
    }
    RerouteExecutionResult executeReroutePlan(RerouteContext context,
                                              RerouteCommandPlan plan) {
        if (context == null) {
            return RerouteExecutionResult.skip("missing-context");
        }
        if (plan == null) {
            return RerouteExecutionResult.skip("missing-plan");
        }
        if (plan.skip()) {
            return RerouteExecutionResult.skip(plan.skipReason());
        }
        StationProtocol stationProtocol = context.stationProtocol();
        if (stationProtocol == null) {
            return RerouteExecutionResult.skip("missing-station-protocol");
        }
        Integer taskNo = stationProtocol.getTaskNo();
        Integer stationId = stationProtocol.getStationId();
        if (taskNo == null || taskNo <= 0 || stationId == null) {
            return RerouteExecutionResult.skip("invalid-station-task");
        }
        if (context.checkRecentDispatch()
                && shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId)) {
            return RerouteExecutionResult.skip("recent-dispatch");
        }
        if (countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), taskNo) > 0) {
            return RerouteExecutionResult.skip("buffer-has-current-task");
        }
        if (context.checkSuppressDispatch()
                && stationMoveCoordinator != null
                && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command())) {
            return RerouteExecutionResult.skip("dispatch-suppressed");
        }
        if (context.requireOutOrderDispatchLock()
                && !tryAcquireOutOrderDispatchLock(taskNo, stationId)) {
            return RerouteExecutionResult.skip("out-order-lock");
        }
        if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
            stationMoveCoordinator.cancelSession(taskNo);
        }
        if (!isBlank(context.executionLockKey())) {
            Object lock = redisUtil.get(context.executionLockKey());
            if (lock != null) {
                return RerouteExecutionResult.skip("scene-lock");
            }
            redisUtil.set(context.executionLockKey(), "lock", context.executionLockSeconds());
        }
        if (context.resetSegmentCommandsBeforeDispatch()) {
            resetSegmentMoveCommandsBeforeReroute(taskNo);
        }
        int clearedCommandCount = 0;
        if (context.clearIdleIssuedCommands()) {
            clearedCommandCount = clearIssuedMoveCommandsDuringIdleStay(context.idleTrack(), taskNo, stationId);
        }
        boolean offered = offerDevpCommandWithDedup(context.dispatchDeviceNo(), plan.command(), plan.dispatchScene());
        if (!offered) {
            return RerouteExecutionResult.skip("dispatch-dedup");
        }
        applyRerouteDispatchEffects(context, plan, clearedCommandCount);
        return RerouteExecutionResult.dispatched(plan.command(), clearedCommandCount);
    }
    private RerouteDecision resolveSharedRerouteDecision(RerouteContext context) {
        if (context == null || context.wrkMast() == null || context.stationProtocol() == null) {
            return RerouteDecision.skip("missing-runtime-dependency");
        }
        Integer currentStationId = context.stationProtocol().getStationId();
        if (currentStationId == null) {
            return RerouteDecision.skip("missing-current-station");
        }
        if (context.sceneType() == RerouteSceneType.IDLE_RECOVER
                && !Objects.equals(context.wrkMast().getWrkSts(), WrkStsType.STATION_RUN.sts)) {
            Integer targetStationId = context.wrkMast().getStaNo();
            return targetStationId == null || Objects.equals(targetStationId, currentStationId)
                    ? RerouteDecision.skip("same-station")
                    : RerouteDecision.proceed(targetStationId);
        }
        OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
                currentStationId,
                context.wrkMast(),
                context.outOrderStationIds(),
                context.pathLenFactor()
        );
        Integer targetStationId = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
        if (targetStationId == null || Objects.equals(targetStationId, currentStationId)) {
            return RerouteDecision.skip("same-station");
        }
        return RerouteDecision.proceed(targetStationId, dispatchDecision);
    }
    private RerouteExecutionResult executeSharedReroute(RerouteContext context) {
        RerouteDecision decision = resolveSharedRerouteDecision(context);
        if (decision.skip()) {
            return RerouteExecutionResult.skip(decision.skipReason());
        }
        RerouteCommandPlan plan = buildRerouteCommandPlan(context, decision);
        return executeReroutePlan(context, plan);
    }
    private void applyRerouteDispatchEffects(RerouteContext context,
                                             RerouteCommandPlan plan,
                                             int clearedCommandCount) {
        if (context == null || plan == null || plan.command() == null || context.wrkMast() == null || context.stationProtocol() == null) {
            return;
        }
        WrkMast wrkMast = context.wrkMast();
        StationProtocol stationProtocol = context.stationProtocol();
        OutOrderDispatchDecision dispatchDecision = plan.decision() == null ? null : plan.decision().dispatchDecision();
        syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), context.outOrderStationIds(), dispatchDecision, plan.command());
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.recordDispatch(
                    wrkMast.getWrkNo(),
                    stationProtocol.getStationId(),
                    plan.dispatchScene(),
                    plan.command(),
                    dispatchDecision != null && dispatchDecision.isCircle()
            );
        }
        if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) {
            saveStationTaskIdleTrack(new StationTaskIdleTrack(wrkMast.getWrkNo(), stationProtocol.getStationId(), System.currentTimeMillis()));
            News.info("输送站点任务停留{}秒未运行,已重新计算路径并重启运行,站点号={},目标站={},工作号={},清理旧分段命令数={},命令数据={}",
                    STATION_IDLE_RECOVER_SECONDS,
                    stationProtocol.getStationId(),
                    plan.command().getTargetStaNo(),
                    wrkMast.getWrkNo(),
                    clearedCommandCount,
                    JSON.toJSONString(plan.command()));
            return;
        }
        if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) {
            News.info("输送站点堵塞后重新计算路径命令下发成功,站点号={},工作号={},命令数据={}",
                    stationProtocol.getStationId(),
                    wrkMast.getWrkNo(),
                    JSON.toJSONString(plan.command()));
            return;
        }
        if (context.sceneType() == RerouteSceneType.OUT_ORDER) {
            News.info(dispatchDecision != null && dispatchDecision.isCircle() ? "{}任务进行绕圈" : "{}任务直接去目标点", wrkMast.getWrkNo());
        }
    }
    private List<NavigateNode> calcOutboundNavigatePath(WrkMast wrkMast,
                                                        Integer sourceStationId,
                                                        Integer targetStationId,
@@ -1951,19 +2142,29 @@
        private final boolean skip;
        private final String skipReason;
        private final Integer targetStationId;
        private final OutOrderDispatchDecision dispatchDecision;
        private RerouteDecision(boolean skip, String skipReason, Integer targetStationId) {
        private RerouteDecision(boolean skip,
                                String skipReason,
                                Integer targetStationId,
                                OutOrderDispatchDecision dispatchDecision) {
            this.skip = skip;
            this.skipReason = skipReason;
            this.targetStationId = targetStationId;
            this.dispatchDecision = dispatchDecision;
        }
        static RerouteDecision skip(String reason) {
            return new RerouteDecision(true, reason, null);
            return new RerouteDecision(true, reason, null, null);
        }
        static RerouteDecision proceed(Integer targetStationId) {
            return new RerouteDecision(false, null, targetStationId);
            return new RerouteDecision(false, null, targetStationId, null);
        }
        static RerouteDecision proceed(Integer targetStationId,
                                       OutOrderDispatchDecision dispatchDecision) {
            return new RerouteDecision(false, null, targetStationId, dispatchDecision);
        }
        boolean skip() {
@@ -1977,6 +2178,284 @@
        Integer targetStationId() {
            return targetStationId;
        }
        OutOrderDispatchDecision dispatchDecision() {
            return dispatchDecision;
        }
    }
    static final class RerouteContext {
        private final RerouteSceneType sceneType;
        private final BasDevp basDevp;
        private final StationThread stationThread;
        private final StationProtocol stationProtocol;
        private final WrkMast wrkMast;
        private final List<Integer> outOrderStationIds;
        private final Double pathLenFactor;
        private final String dispatchScene;
        private Integer dispatchDeviceNo;
        private boolean useRunBlockCommand;
        private boolean checkSuppressDispatch;
        private boolean requireOutOrderDispatchLock;
        private boolean cancelSessionBeforeDispatch;
        private boolean resetSegmentCommandsBeforeDispatch;
        private boolean clearIdleIssuedCommands;
        private boolean checkRecentDispatch;
        private String executionLockKey;
        private int executionLockSeconds;
        private StationTaskIdleTrack idleTrack;
        private RerouteContext(RerouteSceneType sceneType,
                               BasDevp basDevp,
                               StationThread stationThread,
                               StationProtocol stationProtocol,
                               WrkMast wrkMast,
                               List<Integer> outOrderStationIds,
                               Double pathLenFactor,
                               String dispatchScene) {
            this.sceneType = sceneType;
            this.basDevp = basDevp;
            this.stationThread = stationThread;
            this.stationProtocol = stationProtocol;
            this.wrkMast = wrkMast;
            this.outOrderStationIds = outOrderStationIds == null ? Collections.emptyList() : outOrderStationIds;
            this.pathLenFactor = pathLenFactor;
            this.dispatchScene = dispatchScene;
            this.dispatchDeviceNo = basDevp == null ? null : basDevp.getDevpNo();
        }
        static RerouteContext create(RerouteSceneType sceneType,
                                     BasDevp basDevp,
                                     StationThread stationThread,
                                     StationProtocol stationProtocol,
                                     WrkMast wrkMast,
                                     List<Integer> outOrderStationIds,
                                     Double pathLenFactor,
                                     String dispatchScene) {
            return new RerouteContext(sceneType, basDevp, stationThread, stationProtocol, wrkMast, outOrderStationIds, pathLenFactor, dispatchScene);
        }
        RerouteContext withDispatchDeviceNo(Integer dispatchDeviceNo) {
            this.dispatchDeviceNo = dispatchDeviceNo;
            return this;
        }
        RerouteContext withRunBlockCommand() {
            this.useRunBlockCommand = true;
            return this;
        }
        RerouteContext withSuppressDispatchGuard() {
            this.checkSuppressDispatch = true;
            return this;
        }
        RerouteContext withOutOrderDispatchLock() {
            this.requireOutOrderDispatchLock = true;
            return this;
        }
        RerouteContext withCancelSessionBeforeDispatch() {
            this.cancelSessionBeforeDispatch = true;
            return this;
        }
        RerouteContext withResetSegmentCommandsBeforeDispatch() {
            this.resetSegmentCommandsBeforeDispatch = true;
            return this;
        }
        RerouteContext clearIdleIssuedCommands(StationTaskIdleTrack idleTrack) {
            this.clearIdleIssuedCommands = true;
            this.idleTrack = idleTrack;
            return this;
        }
        RerouteContext withRecentDispatchGuard() {
            this.checkRecentDispatch = true;
            return this;
        }
        RerouteContext withExecutionLock(String executionLockKey, int executionLockSeconds) {
            this.executionLockKey = executionLockKey;
            this.executionLockSeconds = executionLockSeconds;
            return this;
        }
        RerouteSceneType sceneType() {
            return sceneType;
        }
        BasDevp basDevp() {
            return basDevp;
        }
        StationThread stationThread() {
            return stationThread;
        }
        StationProtocol stationProtocol() {
            return stationProtocol;
        }
        WrkMast wrkMast() {
            return wrkMast;
        }
        List<Integer> outOrderStationIds() {
            return outOrderStationIds;
        }
        Double pathLenFactor() {
            return pathLenFactor;
        }
        String dispatchScene() {
            return dispatchScene;
        }
        Integer dispatchDeviceNo() {
            return dispatchDeviceNo;
        }
        boolean useRunBlockCommand() {
            return useRunBlockCommand;
        }
        boolean checkSuppressDispatch() {
            return checkSuppressDispatch;
        }
        boolean requireOutOrderDispatchLock() {
            return requireOutOrderDispatchLock;
        }
        boolean cancelSessionBeforeDispatch() {
            return cancelSessionBeforeDispatch;
        }
        boolean resetSegmentCommandsBeforeDispatch() {
            return resetSegmentCommandsBeforeDispatch;
        }
        boolean clearIdleIssuedCommands() {
            return clearIdleIssuedCommands;
        }
        boolean checkRecentDispatch() {
            return checkRecentDispatch;
        }
        String executionLockKey() {
            return executionLockKey;
        }
        int executionLockSeconds() {
            return executionLockSeconds;
        }
        StationTaskIdleTrack idleTrack() {
            return idleTrack;
        }
    }
    static final class RerouteCommandPlan {
        private final boolean skip;
        private final String skipReason;
        private final StationCommand command;
        private final RerouteDecision decision;
        private final String dispatchScene;
        private RerouteCommandPlan(boolean skip,
                                   String skipReason,
                                   StationCommand command,
                                   RerouteDecision decision,
                                   String dispatchScene) {
            this.skip = skip;
            this.skipReason = skipReason;
            this.command = command;
            this.decision = decision;
            this.dispatchScene = dispatchScene;
        }
        static RerouteCommandPlan skip(String reason) {
            return new RerouteCommandPlan(true, reason, null, null, null);
        }
        static RerouteCommandPlan dispatch(StationCommand command,
                                           RerouteDecision decision,
                                           String dispatchScene) {
            return new RerouteCommandPlan(false, null, command, decision, dispatchScene);
        }
        boolean skip() {
            return skip;
        }
        String skipReason() {
            return skipReason;
        }
        StationCommand command() {
            return command;
        }
        RerouteDecision decision() {
            return decision;
        }
        String dispatchScene() {
            return dispatchScene;
        }
    }
    static final class RerouteExecutionResult {
        private final boolean skipped;
        private final String skipReason;
        private final boolean dispatched;
        private final StationCommand command;
        private final int clearedCommandCount;
        private RerouteExecutionResult(boolean skipped,
                                       String skipReason,
                                       boolean dispatched,
                                       StationCommand command,
                                       int clearedCommandCount) {
            this.skipped = skipped;
            this.skipReason = skipReason;
            this.dispatched = dispatched;
            this.command = command;
            this.clearedCommandCount = clearedCommandCount;
        }
        static RerouteExecutionResult skip(String reason) {
            return new RerouteExecutionResult(true, reason, false, null, 0);
        }
        static RerouteExecutionResult dispatched(StationCommand command,
                                                 int clearedCommandCount) {
            return new RerouteExecutionResult(false, null, true, command, clearedCommandCount);
        }
        boolean skipped() {
            return skipped;
        }
        String skipReason() {
            return skipReason;
        }
        boolean dispatched() {
            return dispatched;
        }
        StationCommand command() {
            return command;
        }
        int clearedCommandCount() {
            return clearedCommandCount;
        }
    }
    private static class OutOrderDispatchDecision {
src/test/java/com/zy/core/utils/StationOperateProcessUtilsReroutePipelineTest.java
@@ -1,6 +1,22 @@
package com.zy.core.utils;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.WrkMast;
import com.zy.core.model.command.StationCommand;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.model.protocol.StationTaskBufferItem;
import com.zy.core.thread.StationThread;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class StationOperateProcessUtilsReroutePipelineTest {
@@ -8,6 +24,8 @@
    void choosesRunBlockCommandBuilderForRunBlockRerouteScene() {
        StationOperateProcessUtils.RerouteSceneType scene =
                StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE;
        assertSame(StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE, scene);
    }
    @Test
@@ -15,7 +33,105 @@
        StationOperateProcessUtils.RerouteDecision decision =
                StationOperateProcessUtils.RerouteDecision.skip("same-station");
        assert decision.skip();
        assert "same-station".equals(decision.skipReason());
        assertTrue(decision.skip());
        assertEquals("same-station", decision.skipReason());
    }
    @Test
    void buildCommandPlan_usesRunBlockCommandBuilderForRunBlockScene() {
        StationOperateProcessUtils utils = new StationOperateProcessUtils();
        StationThread stationThread = mock(StationThread.class);
        StationCommand command = new StationCommand();
        command.setTaskNo(100);
        command.setStationId(10);
        command.setTargetStaNo(20);
        when(stationThread.getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d)).thenReturn(command);
        StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create(
                StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE,
                buildBasDevp(1),
                stationThread,
                buildStationProtocol(10, 100, 10),
                buildWrkMast(100, 99),
                Collections.emptyList(),
                0.25d,
                "checkStationRunBlock_reroute"
        ).withRunBlockCommand()
                .withCancelSessionBeforeDispatch()
                .withResetSegmentCommandsBeforeDispatch();
        StationOperateProcessUtils.RerouteCommandPlan plan = utils.buildRerouteCommandPlan(
                context,
                StationOperateProcessUtils.RerouteDecision.proceed(20)
        );
        verify(stationThread).getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d);
        assertSame(command, plan.command());
    }
    @Test
    void executePlan_skipsWhenCurrentTaskStillExistsInBuffer() {
        StationOperateProcessUtils utils = new StationOperateProcessUtils();
        StationCommand command = new StationCommand();
        command.setTaskNo(100);
        command.setStationId(10);
        command.setTargetStaNo(20);
        StationTaskBufferItem bufferItem = new StationTaskBufferItem();
        bufferItem.setTaskNo(100);
        StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create(
                StationOperateProcessUtils.RerouteSceneType.OUT_ORDER,
                buildBasDevp(1),
                mock(StationThread.class),
                buildStationProtocol(10, 100, 10, Collections.singletonList(bufferItem)),
                buildWrkMast(100, 20),
                List.of(10, 20),
                0.0d,
                "checkStationOutOrder"
        );
        StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan(
                context,
                StationOperateProcessUtils.RerouteCommandPlan.dispatch(
                        command,
                        StationOperateProcessUtils.RerouteDecision.proceed(20),
                        "checkStationOutOrder"
                )
        );
        assertTrue(result.skipped());
        assertEquals("buffer-has-current-task", result.skipReason());
    }
    private static BasDevp buildBasDevp(int devpNo) {
        BasDevp basDevp = new BasDevp();
        basDevp.setDevpNo(devpNo);
        return basDevp;
    }
    private static WrkMast buildWrkMast(int wrkNo, int targetStationId) {
        WrkMast wrkMast = new WrkMast();
        wrkMast.setWrkNo(wrkNo);
        wrkMast.setStaNo(targetStationId);
        return wrkMast;
    }
    private static StationProtocol buildStationProtocol(int stationId,
                                                        int taskNo,
                                                        int targetStationId) {
        return buildStationProtocol(stationId, taskNo, targetStationId, Collections.emptyList());
    }
    private static StationProtocol buildStationProtocol(int stationId,
                                                        int taskNo,
                                                        int targetStationId,
                                                        List<StationTaskBufferItem> taskBufferItems) {
        StationProtocol stationProtocol = new StationProtocol();
        stationProtocol.setStationId(stationId);
        stationProtocol.setTaskNo(taskNo);
        stationProtocol.setTargetStaNo(targetStationId);
        stationProtocol.setTaskBufferItems(taskBufferItems);
        return stationProtocol;
    }
}