Junjie
2026-04-16 413b42a497ed1b695ceb0acada0122e553021ddb
#仿真系统优化V3.0.0.2
11个文件已添加
13个文件已修改
1个文件已删除
5226 ■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/OpenController.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/domain/param/FakeConfigSaveParam.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/ServerBootstrap.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/ZyStationConnectDriver.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/FakeConfigKeys.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/FakeConfigSupport.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/FakeStationBlockManager.java 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/FakeStationMoveEngine.java 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/FakeStationStateManager.java 504 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyCrnFakeConnect.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyRgvFakeConnect.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java 2000 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/network/fake/ZyStationV4FakeSegConnect.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/plugin/FakeProcess.java 630 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/plugin/station/FakeStationMonitor.java 336 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/plugin/station/FakeTaskGenerator.java 304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/controller/ConfigController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/service/ConfigService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/system/service/impl/ConfigServiceImpl.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sql/20260416_add_fake_advanced_config.sql 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/components/MapCanvas.js 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/watch/fakeOperationConfig.js 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/watch/fakeOperationConfig.html 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/OpenController.java
@@ -6,6 +6,7 @@
import com.core.exception.CoolException;
import com.zy.asrs.domain.Result.CancelTaskBatchResult;
import com.zy.asrs.domain.param.*;
import com.core.annotations.ManagerAuth;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.asrs.entity.LocMast;
import com.zy.asrs.entity.WrkMast;
@@ -21,6 +22,7 @@
import com.zy.core.model.protocol.DualCrnProtocol;
import com.zy.core.model.protocol.RgvProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.network.fake.FakeConfigKeys;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.DualCrnThread;
import com.zy.core.thread.RgvThread;
@@ -35,9 +37,11 @@
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Slf4j
@RestController
@@ -424,4 +428,62 @@
        return R.ok();
    }
    @GetMapping("/fakeConfig/auth")
    @ManagerAuth
    public R getFakeConfig() {
        LinkedHashMap<String, Object> map = new LinkedHashMap<>();
        for (String key : FakeConfigKeys.ALL_KEYS) {
            String defaultValue = FakeConfigKeys.DEFAULTS.get(key);
            map.put(key, configService.getConfigValue(key, defaultValue));
        }
        return R.ok().add(map);
    }
    @PostMapping("/fakeConfig/save/auth")
    @ManagerAuth
    public R saveFakeConfig(@RequestBody FakeConfigSaveParam param) {
        if (param == null || param.getConfigMap() == null || param.getConfigMap().isEmpty()) {
            return R.error("参数不能为空");
        }
        Set<String> allowKeys = FakeConfigKeys.ALL_KEYS;
        for (Map.Entry<String, Object> entry : param.getConfigMap().entrySet()) {
            String key = entry.getKey();
            if (!allowKeys.contains(key)) {
                return R.error("不支持的配置项: " + key);
            }
            String normalized = normalizeFakeConfigValue(key, entry.getValue());
            if (!configService.saveConfigValue(key, normalized)) {
                return R.error("保存失败: " + key);
            }
        }
        configService.refreshSystemConfigCache();
        return getFakeConfig();
    }
    private String normalizeFakeConfigValue(String key, Object rawValue) {
        if (rawValue == null) {
            throw new CoolException(key + " 参数不能为空");
        }
        String value = String.valueOf(rawValue).trim();
        if (FakeConfigKeys.BOOLEAN_KEYS.contains(key)) {
            if (!"Y".equalsIgnoreCase(value) && !"N".equalsIgnoreCase(value)) {
                throw new CoolException(key + " 仅支持 Y/N");
            }
            return value.toUpperCase();
        }
        if (FakeConfigKeys.LONG_KEYS.contains(key)) {
            long parsed;
            try {
                parsed = Long.parseLong(value);
            } catch (Exception e) {
                throw new CoolException(key + " 必须为非负整数");
            }
            if (parsed < 0) {
                throw new CoolException(key + " 必须为非负整数");
            }
            return String.valueOf(parsed);
        }
        throw new CoolException("不支持的配置项: " + key);
    }
}
src/main/java/com/zy/asrs/domain/param/FakeConfigSaveParam.java
New file
@@ -0,0 +1,11 @@
package com.zy.asrs.domain.param;
import lombok.Data;
import java.util.Map;
@Data
public class FakeConfigSaveParam {
    private Map<String, Object> configMap;
}
src/main/java/com/zy/core/ServerBootstrap.java
@@ -25,6 +25,8 @@
@Component
public class ServerBootstrap {
    private static boolean initThread = false;
    @Autowired
    private MainProcess mainProcess;
    @Autowired
@@ -34,6 +36,9 @@
    @Async
    public void init() throws InterruptedException {
        if (initThread) {
            return;
        }
        News.info("核心控制层开始初始化...............................................");
        clearStartupRuntimeLocks();
        // 初始化消息队列
@@ -42,6 +47,7 @@
        initThread();
        // 开始主流程进程
        mainProcess.start();
        initThread = true;
        News.info("核心控制层已启动...............................................");
    }
src/main/java/com/zy/core/network/ZyStationConnectDriver.java
@@ -10,7 +10,6 @@
import com.zy.core.network.entity.ZyStationStatusEntity;
import java.util.List;
import com.zy.core.network.fake.ZyStationFakeSegConnect;
import com.zy.core.network.fake.ZyStationV4FakeSegConnect;
import com.zy.core.network.real.ZyStationRealConnect;
import com.zy.core.network.real.ZyStationV3RealConnect;
import com.zy.core.network.real.ZyStationV5RealConnect;
@@ -29,7 +28,6 @@
public class ZyStationConnectDriver implements ThreadHandler {
    private static final ZyStationFakeSegConnect zyStationFakeSegConnect = new ZyStationFakeSegConnect();
    private static final ZyStationV4FakeSegConnect zyStationV4FakeSegConnect = new ZyStationV4FakeSegConnect();
    private static final long SEND_LOCK_WARN_MS = 500L;
    private static final long SEND_COST_WARN_MS = 5_000L;
    private static final long SEND_LOCK_POLL_MS = 20L;
@@ -80,19 +78,8 @@
                        connectApi = new ZyStationRealConnect(deviceConfig, redisUtil);
                    }
                } else {
                    if ("ZyStationV3Thread".equals(deviceConfig.getThreadImpl())) {
                        zyStationFakeSegConnect.addFakeConnect(deviceConfig, redisUtil);
                        connectApi = zyStationFakeSegConnect;
                    } else if ("ZyStationV5Thread".equals(deviceConfig.getThreadImpl())) {
                        zyStationV4FakeSegConnect.addFakeConnect(deviceConfig, redisUtil);
                        connectApi = zyStationV4FakeSegConnect;
                    } else {
                        fakeConfigUnsupported = true;
                        zyStationConnectApi = null;
                        log.error("旧版输送站 fake 已移除,deviceNo={}, threadImpl={}, 请切换到 ZyStationV3Thread 或 ZyStationV5Thread",
                                deviceConfig.getDeviceNo(), deviceConfig.getThreadImpl());
                        return false;
                    }
                    zyStationFakeSegConnect.addFakeConnect(deviceConfig, redisUtil);
                    connectApi = zyStationFakeSegConnect;
                }
                boolean connect = connectApi.connect();
src/main/java/com/zy/core/network/fake/FakeConfigKeys.java
New file
@@ -0,0 +1,116 @@
package com.zy.core.network.fake;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
public final class FakeConfigKeys {
    public static final String ENABLE_FAKE = "enableFake";
    public static final String FAKE_REAL_TASK_REQUEST_WMS = "fakeRealTaskRequestWms";
    public static final String FAKE_GENERATE_IN_TASK = "fakeGenerateInTask";
    public static final String FAKE_GENERATE_OUT_TASK = "fakeGenerateOutTask";
    public static final String FAKE_ALLOW_CHECK_BLOCK = "fakeAllowCheckBlock";
    public static final String FAKE_RUN_BLOCK_TIMEOUT_MS = "fakeRunBlockTimeoutMs";
    public static final String FAKE_CRN_FETCH_PUT_DURATION_MS = "fakeCrnFetchPutDurationMs";
    public static final String FAKE_CRN_LEVEL_STEP_DURATION_MS = "fakeCrnLevelStepDurationMs";
    public static final String FAKE_CRN_BAY_STEP_DURATION_MS = "fakeCrnBayStepDurationMs";
    public static final String FAKE_CRN_RESET_DURATION_MS = "fakeCrnResetDurationMs";
    public static final String FAKE_DUAL_CRN_COMMAND_WAIT_MS = "fakeDualCrnCommandWaitMs";
    public static final String FAKE_DUAL_CRN_TRANSFER_DURATION_MS = "fakeDualCrnTransferDurationMs";
    public static final String FAKE_DUAL_CRN_PICK_DURATION_MS = "fakeDualCrnPickDurationMs";
    public static final String FAKE_DUAL_CRN_PUT_DURATION_MS = "fakeDualCrnPutDurationMs";
    public static final String FAKE_DUAL_CRN_LEVEL_STEP_DURATION_MS = "fakeDualCrnLevelStepDurationMs";
    public static final String FAKE_DUAL_CRN_BAY_STEP_DURATION_MS = "fakeDualCrnBayStepDurationMs";
    public static final String FAKE_DUAL_CRN_RESET_DURATION_MS = "fakeDualCrnResetDurationMs";
    public static final String FAKE_RGV_MOVE_STEP_DURATION_MS = "fakeRgvMoveStepDurationMs";
    public static final String FAKE_RGV_LOAD_DURATION_MS = "fakeRgvLoadDurationMs";
    public static final String FAKE_RGV_RESET_DURATION_MS = "fakeRgvResetDurationMs";
    public static final String FAKE_STATION_SEGMENT_WAIT_TIMEOUT_MS = "fakeStationSegmentWaitTimeoutMs";
    public static final String FAKE_STATION_MOVE_STEP_DURATION_MS = "fakeStationMoveStepDurationMs";
    public static final String FAKE_STATION_IDLE_LOOP_DELAY_MS = "fakeStationIdleLoopDelayMs";
    public static final String FAKE_STATION_BLOCKED_LOOP_DELAY_MS = "fakeStationBlockedLoopDelayMs";
    public static final String FAKE_STATION_INITIALIZE_DELAY_MS = "fakeStationInitializeDelayMs";
    public static final String FAKE_STATION_FINISH_DELAY_MS = "fakeStationFinishDelayMs";
    public static final String FAKE_OUT_STATION_STAY_TIMEOUT_MS = "fakeOutStationStayTimeoutMs";
    public static final Map<String, String> DEFAULTS;
    public static final Set<String> BOOLEAN_KEYS;
    public static final Set<String> LONG_KEYS;
    public static final Set<String> ALL_KEYS;
    static {
        Map<String, String> defaults = new LinkedHashMap<>();
        defaults.put(ENABLE_FAKE, "N");
        defaults.put(FAKE_REAL_TASK_REQUEST_WMS, "N");
        defaults.put(FAKE_GENERATE_IN_TASK, "Y");
        defaults.put(FAKE_GENERATE_OUT_TASK, "Y");
        defaults.put(FAKE_ALLOW_CHECK_BLOCK, "Y");
        defaults.put(FAKE_RUN_BLOCK_TIMEOUT_MS, "20000");
        defaults.put(FAKE_CRN_FETCH_PUT_DURATION_MS, "2000");
        defaults.put(FAKE_CRN_LEVEL_STEP_DURATION_MS, "1000");
        defaults.put(FAKE_CRN_BAY_STEP_DURATION_MS, "1000");
        defaults.put(FAKE_CRN_RESET_DURATION_MS, "0");
        defaults.put(FAKE_DUAL_CRN_COMMAND_WAIT_MS, "200");
        defaults.put(FAKE_DUAL_CRN_TRANSFER_DURATION_MS, "2000");
        defaults.put(FAKE_DUAL_CRN_PICK_DURATION_MS, "3000");
        defaults.put(FAKE_DUAL_CRN_PUT_DURATION_MS, "3000");
        defaults.put(FAKE_DUAL_CRN_LEVEL_STEP_DURATION_MS, "1000");
        defaults.put(FAKE_DUAL_CRN_BAY_STEP_DURATION_MS, "500");
        defaults.put(FAKE_DUAL_CRN_RESET_DURATION_MS, "0");
        defaults.put(FAKE_RGV_MOVE_STEP_DURATION_MS, "1000");
        defaults.put(FAKE_RGV_LOAD_DURATION_MS, "1000");
        defaults.put(FAKE_RGV_RESET_DURATION_MS, "0");
        defaults.put(FAKE_STATION_SEGMENT_WAIT_TIMEOUT_MS, "30000");
        defaults.put(FAKE_STATION_MOVE_STEP_DURATION_MS, "500");
        defaults.put(FAKE_STATION_IDLE_LOOP_DELAY_MS, "200");
        defaults.put(FAKE_STATION_BLOCKED_LOOP_DELAY_MS, "1000");
        defaults.put(FAKE_STATION_INITIALIZE_DELAY_MS, "0");
        defaults.put(FAKE_STATION_FINISH_DELAY_MS, "0");
        defaults.put(FAKE_OUT_STATION_STAY_TIMEOUT_MS, "3000");
        DEFAULTS = Collections.unmodifiableMap(defaults);
        BOOLEAN_KEYS = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
                ENABLE_FAKE,
                FAKE_REAL_TASK_REQUEST_WMS,
                FAKE_GENERATE_IN_TASK,
                FAKE_GENERATE_OUT_TASK,
                FAKE_ALLOW_CHECK_BLOCK
        )));
        LONG_KEYS = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
                FAKE_RUN_BLOCK_TIMEOUT_MS,
                FAKE_CRN_FETCH_PUT_DURATION_MS,
                FAKE_CRN_LEVEL_STEP_DURATION_MS,
                FAKE_CRN_BAY_STEP_DURATION_MS,
                FAKE_CRN_RESET_DURATION_MS,
                FAKE_DUAL_CRN_COMMAND_WAIT_MS,
                FAKE_DUAL_CRN_TRANSFER_DURATION_MS,
                FAKE_DUAL_CRN_PICK_DURATION_MS,
                FAKE_DUAL_CRN_PUT_DURATION_MS,
                FAKE_DUAL_CRN_LEVEL_STEP_DURATION_MS,
                FAKE_DUAL_CRN_BAY_STEP_DURATION_MS,
                FAKE_DUAL_CRN_RESET_DURATION_MS,
                FAKE_RGV_MOVE_STEP_DURATION_MS,
                FAKE_RGV_LOAD_DURATION_MS,
                FAKE_RGV_RESET_DURATION_MS,
                FAKE_STATION_SEGMENT_WAIT_TIMEOUT_MS,
                FAKE_STATION_MOVE_STEP_DURATION_MS,
                FAKE_STATION_IDLE_LOOP_DELAY_MS,
                FAKE_STATION_BLOCKED_LOOP_DELAY_MS,
                FAKE_STATION_INITIALIZE_DELAY_MS,
                FAKE_STATION_FINISH_DELAY_MS,
                FAKE_OUT_STATION_STAY_TIMEOUT_MS
        )));
        ALL_KEYS = Collections.unmodifiableSet(new LinkedHashSet<>(defaults.keySet()));
    }
    private FakeConfigKeys() {
    }
}
src/main/java/com/zy/core/network/fake/FakeConfigSupport.java
New file
@@ -0,0 +1,42 @@
package com.zy.core.network.fake;
import com.zy.system.service.ConfigService;
import com.core.common.SpringUtils;
public final class FakeConfigSupport {
    private FakeConfigSupport() {
    }
    public static String getString(String code) {
        return getString(code, FakeConfigKeys.DEFAULTS.get(code));
    }
    public static String getString(String code, String defaultValue) {
        ConfigService configService = SpringUtils.getBean(ConfigService.class);
        if (configService == null) {
            return defaultValue;
        }
        return configService.getConfigValue(code, defaultValue);
    }
    public static long getLong(String code) {
        String defaultValue = FakeConfigKeys.DEFAULTS.get(code);
        long fallback = 0L;
        if (defaultValue != null) {
            try {
                fallback = Long.parseLong(defaultValue);
            } catch (Exception ignore) {
            }
        }
        return getLong(code, fallback);
    }
    public static long getLong(String code, long defaultValue) {
        ConfigService configService = SpringUtils.getBean(ConfigService.class);
        if (configService == null) {
            return defaultValue;
        }
        return configService.getConfigLongValue(code, defaultValue);
    }
}
src/main/java/com/zy/core/network/fake/FakeStationBlockManager.java
New file
@@ -0,0 +1,160 @@
package com.zy.core.network.fake;
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.BasDualCrnp;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.BasDevpService;
import com.zy.asrs.service.BasDualCrnpService;
import com.zy.core.News;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.model.StationObjModel;
import com.zy.core.network.entity.ZyStationStatusEntity;
import com.zy.common.utils.RedisUtil;
import com.core.common.SpringUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 仿真堵塞管理器
 * 管理 fakeBlockedStations 集合,并通过 FakeStationStateManager 统一维护站点 runBlock 状态
 * 提供堵塞判定配置读取(fakeAllowCheckBlock、fakeRunBlockTimeoutMs)
 */
public class FakeStationBlockManager {
    private static final long DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS = 20000L;
    // 仿真内部堵塞站点集合,用于仿真侧快速判定
    // 站点 runBlock 仍通过 FakeStationStateManager 统一写入,避免绕过集中状态管理
    private final Set<Integer> fakeBlockedStations = ConcurrentHashMap.newKeySet();
    private final FakeStationStateManager stateManager;
    public FakeStationBlockManager(FakeStationStateManager stateManager) {
        this.stateManager = stateManager;
    }
    public boolean isBlocked(Integer stationId) {
        return stationId != null && fakeBlockedStations.contains(stationId);
    }
    public void markBlocked(Integer stationId) {
        if (stationId != null) {
            fakeBlockedStations.add(stationId);
        }
    }
    public void clearBlocked(Integer stationId) {
        if (stationId != null) {
            fakeBlockedStations.remove(stationId);
        }
    }
    /**
     * 标记站点为堵塞状态
     * 仿真场景也同步设置 runBlock=true,确保堵塞重算路径、更换库位等业务逻辑可生效。
     */
    public boolean runBlockStation(Integer lockTaskNo, Integer currentStationId,
            Integer currentStationDeviceNo, Integer taskNo, Integer blockStationId) {
        boolean updated = stateManager.updateStationDataInternal(
                currentStationId, currentStationDeviceNo, taskNo, blockStationId, true, "", true);
        if (updated) {
            fakeBlockedStations.add(currentStationId);
            stateManager.publishTaskLocation(taskNo, currentStationDeviceNo, currentStationId, true, true);
            News.info("[WCS Debug] 仿真站点堵塞上报,站点号={},设备号={},任务号={},阻塞目标站={},runBlock=true",
                    currentStationId, currentStationDeviceNo, taskNo, blockStationId);
        }
        return updated;
    }
    /**
     * 清除站点堵塞状态
     * 同时清除内部 fakeBlockedStations,并通过 FakeStationStateManager 统一清除 runBlock 标记
     */
    public void clearRunBlock(Integer stationId, Integer deviceNo) {
        ZyStationStatusEntity status = stateManager.findStationStatus(deviceNo, stationId);
        if (status == null) {
            fakeBlockedStations.remove(stationId);
            return;
        }
        if (status.isRunBlock()) {
            stateManager.updateStationDataInternal(stationId, deviceNo, null, null, null, null, false);
            stateManager.syncTaskLocation(deviceNo, stationId);
            News.info("[WCS Debug] 站点{}堵塞标记已清除", stationId);
        }
        fakeBlockedStations.remove(stationId);
    }
    public boolean isSpecialStation(Integer stationId) {
        if (stationId == null) { return false; }
        Set<Integer> specialStationIds = new HashSet<>();
        BasDevpService basDevpService = SpringUtils.getBean(BasDevpService.class);
        List<BasDevp> basDevps = basDevpService.list();
        for (BasDevp basDevp : basDevps) {
            for (StationObjModel station : basDevp.getInStationList$()) {
                specialStationIds.add(station.getStationId());
            }
            for (StationObjModel station : basDevp.getOutStationList$()) {
                specialStationIds.add(station.getStationId());
            }
            for (StationObjModel station : basDevp.getBarcodeStationList$()) {
                specialStationIds.add(station.getStationId());
            }
        }
        BasCrnpService basCrnpService = SpringUtils.getBean(BasCrnpService.class);
        List<BasCrnp> basCrnps = basCrnpService.list();
        for (BasCrnp basCrnp : basCrnps) {
            for (StationObjModel station : basCrnp.getInStationList$()) {
                specialStationIds.add(station.getStationId());
            }
            for (StationObjModel station : basCrnp.getOutStationList$()) {
                specialStationIds.add(station.getStationId());
            }
        }
        BasDualCrnpService basDualCrnpService = SpringUtils.getBean(BasDualCrnpService.class);
        List<BasDualCrnp> basDualCrnps = basDualCrnpService.list();
        for (BasDualCrnp basDualCrnp : basDualCrnps) {
            for (StationObjModel station : basDualCrnp.getInStationList$()) {
                specialStationIds.add(station.getStationId());
            }
            for (StationObjModel station : basDualCrnp.getOutStationList$()) {
                specialStationIds.add(station.getStationId());
            }
        }
        return specialStationIds.contains(stationId);
    }
    @SuppressWarnings("unchecked")
    public boolean getFakeAllowCheckBlock(RedisUtil redisUtil) {
        boolean fakeAllowCheckBlock = true;
        Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
        if (systemConfigMapObj instanceof Map) {
            Map<String, String> systemConfigMap = (Map<String, String>) systemConfigMapObj;
            String value = systemConfigMap.get("fakeAllowCheckBlock");
            if (value != null && !"Y".equals(value)) {
                fakeAllowCheckBlock = false;
            }
        }
        return fakeAllowCheckBlock;
    }
    public long getFakeRunBlockTimeoutMs(RedisUtil redisUtil) {
        long timeoutMs = DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS;
        Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
        if (systemConfigMapObj instanceof Map) {
            Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
            Object value = systemConfigMap.get("fakeRunBlockTimeoutMs");
            if (value != null) {
                try {
                    long parsed = Long.parseLong(String.valueOf(value).trim());
                    if (parsed > 0) { timeoutMs = parsed; }
                } catch (Exception ignore) { }
            }
        }
        return timeoutMs;
    }
}
src/main/java/com/zy/core/network/fake/FakeStationMoveEngine.java
New file
@@ -0,0 +1,154 @@
package com.zy.core.network.fake;
import com.zy.core.News;
import com.zy.core.network.entity.ZyStationStatusEntity;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 仿真站点移动引擎
 * 负责站点间的移动执行:初始化、移动到下一站、清除站点
 * 使用 ReentrantLock 做站点级并发控制
 */
public class FakeStationMoveEngine {
    private final Map<Integer, ReentrantLock> stationLocks = new ConcurrentHashMap<>();
    private final FakeStationStateManager stateManager;
    public FakeStationMoveEngine(FakeStationStateManager stateManager) {
        this.stateManager = stateManager;
    }
    public boolean initStationMove(Integer lockTaskNo, Integer currentStationId,
            Integer currentStationDeviceNo, Integer taskNo, Integer targetStationId,
            Boolean isLoading, String barcode) {
        lockStations(currentStationId);
        try {
            ZyStationStatusEntity currentStatus = stateManager.findStationStatus(currentStationDeviceNo, currentStationId);
            if (currentStatus == null) { return false; }
            if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0
                    && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) {
                return false;
            }
            boolean updated = stateManager.updateStationDataWithHeldLock(currentStationId, currentStationDeviceNo,
                    taskNo, targetStationId, isLoading, barcode, false);
            if (updated) {
                stateManager.publishTaskLocation(taskNo, currentStationDeviceNo, currentStationId,
                        Boolean.TRUE.equals(isLoading), false);
            }
            return updated;
        } finally {
            unlockStations(currentStationId);
        }
    }
    public boolean stationMoveToNext(Integer lockTaskNo, Integer currentStationId,
            Integer currentStationDeviceNo, Integer nextStationId, Integer nextStationDeviceNo,
            Integer taskNo, Integer targetStaNo) {
        News.info("[WCS Debug] fake stationMoveToNext开始,taskNo={},currentStationId={},currentDeviceNo={},nextStationId={},nextDeviceNo={},targetStaNo={}",
                taskNo, currentStationId, currentStationDeviceNo, nextStationId, nextStationDeviceNo, targetStaNo);
        lockStations(currentStationId, nextStationId);
        try {
            ZyStationStatusEntity currentStatus = stateManager.findStationStatus(currentStationDeviceNo, currentStationId);
            ZyStationStatusEntity nextStatus = stateManager.findStationStatus(nextStationDeviceNo, nextStationId);
            if (currentStatus == null || nextStatus == null) {
                News.info("仿真站点移动失败,原因=station_status_missing,taskNo={},currentStationId={},currentDeviceNo={},nextStationId={},nextDeviceNo={},targetStaNo={},currentStatusNull={},nextStatusNull={}",
                        taskNo, currentStationId, currentStationDeviceNo, nextStationId, nextStationDeviceNo, targetStaNo,
                        currentStatus == null, nextStatus == null);
                return false;
            }
            if ((nextStatus.getTaskNo() != null && nextStatus.getTaskNo() > 0) || nextStatus.isLoading()) {
                News.info("仿真站点移动失败,原因=next_station_busy,taskNo={},currentStationId={},nextStationId={},targetStaNo={},currentTaskNo={},currentLoading={},currentRunBlock={},nextTaskNo={},nextLoading={},nextRunBlock={},nextTargetStaNo={}",
                        taskNo, currentStationId, nextStationId, targetStaNo,
                        currentStatus.getTaskNo(), currentStatus.isLoading(), currentStatus.isRunBlock(),
                        nextStatus.getTaskNo(), nextStatus.isLoading(), nextStatus.isRunBlock(), nextStatus.getTargetStaNo());
                return false;
            }
            if (currentStatus.getTaskNo() == null || !taskNo.equals(currentStatus.getTaskNo()) || !currentStatus.isLoading()) {
                News.info("仿真站点移动失败,原因=current_station_task_mismatch,taskNo={},currentStationId={},nextStationId={},currentTaskNo={},currentLoading={},currentTargetStaNo={}",
                        taskNo, currentStationId, nextStationId,
                        currentStatus.getTaskNo(), currentStatus.isLoading(), currentStatus.getTargetStaNo());
                return false;
            }
            String barcode = currentStatus.getBarcode();
            News.info("[WCS Debug] fake handoff准备提交,taskNo={},sourceStationId={},sourceTaskNo={},sourceLoading={},sourceTargetStaNo={},sourceBarcode={},sourceRunBlock={},targetStationId={},targetTaskNo={},targetLoading={},targetTargetStaNo={},targetBarcode={},targetRunBlock={}",
                    taskNo,
                    currentStationId, currentStatus.getTaskNo(), currentStatus.isLoading(), currentStatus.getTargetStaNo(), currentStatus.getBarcode(), currentStatus.isRunBlock(),
                    nextStationId, nextStatus.getTaskNo(), nextStatus.isLoading(), nextStatus.getTargetStaNo(), nextStatus.getBarcode(), nextStatus.isRunBlock());
            boolean prepared = stateManager.updateStationDataWithHeldLock(nextStationId, nextStationDeviceNo,
                    taskNo, targetStaNo, false, barcode, false);
            if (!prepared) {
                News.info("仿真站点移动失败,原因=next_station_prepare_failed,taskNo={},currentStationId={},nextStationId={},targetStaNo={}",
                        taskNo, currentStationId, nextStationId, targetStaNo);
                return false;
            }
            boolean loaded = stateManager.updateStationDataWithHeldLock(nextStationId, nextStationDeviceNo,
                    null, null, true, null, false);
            if (!loaded) {
                News.info("仿真站点移动失败,原因=next_station_loading_commit_failed,taskNo={},currentStationId={},nextStationId={},targetStaNo={}",
                        taskNo, currentStationId, nextStationId, targetStaNo);
                stateManager.updateStationDataWithHeldLock(nextStationId, nextStationDeviceNo, 0, 0, false, "", false);
                return false;
            }
            boolean cleared = stateManager.updateStationDataWithHeldLock(currentStationId, currentStationDeviceNo,
                    0, 0, false, "", false);
            if (!cleared) {
                News.info("仿真站点移动失败,原因=current_station_clear_failed,taskNo={},currentStationId={},nextStationId={},targetStaNo={},currentTaskNo={},currentLoading={},currentRunBlock={},nextTaskNo={},nextLoading={},nextRunBlock={}",
                        taskNo, currentStationId, nextStationId, targetStaNo,
                        currentStatus.getTaskNo(), currentStatus.isLoading(), currentStatus.isRunBlock(),
                        taskNo, true, false);
                stateManager.updateStationDataWithHeldLock(nextStationId, nextStationDeviceNo, 0, 0, false, "", false);
                return false;
            }
            News.info("[WCS Debug] fake handoff提交成功,taskNo={},sourceStationId={},targetStationId={},targetStaNo={},sourceCleared=true,targetLoading=true",
                    taskNo, currentStationId, nextStationId, targetStaNo);
            stateManager.clearTaskLocationIfMatches(taskNo, currentStationDeviceNo, currentStationId);
            stateManager.publishTaskLocation(taskNo, nextStationDeviceNo, nextStationId, true, false);
            return true;
        } finally {
            unlockStations(currentStationId, nextStationId);
        }
    }
    public boolean clearStation(Integer deviceNo, Integer lockTaskNo, Integer currentStationId) {
        lockStations(currentStationId);
        try {
            ZyStationStatusEntity currentStatus = stateManager.findStationStatus(deviceNo, currentStationId);
            if (currentStatus == null) { return false; }
            Integer taskNo = currentStatus.getTaskNo();
            boolean cleared = stateManager.updateStationDataWithHeldLock(currentStationId, deviceNo, 0, 0, false, "", false);
            if (cleared) {
                stateManager.clearTaskLocationIfMatches(taskNo, deviceNo, currentStationId);
            }
            return cleared;
        } finally {
            unlockStations(currentStationId);
        }
    }
    private ReentrantLock getStationLock(Integer stationId) {
        return stationLocks.computeIfAbsent(stationId, key -> new ReentrantLock());
    }
    public void lockStations(Integer... stationIds) {
        Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length);
        Arrays.sort(sorted);
        for (Integer stationId : sorted) {
            getStationLock(stationId).lock();
        }
    }
    public void unlockStations(Integer... stationIds) {
        Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length);
        Arrays.sort(sorted);
        for (int i = sorted.length - 1; i >= 0; i--) {
            getStationLock(sorted[i]).unlock();
        }
    }
}
src/main/java/com/zy/core/network/fake/FakeStationStateManager.java
New file
@@ -0,0 +1,504 @@
package com.zy.core.network.fake;
import com.alibaba.fastjson.JSON;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.core.News;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.protocol.StationTaskBufferItem;
import com.zy.core.network.entity.ZyStationStatusEntity;
import com.zy.core.thread.support.StationTaskLocationRegistry;
import com.core.common.SpringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
public class FakeStationStateManager {
    private final StationTaskLocationRegistry stationTaskLocationRegistry = SpringUtils.getBean(StationTaskLocationRegistry.class);
    private interface StationMutation {
        void apply(ZyStationStatusEntity status);
    }
    private interface StationRead<T> {
        T apply(ZyStationStatusEntity status);
    }
    private final Map<Integer, List<ZyStationStatusEntity>> deviceStatusMap = new ConcurrentHashMap<Integer, List<ZyStationStatusEntity>>();
    private final Map<Integer, DeviceConfig> deviceConfigMap = new ConcurrentHashMap<Integer, DeviceConfig>();
    private final Map<Integer, ReentrantLock> stationLocks = new ConcurrentHashMap<Integer, ReentrantLock>();
    private interface StationLockedMutation {
        void apply(ZyStationStatusEntity status);
    }
    private interface StationLockedRead<T> {
        T apply(ZyStationStatusEntity status);
    }
    private ReentrantLock getStationLock(Integer stationId) {
        return stationLocks.computeIfAbsent(stationId, key -> new ReentrantLock());
    }
    public void lockStations(Integer... stationIds) {
        if (stationIds == null || stationIds.length == 0) {
            return;
        }
        Integer[] sorted = java.util.Arrays.copyOf(stationIds, stationIds.length);
        java.util.Arrays.sort(sorted);
        for (Integer stationId : sorted) {
            if (stationId != null) {
                getStationLock(stationId).lock();
            }
        }
    }
    public void unlockStations(Integer... stationIds) {
        if (stationIds == null || stationIds.length == 0) {
            return;
        }
        Integer[] sorted = java.util.Arrays.copyOf(stationIds, stationIds.length);
        java.util.Arrays.sort(sorted);
        for (int i = sorted.length - 1; i >= 0; i--) {
            Integer stationId = sorted[i];
            if (stationId != null) {
                getStationLock(stationId).unlock();
            }
        }
    }
    private boolean mutateLockedStation(Integer deviceNo, Integer stationId, StationLockedMutation mutation) {
        if (stationId == null || mutation == null) {
            return false;
        }
        lockStations(stationId);
        try {
            ZyStationStatusEntity status = findStationStatus(deviceNo, stationId);
            if (status == null) {
                return false;
            }
            mutation.apply(status);
            return true;
        } finally {
            unlockStations(stationId);
        }
    }
    private <T> T readLockedStation(Integer deviceNo, Integer stationId, StationLockedRead<T> read) {
        if (stationId == null || read == null) {
            return null;
        }
        lockStations(stationId);
        try {
            ZyStationStatusEntity status = findStationStatus(deviceNo, stationId);
            if (status == null) {
                return null;
            }
            return read.apply(status);
        } finally {
            unlockStations(stationId);
        }
    }
    private boolean updateStationDataWithinLock(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStaNo,
            Boolean isLoading, String barcode, Boolean runBlock) {
        ZyStationStatusEntity currentStatus = findStationStatus(deviceNo, stationId);
        if (currentStatus == null) {
            return false;
        }
        if (taskNo != null) {
            currentStatus.setTaskNo(taskNo);
        }
        if (targetStaNo != null) {
            currentStatus.setTargetStaNo(targetStaNo);
        }
        if (isLoading != null) {
            currentStatus.setLoading(isLoading);
        }
        if (barcode != null) {
            currentStatus.setBarcode(barcode);
        }
        if (runBlock != null) {
            currentStatus.setRunBlock(runBlock);
        }
        syncTaskLocationInternal(deviceNo, currentStatus);
        return true;
    }
    public void registerDevice(DeviceConfig deviceConfig) {
        if (deviceConfigMap.containsKey(deviceConfig.getDeviceNo())) {
            return;
        }
        deviceConfigMap.put(deviceConfig.getDeviceNo(), deviceConfig);
        deviceStatusMap.put(deviceConfig.getDeviceNo(), new CopyOnWriteArrayList<ZyStationStatusEntity>());
    }
    public Map<Integer, List<ZyStationStatusEntity>> getDeviceStatusMap() {
        return deviceStatusMap;
    }
    public Map<Integer, DeviceConfig> getDeviceConfigMap() {
        return deviceConfigMap;
    }
    public List<ZyStationStatusEntity> getStatus(Integer deviceNo) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return new ArrayList<ZyStationStatusEntity>();
        }
        DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo);
        if (statusList.isEmpty() && deviceConfig != null) {
            List<ZyStationStatusEntity> init = JSON.parseArray(deviceConfig.getFakeInitStatus(),
                    ZyStationStatusEntity.class);
            if (init != null) {
                statusList.addAll(init);
                for (ZyStationStatusEntity status : statusList) {
                    status.setAutoing(true);
                    status.setLoading(false);
                    status.setInEnable(true);
                    status.setOutEnable(true);
                    status.setIoMode(2);
                    status.setEmptyMk(false);
                    status.setFullPlt(false);
                    status.setRunBlock(false);
                    status.setPalletHeight(0);
                    status.setError(0);
                    status.setBarcode("");
                }
            }
        }
        return statusList;
    }
    public ZyStationStatusEntity findStationStatus(Integer deviceNo, Integer stationId) {
        if (deviceNo == null || stationId == null) {
            return null;
        }
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return null;
        }
        return statusList.stream()
                .filter(item -> item.getStationId().equals(stationId))
                .findFirst().orElse(null);
    }
    public Integer getDeviceNoByStationId(Integer stationId) {
        for (Map.Entry<Integer, List<ZyStationStatusEntity>> entry : deviceStatusMap.entrySet()) {
            List<ZyStationStatusEntity> list = entry.getValue();
            if (list == null) {
                continue;
            }
            for (ZyStationStatusEntity entity : list) {
                if (entity.getStationId() != null && entity.getStationId().equals(stationId)) {
                    return entry.getKey();
                }
            }
        }
        return null;
    }
    public Integer findCurrentStationIdByTask(Integer taskNo) {
        for (List<ZyStationStatusEntity> list : deviceStatusMap.values()) {
            if (list == null) {
                continue;
            }
            for (ZyStationStatusEntity entity : list) {
                Integer stationId = entity == null ? null : entity.getStationId();
                Integer matchedStationId = stationId == null ? null : readStation(getDeviceNoByStationId(stationId), stationId,
                        new StationRead<Integer>() {
                            @Override
                            public Integer apply(ZyStationStatusEntity status) {
                                if (status.getTaskNo() != null && status.getTaskNo().equals(taskNo) && status.isLoading()) {
                                    return status.getStationId();
                                }
                                return null;
                            }
                        });
                if (matchedStationId != null) {
                    return matchedStationId;
                }
            }
        }
        return null;
    }
    public String getThreadImpl(Integer deviceNo) {
        DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo);
        return deviceConfig == null ? "" : deviceConfig.getThreadImpl();
    }
    public boolean isStationLoadingTask(Integer deviceNo, Integer stationId, Integer taskNo) {
        Boolean result = readStation(deviceNo, stationId, new StationRead<Boolean>() {
            @Override
            public Boolean apply(ZyStationStatusEntity status) {
                return status.getTaskNo() != null && status.getTaskNo().equals(taskNo) && status.isLoading();
            }
        });
        return Boolean.TRUE.equals(result);
    }
    public boolean isStationClearedForTask(Integer deviceNo, Integer stationId, Integer taskNo) {
        Boolean result = readStation(deviceNo, stationId, new StationRead<Boolean>() {
            @Override
            public Boolean apply(ZyStationStatusEntity status) {
                boolean emptyStation = !status.isLoading()
                        && (status.getTaskNo() == null || status.getTaskNo() <= 0);
                return emptyStation;
            }
        });
        return result == null || result;
    }
    public boolean isFinalStationOwnerConflict(Integer deviceNo, Integer stationId, Integer taskNo) {
        Boolean result = readStation(deviceNo, stationId, new StationRead<Boolean>() {
            @Override
            public Boolean apply(ZyStationStatusEntity status) {
                return status.isLoading()
                        && taskNo != null
                        && status.getTaskNo() != null
                        && status.getTaskNo() > 0
                        && !taskNo.equals(status.getTaskNo());
            }
        });
        return Boolean.TRUE.equals(result);
    }
    public boolean isOccupiedByOtherLoadingTask(Integer deviceNo, Integer stationId, Integer taskNo) {
        Boolean result = readStation(deviceNo, stationId, new StationRead<Boolean>() {
            @Override
            public Boolean apply(ZyStationStatusEntity status) {
                return status.isLoading()
                        && status.getTaskNo() != null
                        && status.getTaskNo() > 0
                        && (taskNo == null || !taskNo.equals(status.getTaskNo()));
            }
        });
        return Boolean.TRUE.equals(result);
    }
    public ZyStationStatusEntity snapshotStation(Integer deviceNo, Integer stationId) {
        return readStation(deviceNo, stationId, new StationRead<ZyStationStatusEntity>() {
            @Override
            public ZyStationStatusEntity apply(ZyStationStatusEntity status) {
                return JSON.parseObject(JSON.toJSONString(status), ZyStationStatusEntity.class);
            }
        });
    }
    private boolean mutateStation(Integer deviceNo, Integer stationId, StationMutation mutation) {
        if (mutation == null) {
            return false;
        }
        return mutateLockedStation(deviceNo, stationId, new StationLockedMutation() {
            @Override
            public void apply(ZyStationStatusEntity status) {
                mutation.apply(status);
            }
        });
    }
    private <T> T readStation(Integer deviceNo, Integer stationId, StationRead<T> read) {
        if (read == null) {
            return null;
        }
        return readLockedStation(deviceNo, stationId, new StationLockedRead<T>() {
            @Override
            public T apply(ZyStationStatusEntity status) {
                return read.apply(status);
            }
        });
    }
    public boolean updateStationDataWithHeldLock(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStaNo,
            Boolean isLoading, String barcode, Boolean runBlock) {
        return updateStationDataWithinLock(stationId, deviceNo, taskNo, targetStaNo, isLoading, barcode, runBlock);
    }
    public boolean updateStationDataInternal(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStaNo,
            Boolean isLoading, String barcode, Boolean runBlock) {
        return mutateLockedStation(deviceNo, stationId, new StationLockedMutation() {
            @Override
            public void apply(ZyStationStatusEntity currentStatus) {
                updateStationDataWithinLock(stationId, deviceNo, taskNo, targetStaNo, isLoading, barcode, runBlock);
            }
        });
    }
    public void syncTaskLocation(Integer deviceNo, Integer stationId) {
        if (deviceNo == null || stationId == null) {
            return;
        }
        readStation(deviceNo, stationId, new StationRead<Void>() {
            @Override
            public Void apply(ZyStationStatusEntity status) {
                syncTaskLocationInternal(deviceNo, status);
                return null;
            }
        });
    }
    public void removeTaskLocation(Integer taskNo, Integer deviceNo, Integer stationId) {
        if (stationTaskLocationRegistry == null || taskNo == null || taskNo <= 0) {
            return;
        }
        stationTaskLocationRegistry.remove(taskNo, deviceNo, stationId);
    }
    private void syncTaskLocationInternal(Integer deviceNo, ZyStationStatusEntity status) {
        if (stationTaskLocationRegistry == null || deviceNo == null || status == null) {
            return;
        }
        Integer taskNo = status.getTaskNo();
        if (taskNo != null && taskNo > 0 && status.isLoading() && status.getStationId() != null) {
            stationTaskLocationRegistry.update(taskNo, deviceNo, status.getStationId(), true,
                    status.isRunBlock(), System.currentTimeMillis());
        }
    }
    public void clearTaskLocationIfMatches(Integer taskNo, Integer deviceNo, Integer stationId) {
        if (taskNo == null || taskNo <= 0 || deviceNo == null || stationId == null) {
            return;
        }
        readStation(deviceNo, stationId, new StationRead<Void>() {
            @Override
            public Void apply(ZyStationStatusEntity status) {
                if (status.getTaskNo() == null || !taskNo.equals(status.getTaskNo()) || !status.isLoading()) {
                    removeTaskLocation(taskNo, deviceNo, stationId);
                }
                return null;
            }
        });
    }
    public void publishTaskLocation(Integer taskNo, Integer deviceNo, Integer stationId, boolean loading, boolean runBlock) {
        if (stationTaskLocationRegistry == null || taskNo == null || taskNo <= 0 || deviceNo == null || stationId == null || !loading) {
            return;
        }
        stationTaskLocationRegistry.update(taskNo, deviceNo, stationId, true, runBlock, System.currentTimeMillis());
    }
    public void generateStationData(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
        mutateStation(deviceNo, stationId, new StationMutation() {
            @Override
            public void apply(ZyStationStatusEntity status) {
                News.info("仿真站点生成任务数据,设备号={},站点号={},原taskNo={},新taskNo={},原targetStaNo={},新targetStaNo={},原loading={},barcode={}",
                        deviceNo, stationId, status.getTaskNo(), taskNo, status.getTargetStaNo(), targetStationId,
                        status.isLoading(), status.getBarcode());
                status.setTaskNo(taskNo);
                status.setTargetStaNo(targetStationId);
                status.setLoading(false);
            }
        });
    }
    public void generateFakeOutStationData(Integer deviceNo, Integer stationId) {
        mutateStation(deviceNo, stationId, new StationMutation() {
            @Override
            public void apply(ZyStationStatusEntity status) {
                status.setLoading(true);
            }
        });
    }
    public void resetStation(Integer deviceNo, Integer stationId) {
        if (findStationStatus(deviceNo, stationId) == null) {
            return;
        }
        clearStationForDispatch(deviceNo, stationId, "resetStation");
    }
    public void clearStationForDispatch(Integer deviceNo, Integer stationId, String reason) {
        mutateStation(deviceNo, stationId, new StationMutation() {
            @Override
            public void apply(ZyStationStatusEntity status) {
                News.info("仿真站点状态清除,设备号={},站点号={},原taskNo={},原targetStaNo={},原loading={},原barcode={},动作={}",
                        deviceNo, stationId, status.getTaskNo(), status.getTargetStaNo(), status.isLoading(), status.getBarcode(), reason);
                status.setTaskNo(0);
                status.setTargetStaNo(0);
                status.setLoading(false);
                status.setBarcode("");
                status.setRunBlock(false);
            }
        });
    }
    public void handoffBarcodeStation(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
        mutateLockedStation(deviceNo, stationId, new StationLockedMutation() {
            @Override
            public void apply(ZyStationStatusEntity status) {
                News.info("仿真站点生成任务数据,设备号={},站点号={},原taskNo={},新taskNo={},原targetStaNo={},新targetStaNo={},原loading={},barcode={}",
                        deviceNo, stationId, status.getTaskNo(), taskNo, status.getTargetStaNo(), targetStationId,
                        status.isLoading(), status.getBarcode());
                status.setTaskNo(taskNo);
                status.setTargetStaNo(targetStationId);
                status.setLoading(false);
                News.info("仿真站点状态清除,设备号={},站点号={},原taskNo={},原targetStaNo={},原loading={},原barcode={},动作={}",
                        deviceNo, stationId, status.getTaskNo(), status.getTargetStaNo(), status.isLoading(), status.getBarcode(), "barcodeTaskHandoff");
                status.setTaskNo(0);
                status.setTargetStaNo(0);
                status.setLoading(false);
                status.setBarcode("");
                status.setRunBlock(false);
            }
        });
    }
    public void updateStationBarcode(Integer deviceNo, Integer stationId, String barcode) {
        mutateStation(deviceNo, stationId, new StationMutation() {
            @Override
            public void apply(ZyStationStatusEntity status) {
                status.setBarcode(barcode);
            }
        });
    }
    public boolean generateStationBarcode(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo) {
        ZyStationStatusEntity currentStatus = findStationStatus(currentStationDeviceNo, currentStationId);
        if (currentStatus == null) {
            return false;
        }
        Random random = new Random();
        String barcodeTime = String.valueOf(System.currentTimeMillis());
        String barcode = String.valueOf(random.nextInt(10)) + String.valueOf(random.nextInt(10))
                + barcodeTime.substring(7);
        return updateStationDataInternal(currentStationId, currentStationDeviceNo, null, null, null, barcode, null);
    }
    public CommandResponse clearTaskBufferSlot(Integer deviceNo, Integer stationId, Integer slotIdx) {
        if (deviceNo == null || stationId == null || slotIdx == null || slotIdx <= 0) {
            return new CommandResponse(false, "清理缓存区槽位参数无效");
        }
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return new CommandResponse(false, "未找到设备状态");
        }
        for (ZyStationStatusEntity status : statusList) {
            if (status == null || !stationId.equals(status.getStationId())) {
                continue;
            }
            List<StationTaskBufferItem> taskBufferItems = status.getTaskBufferItems();
            if (taskBufferItems == null || taskBufferItems.isEmpty()) {
                return new CommandResponse(true, "缓存区槽位已为空");
            }
            for (StationTaskBufferItem item : taskBufferItems) {
                if (item != null && slotIdx.equals(item.getSlotIdx())) {
                    item.setTaskNo(0);
                    item.setTargetStaNo(0);
                    return new CommandResponse(true, "缓存区槽位清理成功");
                }
            }
            return new CommandResponse(false, "未找到缓存区槽位");
        }
        return new CommandResponse(false, "未找到站点状态");
    }
}
src/main/java/com/zy/core/network/fake/ZyCrnFakeConnect.java
@@ -17,6 +17,11 @@
    private DeviceConfig deviceConfig;
    private final ExecutorService executor = Executors.newSingleThreadExecutor();
    private long fetchPutDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_CRN_FETCH_PUT_DURATION_MS); }
    private long levelStepDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_CRN_LEVEL_STEP_DURATION_MS); }
    private long bayStepDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_CRN_BAY_STEP_DURATION_MS); }
    private long resetDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_CRN_RESET_DURATION_MS); }
    public ZyCrnFakeConnect(DeviceConfig deviceConfig) {
        this.deviceConfig = deviceConfig;
        this.crnStatus = JSON.parseObject(deviceConfig.getFakeInitStatus(), ZyCrnStatusEntity.class);
@@ -56,6 +61,8 @@
    }
    private void commandTaskComplete(CrnCommand command) {
        sleep(resetDurationMs());
        this.crnStatus.setLoaded(0);
        this.crnStatus.setTaskNo(0);
        this.crnStatus.setStatus(CrnStatusType.IDLE.id);
    }
@@ -90,7 +97,7 @@
        moveY(this.crnStatus.getBay(), sourcePosY);
        moveZ(this.crnStatus.getLevel(), sourcePosZ);
        this.crnStatus.setStatus(CrnStatusType.FETCHING.id);
        sleep(2000);
        sleep(fetchPutDurationMs());
        if (Thread.currentThread().isInterrupted()) {
            return;
        }
@@ -100,7 +107,7 @@
        moveY(this.crnStatus.getBay(), destinationPosY);
        moveZ(this.crnStatus.getLevel(), destinationPosZ);
        this.crnStatus.setStatus(CrnStatusType.PUTTING.id);
        sleep(2000);
        sleep(fetchPutDurationMs());
        if (Thread.currentThread().isInterrupted()) {
            return;
        }
@@ -115,7 +122,7 @@
            for(int i = 0; i < moveLength; i++) {
                initSourcePosZ++;
                this.crnStatus.setLevel(initSourcePosZ);
                sleep(1000);
                sleep(levelStepDurationMs());
                if (Thread.currentThread().isInterrupted()) {
                    return;
                }
@@ -126,7 +133,7 @@
            for(int i = 0; i < moveLength; i++) {
                initSourcePosZ--;
                this.crnStatus.setLevel(initSourcePosZ);
                sleep(1000);
                sleep(levelStepDurationMs());
                if (Thread.currentThread().isInterrupted()) {
                    return;
                }
@@ -141,7 +148,7 @@
            for(int i = 0; i < moveLength; i++) {
                initSourcePosY++;
                this.crnStatus.setBay(initSourcePosY);
                sleep(1000);
                sleep(bayStepDurationMs());
                if (Thread.currentThread().isInterrupted()) {
                    return;
                }
@@ -152,7 +159,7 @@
            for(int i = 0; i < moveLength; i++) {
                initSourcePosY--;
                this.crnStatus.setBay(initSourcePosY);
                sleep(1000);
                sleep(bayStepDurationMs());
                if (Thread.currentThread().isInterrupted()) {
                    return;
                }
src/main/java/com/zy/core/network/fake/ZyDualCrnFakeConnect.java
@@ -23,6 +23,14 @@
    private DualCrnCommand station1LastCommand = null;
    private DualCrnCommand station2LastCommand = null;
    private long commandWaitMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_DUAL_CRN_COMMAND_WAIT_MS); }
    private long transferDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_DUAL_CRN_TRANSFER_DURATION_MS); }
    private long pickDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_DUAL_CRN_PICK_DURATION_MS); }
    private long putDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_DUAL_CRN_PUT_DURATION_MS); }
    private long levelStepDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_DUAL_CRN_LEVEL_STEP_DURATION_MS); }
    private long bayStepDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_DUAL_CRN_BAY_STEP_DURATION_MS); }
    private long resetDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_DUAL_CRN_RESET_DURATION_MS); }
    public ZyDualCrnFakeConnect(DeviceConfig deviceConfig) {
        this.deviceConfig = deviceConfig;
        this.crnStatus = JSON.parseObject(deviceConfig.getFakeInitStatus(), ZyDualCrnStatusEntity.class);
@@ -68,6 +76,7 @@
    }
    private void commandTaskComplete(DualCrnCommand command) {
        sleep(resetDurationMs());
        if(command.getStation() == 1) {
            if (crnStatus.getLoaded() == 0) {
                this.crnStatus.setTaskNo(0);
@@ -90,7 +99,7 @@
            if (station2LastCommand == null) {
                this.crnStatus.setStatusTwo(DualCrnStatusType.IDLE.id);
            }else {
                if (station1LastCommand.getTaskMode().intValue() == DualCrnTaskModeType.PICK.id) {
                if (station2LastCommand.getTaskMode().intValue() == DualCrnTaskModeType.PICK.id) {
                    this.crnStatus.setStatusTwo(DualCrnStatusType.FETCH_COMPLETE.id);
                }else {
                    this.crnStatus.setStatusTwo(DualCrnStatusType.IDLE.id);
@@ -116,7 +125,7 @@
                break;
            }
            sleep(200);
            sleep(commandWaitMs());
        }
        if(command.getStation() == 1) {
@@ -128,7 +137,7 @@
        }else {
            this.crnStatus.setTaskNoTwo(taskNo);
            this.crnStatus.setStatusTwo(DualCrnStatusType.PUT_MOVING.id);
            this.crnStatus.setTaskReceive(1);
            this.crnStatus.setTaskReceiveTwo(1);
            moveYZ(this.crnStatus.getBay(), destinationPosY, this.crnStatus.getLevel(), destinationPosZ, command.getStation());
            this.crnStatus.setStatusTwo(DualCrnStatusType.WAITING.id);
        }
@@ -156,7 +165,7 @@
                break;
            }
            sleep(200);
            sleep(commandWaitMs());
        }
        if(command.getStation() == 1) {
@@ -166,7 +175,7 @@
            moveYZ(this.crnStatus.getBay(), sourcePosY,this.crnStatus.getLevel(), sourcePosZ,command.getStation());
            this.crnStatus.setStatus(DualCrnStatusType.FETCHING.id);
            sleep(2000);
            sleep(transferDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -175,7 +184,7 @@
            this.crnStatus.setStatus(DualCrnStatusType.PUT_MOVING.id);
            moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
            this.crnStatus.setStatus(DualCrnStatusType.PUTTING.id);
            sleep(2000);
            sleep(transferDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -190,7 +199,7 @@
            moveYZ(this.crnStatus.getBay(), sourcePosY,this.crnStatus.getLevel(), sourcePosZ, command.getStation());
            this.crnStatus.setStatusTwo(DualCrnStatusType.FETCHING.id);
            sleep(2000);
            sleep(transferDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -199,7 +208,7 @@
            this.crnStatus.setStatusTwo(DualCrnStatusType.PUT_MOVING.id);
            moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
            this.crnStatus.setStatusTwo(DualCrnStatusType.PUTTING.id);
            sleep(2000);
            sleep(transferDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -229,7 +238,7 @@
                break;
            }
            sleep(200);
            sleep(commandWaitMs());
        }
        if(command.getStation() == 1) {
@@ -239,7 +248,7 @@
            moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
            this.crnStatus.setStatus(DualCrnStatusType.FETCHING.id);
            sleep(3000);
            sleep(pickDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -254,7 +263,7 @@
            moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
            this.crnStatus.setStatusTwo(DualCrnStatusType.FETCHING.id);
            sleep(3000);
            sleep(pickDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -284,7 +293,7 @@
                break;
            }
            sleep(200);
            sleep(commandWaitMs());
        }
        if(command.getStation() == 1) {
@@ -294,7 +303,7 @@
            moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
            this.crnStatus.setStatus(DualCrnStatusType.PUTTING.id);
            sleep(3000);
            sleep(putDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -309,7 +318,7 @@
            moveYZ(this.crnStatus.getBay(), destinationPosY,this.crnStatus.getLevel(), destinationPosZ, command.getStation());
            this.crnStatus.setStatusTwo(DualCrnStatusType.PUTTING.id);
            sleep(3000);
            sleep(putDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -329,7 +338,7 @@
            for(int i = 0; i < moveLength; i++) {
                initSourcePosZ++;
                this.crnStatus.setLevel(initSourcePosZ);
                sleep(1000);
                sleep(levelStepDurationMs());
                if (Thread.currentThread().isInterrupted()) {
                    return;
                }
@@ -341,7 +350,7 @@
                initSourcePosZ--;
                this.crnStatus.setLevel(initSourcePosZ);
                this.crnStatus.setLevel(initSourcePosZ);
                sleep(1000);
                sleep(levelStepDurationMs());
                if (Thread.currentThread().isInterrupted()) {
                    return;
                }
@@ -356,7 +365,7 @@
            for(int i = 0; i < moveLength; i++) {
                initSourcePosY++;
                this.crnStatus.setBay(initSourcePosY);
                sleep(500);
                sleep(bayStepDurationMs());
                if (Thread.currentThread().isInterrupted()) {
                    return;
                }
@@ -367,7 +376,7 @@
            for(int i = 0; i < moveLength; i++) {
                initSourcePosY--;
                this.crnStatus.setBay(initSourcePosY);
                sleep(500);
                sleep(bayStepDurationMs());
                if (Thread.currentThread().isInterrupted()) {
                    return;
                }
src/main/java/com/zy/core/network/fake/ZyRgvFakeConnect.java
@@ -24,6 +24,10 @@
    private final DeviceConfig deviceConfig;
    private final ExecutorService executor = Executors.newSingleThreadExecutor();
    private long moveStepDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_RGV_MOVE_STEP_DURATION_MS); }
    private long loadDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_RGV_LOAD_DURATION_MS); }
    private long resetDurationMs() { return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_RGV_RESET_DURATION_MS); }
    public ZyRgvFakeConnect(DeviceConfig deviceConfig) {
        this.deviceConfig = deviceConfig;
        ZyRgvStatusEntity init = JSON.parseObject(deviceConfig.getFakeInitStatus(), ZyRgvStatusEntity.class);
@@ -120,7 +124,7 @@
            Integer currentTrackSiteNo = valueObject.getInteger("trackSiteNo");
            status.setRgvPos(currentTrackSiteNo);
            sleep(1000);
            sleep(moveStepDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -128,7 +132,7 @@
        status.setStatus(RgvStatusType.PUTTING.id);
        status.setLoaded(1);
        sleep(1000);
        sleep(loadDurationMs());
        if (Thread.currentThread().isInterrupted()) {
            return;
        }
@@ -139,13 +143,13 @@
            Integer currentTrackSiteNo = valueObject.getInteger("trackSiteNo");
            status.setRgvPos(currentTrackSiteNo);
            sleep(1000);
            sleep(moveStepDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
        }
        sleep(1000);
        sleep(loadDurationMs());
        if (Thread.currentThread().isInterrupted()) {
            return;
        }
@@ -193,7 +197,7 @@
            Integer currentTrackSiteNo = valueObject.getInteger("trackSiteNo");
            status.setRgvPos(currentTrackSiteNo);
            sleep(1000);
            sleep(moveStepDurationMs());
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
@@ -202,6 +206,7 @@
    }
    private void commandTaskComplete(RgvCommand command) {
        sleep(resetDurationMs());
        status.setTaskNo(0);
        status.setStatus(RgvStatusType.IDLE.id);
    }
src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
@@ -2,18 +2,13 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.core.common.SpringUtils;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.asrs.service.BasDevpService;
import com.zy.core.model.StationObjModel;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.StationCommandType;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.command.StationCommand;
import com.zy.core.model.protocol.StationTaskBufferItem;
import com.zy.core.network.api.ZyStationConnectApi;
import com.zy.core.network.entity.ZyStationStatusEntity;
@@ -23,21 +18,42 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicInteger;
public class ZyStationFakeSegConnect implements ZyStationConnectApi {
    private static final long DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS = 10000L;
    private static final long WAIT_SEGMENT_TIMEOUT_MS = 30000L;
    private long getWaitSegmentTimeoutMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_SEGMENT_WAIT_TIMEOUT_MS, 30000L);
    }
    private long getMoveStepDurationMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_MOVE_STEP_DURATION_MS, 500L);
    }
    private long getIdleLoopDelayMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_IDLE_LOOP_DELAY_MS, 200L);
    }
    private long getBlockedLoopDelayMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_BLOCKED_LOOP_DELAY_MS, 1000L);
    }
    private long getInitializeDelayMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_INITIALIZE_DELAY_MS, 0L);
    }
    private long getFinishDelayMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_FINISH_DELAY_MS, 0L);
    }
    private static final String STATUS_WAITING = "WAITING";
    private static final String STATUS_RUNNING = "RUNNING";
@@ -46,65 +62,66 @@
    private static final String STATUS_TIMEOUT = "TIMEOUT";
    private static final String STATUS_FINISHED = "FINISHED";
    private final Map<Integer, ReentrantLock> stationLocks = new ConcurrentHashMap<Integer, ReentrantLock>();
    private final Map<Integer, List<ZyStationStatusEntity>> deviceStatusMap = new ConcurrentHashMap<Integer, List<ZyStationStatusEntity>>();
    private final Map<Integer, DeviceConfig> deviceConfigMap = new ConcurrentHashMap<Integer, DeviceConfig>();
    private static final String SEGMENT_MERGE_APPEND = "APPEND";
    private static final String SEGMENT_MERGE_REPLACE_TARGET_CHANGED = "REPLACE_TARGET_CHANGED";
    private static final String SEGMENT_MERGE_REPLACE_REROUTE = "REPLACE_REROUTE";
    private static final String SEGMENT_MERGE_IGNORE_DISCONNECTED = "IGNORE_DISCONNECTED";
    private static final String SEGMENT_MERGE_IGNORE_CURRENT_MISSING = "IGNORE_CURRENT_MISSING";
    private final FakeStationStateManager stateManager = new FakeStationStateManager();
    private final FakeStationMoveEngine moveEngine = new FakeStationMoveEngine(stateManager);
    private final FakeStationBlockManager blockManager = new FakeStationBlockManager(stateManager);
    private static final AtomicInteger DEVICE_EXECUTOR_THREAD_SEQ = new AtomicInteger(1);
    private volatile ScheduledExecutorService loopScheduler = createLoopScheduler();
    private RedisUtil redisUtil;
    private final Map<Integer, ExecutorService> deviceExecutors = new ConcurrentHashMap<Integer, ExecutorService>();
    private final Map<Integer, BlockingQueue<StationCommand>> taskQueues = new ConcurrentHashMap<Integer, BlockingQueue<StationCommand>>();
    private final Map<Integer, Long> taskLastUpdateTime = new ConcurrentHashMap<Integer, Long>();
    private final Map<Integer, Boolean> taskRunning = new ConcurrentHashMap<Integer, Boolean>();
    private final ExecutorService executor = Executors.newCachedThreadPool();
    private RedisUtil redisUtil;
    private final Map<Integer, TaskRuntimeContext> taskContexts = new ConcurrentHashMap<Integer, TaskRuntimeContext>();
    private final Map<Integer, AtomicInteger> taskLoopGenerations = new ConcurrentHashMap<Integer, AtomicInteger>();
    private final Map<Integer, Object> taskLifecycleLocks = new ConcurrentHashMap<Integer, Object>();
    private volatile Set<Integer> legalClearStationIds = new HashSet<Integer>();
    private volatile Set<Integer> barcodeStationIds = new HashSet<Integer>();
    public void addFakeConnect(DeviceConfig deviceConfig, RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
        if (deviceConfigMap.containsKey(deviceConfig.getDeviceNo())) {
            return;
        }
        deviceConfigMap.put(deviceConfig.getDeviceNo(), deviceConfig);
        deviceStatusMap.put(deviceConfig.getDeviceNo(), new CopyOnWriteArrayList<ZyStationStatusEntity>());
        refreshLegalClearStationIds();
        refreshBarcodeStationIds();
        stateManager.registerDevice(deviceConfig);
    }
    @Override
    public boolean connect() {
        if (loopScheduler == null || loopScheduler.isShutdown() || loopScheduler.isTerminated()) {
            loopScheduler = createLoopScheduler();
        }
        return true;
    }
    @Override
    public boolean disconnect() {
        executor.shutdownNow();
        ScheduledExecutorService scheduler = loopScheduler;
        if (scheduler != null) {
            scheduler.shutdownNow();
        }
        for (ExecutorService executor : deviceExecutors.values()) {
            executor.shutdownNow();
        }
        deviceExecutors.clear();
        taskQueues.clear();
        taskLastUpdateTime.clear();
        taskRunning.clear();
        taskContexts.clear();
        taskLoopGenerations.clear();
        taskLifecycleLocks.clear();
        return true;
    }
    @Override
    public List<ZyStationStatusEntity> getStatus(Integer deviceNo) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return new ArrayList<ZyStationStatusEntity>();
        }
        DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo);
        if (statusList.isEmpty() && deviceConfig != null) {
            List<ZyStationStatusEntity> init = JSON.parseArray(deviceConfig.getFakeInitStatus(),
                    ZyStationStatusEntity.class);
            if (init != null) {
                statusList.addAll(init);
                for (ZyStationStatusEntity status : statusList) {
                    status.setAutoing(true);
                    status.setLoading(false);
                    status.setInEnable(true);
                    status.setOutEnable(true);
                    status.setEmptyMk(false);
                    status.setFullPlt(false);
                    status.setRunBlock(false);
                    status.setPalletHeight(0);
                    status.setError(0);
                    status.setBarcode("");
                }
            }
        }
        return statusList;
        return stateManager.getStatus(deviceNo);
    }
    @Override
@@ -115,25 +132,44 @@
        }
        if (command.getCommandType() != StationCommandType.MOVE) {
            handleCommand(deviceNo, command);
            return new CommandResponse(true, "命令已受理(异步执行)");
        }
        if (isDirectMoveCommand(command)) {
            handleDirectMoveCommand(deviceNo, command);
            return new CommandResponse(true, "命令已受理(异步执行)");
        }
        taskQueues.computeIfAbsent(taskNo, key -> new LinkedBlockingQueue<StationCommand>()).offer(command);
        taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
        if (taskRunning.putIfAbsent(taskNo, true) == null) {
            executor.submit(new Runnable() {
            News.info("[WCS Debug] fake sendCommand收到非MOVE命令,deviceNo={},taskNo={},stationId={},targetStaNo={},commandType={}",
                    deviceNo, taskNo, command.getStationId(), command.getTargetStaNo(), command.getCommandType());
            getDeviceExecutor(deviceNo).submit(new Runnable() {
                @Override
                public void run() {
                    runTaskLoop(deviceNo, taskNo);
                    handleCommand(deviceNo, command);
                }
            });
            return new CommandResponse(true, "命令已受理(异步执行)");
        }
        int loopGeneration = 0;
        boolean shouldSchedule = false;
        Object lifecycleLock = getTaskLifecycleLock(taskNo);
        synchronized (lifecycleLock) {
            BlockingQueue<StationCommand> queue = taskQueues.computeIfAbsent(taskNo, key -> new LinkedBlockingQueue<StationCommand>());
            queue.offer(command);
            taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
            News.info("[WCS Debug] fake sendCommand入队,deviceNo={},taskNo={},stationId={},targetStaNo={},segmentNo={},segmentCount={},queueSize={},running={}",
                    deviceNo, taskNo, command.getStationId(), command.getTargetStaNo(), command.getSegmentNo(), command.getSegmentCount(),
                    queue.size(), taskRunning.containsKey(taskNo));
            if (taskRunning.putIfAbsent(taskNo, true) == null) {
                TaskRuntimeContext context = new TaskRuntimeContext(taskNo, deviceNo, stateManager.getThreadImpl(deviceNo));
                taskContexts.put(taskNo, context);
                AtomicInteger generation = taskLoopGenerations.computeIfAbsent(taskNo, key -> new AtomicInteger(0));
                loopGeneration = generation.incrementAndGet();
                context.loopGeneration = loopGeneration;
                shouldSchedule = true;
                News.info("[WCS Debug] fake task准备启动执行线程,deviceNo={},taskNo={},queueSize={},loopGeneration={}",
                        deviceNo, taskNo, queue.size(), loopGeneration);
            } else {
                News.info("[WCS Debug] fake task复用已存在执行线程,deviceNo={},taskNo={},queueSize={}",
                        deviceNo, taskNo, queue.size());
            }
        }
        if (shouldSchedule) {
            scheduleTaskLoop(deviceNo, taskNo, loopGeneration);
        }
        return new CommandResponse(true, "命令已受理(异步执行)");
@@ -146,31 +182,7 @@
    @Override
    public CommandResponse clearTaskBufferSlot(Integer deviceNo, Integer stationId, Integer slotIdx) {
        if (deviceNo == null || stationId == null || slotIdx == null || slotIdx <= 0) {
            return new CommandResponse(false, "清理缓存区槽位参数无效");
        }
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return new CommandResponse(false, "未找到设备状态");
        }
        for (ZyStationStatusEntity status : statusList) {
            if (status == null || !stationId.equals(status.getStationId())) {
                continue;
            }
            List<StationTaskBufferItem> taskBufferItems = status.getTaskBufferItems();
            if (taskBufferItems == null || taskBufferItems.isEmpty()) {
                return new CommandResponse(true, "缓存区槽位已为空");
            }
            for (StationTaskBufferItem item : taskBufferItems) {
                if (item != null && slotIdx.equals(item.getSlotIdx())) {
                    item.setTaskNo(0);
                    item.setTargetStaNo(0);
                    return new CommandResponse(true, "缓存区槽位清理成功");
                }
            }
            return new CommandResponse(false, "未找到缓存区槽位");
        }
        return new CommandResponse(false, "未找到站点状态");
        return stateManager.clearTaskBufferSlot(deviceNo, stationId, slotIdx);
    }
    @Override
@@ -178,116 +190,209 @@
        return new byte[0];
    }
    private boolean isDirectMoveCommand(StationCommand command) {
        if (command == null || command.getCommandType() != StationCommandType.MOVE) {
            return false;
        }
        List<Integer> path = command.getNavigatePath();
        if (command.getStationId() == null || !command.getStationId().equals(command.getTargetStaNo())) {
            return false;
        }
        if (path == null || path.isEmpty()) {
            return true;
        }
        return path.size() == 1 && command.getStationId().equals(path.get(0));
    private ScheduledExecutorService createLoopScheduler() {
        return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable, "fake-station-loop-scheduler");
                thread.setDaemon(true);
                return thread;
            }
        });
    }
    private void handleDirectMoveCommand(Integer deviceNo, StationCommand command) {
        Integer taskNo = command.getTaskNo();
        Integer stationId = command.getStationId();
        Integer targetStationId = command.getTargetStaNo();
        if (taskNo != null && taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId != null
                && stationId.equals(targetStationId)) {
            generateStationData(deviceNo, taskNo, stationId, targetStationId);
        }
        TaskRuntimeContext context = new TaskRuntimeContext(taskNo, getThreadImpl(deviceNo));
        context.startStationId = stationId;
        context.currentStationId = stationId;
        context.finalTargetStationId = targetStationId;
        context.initialized = true;
        context.status = STATUS_RUNNING;
        context.appendStitchedPath(Arrays.asList(stationId));
        context.addPassedStation(stationId);
        context.generateBarcode = checkTaskNoInArea(taskNo);
        traceEvent(deviceNo, context, "MOVE_INIT", "同站点任务直接到位", buildDetails("stationId", stationId), false);
        if (context.generateBarcode) {
            generateStationBarcode(taskNo, stationId, deviceNo);
        }
        traceEvent(deviceNo, context, "ARRIVED", "任务已在起点站点完成", buildDetails("barcodeGenerated",
                context.generateBarcode, "stationId", stationId), false);
        context.status = STATUS_FINISHED;
        traceEvent(deviceNo, context, "TASK_END", "任务执行完成", buildDetails("reason", STATUS_FINISHED), true);
    private ExecutorService getDeviceExecutor(Integer deviceNo) {
        return deviceExecutors.computeIfAbsent(deviceNo, key -> Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable,
                        "fake-station-device-" + key + "-" + DEVICE_EXECUTOR_THREAD_SEQ.getAndIncrement());
                thread.setDaemon(true);
                return thread;
            }
        }));
    }
    private void runTaskLoop(Integer deviceNo, Integer taskNo) {
        TaskRuntimeContext context = new TaskRuntimeContext(taskNo, getThreadImpl(deviceNo));
        try {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    if (!isTerminalStatus(context.status)) {
                        context.status = STATUS_CANCELLED;
    private void scheduleTaskLoop(Integer deviceNo, Integer taskNo, int loopGeneration) {
        scheduleTaskLoop(deviceNo, taskNo, loopGeneration, 0L);
    }
    private void scheduleTaskLoop(Integer deviceNo, Integer taskNo, int loopGeneration, long delayMs) {
        Runnable task = new Runnable() {
            @Override
            public void run() {
                getDeviceExecutor(deviceNo).submit(new Runnable() {
                    @Override
                    public void run() {
                        runTaskLoop(deviceNo, taskNo, loopGeneration);
                    }
                    break;
                }
                });
            }
        };
        if (delayMs <= 0L) {
            task.run();
            return;
        }
        loopScheduler.schedule(task, delayMs, TimeUnit.MILLISECONDS);
    }
                if (hasTaskReset(taskNo)) {
    private void scheduleTaskLoopByDelay(Integer deviceNo, Integer taskNo, int loopGeneration, long delayMs) {
        scheduleTaskLoop(deviceNo, taskNo, loopGeneration, Math.max(delayMs, 1L));
    }
    private void runTaskLoop(Integer deviceNo, Integer taskNo, int loopGeneration) {
        TaskRuntimeContext context = taskContexts.get(taskNo);
        if (context == null) {
            News.info("[WCS Debug] fake task忽略无上下文续跑片段,deviceNo={},taskNo={},loopGeneration={}",
                    deviceNo, taskNo, loopGeneration);
            return;
        }
        if (!deviceNo.equals(context.deviceNo)) {
            News.info("[WCS Debug] fake task忽略跨设备续跑片段,taskNo={},expectedDeviceNo={},actualDeviceNo={},loopGeneration={}",
                    taskNo, context.deviceNo, deviceNo, loopGeneration);
            return;
        }
        if (context.loopGeneration != loopGeneration || !isCurrentLoopGeneration(taskNo, loopGeneration)) {
            News.info("[WCS Debug] fake task忽略过期续跑片段,deviceNo={},taskNo={},expectedLoopGeneration={},actualLoopGeneration={}",
                    deviceNo, taskNo, loopGeneration, context.loopGeneration);
            return;
        }
        News.info("[WCS Debug] fake task进入runTaskLoop,deviceNo={},taskNo={},threadImpl={},queueExists={},queueSize={},loopGeneration={},status={},initialized={},currentStationId={},targetStationId={},blockedStationId={},pendingPath={}",
                deviceNo, taskNo, context.threadImpl, taskQueues.containsKey(taskNo),
                taskQueues.containsKey(taskNo) && taskQueues.get(taskNo) != null ? taskQueues.get(taskNo).size() : null, loopGeneration,
                context.status, context.initialized, context.currentStationId, context.finalTargetStationId,
                context.blockedStationId, context.getPendingStationIds());
        long nextDelayMs = 0L;
        boolean shouldContinue = false;
        try {
            if (Thread.currentThread().isInterrupted()) {
                if (!isTerminalStatus(context.status)) {
                    context.status = STATUS_CANCELLED;
                    break;
                }
            } else if (hasTaskReset(taskNo)) {
                context.status = STATUS_CANCELLED;
            } else {
                BlockingQueue<StationCommand> commandQueue = taskQueues.get(taskNo);
                if (commandQueue == null) {
                    break;
                }
                StationCommand command = commandQueue.poll(100, TimeUnit.MILLISECONDS);
                if (command != null) {
                    taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
                    context.lastCommandAt = System.currentTimeMillis();
                    handleIncomingSegment(deviceNo, context, command);
                }
                if (!context.pendingPathQueue.isEmpty()) {
                    if (!context.initialized || context.currentStationId == null) {
                        initializeTaskPosition(deviceNo, context);
                        continue;
                    context.status = STATUS_FINISHED;
                } else {
                    StationCommand command = commandQueue.poll();
                    if (command != null) {
                        taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
                        context.lastCommandAt = System.currentTimeMillis();
                        handleIncomingSegment(deviceNo, context, command);
                    }
                    if (!executeNextMove(deviceNo, context)) {
                        break;
                    if (!isTerminalStatus(context.status)) {
                        if (!context.pendingPathQueue.isEmpty()) {
                            News.info("[WCS Debug] fake task准备推进,deviceNo={},taskNo={},initialized={},currentStationId={},targetStationId={},blockedStationId={},pendingPath={}",
                                    deviceNo, taskNo, context.initialized, context.currentStationId,
                                    context.finalTargetStationId, context.blockedStationId, context.getPendingStationIds());
                            if (!context.initialized || context.currentStationId == null) {
                                nextDelayMs = initializeTaskPosition(deviceNo, context);
                            } else {
                                MoveStepResult moveResult = executeNextMove(deviceNo, context);
                                if (!moveResult.shouldContinue()) {
                                    context.status = STATUS_FINISHED;
                                }
                                nextDelayMs = moveResult.getNextDelayMs();
                            }
                        } else {
                            News.info("[WCS Debug] fake task进入空闲态,deviceNo={},taskNo={},status={},currentStationId={},targetStationId={},blockedStationId={},pendingPath={}",
                                    deviceNo, taskNo, context.status, context.currentStationId,
                                    context.finalTargetStationId, context.blockedStationId, context.getPendingStationIds());
                            IdleStepResult idleResult = handleIdleState(deviceNo, context);
                            if (idleResult.isFinished() && !isTerminalStatus(context.status)) {
                                context.status = STATUS_FINISHED;
                            }
                            nextDelayMs = idleResult.getNextDelayMs();
                        }
                    }
                    continue;
                    shouldContinue = !isTerminalStatus(context.status)
                            && taskQueues.containsKey(taskNo)
                            && (commandQueue != null);
                }
            }
        } catch (Exception e) {
            context.status = STATUS_CANCELLED;
            if (e instanceof InterruptedException) {
                Thread.currentThread().interrupt();
            }
            News.info("[WCS Debug] 任务{}执行异常,当前站点={},目标站={},待执行路径={},异常类型={},异常信息={}",
                    taskNo, context.currentStationId, context.finalTargetStationId, context.getPendingStationIds(),
                    e.getClass().getSimpleName(), e.getMessage());
        }
                if (handleIdleState(deviceNo, context)) {
                    break;
                }
        if (shouldContinue) {
            if (nextDelayMs > 0L) {
                scheduleTaskLoopByDelay(deviceNo, taskNo, loopGeneration, nextDelayMs);
            } else {
                scheduleTaskLoop(deviceNo, taskNo, loopGeneration);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            if (!isTerminalStatus(context.status)) {
                context.status = STATUS_CANCELLED;
            return;
        }
        finishTaskLoop(deviceNo, taskNo, context, loopGeneration);
    }
    private void finishTaskLoop(Integer deviceNo, Integer taskNo, TaskRuntimeContext context, int loopGeneration) {
        Object lifecycleLock = getTaskLifecycleLock(taskNo);
        synchronized (lifecycleLock) {
            if (context.loopGeneration != loopGeneration || !isCurrentLoopGeneration(taskNo, loopGeneration)) {
                News.info("[WCS Debug] fake task忽略过期结束清理,deviceNo={},taskNo={},expectedLoopGeneration={},actualLoopGeneration={}",
                        deviceNo, taskNo, loopGeneration, context.loopGeneration);
                return;
            }
        } finally {
            BlockingQueue<StationCommand> queue = taskQueues.get(taskNo);
            if (queue != null && !queue.isEmpty()) {
                News.info("[WCS Debug] fake task结束清理前发现新命令,恢复续跑,deviceNo={},taskNo={},queueSize={},loopGeneration={}",
                        deviceNo, taskNo, queue.size(), loopGeneration);
                scheduleTaskLoop(deviceNo, taskNo, loopGeneration);
                return;
            }
            News.info("[WCS Debug] fake task即将退出runTaskLoop,deviceNo={},taskNo={},status={},queueSizeBeforeCleanup={},lastCurrentStationId={},targetStationId={},loopGeneration={}",
                    deviceNo, taskNo, context.status, queue == null ? null : queue.size(), context.currentStationId, context.finalTargetStationId, loopGeneration);
            taskQueues.remove(taskNo);
            taskLastUpdateTime.remove(taskNo);
            taskRunning.remove(taskNo);
            if (!isTerminalStatus(context.status)) {
                context.status = STATUS_FINISHED;
            }
            traceEvent(deviceNo, context, "TASK_END", "任务执行结束并清理资源",
                    buildDetails("reason", context.status), true);
            News.info("[WCS Debug] 任务{}执行结束并清理资源,状态={}", taskNo, context.status);
            taskContexts.remove(taskNo);
            taskLoopGenerations.remove(taskNo);
            taskLifecycleLocks.remove(taskNo, lifecycleLock);
        }
        if (!isTerminalStatus(context.status)) {
            context.status = STATUS_FINISHED;
        }
        traceEvent(deviceNo, context, "TASK_END", "任务执行结束并清理资源",
                buildDetails("reason", context.status, "loopGeneration", loopGeneration), true);
        News.info("[WCS Debug] 任务{}执行结束并清理资源,状态={},loopGeneration={}", taskNo, context.status, loopGeneration);
    }
    private void handleIncomingSegment(Integer deviceNo, TaskRuntimeContext context, StationCommand command) {
        if (!deviceNo.equals(context.deviceNo)) {
            traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段来自不同设备车道,已忽略",
                    buildDetails("expectedDeviceNo", context.deviceNo, "actualDeviceNo", deviceNo,
                            "segmentNo", command.getSegmentNo(), "segmentCount", command.getSegmentCount()), false);
            return;
        }
        List<Integer> newPath = normalizePath(command.getNavigatePath());
        Integer lastInQueue = getLastInQueue(context.pendingPathQueue);
        int startIndex = getPathAppendStartIndex(newPath, context.currentStationId, lastInQueue);
        Integer previousTargetStationId = context.finalTargetStationId;
        Integer commandTargetStationId = command.getTargetStaNo();
        boolean targetChanged = commandTargetStationId != null
                && !commandTargetStationId.equals(previousTargetStationId);
        boolean queueEmpty = context.pendingPathQueue.isEmpty();
        boolean newPathContainsCurrent = context.currentStationId != null && newPath.contains(context.currentStationId);
        boolean pathConnectedToTail = startIndex >= 0;
        List<Integer> oldPendingStations = context.getPendingStationIds();
        boolean shouldClearBarcodeSourceOnReroute = context.currentStationId != null
                && context.currentStationId.equals(command.getStationId())
                && isBarcodeStation(context.currentStationId)
                && previousTargetStationId != null
                && previousTargetStationId.equals(context.currentStationId)
                && commandTargetStationId != null
                && !commandTargetStationId.equals(context.currentStationId);
        context.setStartStationIdIfAbsent(command.getStationId());
        if (!context.generateBarcode && checkTaskNoInArea(context.taskNo)) {
@@ -297,15 +402,15 @@
        traceEvent(deviceNo, context, "SEGMENT_RECEIVED", "收到新的路径分段命令",
                buildDetails("segmentPath", newPath, "appendStartIndex", startIndex, "currentStationId",
                        context.currentStationId, "queueTailStationId", lastInQueue, "commandStationId",
                        command.getStationId(), "commandTargetStationId", command.getTargetStaNo()),
                        command.getStationId(), "commandTargetStationId", commandTargetStationId,
                        "previousTargetStationId", previousTargetStationId),
                false);
        Integer commandTargetStationId = command.getTargetStaNo();
        if (commandTargetStationId != null) {
            if (!commandTargetStationId.equals(context.finalTargetStationId)) {
            if (targetChanged) {
                traceEvent(deviceNo, context, "TARGET_SWITCHED",
                        "任务目标站发生切换: " + context.finalTargetStationId + " -> " + commandTargetStationId,
                        buildDetails("fromTargetStationId", context.finalTargetStationId, "toTargetStationId",
                        "任务目标站发生切换: " + previousTargetStationId + " -> " + commandTargetStationId,
                        buildDetails("fromTargetStationId", previousTargetStationId, "toTargetStationId",
                                commandTargetStationId),
                        false);
                context.arrivalHandled = false;
@@ -313,50 +418,140 @@
            context.finalTargetStationId = commandTargetStationId;
            syncCurrentStationTarget(context.taskNo, context.currentStationId, context.finalTargetStationId);
        }
        context.segmentNo = command.getSegmentNo();
        context.segmentCount = command.getSegmentCount();
        if (!newPath.isEmpty() && startIndex < 0) {
            traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法与当前运行上下文衔接,已忽略",
                    buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId,
                            "queueTailStationId", lastInQueue, "ignoreReason", "PATH_NOT_CONNECTED"),
                    false);
            context.latestAppendedPath.clear();
            return;
        boolean tailConnectedAppend = !queueEmpty && pathConnectedToTail;
        boolean shouldReplace = !tailConnectedAppend && !queueEmpty
                && (targetChanged || (!pathConnectedToTail && newPathContainsCurrent));
        String ignoreReason = null;
        if (!shouldReplace && !tailConnectedAppend && !newPath.isEmpty() && !pathConnectedToTail) {
            ignoreReason = newPathContainsCurrent ? SEGMENT_MERGE_IGNORE_DISCONNECTED : SEGMENT_MERGE_IGNORE_CURRENT_MISSING;
            if (queueEmpty && context.currentStationId == null) {
                startIndex = 0;
            } else {
                traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法接入当前运行上下文,已忽略",
                        buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId,
                                "queueTailStationId", lastInQueue, "ignoreReason", ignoreReason,
                                "queueEmpty", queueEmpty, "tailConnectedAppend", tailConnectedAppend,
                                "targetChanged", targetChanged),
                        false);
                context.latestAppendedPath.clear();
                return;
            }
        }
        if (tailConnectedAppend && startIndex < 0) {
            startIndex = 0;
        }
        List<Integer> appendedPath = new ArrayList<Integer>();
        for (int i = startIndex; i < newPath.size(); i++) {
            Integer stationId = newPath.get(i);
            context.pendingPathQueue.offer(stationId);
            appendedPath.add(stationId);
        }
        context.appendStitchedPath(appendedPath);
        if (!appendedPath.isEmpty()) {
        List<Integer> replacedFuturePath = new ArrayList<Integer>();
        Integer duplicateHeadStationId = null;
        String mergeMode = SEGMENT_MERGE_APPEND;
        if (shouldReplace) {
            replacedFuturePath = rebuildPendingPathFromCurrent(context, newPath);
            boolean atTargetAlready = context.currentStationId != null && context.currentStationId.equals(commandTargetStationId);
            if (context.currentStationId != null && replacedFuturePath.isEmpty() && !atTargetAlready) {
                traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法接入当前运行上下文,已忽略",
                        buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId,
                                "queueTailStationId", lastInQueue, "ignoreReason", SEGMENT_MERGE_IGNORE_CURRENT_MISSING),
                        false);
                context.latestAppendedPath.clear();
                return;
            }
            replacePendingPathQueue(context, replacedFuturePath);
            context.replaceLatestPath(replacedFuturePath);
            mergeMode = targetChanged ? SEGMENT_MERGE_REPLACE_TARGET_CHANGED : SEGMENT_MERGE_REPLACE_REROUTE;
            traceEvent(deviceNo, context, "SEGMENT_REPLACED",
                    "新的 reroute 路径已覆盖旧 future queue",
                    buildDetails("mergeMode", mergeMode, "currentStationId", context.currentStationId,
                            "previousTargetStationId", previousTargetStationId, "newTargetStationId", commandTargetStationId,
                            "oldQueue", oldPendingStations, "replacedFuturePath", replacedFuturePath,
                            "queueSize", context.pendingPathQueue.size()),
                    false);
        } else {
            for (int i = startIndex; i < newPath.size(); i++) {
                Integer stationId = newPath.get(i);
                context.pendingPathQueue.offer(stationId);
                appendedPath.add(stationId);
            }
            if (context.currentStationId != null && context.currentStationId.equals(context.pendingPathQueue.peek())) {
                duplicateHeadStationId = context.pendingPathQueue.poll();
                if (!appendedPath.isEmpty() && duplicateHeadStationId.equals(appendedPath.get(0))) {
                    appendedPath.remove(0);
                }
            }
            context.appendStitchedPath(appendedPath);
            traceEvent(deviceNo, context, "SEGMENT_APPENDED",
                    "路径分段已追加到待执行队列,队列长度=" + context.pendingPathQueue.size(),
                    buildDetails("segmentPath", newPath, "appendedPath", appendedPath, "appendStartIndex",
                            startIndex, "queueSize", context.pendingPathQueue.size()),
                    buildDetails("mergeMode", mergeMode, "segmentPath", newPath, "appendedPath", appendedPath,
                            "appendStartIndex", startIndex, "queueSize", context.pendingPathQueue.size(),
                            "tailConnectedAppend", tailConnectedAppend, "targetChanged", targetChanged,
                            "duplicateHeadStationId", duplicateHeadStationId),
                    false);
        }
        if (duplicateHeadStationId != null) {
            traceEvent(deviceNo, context, "SEGMENT_TRIMMED", "待执行队列头部重复当前站点,已移除",
                    buildDetails("currentStationId", context.currentStationId,
                            "duplicateHeadStationId", duplicateHeadStationId,
                            "queueSize", context.pendingPathQueue.size(),
                            "remainingPendingPath", context.getPendingStationIds()),
                    false);
        }
        if (context.currentStationId != null && context.currentStationId.equals(context.pendingPathQueue.peek())) {
            Integer trimmedHead = context.pendingPathQueue.poll();
            traceEvent(deviceNo, context, "SEGMENT_TRIMMED", "待执行队列头部仍与当前站点重复,已再次移除",
                    buildDetails("currentStationId", context.currentStationId,
                            "duplicateHeadStationId", trimmedHead,
                            "queueSize", context.pendingPathQueue.size(),
                            "remainingPendingPath", context.getPendingStationIds()),
                    false);
        }
        context.lastCommandAt = System.currentTimeMillis();
        boolean segmentAccepted = !appendedPath.isEmpty() || !replacedFuturePath.isEmpty()
                || (context.currentStationId != null && context.currentStationId.equals(commandTargetStationId))
                || (context.currentStationId != null && context.currentStationId.equals(command.getStationId())
                && commandTargetStationId != null && commandTargetStationId.equals(context.finalTargetStationId));
        if (segmentAccepted) {
            if (shouldClearBarcodeSourceOnReroute && context.currentStationId != null) {
                Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId);
                if (currentDeviceNo != null) {
                    guardedClearStationForDispatch(currentDeviceNo, context.currentStationId, context.taskNo, "barcodeSourceRerouteAccepted");
                    traceEvent(deviceNo, context, "SOURCE_STATION_CLEARED", "条码源站已完成任务交接,源站状态已清除",
                            buildDetails("stationId", context.currentStationId,
                                    "previousTargetStationId", previousTargetStationId,
                                    "newTargetStationId", commandTargetStationId,
                                    "mergeMode", mergeMode),
                            false);
                }
            }
            resumeFromCurrentStation(deviceNo, context, mergeMode, appendedPath, replacedFuturePath,
                    shouldClearBarcodeSourceOnReroute);
        }
    }
    private void initializeTaskPosition(Integer deviceNo, TaskRuntimeContext context) {
    private long initializeTaskPosition(Integer deviceNo, TaskRuntimeContext context) {
        Integer nextStationId = context.pendingPathQueue.peek();
        if (nextStationId == null) {
            return;
            return 0L;
        }
        if (context.currentStationId == null) {
            Integer actualCurrentStationId = findCurrentStationIdByTask(context.taskNo);
            Integer actualCurrentStationId = stateManager.findCurrentStationIdByTask(context.taskNo);
            if (actualCurrentStationId != null) {
                context.currentStationId = actualCurrentStationId;
                context.initialized = true;
                context.status = STATUS_RUNNING;
                context.blockedStationId = null;
                Integer actualDeviceNo = getDeviceNoByStationId(actualCurrentStationId);
                Integer actualDeviceNo = stateManager.getDeviceNoByStationId(actualCurrentStationId);
                if (actualDeviceNo != null) {
                    clearRunBlock(actualCurrentStationId, actualDeviceNo);
                    guardedClearRunBlock(context.taskNo, actualCurrentStationId, actualDeviceNo);
                }
                trimPendingPathToCurrent(context.pendingPathQueue, actualCurrentStationId);
@@ -366,24 +561,26 @@
                context.addPassedStation(actualCurrentStationId);
                context.lastStepAt = System.currentTimeMillis();
                context.lastProgressAt = context.lastStepAt;
                context.lastProgressStationId = context.currentStationId;
                guardedPublishTaskLocation(context.taskNo, actualDeviceNo, actualCurrentStationId, true, false);
                traceEvent(deviceNo, context, "MOVE_INIT", "任务从当前实际站点恢复执行",
                        buildDetails("stationId", actualCurrentStationId, "recovered", true), false);
                return;
                return 0L;
            }
        }
        context.currentStationId = nextStationId;
        Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId);
        Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId);
        if (currentDeviceNo == null) {
            context.pendingPathQueue.poll();
            return;
            return 0L;
        }
        boolean result = initStationMove(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
        boolean result = moveEngine.initStationMove(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
                context.finalTargetStationId, true, null);
        if (!result) {
            sleep(200);
            return;
            return getIdleLoopDelayMs();
        }
        context.initialized = true;
@@ -391,25 +588,28 @@
        context.pendingPathQueue.poll();
        context.addPassedStation(context.currentStationId);
        context.lastStepAt = System.currentTimeMillis();
        context.lastProgressAt = context.lastStepAt;
        context.lastProgressStationId = context.currentStationId;
        guardedPublishTaskLocation(context.taskNo, currentDeviceNo, context.currentStationId, true, false);
        traceEvent(deviceNo, context, "MOVE_INIT", "任务初始化起点站点",
                buildDetails("stationId", context.currentStationId, "recovered", false), false);
        sleep(500);
        return Math.max(getInitializeDelayMs(), getMoveStepDurationMs());
    }
    private boolean executeNextMove(Integer deviceNo, TaskRuntimeContext context) {
    private MoveStepResult executeNextMove(Integer deviceNo, TaskRuntimeContext context) {
        Integer nextStationId = context.pendingPathQueue.peek();
        if (nextStationId == null || context.currentStationId == null) {
            return true;
            return MoveStepResult.continueNow();
        }
        Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId);
        Integer nextDeviceNo = getDeviceNoByStationId(nextStationId);
        Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId);
        Integer nextDeviceNo = stateManager.getDeviceNoByStationId(nextStationId);
        if (currentDeviceNo == null || nextDeviceNo == null) {
            context.pendingPathQueue.poll();
            return true;
            return MoveStepResult.continueNow();
        }
        boolean moveSuccess = stationMoveToNext(context.taskNo, context.currentStationId, currentDeviceNo,
        boolean moveSuccess = moveEngine.stationMoveToNext(context.taskNo, context.currentStationId, currentDeviceNo,
                nextStationId, nextDeviceNo, context.taskNo, context.finalTargetStationId);
        if (moveSuccess) {
            Integer previousStationId = context.currentStationId;
@@ -420,64 +620,174 @@
            context.blockedStationId = null;
            context.status = STATUS_RUNNING;
            context.lastStepAt = System.currentTimeMillis();
            context.lastProgressAt = context.lastStepAt;
            context.lastProgressStationId = context.currentStationId;
            blockManager.clearBlocked(previousStationId);
            blockManager.clearBlocked(nextStationId);
            traceEvent(deviceNo, context, "MOVE_STEP_OK", "任务完成一步站点移动",
                    buildDetails("fromStationId", previousStationId, "toStationId", nextStationId,
                            "remainingPendingPath", context.getPendingStationIds()),
                    false);
            sleep(1000);
            return true;
            return MoveStepResult.continueAfter(getBlockedLoopDelayMs());
        }
        if (!checkTaskNoInArea(context.taskNo) && getFakeAllowCheckBlock()
                && !isSpecialStation(context.currentStationId)
                && System.currentTimeMillis() - context.lastStepAt > getFakeRunBlockTimeoutMs()) {
            boolean blocked = runBlockStation(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
        long now = System.currentTimeMillis();
        boolean sameStationNoProgress = context.currentStationId != null
                && context.currentStationId.equals(context.lastProgressStationId);
        long timeoutMs = blockManager.getFakeRunBlockTimeoutMs(redisUtil);
        long dwellMs = now - context.lastProgressAt;
        if (!checkTaskNoInArea(context.taskNo) && blockManager.getFakeAllowCheckBlock(redisUtil)
                && !blockManager.isSpecialStation(context.currentStationId)
                && !blockManager.isBlocked(context.currentStationId)
                && sameStationNoProgress
                && dwellMs > timeoutMs) {
            List<Integer> clearedPendingPath = context.getPendingStationIds();
            News.info("[WCS Debug] fake task清空待执行路径,原因=RUN_BLOCKED,deviceNo={},taskNo={},currentStationId={},targetStationId={},blockedStationId={},pendingBeforeClear={}",
                    deviceNo, context.taskNo, context.currentStationId, context.finalTargetStationId,
                    context.blockedStationId, clearedPendingPath);
            context.clearPendingPathWithLog(deviceNo, context.taskNo, context.currentStationId,
                    context.finalTargetStationId, context.blockedStationId, "RUN_BLOCKED");
            guardedRunBlockStation(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
                    context.currentStationId);
            if (blocked) {
                context.blockedStationId = context.currentStationId;
                context.status = STATUS_BLOCKED;
                context.pendingPathQueue.clear();
                traceEvent(deviceNo, context, "RUN_BLOCKED", "任务在当前站点被标记为堵塞",
                        buildDetails("blockedStationId", context.currentStationId), false);
                return false;
            }
            context.blockedStationId = context.currentStationId;
            context.status = STATUS_BLOCKED;
            traceEvent(deviceNo, context, "RUN_BLOCKED", "任务在当前站点停留超时,被标记为堵塞,待执行路径已清空",
                    buildDetails("blockedStationId", context.currentStationId,
                            "lastProgressStationId", context.lastProgressStationId,
                            "dwellMs", dwellMs,
                            "timeoutMs", timeoutMs,
                            "clearedPendingPath", clearedPendingPath), false);
            return MoveStepResult.continueAfter(getMoveStepDurationMs());
        }
        sleep(500);
        return true;
        context.status = STATUS_WAITING;
        traceEvent(deviceNo, context, "MOVE_STEP_WAIT", "当前站已更新目标但尚未完成落站,保持当前位置等待下一次推进",
                buildDetails("currentStationId", context.currentStationId,
                        "nextStationId", nextStationId,
                        "targetStationId", context.finalTargetStationId,
                        "blockedStationId", context.blockedStationId,
                        "pendingPath", context.getPendingStationIds()), false);
        return MoveStepResult.continueAfter(500L);
    }
    private boolean handleIdleState(Integer deviceNo, TaskRuntimeContext context) {
    private void resumeFromCurrentStation(Integer deviceNo, TaskRuntimeContext context, String mergeMode,
            List<Integer> appendedPath, List<Integer> replacedFuturePath, boolean sourceCleared) {
        if (context.currentStationId != null) {
            Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId);
            if (currentDeviceNo != null) {
                guardedClearRunBlock(context.taskNo, context.currentStationId, currentDeviceNo);
                guardedPublishTaskLocation(context.taskNo, currentDeviceNo, context.currentStationId, true, false);
            }
        }
        context.blockedStationId = null;
        context.status = STATUS_RUNNING;
        context.lastProgressAt = System.currentTimeMillis();
        context.lastProgressStationId = context.currentStationId;
        traceEvent(deviceNo, context, "BLOCK_RESET", "收到新的路径分段,已清除堵塞并重新计时",
                buildDetails("mergeMode", mergeMode, "currentStationId", context.currentStationId,
                        "targetStationId", context.finalTargetStationId, "queueSize", context.pendingPathQueue.size(),
                        "appendedPath", appendedPath, "replacedFuturePath", replacedFuturePath,
                        "sourceCleared", sourceCleared),
                false);
    }
    private boolean shouldAdvanceFromCurrentState(TaskRuntimeContext context) {
        return context != null
                && context.initialized
                && context.currentStationId != null
                && !context.pendingPathQueue.isEmpty()
                && !STATUS_BLOCKED.equals(context.status)
                && !STATUS_CANCELLED.equals(context.status)
                && !STATUS_TIMEOUT.equals(context.status)
                && !STATUS_FINISHED.equals(context.status);
    }
    private IdleStepResult handleIdleState(Integer deviceNo, TaskRuntimeContext context) {
        if (shouldAdvanceFromCurrentState(context)) {
            traceEvent(deviceNo, context, "IDLE_RESUME", "当前站点存在未完成路径,立即从当前站恢复推进",
                    buildDetails("currentStationId", context.currentStationId,
                            "targetStationId", context.finalTargetStationId,
                            "pendingPath", context.getPendingStationIds()), false);
            context.status = STATUS_RUNNING;
            return IdleStepResult.waitNext(1L);
        }
        if (context.currentStationId != null && context.finalTargetStationId != null
                && context.currentStationId.equals(context.finalTargetStationId)) {
            if (!context.arrivalHandled) {
                boolean barcodeGenerated = false;
                if (context.generateBarcode) {
                    Integer targetDeviceNo = getDeviceNoByStationId(context.finalTargetStationId);
                    Integer targetDeviceNo = stateManager.getDeviceNoByStationId(context.finalTargetStationId);
                    if (targetDeviceNo != null) {
                        barcodeGenerated = generateStationBarcode(context.taskNo, context.finalTargetStationId,
                        barcodeGenerated = guardedGenerateStationBarcode(context.taskNo, context.finalTargetStationId,
                                targetDeviceNo);
                    }
                }
                context.arrivalHandled = true;
                context.lastProgressAt = System.currentTimeMillis();
                context.lastProgressStationId = context.currentStationId;
                traceEvent(deviceNo, context, "ARRIVED", "任务到达最终目标站点",
                        buildDetails("stationId", context.currentStationId, "barcodeGenerated", barcodeGenerated),
                        false);
            }
            context.status = STATUS_FINISHED;
            return true;
            Integer targetDeviceNo = stateManager.getDeviceNoByStationId(context.finalTargetStationId);
            if (targetDeviceNo != null) {
                boolean ownerConflict = stateManager.isFinalStationOwnerConflict(targetDeviceNo, context.finalTargetStationId,
                        context.taskNo);
                if (ownerConflict) {
                    logFinalStationOwnershipLost(deviceNo, context);
                    context.status = STATUS_BLOCKED;
                    return IdleStepResult.waitNext(getMoveStepDurationMs());
                }
                boolean stationCleared = stateManager.isStationClearedForTask(targetDeviceNo, context.finalTargetStationId,
                        context.taskNo);
                boolean crnTaken = isCrnTakenByTask(context.taskNo);
                if (stationCleared || crnTaken) {
                    context.status = STATUS_FINISHED;
                    traceEvent(deviceNo, context, "TASK_COMPLETE", "堆垛机已取走货物,任务完成",
                            buildDetails("stationId", context.finalTargetStationId, "stationCleared", stationCleared, "crnTaken", crnTaken), false);
                    return IdleStepResult.finish();
                }
            }
            if (targetDeviceNo != null && !guardedArrivalCompletion(targetDeviceNo, context)) {
                context.status = STATUS_BLOCKED;
                return IdleStepResult.waitNext(getMoveStepDurationMs());
            }
            long dwellMs = System.currentTimeMillis() - context.lastProgressAt;
            long timeoutMs = blockManager.getFakeRunBlockTimeoutMs(redisUtil);
            if (!checkTaskNoInArea(context.taskNo) && blockManager.getFakeAllowCheckBlock(redisUtil)
                    && !blockManager.isSpecialStation(context.currentStationId)
                    && !blockManager.isBlocked(context.currentStationId)
                    && dwellMs > timeoutMs) {
                context.status = STATUS_BLOCKED;
                context.clearPendingPathWithLog(deviceNo, context.taskNo, context.currentStationId,
                        context.finalTargetStationId, context.blockedStationId, "TARGET_RUN_BLOCKED");
                guardedRunBlockStation(context.taskNo, context.currentStationId, targetDeviceNo, context.taskNo,
                        context.currentStationId);
                context.blockedStationId = context.currentStationId;
                traceEvent(deviceNo, context, "RUN_BLOCKED", "任务到达终点站后停留超时,被标记为堵塞,等待堆垛机取货",
                        buildDetails("blockedStationId", context.currentStationId,
                                "lastProgressStationId", context.lastProgressStationId,
                                "dwellMs", dwellMs,
                                "timeoutMs", timeoutMs), false);
                return IdleStepResult.waitNext(getMoveStepDurationMs());
            }
            return IdleStepResult.waitNext(getFinishDelayMs());
        }
        Long lastTime = taskLastUpdateTime.get(context.taskNo);
        if (lastTime != null && System.currentTimeMillis() - lastTime > WAIT_SEGMENT_TIMEOUT_MS) {
            context.status = STATUS_TIMEOUT;
        long waitSegmentTimeoutMs = getWaitSegmentTimeoutMs();
        if (lastTime != null && System.currentTimeMillis() - lastTime > waitSegmentTimeoutMs) {
            traceEvent(deviceNo, context, "WAIT_TIMEOUT", "等待新的路径分段超时",
                    buildDetails("timeoutMs", WAIT_SEGMENT_TIMEOUT_MS, "currentStationId", context.currentStationId,
                    buildDetails("timeoutMs", waitSegmentTimeoutMs, "currentStationId", context.currentStationId,
                            "targetStationId", context.finalTargetStationId),
                    false);
            return true;
            taskLastUpdateTime.put(context.taskNo, System.currentTimeMillis());
        }
        return false;
        return IdleStepResult.waitNext(getIdleLoopDelayMs());
    }
    private List<Integer> normalizePath(List<Integer> path) {
@@ -507,12 +817,40 @@
        }
    }
    private boolean hasTaskReset(Integer taskNo) {
        if (redisUtil == null || taskNo == null) {
            return false;
    private List<Integer> rebuildPendingPathFromCurrent(TaskRuntimeContext context, List<Integer> newPath) {
        List<Integer> rebuilt = new ArrayList<Integer>();
        if (newPath == null || newPath.isEmpty()) {
            return rebuilt;
        }
        Object cancel = redisUtil.get(RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo);
        return cancel != null;
        Integer currentStationId = context.currentStationId;
        int startIndex = 0;
        if (currentStationId != null) {
            int currentIndex = newPath.indexOf(currentStationId);
            if (currentIndex < 0) {
                return rebuilt;
            }
            startIndex = currentIndex + 1;
        }
        for (int i = startIndex; i < newPath.size(); i++) {
            rebuilt.add(newPath.get(i));
        }
        return rebuilt;
    }
    private void replacePendingPathQueue(TaskRuntimeContext context, List<Integer> futurePath) {
        context.pendingPathQueue.clear();
        if (futurePath == null) {
            return;
        }
        for (Integer stationId : futurePath) {
            context.pendingPathQueue.offer(stationId);
        }
    }
    private boolean hasTaskReset(Integer taskNo) {
        // 仿真系统不响应外部取消信号(如堵塞重路由触发的 signalSegmentReset),
        // 避免任务在站点行走过程中被意外清除
        return false;
    }
    private Integer getLastInQueue(LinkedBlockingQueue<Integer> queue) {
@@ -554,403 +892,715 @@
        if (currentStationId == null || targetStationId == null) {
            return;
        }
        Integer currentDeviceNo = getDeviceNoByStationId(currentStationId);
        Integer currentDeviceNo = stateManager.getDeviceNoByStationId(currentStationId);
        if (currentDeviceNo == null) {
            return;
        }
        lockStations(currentStationId);
        moveEngine.lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentDeviceNo);
            if (statusList == null) {
                return;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            ZyStationStatusEntity currentStatus = stateManager.findStationStatus(currentDeviceNo, currentStationId);
            if (currentStatus == null) {
                return;
            }
            if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0
                    && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) {
            if (hasOwnerConflict(currentStatus, taskNo)) {
                logOwnerConflict("syncCurrentStationTarget", currentStationId, currentStatus, taskNo, targetStationId, false,
                        "owner_conflict");
                return;
            }
            updateStationDataInternal(currentStationId, currentDeviceNo, taskNo, targetStationId, null, null, null);
            stateManager.updateStationDataInternal(currentStationId, currentDeviceNo, taskNo, targetStationId, null, null, null);
        } finally {
            unlockStations(currentStationId);
            moveEngine.unlockStations(currentStationId);
        }
    }
    @SuppressWarnings("unchecked")
    private boolean getFakeAllowCheckBlock() {
        boolean fakeAllowCheckBlock = true;
        Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
        if (systemConfigMapObj instanceof Map) {
            Map<String, String> systemConfigMap = (Map<String, String>) systemConfigMapObj;
            String value = systemConfigMap.get("fakeAllowCheckBlock");
            if (value != null && !"Y".equals(value)) {
                fakeAllowCheckBlock = false;
            }
        }
        return fakeAllowCheckBlock;
    private boolean hasOwnerConflict(ZyStationStatusEntity currentStatus, Integer incomingTaskNo) {
        return currentStatus != null
                && currentStatus.isLoading()
                && currentStatus.getTaskNo() != null
                && currentStatus.getTaskNo() > 0
                && (incomingTaskNo == null || !currentStatus.getTaskNo().equals(incomingTaskNo));
    }
    private boolean isSpecialStation(Integer stationId) {
    private void logOwnerConflict(String operation, Integer stationId, ZyStationStatusEntity currentStatus,
            Integer incomingTaskNo, Integer incomingTargetStationId, boolean finalStation, String reason) {
        News.info("[WCS Debug] fake station owner冲突,operation={},stationId={},currentTaskNo={},currentLoading={},currentTargetStaNo={},incomingTaskNo={},incomingTargetStaNo={},finalStation={},reason={}",
                operation,
                stationId,
                currentStatus == null ? null : currentStatus.getTaskNo(),
                currentStatus == null ? null : currentStatus.isLoading(),
                currentStatus == null ? null : currentStatus.getTargetStaNo(),
                incomingTaskNo,
                incomingTargetStationId,
                finalStation,
                reason);
    }
    private boolean ensureStationWritable(String operation, Integer deviceNo, Integer stationId,
            Integer incomingTaskNo, Integer incomingTargetStationId, boolean finalStation) {
        if (deviceNo == null || stationId == null) {
            return false;
        }
        ZyStationStatusEntity currentStatus = stateManager.findStationStatus(deviceNo, stationId);
        if (currentStatus == null) {
            return false;
        }
        if (!hasOwnerConflict(currentStatus, incomingTaskNo)) {
            return true;
        }
        logOwnerConflict(operation, stationId, currentStatus, incomingTaskNo, incomingTargetStationId, finalStation,
                finalStation ? "final_station_owner_conflict" : "owner_conflict");
        return false;
    }
    private boolean ensureStationClearable(String operation, Integer deviceNo, Integer stationId, Integer expectedTaskNo,
            boolean finalStation) {
        if (deviceNo == null || stationId == null) {
            return false;
        }
        ZyStationStatusEntity currentStatus = stateManager.findStationStatus(deviceNo, stationId);
        if (currentStatus == null) {
            return false;
        }
        if (!hasOwnerConflict(currentStatus, expectedTaskNo)) {
            return true;
        }
        if (isLegalClearStation(stationId)) {
            return true;
        }
        logOwnerConflict(operation, stationId, currentStatus, expectedTaskNo, currentStatus.getTargetStaNo(), finalStation,
                "illegal_clear_attempt");
        return false;
    }
    private boolean isLegalClearStation(Integer stationId) {
        if (stationId == null) {
            return false;
        }
        BasDevpService basDevpService = SpringUtils.getBean(BasDevpService.class);
        List<BasDevp> basDevps = basDevpService.list();
        Set<Integer> specialStationIds = new HashSet<>();
        for (BasDevp basDevp : basDevps) {
            for (StationObjModel station : basDevp.getInStationList$()) {
                specialStationIds.add(station.getStationId());
            }
            for (StationObjModel station : basDevp.getOutStationList$()) {
                specialStationIds.add(station.getStationId());
            }
            for (StationObjModel station : basDevp.getBarcodeStationList$()) {
                specialStationIds.add(station.getStationId());
            }
        Set<Integer> stationIds = legalClearStationIds;
        if (stationIds == null || stationIds.isEmpty()) {
            refreshLegalClearStationIds();
            stationIds = legalClearStationIds;
        }
        return specialStationIds.contains(stationId);
        return stationIds != null && stationIds.contains(stationId);
    }
    private long getFakeRunBlockTimeoutMs() {
        long timeoutMs = DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS;
        Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
        if (systemConfigMapObj instanceof Map) {
            Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
            Object value = systemConfigMap.get("fakeRunBlockTimeoutMs");
            if (value != null) {
                try {
                    long parsed = Long.parseLong(String.valueOf(value).trim());
                    if (parsed > 0) {
                        timeoutMs = parsed;
                    }
                } catch (Exception ignore) {
    private boolean isBarcodeStation(Integer stationId) {
        if (stationId == null) {
            return false;
        }
        Set<Integer> stationIds = barcodeStationIds;
        if (stationIds == null || stationIds.isEmpty()) {
            refreshBarcodeStationIds();
            stationIds = barcodeStationIds;
        }
        return stationIds != null && stationIds.contains(stationId);
    }
    private void refreshLegalClearStationIds() {
        try {
            Set<Integer> stationIds = new HashSet<Integer>();
            com.zy.asrs.service.BasCrnpService basCrnpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasCrnpService.class);
            List<com.zy.asrs.entity.BasCrnp> basCrnps = basCrnpService.list();
            for (com.zy.asrs.entity.BasCrnp basCrnp : basCrnps) {
                if (basCrnp == null) {
                    continue;
                }
                collectStationIds(stationIds, basCrnp.getInStationList$());
                collectStationIds(stationIds, basCrnp.getOutStationList$());
            }
            com.zy.asrs.service.BasDevpService basDevpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasDevpService.class);
            List<com.zy.asrs.entity.BasDevp> basDevps = basDevpService.list();
            for (com.zy.asrs.entity.BasDevp basDevp : basDevps) {
                if (basDevp == null) {
                    continue;
                }
                collectStationIds(stationIds, basDevp.getBarcodeStationList$());
                collectStationIds(stationIds, basDevp.getInStationList$());
                collectStationIds(stationIds, basDevp.getOutStationList$());
            }
            legalClearStationIds = stationIds;
        } catch (Exception e) {
            News.info("[WCS Debug] fake 合法清站白名单刷新失败,异常类型={},异常信息={}",
                    e.getClass().getSimpleName(), e.getMessage());
        }
    }
    private void refreshBarcodeStationIds() {
        try {
            Set<Integer> stationIds = new HashSet<Integer>();
            com.zy.asrs.service.BasDevpService basDevpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasDevpService.class);
            List<com.zy.asrs.entity.BasDevp> basDevps = basDevpService.list();
            for (com.zy.asrs.entity.BasDevp basDevp : basDevps) {
                if (basDevp == null) {
                    continue;
                }
                collectStationIds(stationIds, basDevp.getBarcodeStationList$());
            }
            barcodeStationIds = stationIds;
        } catch (Exception e) {
            News.info("[WCS Debug] fake 条码站白名单刷新失败,异常类型={},异常信息={}",
                    e.getClass().getSimpleName(), e.getMessage());
        }
    }
    private void collectStationIds(Set<Integer> stationIds, List<com.zy.core.model.StationObjModel> stations) {
        if (stationIds == null || stations == null || stations.isEmpty()) {
            return;
        }
        for (com.zy.core.model.StationObjModel station : stations) {
            if (station != null && station.getStationId() != null) {
                stationIds.add(station.getStationId());
            }
        }
        return timeoutMs;
    }
    private boolean isFinalStation(Integer taskNo, Integer stationId) {
        TaskRuntimeContext context = taskNo == null ? null : taskContexts.get(taskNo);
        return context != null && stationId != null && stationId.equals(context.finalTargetStationId);
    }
    private void logFinalStationOwnershipLost(Integer deviceNo, TaskRuntimeContext context) {
        Integer stationId = context == null ? null : context.finalTargetStationId;
        Integer targetDeviceNo = stateManager.getDeviceNoByStationId(stationId);
        ZyStationStatusEntity currentStatus = targetDeviceNo == null ? null : stateManager.snapshotStation(targetDeviceNo, stationId);
        News.info("[WCS Debug] fake 最终站所有权丢失,taskNo={},stationId={},currentTaskNo={},currentLoading={},currentTargetStaNo={},reason={}",
                context == null ? null : context.taskNo,
                stationId,
                currentStatus == null ? null : currentStatus.getTaskNo(),
                currentStatus == null ? null : currentStatus.isLoading(),
                currentStatus == null ? null : currentStatus.getTargetStaNo(),
                "final_station_owner_conflict");
        traceEvent(deviceNo, context, "FINAL_STATION_OWNER_CONFLICT", "最终站仍有物但所有者已被其他任务覆盖",
                buildDetails("stationId", stationId,
                        "currentTaskNo", currentStatus == null ? null : currentStatus.getTaskNo(),
                        "currentLoading", currentStatus == null ? null : currentStatus.isLoading(),
                        "currentTargetStaNo", currentStatus == null ? null : currentStatus.getTargetStaNo()), false);
    }
    private boolean guardedClearStationForDispatch(Integer deviceNo, Integer stationId, Integer expectedTaskNo, String reason) {
        if (!ensureStationClearable("clearStationForDispatch", deviceNo, stationId, expectedTaskNo,
                isFinalStation(expectedTaskNo, stationId))) {
            return false;
        }
        stateManager.clearStationForDispatch(deviceNo, stationId, reason);
        return true;
    }
    private boolean guardedResetStation(Integer deviceNo, Integer stationId, Integer expectedTaskNo) {
        if (!ensureStationClearable("resetStation", deviceNo, stationId, expectedTaskNo,
                isFinalStation(expectedTaskNo, stationId))) {
            return false;
        }
        stateManager.resetStation(deviceNo, stationId);
        return true;
    }
    private boolean guardedUpdateStationBarcode(Integer deviceNo, Integer stationId, Integer taskNo, String barcode) {
        if (!ensureStationWritable("updateStationBarcode", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        stateManager.updateStationBarcode(deviceNo, stationId, barcode);
        return true;
    }
    private boolean guardedGenerateFakeOutStationData(Integer deviceNo, Integer stationId, Integer taskNo) {
        if (!ensureStationWritable("generateFakeOutStationData", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        stateManager.generateFakeOutStationData(deviceNo, stationId);
        return true;
    }
    private boolean guardedHandoffBarcodeStation(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
        if (!ensureStationWritable("handoffBarcodeStation", deviceNo, stationId, taskNo, targetStationId,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        stateManager.handoffBarcodeStation(deviceNo, taskNo, stationId, targetStationId);
        return true;
    }
    private boolean guardedRunBlockStation(Integer taskNo, Integer stationId, Integer deviceNo, Integer ownerTaskNo,
            Integer targetStationId) {
        if (!ensureStationWritable("runBlockStation", deviceNo, stationId, ownerTaskNo, targetStationId,
                isFinalStation(ownerTaskNo, stationId))) {
            return false;
        }
        blockManager.runBlockStation(taskNo, stationId, deviceNo, ownerTaskNo, targetStationId);
        return true;
    }
    private boolean guardedClearRunBlock(Integer taskNo, Integer stationId, Integer deviceNo) {
        if (!ensureStationWritable("clearRunBlock", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        blockManager.clearRunBlock(stationId, deviceNo);
        return true;
    }
    private boolean guardedPublishTaskLocation(Integer taskNo, Integer deviceNo, Integer stationId, boolean loading, boolean runBlock) {
        if (loading && !ensureStationWritable("publishTaskLocation", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        stateManager.publishTaskLocation(taskNo, deviceNo, stationId, loading, runBlock);
        return true;
    }
    private boolean guardedGenerateStationData(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
        if (!ensureStationWritable("generateStationData", deviceNo, stationId, taskNo, targetStationId,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        stateManager.generateStationData(deviceNo, taskNo, stationId, targetStationId);
        return true;
    }
    private boolean guardedSyncCurrentStationTarget(Integer taskNo, Integer currentStationId, Integer targetStationId) {
        syncCurrentStationTarget(taskNo, currentStationId, targetStationId);
        return true;
    }
    private boolean guardedClearTaskLocationIfMatches(Integer taskNo, Integer deviceNo, Integer stationId) {
        stateManager.clearTaskLocationIfMatches(taskNo, deviceNo, stationId);
        return true;
    }
    private boolean guardedPublishCurrentLocation(Integer taskNo, Integer deviceNo, Integer stationId) {
        return guardedPublishTaskLocation(taskNo, deviceNo, stationId, true, false);
    }
    private boolean guardedGenerateStationBarcode(Integer taskNo, Integer stationId, Integer deviceNo) {
        if (!ensureStationWritable("generateStationBarcode", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        return stateManager.generateStationBarcode(taskNo, stationId, deviceNo);
    }
    private boolean guardedUpdateStationData(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStationId,
            Boolean loading, String barcode, Boolean runBlock) {
        if ((taskNo != null || Boolean.TRUE.equals(loading))
                && !ensureStationWritable("updateStationData", deviceNo, stationId, taskNo, targetStationId,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        return stateManager.updateStationDataInternal(stationId, deviceNo, taskNo, targetStationId, loading, barcode, runBlock);
    }
    private boolean isFinalStationConflict(Integer taskNo, Integer stationId, Integer deviceNo) {
        return isFinalStation(taskNo, stationId) && stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo);
    }
    private boolean guardedResetOrClearBlockedStation(Integer taskNo, Integer stationId, Integer deviceNo) {
        return ensureStationClearable("blockedStationClear", deviceNo, stationId, taskNo, isFinalStation(taskNo, stationId));
    }
    private boolean guardedStationOccupied(Integer taskNo, Integer stationId, Integer deviceNo) {
        return !stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo);
    }
    private boolean guardedFinalStationOwnership(Integer taskNo, Integer stationId, Integer deviceNo) {
        return !stateManager.isFinalStationOwnerConflict(deviceNo, stationId, taskNo);
    }
    private boolean guardedFinalStationWritable(Integer taskNo, Integer stationId, Integer deviceNo, String operation) {
        if (!guardedFinalStationOwnership(taskNo, stationId, deviceNo)) {
            ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId);
            logOwnerConflict(operation, stationId, currentStatus, taskNo,
                    currentStatus == null ? null : currentStatus.getTargetStaNo(), true,
                    "final_station_owner_conflict");
            return false;
        }
        return true;
    }
    private boolean guardedFinalStationClearable(Integer taskNo, Integer stationId, Integer deviceNo, String operation) {
        if (!guardedFinalStationOwnership(taskNo, stationId, deviceNo)) {
            ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId);
            logOwnerConflict(operation, stationId, currentStatus, taskNo,
                    currentStatus == null ? null : currentStatus.getTargetStaNo(), true,
                    "illegal_clear_attempt");
            return false;
        }
        return true;
    }
    private boolean shouldTreatAsFinalStationConflict(Integer taskNo, Integer stationId, Integer deviceNo) {
        return isFinalStation(taskNo, stationId) && stateManager.isFinalStationOwnerConflict(deviceNo, stationId, taskNo);
    }
    private boolean guardedFinalStationMutation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId) {
        if (shouldTreatAsFinalStationConflict(taskNo, stationId, deviceNo)) {
            ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId);
            logOwnerConflict(operation, stationId, currentStatus, taskNo, targetStationId, true,
                    "final_station_owner_conflict");
            return false;
        }
        return true;
    }
    private boolean guardedFinalStationClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo) {
        if (shouldTreatAsFinalStationConflict(taskNo, stationId, deviceNo)) {
            ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId);
            logOwnerConflict(operation, stationId, currentStatus, taskNo,
                    currentStatus == null ? null : currentStatus.getTargetStaNo(), true,
                    "illegal_clear_attempt");
            return false;
        }
        return true;
    }
    private boolean guardedStationWrite(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId) {
        if (!guardedFinalStationMutation(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        return ensureStationWritable(operation, deviceNo, stationId, taskNo, targetStationId, isFinalStation(taskNo, stationId));
    }
    private boolean guardedStationClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo) {
        if (!guardedFinalStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        return ensureStationClearable(operation, deviceNo, stationId, taskNo, isFinalStation(taskNo, stationId));
    }
    private boolean guardedStateMutation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId,
            Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedStateClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedFinalStationCompletion(Integer deviceNo, TaskRuntimeContext context) {
        if (context == null || context.finalTargetStationId == null || context.taskNo == null) {
            return false;
        }
        return !stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo);
    }
    private boolean guardedCurrentStationOwnership(Integer taskNo, Integer currentStationId, Integer currentDeviceNo) {
        return !stateManager.isOccupiedByOtherLoadingTask(currentDeviceNo, currentStationId, taskNo);
    }
    private boolean guardedTargetStationOwnership(Integer taskNo, Integer stationId, Integer deviceNo) {
        return !stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo);
    }
    private boolean guardedCommandWrite(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedCommandClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedRunBlockMutation(Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId) {
        return guardedStationWrite("runBlockStation", taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedClearMutation(Integer taskNo, Integer stationId, Integer deviceNo, String operation) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private boolean guardedArrivalOwnership(Integer deviceNo, TaskRuntimeContext context) {
        return context != null && context.finalTargetStationId != null && context.taskNo != null
                && !stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo);
    }
    private boolean guardedArrivalStationWritable(Integer deviceNo, TaskRuntimeContext context, String operation) {
        if (!guardedArrivalOwnership(deviceNo, context)) {
            logFinalStationOwnershipLost(deviceNo, context);
            return false;
        }
        return true;
    }
    private boolean guardedArrivalStationClearable(Integer deviceNo, TaskRuntimeContext context, String operation) {
        if (!guardedArrivalOwnership(deviceNo, context)) {
            logFinalStationOwnershipLost(deviceNo, context);
            return false;
        }
        return true;
    }
    private boolean guardedStationMutation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId,
            Runnable mutation, boolean clearOperation) {
        boolean allowed = clearOperation
                ? guardedStationClear(operation, taskNo, stationId, deviceNo)
                : guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
        if (!allowed) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedFinalArrivalState(Integer deviceNo, TaskRuntimeContext context) {
        if (context == null || context.finalTargetStationId == null || context.taskNo == null) {
            return false;
        }
        if (stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo)) {
            logFinalStationOwnershipLost(deviceNo, context);
            return false;
        }
        return true;
    }
    private boolean guardedArrivalCompletion(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedStationOwnership(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId,
            boolean clearOperation) {
        return clearOperation
                ? guardedStationClear(operation, taskNo, stationId, deviceNo)
                : guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedStationMutationIfAllowed(String operation, Integer taskNo, Integer stationId, Integer deviceNo,
            Integer targetStationId, boolean clearOperation, Runnable mutation) {
        if (!guardedStationOwnership(operation, taskNo, stationId, deviceNo, targetStationId, clearOperation)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedArrivalStationState(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedMutateFinalStation(String operation, Integer taskNo, Integer stationId, Integer deviceNo,
            Integer targetStationId, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedClearFinalStation(String operation, Integer taskNo, Integer stationId, Integer deviceNo,
            Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedArrivalState(Integer deviceNo, TaskRuntimeContext context) {
        if (!guardedFinalArrivalState(deviceNo, context)) {
            return false;
        }
        return true;
    }
    private boolean guardedCurrentStationWrite(Integer taskNo, Integer currentStationId, Integer currentDeviceNo, Integer targetStationId,
            String operation) {
        return guardedStationWrite(operation, taskNo, currentStationId, currentDeviceNo, targetStationId);
    }
    private boolean guardedCurrentStationClear(Integer taskNo, Integer currentStationId, Integer currentDeviceNo, String operation) {
        return guardedStationClear(operation, taskNo, currentStationId, currentDeviceNo);
    }
    private boolean guardedFinalStationWrite(Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId, String operation) {
        return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedFinalStationClear(Integer taskNo, Integer stationId, Integer deviceNo, String operation) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private boolean guardedStationBarcodeWrite(Integer deviceNo, Integer stationId, Integer taskNo, String barcode) {
        return guardedUpdateStationBarcode(deviceNo, stationId, taskNo, barcode);
    }
    private boolean guardedStationOccupancyWrite(Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId,
            Runnable mutation, String operation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedStationOccupancyClear(Integer deviceNo, Integer stationId, Integer taskNo,
            Runnable mutation, String operation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedStationWriteForTask(Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId,
            String operation) {
        return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedStationClearForTask(Integer deviceNo, Integer stationId, Integer taskNo, String operation) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private boolean guardedMutateStationIfOwned(Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId,
            String operation, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedClearStationIfOwned(Integer deviceNo, Integer stationId, Integer taskNo, String operation,
            Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedArrivalVisibility(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedFinalStationNotOverwritten(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedMutateOccupiedStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo,
            Integer targetStationId, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedClearOccupiedStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo,
            Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedFinalStationStillOwned(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedStateWrite(String operation, Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId) {
        return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedStateClear(String operation, Integer deviceNo, Integer stationId, Integer taskNo) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private boolean guardedArrivalOwnerCheck(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedArrivalConflict(Integer deviceNo, TaskRuntimeContext context) {
        return stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo);
    }
    private boolean guardedCurrentOwnerCheck(Integer deviceNo, Integer stationId, Integer taskNo) {
        return !stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo);
    }
    private boolean guardedMutateStationState(String operation, Integer deviceNo, Integer stationId, Integer taskNo,
            Integer targetStationId, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedClearStationState(String operation, Integer deviceNo, Integer stationId, Integer taskNo,
            Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedArrivalCompletionState(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedWriteToStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId) {
        return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedClearFromStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private void handleCommand(Integer deviceNo, StationCommand command) {
        News.info("[WCS Debug] 站点仿真模拟(V3)已启动,命令数据={}", JSON.toJSONString(command));
        News.info("[WCS Debug] fake 非MOVE命令进入device串行执行,deviceNo={},stationId={},targetStaNo={},commandType={},命令数据={}",
                deviceNo, command.getStationId(), command.getTargetStaNo(), command.getCommandType(), JSON.toJSONString(command));
        Integer taskNo = command.getTaskNo();
        Integer stationId = command.getStationId();
        Integer targetStationId = command.getTargetStaNo();
        if (command.getCommandType() == StationCommandType.RESET) {
            resetStation(deviceNo, stationId);
            boolean reset = guardedResetStation(deviceNo, stationId, taskNo);
            News.info("[WCS Debug] fake RESET已通过统一站点锁执行,deviceNo={},stationId={},accepted={}", deviceNo, stationId, reset);
            return;
        }
        if (command.getCommandType() == StationCommandType.WRITE_INFO) {
            if (command.getBarcode() != null) {
                updateStationBarcode(deviceNo, stationId, command.getBarcode());
                boolean updated = guardedUpdateStationBarcode(deviceNo, stationId, taskNo, command.getBarcode());
                News.info("[WCS Debug] fake WRITE_INFO条码写入已通过统一站点锁执行,deviceNo={},stationId={},barcode={},accepted={}",
                        deviceNo, stationId, command.getBarcode(), updated);
                return;
            }
            if (taskNo == 9998 && targetStationId == 0) {
                generateFakeOutStationData(deviceNo, stationId);
                boolean generated = guardedGenerateFakeOutStationData(deviceNo, stationId, taskNo);
                News.info("[WCS Debug] fake WRITE_INFO有物写入已通过统一站点锁执行,deviceNo={},stationId={},accepted={}", deviceNo, stationId, generated);
                return;
            }
        }
        if (taskNo != null && taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId != null
                && stationId.equals(targetStationId)) {
            generateStationData(deviceNo, taskNo, stationId, targetStationId);
        }
    }
    private void generateFakeOutStationData(Integer deviceNo, Integer stationId) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return;
        }
        ZyStationStatusEntity status = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (status == null) {
            return;
        }
        synchronized (status) {
            status.setLoading(true);
        }
    }
    private void generateStationData(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return;
        }
        ZyStationStatusEntity status = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (status == null) {
            return;
        }
        synchronized (status) {
            status.setTaskNo(taskNo);
            status.setTargetStaNo(targetStationId);
        }
    }
    private void resetStation(Integer deviceNo, Integer stationId) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return;
        }
        ZyStationStatusEntity status = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (status == null) {
            return;
        }
        synchronized (status) {
            status.setTaskNo(0);
            status.setLoading(false);
            status.setBarcode("");
        }
    }
    private void updateStationBarcode(Integer deviceNo, Integer stationId, String barcode) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return;
        }
        ZyStationStatusEntity status = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (status == null) {
            return;
        }
        synchronized (status) {
            status.setBarcode(barcode);
        }
    }
    private Integer getDeviceNoByStationId(Integer stationId) {
        for (Map.Entry<Integer, List<ZyStationStatusEntity>> entry : deviceStatusMap.entrySet()) {
            List<ZyStationStatusEntity> list = entry.getValue();
            if (list == null) {
                continue;
            }
            for (ZyStationStatusEntity entity : list) {
                if (entity.getStationId() != null && entity.getStationId().equals(stationId)) {
                    return entry.getKey();
                }
            }
        }
        return null;
    }
    private Integer findCurrentStationIdByTask(Integer taskNo) {
        for (List<ZyStationStatusEntity> list : deviceStatusMap.values()) {
            if (list == null) {
                continue;
            }
            for (ZyStationStatusEntity entity : list) {
                if (entity.getTaskNo() != null && entity.getTaskNo().equals(taskNo) && entity.isLoading()) {
                    return entity.getStationId();
                }
            }
        }
        return null;
    }
    private void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    private ReentrantLock getStationLock(Integer stationId) {
        return stationLocks.computeIfAbsent(stationId, key -> new ReentrantLock());
    }
    private void lockStations(Integer... stationIds) {
        Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length);
        Arrays.sort(sorted);
        for (Integer stationId : sorted) {
            getStationLock(stationId).lock();
        }
    }
    private void unlockStations(Integer... stationIds) {
        Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length);
        Arrays.sort(sorted);
        for (int i = sorted.length - 1; i >= 0; i--) {
            getStationLock(sorted[i]).unlock();
        }
    }
    private boolean updateStationDataInternal(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStaNo,
            Boolean isLoading, String barcode, Boolean runBlock) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return false;
        }
        ZyStationStatusEntity currentStatus = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (currentStatus == null) {
            return false;
        }
        if (taskNo != null) {
            currentStatus.setTaskNo(taskNo);
        }
        if (targetStaNo != null) {
            currentStatus.setTargetStaNo(targetStaNo);
        }
        if (isLoading != null) {
            currentStatus.setLoading(isLoading);
        }
        if (barcode != null) {
            currentStatus.setBarcode(barcode);
        }
        if (runBlock != null) {
            currentStatus.setRunBlock(runBlock);
        }
        return true;
    }
    public boolean initStationMove(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo,
            Integer taskNo, Integer targetStationId, Boolean isLoading, String barcode) {
        lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
            if (statusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return false;
            }
            if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0
                    && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) {
                return false;
            }
            return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, targetStationId,
                    isLoading, barcode, false);
        } finally {
            unlockStations(currentStationId);
        }
    }
    public boolean stationMoveToNext(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo,
            Integer nextStationId, Integer nextStationDeviceNo, Integer taskNo, Integer targetStaNo) {
        lockStations(currentStationId, nextStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
            List<ZyStationStatusEntity> nextStatusList = deviceStatusMap.get(nextStationDeviceNo);
            if (statusList == null || nextStatusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            ZyStationStatusEntity nextStatus = nextStatusList.stream()
                    .filter(item -> item.getStationId().equals(nextStationId)).findFirst().orElse(null);
            if (currentStatus == null || nextStatus == null) {
                return false;
            }
            if (nextStatus.getTaskNo() != null && nextStatus.getTaskNo() > 0 || nextStatus.isLoading()) {
                return false;
            }
            boolean result = updateStationDataInternal(nextStationId, nextStationDeviceNo, taskNo, targetStaNo, true,
                    null, false);
            if (!result) {
                return false;
            }
            return updateStationDataInternal(currentStationId, currentStationDeviceNo, 0, 0, false, "", false);
        } finally {
            unlockStations(currentStationId, nextStationId);
        }
    }
    public boolean generateStationBarcode(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo) {
        lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
            if (statusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return false;
            }
            Random random = new Random();
            String barcodeTime = String.valueOf(System.currentTimeMillis());
            String barcode = String.valueOf(random.nextInt(10)) + String.valueOf(random.nextInt(10))
                    + barcodeTime.substring(7);
            return updateStationDataInternal(currentStationId, currentStationDeviceNo, null, null, null, barcode, null);
        } finally {
            unlockStations(currentStationId);
        }
    }
    public boolean clearStation(Integer deviceNo, Integer lockTaskNo, Integer currentStationId) {
        lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
            if (statusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return false;
            }
            return updateStationDataInternal(currentStationId, deviceNo, 0, 0, false, "", false);
        } finally {
            unlockStations(currentStationId);
        }
    }
    public boolean runBlockStation(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo,
            Integer taskNo, Integer blockStationId) {
        lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
            if (statusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return false;
            }
            return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, blockStationId, true, "",
                    true);
        } finally {
            unlockStations(currentStationId);
        }
    }
    public void clearRunBlock(Integer stationId, Integer deviceNo) {
        lockStations(stationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
            if (statusList == null) {
                return;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return;
            }
            if (currentStatus.isRunBlock()) {
                currentStatus.setRunBlock(false);
                News.info("[WCS Debug] 站点{}堵塞标记已清除", stationId);
            }
        } finally {
            unlockStations(stationId);
            boolean handedOff = guardedHandoffBarcodeStation(deviceNo, taskNo, stationId, targetStationId);
            News.info("[WCS Debug] fake 条码站任务交接完成,deviceNo={},stationId={},taskNo={},targetStationId={},动作=barcodeTaskHandoff,accepted={}",
                    deviceNo, stationId, taskNo, targetStationId, handedOff);
        }
    }
@@ -989,36 +1639,138 @@
    private void traceEvent(Integer deviceNo, TaskRuntimeContext context, String eventType, String message,
            Map<String, Object> details, boolean terminal) {
        if (context != null && context.deviceNo != null && deviceNo != null && !context.deviceNo.equals(deviceNo)) {
            deviceNo = context.deviceNo;
        }
        if (context == null || context.taskNo == null) {
            return;
        }
        try {
            FakeTaskTraceRegistry registry = SpringUtils.getBean(FakeTaskTraceRegistry.class);
            if (registry == null) {
                return;
            }
            registry.record(context.taskNo, context.threadImpl != null ? context.threadImpl : getThreadImpl(deviceNo),
                    context.status, context.startStationId, context.currentStationId, context.finalTargetStationId,
                    context.blockedStationId, context.getStitchedPathStationIds(), context.getPassedStationIds(),
                    context.getPendingStationIds(), context.getLatestAppendedPath(), eventType, message, details,
                    terminal);
        } catch (Exception ignore) {
        Map<String, Object> safeDetails = details == null ? new LinkedHashMap<String, Object>() : new LinkedHashMap<String, Object>(details);
        if (!safeDetails.containsKey("segmentNo") && context.segmentNo != null) {
            safeDetails.put("segmentNo", context.segmentNo);
        }
    }
    private String getThreadImpl(Integer deviceNo) {
        DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo);
        return deviceConfig == null ? "" : deviceConfig.getThreadImpl();
        if (!safeDetails.containsKey("segmentCount") && context.segmentCount != null) {
            safeDetails.put("segmentCount", context.segmentCount);
        }
        try {
            FakeTaskTraceRegistry registry = com.core.common.SpringUtils.getBean(FakeTaskTraceRegistry.class);
            if (registry != null) {
                registry.record(context.taskNo, context.threadImpl != null ? context.threadImpl : stateManager.getThreadImpl(deviceNo),
                        context.status, context.startStationId, context.currentStationId, context.finalTargetStationId,
                        context.blockedStationId, context.getStitchedPathStationIds(), context.getPassedStationIds(),
                        context.getPendingStationIds(), context.getLatestAppendedPath(), eventType, message, safeDetails,
                        terminal);
            }
        } catch (Exception e) {
            News.info("[WCS Debug] fake trace记录失败,taskNo={},eventType={},异常类型={},异常信息={}",
                    context.taskNo, eventType, e.getClass().getSimpleName(), e.getMessage());
        }
        News.info("[WCS Debug] fake task event,taskNo={},eventType={},status={},currentStationId={},targetStationId={},pending={},details={}",
                context.taskNo, eventType, context.status, context.currentStationId, context.finalTargetStationId,
                context.getPendingStationIds(), JSON.toJSONString(safeDetails));
    }
    private boolean isTerminalStatus(String status) {
        return STATUS_BLOCKED.equals(status) || STATUS_CANCELLED.equals(status) || STATUS_TIMEOUT.equals(status)
        return STATUS_CANCELLED.equals(status) || STATUS_TIMEOUT.equals(status)
                || STATUS_FINISHED.equals(status);
    }
    private boolean isCurrentLoopGeneration(Integer taskNo, int loopGeneration) {
        AtomicInteger generation = taskLoopGenerations.get(taskNo);
        return generation != null && generation.get() == loopGeneration;
    }
    private Object getTaskLifecycleLock(Integer taskNo) {
        return taskLifecycleLocks.computeIfAbsent(taskNo, key -> new Object());
    }
    /**
     * 检查堆垛机是否已取走指定任务的货物
     * 通过遍历所有堆垛机设备,检查是否有堆垛机正在执行该任务且已 loaded
     */
    private boolean isCrnTakenByTask(Integer taskNo) {
        if (taskNo == null || taskNo <= 0) {
            return false;
        }
        try {
            com.zy.asrs.service.BasCrnpService basCrnpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasCrnpService.class);
            List<com.zy.asrs.entity.BasCrnp> basCrnps = basCrnpService.list();
            for (com.zy.asrs.entity.BasCrnp basCrnp : basCrnps) {
                com.zy.core.thread.CrnThread ct = (com.zy.core.thread.CrnThread) com.zy.core.cache.SlaveConnection.get(
                        com.zy.core.enums.SlaveType.Crn, basCrnp.getCrnNo());
                if (ct == null) {
                    continue;
                }
                com.zy.core.model.protocol.CrnProtocol protocol = ct.getStatus();
                if (protocol != null && taskNo.equals(protocol.getTaskNo())
                        && protocol.getLoaded() == 1) {
                    return true;
                }
            }
        } catch (Exception ignore) {
        }
        return false;
    }
    private static class MoveStepResult {
        private final boolean shouldContinue;
        private final long nextDelayMs;
        private MoveStepResult(boolean shouldContinue, long nextDelayMs) {
            this.shouldContinue = shouldContinue;
            this.nextDelayMs = nextDelayMs;
        }
        private static MoveStepResult continueNow() {
            return new MoveStepResult(true, 0L);
        }
        private static MoveStepResult continueAfter(long nextDelayMs) {
            return new MoveStepResult(true, nextDelayMs);
        }
        private boolean shouldContinue() {
            return shouldContinue;
        }
        private long getNextDelayMs() {
            return nextDelayMs;
        }
    }
    private static class IdleStepResult {
        private final boolean finished;
        private final long nextDelayMs;
        private IdleStepResult(boolean finished, long nextDelayMs) {
            this.finished = finished;
            this.nextDelayMs = nextDelayMs;
        }
        private static IdleStepResult finish() {
            return new IdleStepResult(true, 0L);
        }
        private static IdleStepResult waitNext(long nextDelayMs) {
            return new IdleStepResult(false, nextDelayMs);
        }
        private boolean isFinished() {
            return finished;
        }
        private long getNextDelayMs() {
            return nextDelayMs;
        }
    }
    private static class TaskRuntimeContext {
        private final Integer taskNo;
        private final Integer deviceNo;
        private final String threadImpl;
        private final LinkedBlockingQueue<Integer> pendingPathQueue = new LinkedBlockingQueue<Integer>();
        private final List<Integer> stitchedPathStationIds = new ArrayList<Integer>();
@@ -1032,12 +1784,18 @@
        private boolean generateBarcode;
        private boolean initialized;
        private boolean arrivalHandled;
        private Integer segmentNo;
        private Integer segmentCount;
        private long lastStepAt = System.currentTimeMillis();
        private long lastCommandAt = System.currentTimeMillis();
        private long lastProgressAt = System.currentTimeMillis();
        private Integer lastProgressStationId;
        private int loopGeneration;
        private String status = STATUS_WAITING;
        private TaskRuntimeContext(Integer taskNo, String threadImpl) {
        private TaskRuntimeContext(Integer taskNo, Integer deviceNo, String threadImpl) {
            this.taskNo = taskNo;
            this.deviceNo = deviceNo;
            this.threadImpl = threadImpl;
        }
@@ -1064,6 +1822,30 @@
            }
        }
        private void replaceLatestPath(List<Integer> path) {
            latestAppendedPath.clear();
            if (path == null) {
                return;
            }
            for (Integer stationId : path) {
                if (stationId != null) {
                    latestAppendedPath.add(stationId);
                }
            }
        }
        private void clearPendingPath() {
            pendingPathQueue.clear();
            latestAppendedPath.clear();
        }
        private void clearPendingPathWithLog(Integer deviceNo, Integer taskNo, Integer currentStationId,
                Integer targetStationId, Integer blockedStationId, String reason) {
            News.info("[WCS Debug] fake task清空待执行路径,原因={},deviceNo={},taskNo={},currentStationId={},targetStationId={},blockedStationId={},pendingBeforeClear={}",
                    reason, deviceNo, taskNo, currentStationId, targetStationId, blockedStationId, getPendingStationIds());
            clearPendingPath();
        }
        private void addPassedStation(Integer stationId) {
            if (stationId == null) {
                return;
src/main/java/com/zy/core/network/fake/ZyStationV4FakeSegConnect.java
File was deleted
src/main/java/com/zy/core/plugin/FakeProcess.java
@@ -2,14 +2,8 @@
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.core.common.Cools;
import com.zy.asrs.domain.param.CreateInTaskParam;
import com.zy.asrs.domain.param.CreateOutTaskParam;
import com.zy.asrs.utils.Utils;
import com.zy.asrs.entity.*;
import com.zy.asrs.service.*;
import com.zy.common.entity.FindCrnNoResult;
import com.zy.common.service.CommonService;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.MessageQueue;
@@ -21,16 +15,17 @@
import com.zy.core.model.command.CrnCommand;
import com.zy.core.model.command.StationCommand;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.DualCrnProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.network.fake.FakeConfigKeys;
import com.zy.core.network.fake.FakeConfigSupport;
import com.zy.core.plugin.api.MainProcessPluginApi;
import com.zy.core.plugin.station.FakeStationMonitor;
import com.zy.core.plugin.station.FakeTaskGenerator;
import com.zy.core.plugin.store.StoreInTaskContext;
import com.zy.core.plugin.store.StoreInTaskGenerationService;
import com.zy.core.plugin.store.StoreInTaskPolicy;
import com.zy.core.task.MainProcessLane;
import com.zy.core.task.MainProcessTaskSubmitter;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.DualCrnThread;
import com.zy.core.thread.StationThread;
import com.zy.core.utils.CrnOperateProcessUtils;
import com.zy.core.utils.DualCrnOperateProcessUtils;
@@ -42,34 +37,83 @@
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class FakeProcess implements MainProcessPluginApi, StoreInTaskPolicy {
    private static final long MAIN_DISPATCH_INTERVAL_MS = 200L;
    private static final long ASYNC_DISPATCH_INTERVAL_MS = 50L;
    private long getMainDispatchIntervalMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_IDLE_LOOP_DELAY_MS, 200L);
    }
    private static final Map<Integer, Long> stationStayTimeMap = new ConcurrentHashMap<>();
    private long getAsyncDispatchIntervalMs() {
        return Math.max(50L, FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_INITIALIZE_DELAY_MS, 50L));
    }
    private long getOutStationStayTimeoutMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_OUT_STATION_STAY_TIMEOUT_MS, 3000L);
    }
    private static volatile String enableFake = "N";
    private static volatile String fakeRealTaskRequestWms = "N";
    private static volatile String fakeGenerateInTask = "Y";
    private static volatile String fakeGenerateOutTask = "Y";
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private BasDevpService basDevpService;
    private static final Map<String, Long> OUT_STATION_STAY_MARKER = new HashMap<String, Long>();
    private final Object outStationStayLock = new Object();
    private boolean shouldDispatchOutStationWriteInfo(String taskNo) {
        long now = System.currentTimeMillis();
        long timeoutMs = getOutStationStayTimeoutMs();
        synchronized (outStationStayLock) {
            Long lastTime = OUT_STATION_STAY_MARKER.get(taskNo);
            if (lastTime == null || now - lastTime >= timeoutMs) {
                OUT_STATION_STAY_MARKER.put(taskNo, now);
                return true;
            }
            return false;
        }
    }
    private void clearOutStationStayMarker(String taskNo) {
        synchronized (outStationStayLock) {
            OUT_STATION_STAY_MARKER.remove(taskNo);
        }
    }
    private void dispatchOutStationWriteInfoIfReady(WrkMast wrkMast, BasCrnp basCrnp) {
        String taskNo = wrkMast == null ? null : wrkMast.getWmsWrkNo();
        if (taskNo == null || !shouldDispatchOutStationWriteInfo(taskNo)) {
            return;
        }
        List<StationObjModel> outStationList = basCrnp.getOutStationList$();
        if (outStationList.isEmpty()) {
            News.info("堆垛机:{} 出库站点未设置", basCrnp.getCrnNo());
            return;
        }
        for (StationObjModel stationObjModel : outStationList) {
            if (!stationObjModel.getStationId().equals(wrkMast.getSourceStaNo())) {
                continue;
            }
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
            if (stationThread == null) {
                continue;
            }
            StationCommand command = stationThread.getCommand(StationCommandType.WRITE_INFO, 9998,
                    wrkMast.getSourceStaNo(), 0, 0);
            stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "crn-out-complete-write-info");
        }
    }
    @Autowired
    private ConfigService configService;
    @Autowired
    private LocMastService locMastService;
    private WrkMastService wrkMastService;
    @Autowired
    private CommonService commonService;
    private WrkAnalysisService wrkAnalysisService;
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private BasDualCrnpService basDualCrnpService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
@@ -77,49 +121,52 @@
    @Autowired
    private StationOperateProcessUtils stationOperateProcessUtils;
    @Autowired
    private WrkAnalysisService wrkAnalysisService;
    @Autowired
    private DualCrnOperateProcessUtils dualCrnOperateProcessUtils;
    @Autowired
    private StoreInTaskGenerationService storeInTaskGenerationService;
    @Autowired
    private StationCommandDispatcher stationCommandDispatcher;
    @Autowired
    private MainProcessTaskSubmitter mainProcessTaskSubmitter;
    @Autowired
    private FakeStationMonitor fakeStationMonitor;
    @Autowired
    private FakeTaskGenerator fakeTaskGenerator;
    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
        refreshFakeConfig();
        syncConfigToComponents();
        long asyncDispatchIntervalMs = getAsyncDispatchIntervalMs();
        long mainDispatchIntervalMs = getMainDispatchIntervalMs();
        // 仿真异步任务
        submitAsyncTasks(ASYNC_DISPATCH_INTERVAL_MS);
        fakeStationMonitor.submitMonitorTasks(asyncDispatchIntervalMs);
        // 仿真输送线堵塞检测
        stationOperateProcessUtils.submitCheckStationRunBlockTasks(MainProcessLane.FAKE_STATION_RUN_BLOCK, ASYNC_DISPATCH_INTERVAL_MS);
        stationOperateProcessUtils.submitCheckStationRunBlockTasks(MainProcessLane.FAKE_STATION_RUN_BLOCK, asyncDispatchIntervalMs);
        // 请求生成入库任务
        generateStoreWrkFile();
        fakeTaskGenerator.submitGenerateTasks();
        // 执行堆垛机任务
        crnOperateUtils.submitCrnIoTasks(MainProcessLane.FAKE_CRN_IO, MAIN_DISPATCH_INTERVAL_MS);
        crnOperateUtils.submitCrnIoTasks(MainProcessLane.FAKE_CRN_IO, mainDispatchIntervalMs);
        // 堆垛机任务执行完成
        submitCrnIoExecuteFinishTasks(MAIN_DISPATCH_INTERVAL_MS);
        submitCrnIoExecuteFinishTasks(mainDispatchIntervalMs);
        // 执行输送站点入库任务
        stationOperateProcessUtils.submitStationInTasks(MainProcessLane.FAKE_STATION_IN, MAIN_DISPATCH_INTERVAL_MS);
        stationOperateProcessUtils.submitStationInTasks(MainProcessLane.FAKE_STATION_IN, mainDispatchIntervalMs);
        // 检测入库任务是否已经到达目标站台
        stationOperateProcessUtils.submitInboundStationArrivalTasks(MAIN_DISPATCH_INTERVAL_MS);
        stationOperateProcessUtils.submitInboundStationArrivalTasks(mainDispatchIntervalMs);
        // 输送线执行堆垛机出库后的站台流转
        stationOperateProcessUtils.submitCrnStationOutTasks(MainProcessLane.FAKE_STATION_OUT, MAIN_DISPATCH_INTERVAL_MS);
        stationOperateProcessUtils.submitCrnStationOutTasks(MainProcessLane.FAKE_STATION_OUT, mainDispatchIntervalMs);
        // 检测出库任务是否已经到达目标站台
        stationOperateProcessUtils.submitStationOutExecuteFinishTasks(MAIN_DISPATCH_INTERVAL_MS);
        stationOperateProcessUtils.submitStationOutExecuteFinishTasks(mainDispatchIntervalMs);
        // 检测站台运行完成后的任务转完成
        stationOperateProcessUtils.submitCheckTaskToCompleteTasks(MAIN_DISPATCH_INTERVAL_MS);
        stationOperateProcessUtils.submitCheckTaskToCompleteTasks(mainDispatchIntervalMs);
        // 检测并处理出库排序
        stationOperateProcessUtils.submitCheckStationOutOrderTasks(MainProcessLane.FAKE_STATION_OUT_ORDER, MAIN_DISPATCH_INTERVAL_MS);
        stationOperateProcessUtils.submitCheckStationOutOrderTasks(MainProcessLane.FAKE_STATION_OUT_ORDER, mainDispatchIntervalMs);
        // 执行双工位堆垛机任务
        dualCrnOperateProcessUtils.submitDualCrnIoTasks(MainProcessLane.FAKE_DUAL_CRN_IO, MAIN_DISPATCH_INTERVAL_MS);
        dualCrnOperateProcessUtils.submitDualCrnIoTasks(MainProcessLane.FAKE_DUAL_CRN_IO, mainDispatchIntervalMs);
        // 双工位堆垛机任务执行完成
        dualCrnOperateProcessUtils.submitDualCrnIoExecuteFinishTasks(MainProcessLane.FAKE_DUAL_CRN_IO_FINISH, MAIN_DISPATCH_INTERVAL_MS);
        dualCrnOperateProcessUtils.submitDualCrnIoExecuteFinishTasks(MainProcessLane.FAKE_DUAL_CRN_IO_FINISH, mainDispatchIntervalMs);
        News.info("[WCS Debug] 主线程Run执行完成,耗时:{}ms", System.currentTimeMillis() - startTime);
    }
@@ -149,23 +196,12 @@
        }
    }
    private void submitAsyncTasks(long minIntervalMs) {
        submitAsyncTask("checkInStationHasTask", minIntervalMs, this::checkInStationHasTask);
        submitAsyncTask("generateFakeInTask", minIntervalMs, this::generateFakeInTask);
        submitAsyncTask("generateFakeOutTask", minIntervalMs, this::generateFakeOutTask);
        submitAsyncTask("calcAllStationStayTime", minIntervalMs, this::calcAllStationStayTime);
        submitAsyncTask("checkOutStationStayTimeOut", minIntervalMs, this::checkOutStationStayTimeOut);
        submitAsyncTask("checkInStationCrnTake", minIntervalMs, this::checkInStationCrnTake);
    }
    private void submitAsyncTask(String taskName, long minIntervalMs, Runnable task) {
        mainProcessTaskSubmitter.submitKeyedSerialTask(
                MainProcessLane.FAKE_ASYNC,
                taskName,
                taskName,
                minIntervalMs,
                task
        );
    private void syncConfigToComponents() {
        fakeStationMonitor.setEnableFake(enableFake);
        fakeStationMonitor.setFakeGenerateInTask(fakeGenerateInTask);
        fakeTaskGenerator.setFakeRealTaskRequestWms(fakeRealTaskRequestWms);
        fakeTaskGenerator.setFakeGenerateInTask(fakeGenerateInTask);
        fakeTaskGenerator.setFakeGenerateOutTask(fakeGenerateOutTask);
    }
    private void submitCrnIoExecuteFinishTasks(long minIntervalMs) {
@@ -185,473 +221,19 @@
        }
    }
    private void submitGenerateStoreTasks() {
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
            if (stationMap == null || stationMap.isEmpty()) {
                continue;
            }
            List<StationObjModel> barcodeStations = getBarcodeStations(basDevp);
            for (StationObjModel stationObjModel : barcodeStations) {
                Integer stationId = stationObjModel == null ? null : stationObjModel.getStationId();
                if (stationId == null || !stationMap.containsKey(stationId)) {
                    continue;
                }
                if (!canRequestStoreIn(stationMap.get(stationId))) {
                    continue;
                }
                storeInTaskGenerationService.submitGenerateStoreTask(
                        this,
                        basDevp,
                        stationObjModel,
                        MainProcessLane.FAKE_GENERATE_STORE,
                        MAIN_DISPATCH_INTERVAL_MS,
                        () -> storeInTaskGenerationService.generate(this, basDevp, stationObjModel)
                );
            }
        }
    }
    // 检测入库站是否有任务生成,并仿真生成模拟入库站点数据
    private void checkInStationHasTask() {
        if (!enableFake.equals("Y")) {
            return;
        }
        if (!fakeGenerateInTask.equals("Y")) {
            return;
        }
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
            List<StationObjModel> list = basDevp.getInStationList$();
            for (StationObjModel entity : list) {
                Integer stationId = entity.getStationId();
                if (!stationMap.containsKey(stationId)) {
                    continue;
                }
                StationProtocol stationProtocol = stationMap.get(stationId);
                if (stationProtocol == null) {
                    continue;
                }
                Object lock = redisUtil.get(RedisKeyType.GENERATE_FAKE_IN_STATION_DATA_LIMIT.key + stationId);
                if (lock != null) {
                    continue;
                }
                // 满足自动、无物、工作号0,生成入库数据
                if (stationProtocol.isAutoing()
                        && !stationProtocol.isLoading()
                        && stationProtocol.getTaskNo() == 0) {
                    StationCommand command = stationThread.getCommand(StationCommandType.MOVE,
                            commonService.getWorkNo(WrkIoType.FAKE_TASK_NO.id), stationId,
                            entity.getBarcodeStation().getStationId(), 0);
                    stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "fake-process", "fake-enable-in");
                    if (entity.getBarcodeStation() != null && entity.getBarcodeStation().getStationId() != null) {
                        Utils.precomputeInTaskEnableRow(entity.getBarcodeStation().getStationId());
                    }
                    redisUtil.set(RedisKeyType.GENERATE_FAKE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 5);
                }
            }
        }
    }
    // 生成仿真模拟入库任务
    private void generateFakeInTask() {
        if (!enableFake.equals("Y")) {
            return;
        }
        if (fakeRealTaskRequestWms.equals("Y")) {
            return;
        }
        if (!fakeGenerateInTask.equals("Y")) {
            return;
        }
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
            List<StationObjModel> list = basDevp.getBarcodeStationList$();
            for (StationObjModel model : list) {
                Integer stationId = model.getStationId();
                if (!stationMap.containsKey(stationId)) {
                    continue;
                }
                StationProtocol stationProtocol = stationMap.get(stationId);
                if (stationProtocol == null) {
                    continue;
                }
                Object object = redisUtil.get(RedisKeyType.GENERATE_FAKE_IN_TASK_LIMIT.key + stationId);
                if (object != null) {
                    continue;
                }
                // 满足自动、有物、有工作号,生成入库数据
                if (stationProtocol.isAutoing()
                        && stationProtocol.isLoading()
                        && stationProtocol.getTaskNo() > 0) {
                    if (Cools.isEmpty(stationProtocol.getBarcode())) {
                        continue;
                    }
                    // 检测任务是否生成
                    List<WrkMast> wrkMasts = wrkMastService
                            .list(new QueryWrapper<WrkMast>().eq("barcode", stationProtocol.getBarcode()));
                    if (!wrkMasts.isEmpty()) {
                        continue;
                    }
                    List<LocMast> locMastList = locMastService
                            .list(new QueryWrapper<LocMast>().eq("loc_sts", String.valueOf(LocStsType.O)));
                    if (locMastList.isEmpty()) {
                        continue;
                    }
                    int nextInt = new Random().nextInt(locMastList.size());
                    LocMast locMast = locMastList.get(nextInt);
                    FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locMast.getLocNo());
                    if (findCrnNoResult == null) {
                        continue;
                    }
                    Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationId);
                    if (targetStationId == null) {
                        continue;
                    }
                    CreateInTaskParam taskParam = new CreateInTaskParam();
                    taskParam.setTaskNo(String.valueOf(commonService.getWorkNo(WrkIoType.IN.id)));
                    taskParam.setSourceStaNo(stationId);
                    taskParam.setStaNo(targetStationId);
                    taskParam.setLocNo(locMast.getLocNo());
                    taskParam.setBarcode(stationProtocol.getBarcode());
                    WrkMast wrkMast = commonService.createInTask(taskParam);
                    StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(),
                            stationId, stationId, 0);
                    if (command == null) {
                        News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败");
                        continue;
                    }
                    stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "fake-process", "fake-in-task");
                    redisUtil.set(RedisKeyType.GENERATE_FAKE_IN_TASK_LIMIT.key + stationId, "lock", 5);
                }
            }
        }
    }
    // 生成仿真模拟出库任务
    private void generateFakeOutTask() {
        try {
            if (!enableFake.equals("Y")) {
                return;
            }
            if (fakeRealTaskRequestWms.equals("Y")) {
                return;
            }
            if (!fakeGenerateOutTask.equals("Y")) {
                return;
            }
            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
            for (BasDevp basDevp : basDevps) {
                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
                if (stationThread == null) {
                    continue;
                }
                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
                List<StationObjModel> list = basDevp.getOutStationList$();
                for (StationObjModel entity : list) {
                    Integer stationId = entity.getStationId();
                    if (!stationMap.containsKey(stationId)) {
                        continue;
                    }
                    StationProtocol stationProtocol = stationMap.get(stationId);
                    if (stationProtocol == null) {
                        continue;
                    }
                    Object object = redisUtil.get(RedisKeyType.GENERATE_FAKE_OUT_TASK_LIMIT.key + stationId);
                    if (object != null) {
                        return;
                    }
                    // 满足自动、无物、工作号0,生成出库数据
                    if (stationProtocol.isAutoing()
                            && !stationProtocol.isLoading()
                            && stationProtocol.getTaskNo() == 0) {
                        List<LocMast> locMastList = locMastService
                                .list(new QueryWrapper<LocMast>().eq("loc_sts", String.valueOf(LocStsType.F)));
                        if (locMastList.isEmpty()) {
                            continue;
                        }
                        LocMast locMast = locMastList.get(0);
                        CreateOutTaskParam taskParam = new CreateOutTaskParam();
                        taskParam.setTaskNo(String.valueOf(commonService.getWorkNo(WrkIoType.OUT.id)));
                        taskParam.setStaNo(stationId);
                        taskParam.setLocNo(locMast.getLocNo());
                        boolean result = commonService.createOutTask(taskParam);
                        redisUtil.set(RedisKeyType.GENERATE_FAKE_OUT_TASK_LIMIT.key + stationId, "lock", 10);
                    }
                }
            }
        } catch (Exception e) {
            log.error("generateFakeOutTask execute error", e);
        }
    }
    /**
     * 请求生成入库任务
     * 入库站,根据条码扫描生成入库工作档
     */
    public void generateStoreWrkFile() {
        submitGenerateStoreTasks();
    }
    @Override
    public boolean isEnabled() {
        return !"N".equals(fakeRealTaskRequestWms);
        return fakeTaskGenerator.isEnabled();
    }
    @Override
    public void onRequestPermitGranted(StoreInTaskContext context) {
        redisUtil.set(getGenerateLockKey(context), "lock", 3);
        fakeTaskGenerator.onRequestPermitGranted(context);
    }
    @Override
    public void afterTaskCreated(StoreInTaskContext context, WrkMast wrkMast) {
        Integer stationId = context.getStationObjModel().getStationId();
        StationCommand command = context.getStationThread().getCommand(StationCommandType.WRITE_INFO,
                wrkMast.getWrkNo(), stationId, stationId, 0);
        if (command == null) {
            News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败");
            return;
        }
        stationCommandDispatcher.dispatch(context.getBasDevp().getDevpNo(), command, "fake-process", "write-info");
    }
    private boolean canRequestStoreIn(StationProtocol stationProtocol) {
        return stationProtocol != null
                && stationProtocol.isAutoing()
                && stationProtocol.isLoading()
                && stationProtocol.getTaskNo() > 0
                && !Cools.isEmpty(stationProtocol.getBarcode());
    }
    // 计算所有站点停留时间
    public void calcAllStationStayTime() {
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            List<StationProtocol> list = stationThread.getStatus();
            for (StationProtocol stationProtocol : list) {
                if (stationProtocol.getTaskNo() > 0
                        && !stationStayTimeMap.containsKey(stationProtocol.getStationId())) {
                    stationStayTimeMap.put(stationProtocol.getStationId(), System.currentTimeMillis());
                }
                if (stationProtocol.getTaskNo() <= 0
                        && stationStayTimeMap.containsKey(stationProtocol.getStationId())) {
                    stationStayTimeMap.remove(stationProtocol.getStationId());
                }
            }
        }
    }
    // 检测出库站点停留是否超时
    public void checkOutStationStayTimeOut() {
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            List<StationObjModel> outStationList = basDevp.getOutStationList$();
            if (outStationList.isEmpty()) {
                News.info("输送线:{} 出库站点未设置", basDevp.getDevpNo());
                continue;
            }
            for (StationObjModel stationObjModel : outStationList) {
                Object lock = redisUtil
                        .get(RedisKeyType.CHECK_OUT_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId());
                if (lock != null) {
                    continue;
                }
                Long stayTime = stationStayTimeMap.get(stationObjModel.getStationId());
                if (stayTime == null) {
                    continue;
                }
                if (System.currentTimeMillis() - stayTime > 1000 * 60) {
                    StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp,
                            stationObjModel.getDeviceNo());
                    if (stationThread == null) {
                        continue;
                    }
                    StationCommand command = stationThread.getCommand(StationCommandType.RESET, 0,
                            stationObjModel.getStationId(), 0, 0);
                    if (command == null) {
                        continue;
                    }
                    stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "out-station-reset");
                    redisUtil.set(
                            RedisKeyType.CHECK_OUT_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(),
                            "lock", 10);
                    News.info("输送站点出库重置命令下发成功,站点号={},命令数据={}", stationObjModel.getStationId(),
                            JSON.toJSONString(command));
                }
            }
        }
    }
    // 检测入库站点堆垛机是否取走货物
    public void checkInStationCrnTake() {
        List<BasCrnp> basCrnps = basCrnpService.list(new QueryWrapper<>());
        for (BasCrnp basCrnp : basCrnps) {
            List<StationObjModel> inStationList = basCrnp.getInStationList$();
            if (inStationList.isEmpty()) {
                News.info("堆垛机:{} 入库站点未设置", basCrnp.getCrnNo());
                continue;
            }
            checkInStationListCrnTake(inStationList);
        }
        List<BasDualCrnp> basDualCrnps = basDualCrnpService.list(new QueryWrapper<>());
        for (BasDualCrnp basDualCrnp : basDualCrnps) {
            List<StationObjModel> inStationList = basDualCrnp.getInStationList$();
            if (inStationList.isEmpty()) {
                News.info("双工位堆垛机:{} 入库站点未设置", basDualCrnp.getCrnNo());
                continue;
            }
            checkInStationListCrnTake(inStationList);
        }
    }
    private void checkInStationListCrnTake(List<StationObjModel> inStationList) {
        for (StationObjModel stationObjModel : inStationList) {
            Object lock = redisUtil
                    .get(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId());
            if (lock != null) {
                continue;
            }
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp,
                    stationObjModel.getDeviceNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId());
            if (stationProtocol == null) {
                continue;
            }
            if (stationProtocol.getTaskNo() > 0) {
                StationCommand command = stationThread.getCommand(StationCommandType.RESET, 0,
                        stationObjModel.getStationId(), 0, 0);
                if (command == null) {
                    continue;
                }
                WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
                if (wrkMast == null) {
                    stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-task-over");
                    redisUtil.set(
                            RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(),
                            "lock", 10);
                    News.info("输送站点重置命令下发成功(task_over),站点号={},命令数据={}", stationObjModel.getStationId(),
                            JSON.toJSONString(command));
                } else {
                    if (wrkMast.getWrkSts() != WrkStsType.NEW_INBOUND.sts
                            && wrkMast.getWrkSts() != WrkStsType.INBOUND_STATION_RUN.sts) {
                        Integer crnNo = wrkMast.getCrnNo();
                        if (crnNo != null) {
                            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, crnNo);
                            if (crnThread == null) {
                                continue;
                            }
                            CrnProtocol crnProtocol = crnThread.getStatus();
                            if (!crnProtocol.getStatusType().equals(CrnStatusType.PUT_MOVING)
                                    && !crnProtocol.getStatusType().equals(CrnStatusType.PUTTING)) {
                                continue;
                            }
                            stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-crn-fetch");
                            redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key
                                    + stationObjModel.getStationId(), "lock", 10);
                            News.info("输送站点重置命令下发成功(crn_fetch),站点号={},命令数据={}", stationObjModel.getStationId(),
                                    JSON.toJSONString(command));
                        } else {
                            Integer dualCrnNo = wrkMast.getDualCrnNo();
                            DualCrnThread dualCrnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn,
                                    dualCrnNo);
                            if (dualCrnThread == null) {
                                continue;
                            }
                            DualCrnProtocol dualCrnProtocol = dualCrnThread.getStatus();
                            boolean reset = false;
                            if (dualCrnProtocol.getTaskNo() > 0 && dualCrnProtocol.getLoaded() == 1) {
                                reset = true;
                            }
                            if (dualCrnProtocol.getTaskNoTwo() > 0 && dualCrnProtocol.getLoadedTwo() == 1) {
                                reset = true;
                            }
                            if (!reset) {
                                continue;
                            }
                            stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-dual-crn-fetch");
                            redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key
                                    + stationObjModel.getStationId(), "lock", 10);
                            News.info("输送站点重置命令下发成功(crn_fetch),站点号={},命令数据={}", stationObjModel.getStationId(),
                                    JSON.toJSONString(command));
                        }
                    }
                }
            }
        }
        fakeTaskGenerator.afterTaskCreated(context, wrkMast);
    }
    // 堆垛机任务执行完成
@@ -697,28 +279,7 @@
        } else if (wrkMast.getWrkSts() == WrkStsType.OUTBOUND_RUN.sts) {
            updateWrkSts = WrkStsType.OUTBOUND_RUN_COMPLETE.sts;
            // 生成仿真站点数据
            List<StationObjModel> outStationList = basCrnp.getOutStationList$();
            if (outStationList.isEmpty()) {
                News.info("堆垛机:{} 出库站点未设置", basCrnp.getCrnNo());
                return;
            }
            for (StationObjModel stationObjModel : outStationList) {
                if (!stationObjModel.getStationId().equals(wrkMast.getSourceStaNo())) {
                    continue;
                }
                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp,
                        stationObjModel.getDeviceNo());
                if (stationThread == null) {
                    continue;
                }
                // 生成仿真站点数据
                StationCommand command = stationThread.getCommand(StationCommandType.WRITE_INFO, 9998,
                        wrkMast.getSourceStaNo(), 0, 0);
                stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "crn-out-complete-write-info");
            }
            dispatchOutStationWriteInfoIfReady(wrkMast, basCrnp);
        } else if (wrkMast.getWrkSts() == WrkStsType.LOC_MOVE_RUN.sts) {
            updateWrkSts = WrkStsType.COMPLETE_LOC_MOVE.sts;
        } else if (wrkMast.getWrkSts() == WrkStsType.CRN_MOVE_RUN.sts) {
@@ -734,6 +295,7 @@
        wrkMast.setIoTime(now);
        wrkMast.setModiTime(now);
        if (wrkMastService.updateById(wrkMast)) {
            clearOutStationStayMarker(wrkMast.getWmsWrkNo());
            wrkAnalysisService.markCraneComplete(wrkMast, now, updateWrkSts);
            CrnCommand resetCommand = crnThread.getResetCommand(crnProtocol.getTaskNo(), crnProtocol.getCrnNo());
            MessageQueue.offer(SlaveType.Crn, crnProtocol.getCrnNo(), new Task(2, resetCommand));
src/main/java/com/zy/core/plugin/station/FakeStationMonitor.java
New file
@@ -0,0 +1,336 @@
package com.zy.core.plugin.station;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.core.common.Cools;
import com.zy.asrs.entity.*;
import com.zy.asrs.service.*;
import com.zy.asrs.utils.Utils;
import com.zy.common.service.CommonService;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.dispatch.StationCommandDispatcher;
import com.zy.core.enums.*;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.command.StationCommand;
import com.zy.core.model.protocol.CrnProtocol;
import com.zy.core.model.protocol.DualCrnProtocol;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.thread.DualCrnThread;
import com.zy.core.task.MainProcessLane;
import com.zy.core.task.MainProcessTaskSubmitter;
import com.zy.core.thread.CrnThread;
import com.zy.core.thread.StationThread;
import com.zy.core.cache.SlaveConnection;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class FakeStationMonitor {
    private static final Map<Integer, Long> stationStayTimeMap = new ConcurrentHashMap<>();
    private volatile String enableFake = "N";
    private volatile String fakeGenerateInTask = "Y";
    @Autowired
    private BasDevpService basDevpService;
    @Autowired
    private BasCrnpService basCrnpService;
    @Autowired
    private BasDualCrnpService basDualCrnpService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private CommonService commonService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private StationCommandDispatcher stationCommandDispatcher;
    @Autowired
    private MainProcessTaskSubmitter mainProcessTaskSubmitter;
    public void setEnableFake(String enableFake) {
        this.enableFake = enableFake;
    }
    public void setFakeGenerateInTask(String fakeGenerateInTask) {
        this.fakeGenerateInTask = fakeGenerateInTask;
    }
    public void submitMonitorTasks(long minIntervalMs) {
        submitAsyncTask("checkInStationHasTask", minIntervalMs, this::checkInStationHasTask);
        submitAsyncTask("calcAllStationStayTime", minIntervalMs, this::calcAllStationStayTime);
        submitAsyncTask("checkOutStationStayTimeOut", minIntervalMs, this::checkOutStationStayTimeOut);
        submitAsyncTask("checkInStationCrnTake", minIntervalMs, this::checkInStationCrnTake);
    }
    private void submitAsyncTask(String taskName, long minIntervalMs, Runnable task) {
        mainProcessTaskSubmitter.submitKeyedSerialTask(
                MainProcessLane.FAKE_ASYNC,
                taskName,
                taskName,
                minIntervalMs,
                task
        );
    }
    // 计算所有站点停留时间
    public void calcAllStationStayTime() {
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            List<StationProtocol> list = stationThread.getStatus();
            for (StationProtocol stationProtocol : list) {
                if (stationProtocol.getTaskNo() > 0
                        && !stationStayTimeMap.containsKey(stationProtocol.getStationId())) {
                    stationStayTimeMap.put(stationProtocol.getStationId(), System.currentTimeMillis());
                }
                if (stationProtocol.getTaskNo() <= 0
                        && stationStayTimeMap.containsKey(stationProtocol.getStationId())) {
                    stationStayTimeMap.remove(stationProtocol.getStationId());
                }
            }
        }
    }
    // 检测出库站点停留是否超时
    public void checkOutStationStayTimeOut() {
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            List<StationObjModel> outStationList = basDevp.getOutStationList$();
            if (outStationList.isEmpty()) {
                News.info("输送线:{} 出库站点未设置", basDevp.getDevpNo());
                continue;
            }
            for (StationObjModel stationObjModel : outStationList) {
                Object lock = redisUtil
                        .get(RedisKeyType.CHECK_OUT_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId());
                if (lock != null) {
                    continue;
                }
                Long stayTime = stationStayTimeMap.get(stationObjModel.getStationId());
                if (stayTime == null) {
                    continue;
                }
                if (System.currentTimeMillis() - stayTime > 1000 * 60) {
                    StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp,
                            stationObjModel.getDeviceNo());
                    if (stationThread == null) {
                        continue;
                    }
                    StationCommand command = stationThread.getCommand(StationCommandType.RESET, 0,
                            stationObjModel.getStationId(), 0, 0);
                    if (command == null) {
                        continue;
                    }
                    stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "out-station-reset");
                    redisUtil.set(
                            RedisKeyType.CHECK_OUT_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(),
                            "lock", 10);
                    News.info("输送站点出库重置命令下发成功,站点号={},命令数据={}", stationObjModel.getStationId(),
                            JSON.toJSONString(command));
                }
            }
        }
    }
    // 检测入库站点堆垛机是否取走货物
    public void checkInStationCrnTake() {
        List<BasCrnp> basCrnps = basCrnpService.list(new QueryWrapper<>());
        for (BasCrnp basCrnp : basCrnps) {
            List<StationObjModel> inStationList = basCrnp.getInStationList$();
            if (inStationList.isEmpty()) {
                News.info("堆垛机:{} 入库站点未设置", basCrnp.getCrnNo());
                continue;
            }
            checkInStationListCrnTake(inStationList);
        }
        List<BasDualCrnp> basDualCrnps = basDualCrnpService.list(new QueryWrapper<>());
        for (BasDualCrnp basDualCrnp : basDualCrnps) {
            List<StationObjModel> inStationList = basDualCrnp.getInStationList$();
            if (inStationList.isEmpty()) {
                News.info("双工位堆垛机:{} 入库站点未设置", basDualCrnp.getCrnNo());
                continue;
            }
            checkInStationListCrnTake(inStationList);
        }
    }
    private void checkInStationListCrnTake(List<StationObjModel> inStationList) {
        for (StationObjModel stationObjModel : inStationList) {
            Object lock = redisUtil
                    .get(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId());
            if (lock != null) {
                continue;
            }
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp,
                    stationObjModel.getDeviceNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId());
            if (stationProtocol == null) {
                continue;
            }
            if (stationProtocol.getTaskNo() > 0) {
                StationCommand command = stationThread.getCommand(StationCommandType.RESET, 0,
                        stationObjModel.getStationId(), 0, 0);
                if (command == null) {
                    continue;
                }
                WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
                if (wrkMast == null) {
                    News.info("入库站点清理命中,站点号={},站点taskNo={},原因=wrk_missing,准备RESET",
                            stationObjModel.getStationId(), stationProtocol.getTaskNo());
                    stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-task-over");
                    redisUtil.set(
                            RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(),
                            "lock", 10);
                    News.info("输送站点重置命令下发成功(task_over),站点号={},命令数据={}", stationObjModel.getStationId(),
                            JSON.toJSONString(command));
                } else {
                    if (Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN_COMPLETE.sts)
                            || Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_RUN.sts)) {
                        Integer crnNo = wrkMast.getCrnNo();
                        if (crnNo != null) {
                            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, crnNo);
                            if (crnThread == null) {
                                continue;
                            }
                            CrnProtocol crnProtocol = crnThread.getStatus();
                            boolean sameTaskRunning = stationProtocol.getTaskNo().equals(crnProtocol.getTaskNo())
                                    && Objects.equals(crnProtocol.getLoaded(), 1);
                            if (!sameTaskRunning) {
                                News.info("入库站点清理忽略,站点号={},站点taskNo={},工作状态={},堆垛机号={},堆垛机taskNo={},statusType={},loaded={},原因=crn_not_loaded_this_task",
                                        stationObjModel.getStationId(), stationProtocol.getTaskNo(), wrkMast.getWrkSts(), crnNo,
                                        crnProtocol.getTaskNo(), crnProtocol.getStatusType(), crnProtocol.getLoaded());
                                continue;
                            }
                            News.info("入库站点清理命中,站点号={},站点taskNo={},工作状态={},堆垛机号={},堆垛机taskNo={},statusType={},loaded={},原因=crn_loaded_running",
                                    stationObjModel.getStationId(), stationProtocol.getTaskNo(), wrkMast.getWrkSts(), crnNo,
                                    crnProtocol.getTaskNo(), crnProtocol.getStatusType(), crnProtocol.getLoaded());
                            stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-crn-running");
                            redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key
                                    + stationObjModel.getStationId(), "lock", 10);
                            News.info("输送站点重置命令下发成功(crn_running),站点号={},命令数据={}", stationObjModel.getStationId(),
                                    JSON.toJSONString(command));
                        } else {
                            Integer dualCrnNo = wrkMast.getDualCrnNo();
                            DualCrnThread dualCrnThread = (DualCrnThread) SlaveConnection.get(SlaveType.DualCrn,
                                    dualCrnNo);
                            if (dualCrnThread == null) {
                                continue;
                            }
                            DualCrnProtocol dualCrnProtocol = dualCrnThread.getStatus();
                            Integer taskNo = stationProtocol.getTaskNo();
                            boolean sameTaskRunning = taskNo != null && (
                                    taskNo.equals(dualCrnProtocol.getTaskNo()) && dualCrnProtocol.getLoaded() == 1
                                            || taskNo.equals(dualCrnProtocol.getTaskNoTwo()) && dualCrnProtocol.getLoadedTwo() == 1
                            );
                            if (!sameTaskRunning) {
                                News.info("入库站点清理忽略,站点号={},站点taskNo={},工作状态={},双工位堆垛机号={},taskNo1={},loaded1={},taskNo2={},loaded2={},原因=dual_crn_not_running_this_task",
                                        stationObjModel.getStationId(), stationProtocol.getTaskNo(), wrkMast.getWrkSts(), dualCrnNo,
                                        dualCrnProtocol.getTaskNo(), dualCrnProtocol.getLoaded(),
                                        dualCrnProtocol.getTaskNoTwo(), dualCrnProtocol.getLoadedTwo());
                                continue;
                            }
                            News.info("入库站点清理命中,站点号={},站点taskNo={},工作状态={},双工位堆垛机号={},taskNo1={},loaded1={},taskNo2={},loaded2={},原因=dual_crn_running",
                                    stationObjModel.getStationId(), stationProtocol.getTaskNo(), wrkMast.getWrkSts(), dualCrnNo,
                                    dualCrnProtocol.getTaskNo(), dualCrnProtocol.getLoaded(),
                                    dualCrnProtocol.getTaskNoTwo(), dualCrnProtocol.getLoadedTwo());
                            stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command,
                                    "fake-process", "in-station-reset-dual-crn-running");
                            redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key
                                    + stationObjModel.getStationId(), "lock", 10);
                            News.info("输送站点重置命令下发成功(dual_crn_running),站点号={},命令数据={}",
                                    stationObjModel.getStationId(), JSON.toJSONString(command));
                        }
                    } else {
                        News.info("入库站点清理忽略,站点号={},站点taskNo={},工作状态={},原因=wrk_sts_not_3",
                                stationObjModel.getStationId(), stationProtocol.getTaskNo(), wrkMast.getWrkSts());
                    }
                }
            }
        }
    }
    // 检测入库站是否有任务生成,并仿真生成模拟入库站点数据
    private void checkInStationHasTask() {
        if (!enableFake.equals("Y")) {
            return;
        }
        if (!fakeGenerateInTask.equals("Y")) {
            return;
        }
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
            List<StationObjModel> list = basDevp.getInStationList$();
            for (StationObjModel entity : list) {
                Integer stationId = entity.getStationId();
                if (!stationMap.containsKey(stationId)) {
                    continue;
                }
                StationProtocol stationProtocol = stationMap.get(stationId);
                if (stationProtocol == null) {
                    continue;
                }
                Object lock = redisUtil.get(RedisKeyType.GENERATE_FAKE_IN_STATION_DATA_LIMIT.key + stationId);
                if (lock != null) {
                    continue;
                }
                // 满足自动、无物、工作号0,生成入库数据
                if (stationProtocol.isAutoing()
                        && !stationProtocol.isLoading()
                        && stationProtocol.getTaskNo() == 0) {
                    StationCommand command = stationThread.getCommand(StationCommandType.MOVE,
                            commonService.getWorkNo(WrkIoType.FAKE_TASK_NO.id), stationId,
                            entity.getBarcodeStation().getStationId(), 0);
                    stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "fake-process", "fake-enable-in");
                    if (entity.getBarcodeStation() != null && entity.getBarcodeStation().getStationId() != null) {
                        Utils.precomputeInTaskEnableRow(entity.getBarcodeStation().getStationId());
                    }
                    redisUtil.set(RedisKeyType.GENERATE_FAKE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 5);
                }
            }
        }
    }
}
src/main/java/com/zy/core/plugin/station/FakeTaskGenerator.java
New file
@@ -0,0 +1,304 @@
package com.zy.core.plugin.station;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.core.common.Cools;
import com.zy.asrs.domain.param.CreateInTaskParam;
import com.zy.asrs.domain.param.CreateOutTaskParam;
import com.zy.asrs.entity.*;
import com.zy.asrs.service.*;
import com.zy.common.entity.FindCrnNoResult;
import com.zy.common.service.CommonService;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.dispatch.StationCommandDispatcher;
import com.zy.core.enums.*;
import com.zy.core.model.StationObjModel;
import com.zy.core.model.command.StationCommand;
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.plugin.store.StoreInTaskContext;
import com.zy.core.plugin.store.StoreInTaskGenerationService;
import com.zy.core.plugin.store.StoreInTaskPolicy;
import com.zy.core.task.MainProcessLane;
import com.zy.core.task.MainProcessTaskSubmitter;
import com.zy.core.thread.StationThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Random;
@Slf4j
@Component
public class FakeTaskGenerator implements StoreInTaskPolicy {
    private static final long MAIN_DISPATCH_INTERVAL_MS = 200L;
    private String fakeRealTaskRequestWms = "N";
    private String fakeGenerateInTask = "Y";
    private String fakeGenerateOutTask = "Y";
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private BasDevpService basDevpService;
    @Autowired
    private LocMastService locMastService;
    @Autowired
    private CommonService commonService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private StationCommandDispatcher stationCommandDispatcher;
    @Autowired
    private MainProcessTaskSubmitter mainProcessTaskSubmitter;
    @Autowired
    private StoreInTaskGenerationService storeInTaskGenerationService;
    public void setFakeRealTaskRequestWms(String fakeRealTaskRequestWms) {
        this.fakeRealTaskRequestWms = fakeRealTaskRequestWms;
    }
    public void setFakeGenerateInTask(String fakeGenerateInTask) {
        this.fakeGenerateInTask = fakeGenerateInTask;
    }
    public void setFakeGenerateOutTask(String fakeGenerateOutTask) {
        this.fakeGenerateOutTask = fakeGenerateOutTask;
    }
    public void submitGenerateTasks() {
        submitGenerateStoreTasks();
        mainProcessTaskSubmitter.submitKeyedSerialTask(
                MainProcessLane.FAKE_ASYNC, "generateFakeInTask", "generateFakeInTask",
                MAIN_DISPATCH_INTERVAL_MS, this::generateFakeInTask);
        mainProcessTaskSubmitter.submitKeyedSerialTask(
                MainProcessLane.FAKE_ASYNC, "generateFakeOutTask", "generateFakeOutTask",
                MAIN_DISPATCH_INTERVAL_MS, this::generateFakeOutTask);
    }
    private void submitGenerateStoreTasks() {
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
            if (stationMap == null || stationMap.isEmpty()) {
                continue;
            }
            List<StationObjModel> barcodeStations = getBarcodeStations(basDevp);
            for (StationObjModel stationObjModel : barcodeStations) {
                Integer stationId = stationObjModel == null ? null : stationObjModel.getStationId();
                if (stationId == null || !stationMap.containsKey(stationId)) {
                    continue;
                }
                if (!canRequestStoreIn(stationMap.get(stationId))) {
                    continue;
                }
                storeInTaskGenerationService.submitGenerateStoreTask(
                        this,
                        basDevp,
                        stationObjModel,
                        MainProcessLane.FAKE_GENERATE_STORE,
                        MAIN_DISPATCH_INTERVAL_MS,
                        () -> storeInTaskGenerationService.generate(this, basDevp, stationObjModel)
                );
            }
        }
    }
    // 生成仿真模拟入库任务
    public void generateFakeInTask() {
        if (fakeRealTaskRequestWms.equals("Y")) {
            return;
        }
        if (!fakeGenerateInTask.equals("Y")) {
            return;
        }
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
            List<StationObjModel> list = basDevp.getBarcodeStationList$();
            for (StationObjModel model : list) {
                Integer stationId = model.getStationId();
                if (!stationMap.containsKey(stationId)) {
                    continue;
                }
                StationProtocol stationProtocol = stationMap.get(stationId);
                if (stationProtocol == null) {
                    continue;
                }
                Object object = redisUtil.get(RedisKeyType.GENERATE_FAKE_IN_TASK_LIMIT.key + stationId);
                if (object != null) {
                    continue;
                }
                // 满足自动、有物、有工作号,生成入库数据
                if (stationProtocol.isAutoing()
                        && stationProtocol.isLoading()
                        && stationProtocol.getTaskNo() > 0) {
                    if (Cools.isEmpty(stationProtocol.getBarcode())) {
                        continue;
                    }
                    // 检测任务是否生成
                    List<WrkMast> wrkMasts = wrkMastService
                            .list(new QueryWrapper<WrkMast>().eq("barcode", stationProtocol.getBarcode()));
                    if (!wrkMasts.isEmpty()) {
                        continue;
                    }
                    List<LocMast> locMastList = locMastService
                            .list(new QueryWrapper<LocMast>().eq("loc_sts", String.valueOf(LocStsType.O)));
                    if (locMastList.isEmpty()) {
                        continue;
                    }
                    int nextInt = new Random().nextInt(locMastList.size());
                    LocMast locMast = locMastList.get(nextInt);
                    FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locMast.getLocNo());
                    if (findCrnNoResult == null) {
                        continue;
                    }
                    Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationId);
                    if (targetStationId == null) {
                        continue;
                    }
                    CreateInTaskParam taskParam = new CreateInTaskParam();
                    taskParam.setTaskNo(String.valueOf(commonService.getWorkNo(WrkIoType.IN.id)));
                    taskParam.setSourceStaNo(stationId);
                    taskParam.setStaNo(targetStationId);
                    taskParam.setLocNo(locMast.getLocNo());
                    taskParam.setBarcode(stationProtocol.getBarcode());
                    WrkMast wrkMast = commonService.createInTask(taskParam);
                    StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(),
                            stationId, stationId, 0);
                    if (command == null) {
                        News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败");
                        continue;
                    }
                    stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "fake-process", "fake-in-task");
                    redisUtil.set(RedisKeyType.GENERATE_FAKE_IN_TASK_LIMIT.key + stationId, "lock", 5);
                }
            }
        }
    }
    // 生成仿真模拟出库任务
    public void generateFakeOutTask() {
        try {
            if (fakeRealTaskRequestWms.equals("Y")) {
                return;
            }
            if (!fakeGenerateOutTask.equals("Y")) {
                return;
            }
            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
            for (BasDevp basDevp : basDevps) {
                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
                if (stationThread == null) {
                    continue;
                }
                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
                List<StationObjModel> list = basDevp.getOutStationList$();
                for (StationObjModel entity : list) {
                    Integer stationId = entity.getStationId();
                    if (!stationMap.containsKey(stationId)) {
                        continue;
                    }
                    StationProtocol stationProtocol = stationMap.get(stationId);
                    if (stationProtocol == null) {
                        continue;
                    }
                    Object object = redisUtil.get(RedisKeyType.GENERATE_FAKE_OUT_TASK_LIMIT.key + stationId);
                    if (object != null) {
                        return;
                    }
                    // 满足自动、无物、工作号0,生成出库数据
                    if (stationProtocol.isAutoing()
                            && !stationProtocol.isLoading()
                            && stationProtocol.getTaskNo() == 0) {
                        List<LocMast> locMastList = locMastService
                                .list(new QueryWrapper<LocMast>().eq("loc_sts", String.valueOf(LocStsType.F)));
                        if (locMastList.isEmpty()) {
                            continue;
                        }
                        LocMast locMast = locMastList.get(0);
                        CreateOutTaskParam taskParam = new CreateOutTaskParam();
                        taskParam.setTaskNo(String.valueOf(commonService.getWorkNo(WrkIoType.OUT.id)));
                        taskParam.setStaNo(stationId);
                        taskParam.setLocNo(locMast.getLocNo());
                        boolean result = commonService.createOutTask(taskParam);
                        redisUtil.set(RedisKeyType.GENERATE_FAKE_OUT_TASK_LIMIT.key + stationId, "lock", 10);
                    }
                }
            }
        } catch (Exception e) {
            log.error("generateFakeOutTask execute error", e);
        }
    }
    private boolean canRequestStoreIn(StationProtocol stationProtocol) {
        return stationProtocol != null
                && stationProtocol.isAutoing()
                && stationProtocol.isLoading()
                && stationProtocol.getTaskNo() > 0
                && !Cools.isEmpty(stationProtocol.getBarcode());
    }
    @Override
    public boolean isEnabled() {
        return !"N".equals(fakeRealTaskRequestWms);
    }
    @Override
    public void onRequestPermitGranted(StoreInTaskContext context) {
        redisUtil.set(getGenerateLockKey(context), "lock", 3);
    }
    @Override
    public void afterTaskCreated(StoreInTaskContext context, WrkMast wrkMast) {
        Integer stationId = context.getStationObjModel().getStationId();
        StationCommand command = context.getStationThread().getCommand(StationCommandType.WRITE_INFO,
                wrkMast.getWrkNo(), stationId, stationId, 0);
        if (command == null) {
            News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败");
            return;
        }
        stationCommandDispatcher.dispatch(context.getBasDevp().getDevpNo(), command, "fake-process", "write-info");
    }
    @Override
    public String getGenerateLockKey(StoreInTaskContext context) {
        return RedisKeyType.GENERATE_FAKE_IN_STATION_DATA_LIMIT.key + context.getStationObjModel().getStationId();
    }
}
src/main/java/com/zy/system/controller/ConfigController.java
@@ -237,12 +237,7 @@
    }
    private void refreshCacheData() {
        HashMap<String, String> systemConfigMap = new HashMap<>();
        List<Config> configList = configService.list(new QueryWrapper<>());
        for (Config config : configList) {
            systemConfigMap.put(config.getCode(), config.getValue());
        }
        redisUtil.set(RedisKeyType.SYSTEM_CONFIG_MAP.key, systemConfigMap);
        configService.refreshSystemConfigCache();
    }
}
src/main/java/com/zy/system/service/ConfigService.java
@@ -5,4 +5,11 @@
public interface ConfigService extends IService<Config> {
    String getConfigValue(String code, String defaultValue);
    long getConfigLongValue(String code, long defaultValue);
    boolean saveConfigValue(String code, String value);
    void refreshSystemConfigCache();
}
src/main/java/com/zy/system/service/impl/ConfigServiceImpl.java
@@ -1,12 +1,74 @@
package com.zy.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zy.common.entity.Parameter;
import com.zy.common.utils.RedisUtil;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.network.fake.FakeConfigKeys;
import com.zy.system.entity.Config;
import com.zy.system.mapper.ConfigMapper;
import com.zy.system.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
@Service("configService")
public class ConfigServiceImpl extends ServiceImpl<ConfigMapper, Config> implements ConfigService {
    @Autowired
    private RedisUtil redisUtil;
    @Override
    public String getConfigValue(String code, String defaultValue) {
        if (code == null || code.trim().isEmpty()) {
            return defaultValue;
        }
        Config config = getOne(new QueryWrapper<Config>().eq("code", code).last("limit 1"));
        if (config == null || config.getValue() == null || config.getValue().trim().isEmpty()) {
            return defaultValue;
        }
        return config.getValue();
    }
    @Override
    public long getConfigLongValue(String code, long defaultValue) {
        String value = getConfigValue(code, String.valueOf(defaultValue));
        try {
            long parsed = Long.parseLong(value.trim());
            return parsed >= 0 ? parsed : defaultValue;
        } catch (Exception e) {
            return defaultValue;
        }
    }
    @Override
    public boolean saveConfigValue(String code, String value) {
        Config config = getOne(new QueryWrapper<Config>().eq("code", code).last("limit 1"));
        if (config == null) {
            config = new Config(code, code, value, (short) 1, (short) 1);
            if (FakeConfigKeys.ALL_KEYS.contains(code)) {
                config.setSelectType("fake");
            }
            return save(config);
        }
        config.setValue(value);
        if (FakeConfigKeys.ALL_KEYS.contains(code) && (config.getSelectType() == null || config.getSelectType().trim().isEmpty())) {
            config.setSelectType("fake");
        }
        return updateById(config);
    }
    @Override
    public void refreshSystemConfigCache() {
        HashMap<String, String> systemConfigMap = new HashMap<>();
        List<Config> configList = list(new QueryWrapper<>());
        for (Config config : configList) {
            systemConfigMap.put(config.getCode(), config.getValue());
        }
        redisUtil.set(RedisKeyType.SYSTEM_CONFIG_MAP.key, systemConfigMap);
        Parameter.reset();
    }
}
src/main/resources/application.yml
@@ -1,6 +1,6 @@
# 系统版本信息
app:
  version: 3.0.0.1
  version: 3.0.0.2
  version-type: prd  # prd 或 dev
  i18n:
    default-locale: zh-CN
src/main/resources/sql/20260416_add_fake_advanced_config.sql
New file
@@ -0,0 +1,170 @@
-- 仿真高级参数初始化
-- 仅初始化仿真专属参数,不影响真实系统共享业务配置
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '启用仿真系统', 'enableFake', 'N', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'enableFake');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '仿真时请求真实任务', 'fakeRealTaskRequestWms', 'N', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeRealTaskRequestWms');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '自动生成仿真入库任务', 'fakeGenerateInTask', 'Y', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeGenerateInTask');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '自动生成仿真出库任务', 'fakeGenerateOutTask', 'Y', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeGenerateOutTask');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '仿真堵塞检测开关', 'fakeAllowCheckBlock', 'Y', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeAllowCheckBlock');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '仿真堵塞判定超时(毫秒)', 'fakeRunBlockTimeoutMs', '20000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeRunBlockTimeoutMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '单工位堆垛机取放货耗时(毫秒)', 'fakeCrnFetchPutDurationMs', '2000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeCrnFetchPutDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '单工位堆垛机Z轴每步耗时(毫秒)', 'fakeCrnLevelStepDurationMs', '1000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeCrnLevelStepDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '单工位堆垛机Y轴每步耗时(毫秒)', 'fakeCrnBayStepDurationMs', '1000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeCrnBayStepDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '单工位堆垛机复位耗时(毫秒)', 'fakeCrnResetDurationMs', '0', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeCrnResetDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '双工位堆垛机互斥轮询间隔(毫秒)', 'fakeDualCrnCommandWaitMs', '200', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeDualCrnCommandWaitMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '双工位堆垛机TRANSFER耗时(毫秒)', 'fakeDualCrnTransferDurationMs', '2000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeDualCrnTransferDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '双工位堆垛机PICK耗时(毫秒)', 'fakeDualCrnPickDurationMs', '3000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeDualCrnPickDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '双工位堆垛机PUT耗时(毫秒)', 'fakeDualCrnPutDurationMs', '3000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeDualCrnPutDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '双工位堆垛机Z轴每步耗时(毫秒)', 'fakeDualCrnLevelStepDurationMs', '1000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeDualCrnLevelStepDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '双工位堆垛机Y轴每步耗时(毫秒)', 'fakeDualCrnBayStepDurationMs', '500', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeDualCrnBayStepDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '双工位堆垛机复位耗时(毫秒)', 'fakeDualCrnResetDurationMs', '0', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeDualCrnResetDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT 'RGV导航点移动耗时(毫秒)', 'fakeRgvMoveStepDurationMs', '1000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeRgvMoveStepDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT 'RGV装卸货耗时(毫秒)', 'fakeRgvLoadDurationMs', '1000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeRgvLoadDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT 'RGV复位耗时(毫秒)', 'fakeRgvResetDurationMs', '0', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeRgvResetDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '站点等待新分段超时(毫秒)', 'fakeStationSegmentWaitTimeoutMs', '30000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeStationSegmentWaitTimeoutMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '站点移动推进间隔(毫秒)', 'fakeStationMoveStepDurationMs', '500', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeStationMoveStepDurationMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '站点空闲轮询间隔(毫秒)', 'fakeStationIdleLoopDelayMs', '200', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeStationIdleLoopDelayMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '站点堵塞轮询间隔(毫秒)', 'fakeStationBlockedLoopDelayMs', '1000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeStationBlockedLoopDelayMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '站点初始化等待(毫秒)', 'fakeStationInitializeDelayMs', '0', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeStationInitializeDelayMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '站点结束缓冲等待(毫秒)', 'fakeStationFinishDelayMs', '0', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeStationFinishDelayMs');
INSERT INTO sys_config(name, code, value, type, status, select_type)
SELECT '出库站自动结束时间(毫秒)', 'fakeOutStationStayTimeoutMs', '3000', 1, 1, 'fake'
FROM dual
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'fakeOutStationStayTimeoutMs');
SELECT id, name, code, value, type, status, select_type
FROM sys_config
WHERE code IN (
    'enableFake',
    'fakeRealTaskRequestWms',
    'fakeGenerateInTask',
    'fakeGenerateOutTask',
    'fakeAllowCheckBlock',
    'fakeRunBlockTimeoutMs',
    'fakeCrnFetchPutDurationMs',
    'fakeCrnLevelStepDurationMs',
    'fakeCrnBayStepDurationMs',
    'fakeCrnResetDurationMs',
    'fakeDualCrnCommandWaitMs',
    'fakeDualCrnTransferDurationMs',
    'fakeDualCrnPickDurationMs',
    'fakeDualCrnPutDurationMs',
    'fakeDualCrnLevelStepDurationMs',
    'fakeDualCrnBayStepDurationMs',
    'fakeDualCrnResetDurationMs',
    'fakeRgvMoveStepDurationMs',
    'fakeRgvLoadDurationMs',
    'fakeRgvResetDurationMs',
    'fakeStationSegmentWaitTimeoutMs',
    'fakeStationMoveStepDurationMs',
    'fakeStationIdleLoopDelayMs',
    'fakeStationBlockedLoopDelayMs',
    'fakeStationInitializeDelayMs',
    'fakeStationFinishDelayMs',
    'fakeOutStationStayTimeoutMs'
)
ORDER BY code;
src/main/webapp/components/MapCanvas.js
@@ -33,6 +33,7 @@
          </div>
          <div :style="mapToolRowStyle()">
            <button type="button" @click="openStationColorConfigPage" :style="mapToolButtonStyle(false)">站点颜色</button>
            <button v-if="fakeOperationVisible" type="button" @click="openFakeOperationConfigPage" :style="mapToolButtonStyle(false)">仿真操作</button>
          </div>
          <div v-if="levList && levList.length > 1" :style="mapToolFloorSectionStyle()">
            <div :style="mapToolSectionLabelStyle()">楼层</div>
@@ -153,7 +154,8 @@
        'machine-pakout': 0x97b400,
        'site-run-block': 0xe69138,
        'site-error': 0xDB2828
      }
      },
      fakeOperationVisible: false
    }
  },
    mounted() {
@@ -162,6 +164,7 @@
    this.startContainerResizeObserve();
    this.loadMapTransformConfig();
    this.loadStationColorConfig();
    this.loadFakeProcessStatus();
    this.loadLocList();
    this.connectWs();
@@ -2616,6 +2619,40 @@
      }
      window.open(url, '_blank');
    },
    loadFakeProcessStatus() {
      if (typeof window === 'undefined' || typeof $ === 'undefined' || typeof baseUrl === 'undefined') {
        this.fakeOperationVisible = false;
        return;
      }
      $.ajax({
        url: baseUrl + '/openapi/getFakeSystemRunStatus',
        method: 'get',
        success: function (res) {
          const data = res && res.data ? res.data : null;
          this.fakeOperationVisible = !!(data && data.isFake);
        }.bind(this),
        error: function () {
          this.fakeOperationVisible = false;
        }.bind(this)
      });
    },
    openFakeOperationConfigPage() {
      if (typeof window === 'undefined' || !this.fakeOperationVisible) { return; }
      const url = (typeof baseUrl !== 'undefined' ? baseUrl : '') + '/views/watch/fakeOperationConfig.html';
      const layerInstance = (window.top && window.top.layer) || window.layer;
      if (layerInstance && typeof layerInstance.open === 'function') {
        layerInstance.open({
          type: 2,
          title: '仿真操作',
          maxmin: true,
          area: ['1180px', '820px'],
          shadeClose: false,
          content: url
        });
        return;
      }
      window.open(url, '_blank');
    },
    parseRotation(value) {
      const num = parseInt(value, 10);
      if (!isFinite(num)) { return 0; }
src/main/webapp/static/js/watch/fakeOperationConfig.js
New file
@@ -0,0 +1,226 @@
var fakeConfigSchema = {
    basicGroups: [
        {
            key: 'runtime',
            title: '仿真总控',
            desc: '控制仿真系统是否运行,以及仿真时是否继续向 WMS 请求真实任务。',
            items: [
                { code: 'enableFake', name: '启用仿真系统', desc: '保存为是后表示仿真系统进入运行状态。', type: 'boolean' },
                { code: 'fakeRealTaskRequestWms', name: '仿真时请求真实任务', desc: '启用后,仿真运行时仍可向 WMS 请求真实任务。', type: 'boolean' }
            ]
        },
        {
            key: 'task',
            title: '自动任务',
            desc: '控制仿真模式下是否自动生成入库、出库任务。',
            items: [
                { code: 'fakeGenerateInTask', name: '自动生成入库任务', desc: '启用后,仿真系统会自动生成入库任务。', type: 'boolean' },
                { code: 'fakeGenerateOutTask', name: '自动生成出库任务', desc: '启用后,仿真系统会自动生成出库任务。', type: 'boolean' }
            ]
        },
        {
            key: 'block',
            title: '堵塞策略',
            desc: '控制仿真中的堵塞检测开关和堵塞判定超时时间。',
            items: [
                { code: 'fakeAllowCheckBlock', name: '启用堵塞检测', desc: '启用后按仿真规则检测运行堵塞。', type: 'boolean' },
                { code: 'fakeRunBlockTimeoutMs', name: '堵塞判定超时', desc: '站点持续无推进超过该时间后,按仿真逻辑判定为堵塞。', type: 'number', unit: 'ms', min: 0, max: 600000 }
            ]
        }
    ],
    advancedGroups: [
        {
            key: 'crn',
            title: '单工位堆垛机',
            desc: '控制单工位堆垛机的取放货、Y 轴和 Z 轴节奏。',
            items: [
                { code: 'fakeCrnFetchPutDurationMs', name: '取放货耗时', desc: '单次取货或放货动作耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeCrnBayStepDurationMs', name: 'Y轴每步耗时', desc: '堆垛机 bay 方向单步移动耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeCrnLevelStepDurationMs', name: 'Z轴每步耗时', desc: '堆垛机 level 方向单步移动耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeCrnResetDurationMs', name: '复位耗时', desc: '单工位堆垛机复位命令耗时。', unit: 'ms', min: 0, max: 600000 }
            ]
        },
        {
            key: 'dualCrn',
            title: '双工位堆垛机',
            desc: '控制双工位堆垛机的互斥等待、取放货和轴向移动节奏。',
            items: [
                { code: 'fakeDualCrnCommandWaitMs', name: '工位互斥轮询间隔', desc: '双工位竞争执行权时的等待轮询间隔。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeDualCrnTransferDurationMs', name: 'TRANSFER耗时', desc: 'TRANSFER 模式单次取/放动作耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeDualCrnPickDurationMs', name: 'PICK耗时', desc: 'PICK 模式动作耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeDualCrnPutDurationMs', name: 'PUT耗时', desc: 'PUT 模式动作耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeDualCrnBayStepDurationMs', name: 'Y轴每步耗时', desc: '双工位堆垛机 bay 方向单步移动耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeDualCrnLevelStepDurationMs', name: 'Z轴每步耗时', desc: '双工位堆垛机 level 方向单步移动耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeDualCrnResetDurationMs', name: '复位耗时', desc: '双工位堆垛机复位命令耗时。', unit: 'ms', min: 0, max: 600000 }
            ]
        },
        {
            key: 'rgv',
            title: 'RGV',
            desc: '控制 RGV 导航移动、装卸货与复位节奏。',
            items: [
                { code: 'fakeRgvMoveStepDurationMs', name: '导航点移动耗时', desc: 'RGV 每经过一个导航点的耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeRgvLoadDurationMs', name: '装卸货耗时', desc: 'RGV 取货或放货动作耗时。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeRgvResetDurationMs', name: '复位耗时', desc: 'RGV 复位命令耗时。', unit: 'ms', min: 0, max: 600000 }
            ]
        },
        {
            key: 'station',
            title: '站点节奏',
            desc: '控制站点初始化、移动推进、空闲轮询与堵塞轮询节奏。',
            note: '这些参数只作用于仿真输送站点,不会修改真实系统共享运行逻辑。',
            items: [
                { code: 'fakeStationSegmentWaitTimeoutMs', name: '等待新分段超时', desc: '等待新的路径分段超过该时间后按超时处理。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeStationMoveStepDurationMs', name: '移动推进间隔', desc: '站点每成功推进一步后的等待时间。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeStationIdleLoopDelayMs', name: '空闲轮询间隔', desc: '站点空闲时轮询推进的间隔。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeStationBlockedLoopDelayMs', name: '堵塞轮询间隔', desc: '站点阻塞或重试时的轮询间隔。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeStationInitializeDelayMs', name: '初始化等待', desc: '站点初始化前后的额外等待时间。', unit: 'ms', min: 0, max: 600000 },
                { code: 'fakeStationFinishDelayMs', name: '结束缓冲等待', desc: '站点动作完成后的额外等待时间。', unit: 'ms', min: 0, max: 600000 }
            ]
        },
        {
            key: 'outTask',
            title: '出库站停留策略',
            desc: '控制出库任务在站点停留过久时的自动结束阈值。',
            items: [
                { code: 'fakeOutStationStayTimeoutMs', name: '出库站自动结束时间', desc: '出库任务在站点停留超过该时间后自动 RESET/结束。', unit: 'ms', min: 0, max: 600000 }
            ]
        }
    ]
};
function buildInitialForm() {
    var form = {};
    fakeConfigSchema.basicGroups.concat(fakeConfigSchema.advancedGroups).forEach(function (group) {
        group.items.forEach(function (item) {
            form[item.code] = item.type === 'boolean' ? false : 0;
        });
    });
    return form;
}
var app = new Vue({
    el: '#app',
    data: {
        loading: false,
        saving: false,
        activeTab: 'basic',
        statusInfo: {
            isFake: false,
            running: false
        },
        form: buildInitialForm(),
        basicGroups: fakeConfigSchema.basicGroups,
        advancedGroups: fakeConfigSchema.advancedGroups
    },
    mounted: function () {
        this.reloadData();
    },
    methods: {
        reloadData: function () {
            var that = this;
            this.loading = true;
            $.when(
                $.ajax({
                    url: baseUrl + '/openapi/getFakeSystemRunStatus',
                    method: 'GET'
                }),
                $.ajax({
                    url: baseUrl + '/openapi/fakeConfig/auth',
                    headers: { token: localStorage.getItem('token') },
                    method: 'GET'
                })
            ).done(function (statusRes, configRes) {
                that.loading = false;
                var statusPayload = statusRes && statusRes[0] ? statusRes[0] : {};
                var configPayload = configRes && configRes[0] ? configRes[0] : {};
                var statusData = statusPayload.data || {};
                that.statusInfo.isFake = !!statusData.isFake;
                that.statusInfo.running = !!statusData.running;
                if (configPayload.code === 200) {
                    that.applyConfig(configPayload.data || {});
                } else if (configPayload.code === 403) {
                    top.location.href = baseUrl + '/';
                } else {
                    that.$message.error(configPayload.msg || '加载仿真配置失败');
                }
            }).fail(function () {
                that.loading = false;
                that.$message.error('加载仿真配置失败');
            });
        },
        applyConfig: function (configMap) {
            var next = buildInitialForm();
            this.basicGroups.concat(this.advancedGroups).forEach(function (group) {
                group.items.forEach(function (item) {
                    var rawValue = configMap[item.code];
                    if (item.type === 'boolean') {
                        next[item.code] = String(rawValue || '').toUpperCase() === 'Y';
                    } else {
                        var parsed = parseInt(rawValue, 10);
                        next[item.code] = isNaN(parsed) ? 0 : parsed;
                    }
                });
            });
            this.form = next;
        },
        buildSubmitPayload: function () {
            var payload = {};
            this.basicGroups.concat(this.advancedGroups).forEach(function (group) {
                group.items.forEach(function (item) {
                    var value = this.form[item.code];
                    if (item.type === 'boolean') {
                        payload[item.code] = value ? 'Y' : 'N';
                    } else {
                        var parsed = parseInt(value, 10);
                        if (isNaN(parsed) || parsed < item.min || parsed > item.max) {
                            throw new Error(item.name + ' 超出允许范围');
                        }
                        payload[item.code] = String(parsed);
                    }
                }, this);
            }, this);
            return payload;
        },
        saveConfig: function () {
            if (!this.statusInfo.isFake) {
                this.$message.warning('当前不是仿真模式,不能保存仿真配置');
                return;
            }
            var payload;
            try {
                payload = this.buildSubmitPayload();
            } catch (e) {
                this.$message.error(e.message || '参数校验失败');
                return;
            }
            var that = this;
            this.saving = true;
            $.ajax({
                url: baseUrl + '/openapi/fakeConfig/save/auth',
                headers: { token: localStorage.getItem('token') },
                method: 'POST',
                contentType: 'application/json;charset=UTF-8',
                dataType: 'json',
                data: JSON.stringify({
                    configMap: payload
                }),
                success: function (res) {
                    that.saving = false;
                    if (res.code === 200) {
                        that.$message.success('仿真配置已保存');
                        that.applyConfig(res.data || {});
                    } else if (res.code === 403) {
                        top.location.href = baseUrl + '/';
                    } else {
                        that.$message.error(res.msg || '保存仿真配置失败');
                    }
                },
                error: function () {
                    that.saving = false;
                    that.$message.error('保存仿真配置失败');
                }
            });
        }
    }
});
src/main/webapp/views/watch/fakeOperationConfig.html
New file
@@ -0,0 +1,284 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>仿真操作</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="../../static/vue/element/element.css">
    <link rel="stylesheet" href="../../static/css/cool.css">
    <style>
        html, body {
            height: 100%;
            margin: 0;
            background: linear-gradient(180deg, #eef4fa 0%, #e7edf5 100%);
        }
        body {
            font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
        }
        [v-cloak] {
            display: none;
        }
        #app {
            height: 100%;
            box-sizing: border-box;
            padding: 20px;
            display: flex;
            flex-direction: column;
            gap: 16px;
        }
        .hero {
            padding: 22px 24px;
            border-radius: 20px;
            border: 1px solid rgba(255,255,255,0.82);
            background: linear-gradient(135deg, rgba(255,255,255,0.94), rgba(244,249,255,0.88));
            box-shadow: 0 18px 44px rgba(62, 89, 120, 0.08);
        }
        .hero-title {
            font-size: 26px;
            font-weight: 700;
            color: #213447;
        }
        .hero-desc {
            margin-top: 8px;
            color: #66788c;
            font-size: 13px;
            line-height: 1.8;
        }
        .panel {
            flex: 1;
            min-height: 0;
            display: flex;
            flex-direction: column;
            border-radius: 20px;
            border: 1px solid rgba(222, 231, 241, 0.92);
            background: rgba(251, 253, 255, 0.9);
            box-shadow: 0 18px 42px rgba(57, 84, 114, 0.08);
            overflow: hidden;
        }
        .toolbar {
            padding: 18px 22px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 16px;
            border-bottom: 1px solid rgba(225, 233, 243, 0.95);
            background: rgba(255,255,255,0.74);
        }
        .toolbar-left {
            display: flex;
            align-items: center;
            flex-wrap: wrap;
            gap: 10px;
        }
        .toolbar-tip {
            font-size: 12px;
            color: #73879b;
        }
        .content {
            flex: 1;
            min-height: 0;
            overflow: auto;
            padding: 18px 22px 24px;
        }
        .group-card {
            border: 1px solid rgba(222, 231, 241, 0.95);
            border-radius: 18px;
            background: linear-gradient(180deg, rgba(255,255,255,0.96), rgba(246,250,255,0.92));
            box-shadow: 0 10px 24px rgba(75, 101, 130, 0.06);
            padding: 18px;
            margin-bottom: 16px;
        }
        .group-head {
            display: flex;
            align-items: flex-start;
            justify-content: space-between;
            gap: 12px;
            margin-bottom: 12px;
        }
        .group-title {
            font-size: 16px;
            font-weight: 700;
            color: #25384c;
        }
        .group-desc {
            margin-top: 6px;
            font-size: 12px;
            line-height: 1.8;
            color: #6f8297;
        }
        .config-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 14px;
        }
        .config-item {
            border-radius: 16px;
            border: 1px solid rgba(225, 233, 243, 0.96);
            background: rgba(255,255,255,0.88);
            padding: 14px 14px 12px;
        }
        .config-name {
            font-size: 14px;
            font-weight: 700;
            color: #223548;
        }
        .config-desc {
            margin-top: 6px;
            min-height: 38px;
            font-size: 12px;
            line-height: 1.7;
            color: #71859a;
        }
        .config-control {
            margin-top: 12px;
        }
        .config-meta {
            margin-top: 10px;
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
        }
        .meta-chip {
            padding: 4px 8px;
            border-radius: 999px;
            background: rgba(233, 240, 248, 0.92);
            color: #60758b;
            font-size: 12px;
        }
        .empty-box {
            padding: 56px 16px;
            text-align: center;
            color: #7b8da0;
            font-size: 14px;
        }
        .section-note {
            margin-top: 8px;
            font-size: 12px;
            color: #8a9aab;
        }
        .el-tabs__item {
            font-weight: 600;
        }
        .el-input-number,
        .el-select {
            width: 100%;
        }
        @media (max-width: 900px) {
            #app {
                padding: 14px;
            }
            .toolbar {
                flex-direction: column;
                align-items: stretch;
            }
        }
    </style>
</head>
<body>
<div id="app" v-cloak>
    <div class="hero">
        <div class="hero-title">仿真操作</div>
        <div class="hero-desc">
            仅管理仿真专属参数和仿真节奏参数,不混入真实系统业务配置。保存后直接写入系统配置,并刷新仿真读取缓存。
        </div>
    </div>
    <div class="panel">
        <div class="toolbar">
            <div class="toolbar-left">
                <el-tag size="small" :type="statusInfo.isFake ? 'success' : 'info'">{{ statusInfo.isFake ? '仿真模式' : '非仿真模式' }}</el-tag>
                <el-tag size="small" :type="statusInfo.running ? 'danger' : 'warning'">{{ statusInfo.running ? '运行中' : '未运行' }}</el-tag>
                <span class="toolbar-tip">开关项保存为 Y/N,时间项单位均为毫秒。</span>
            </div>
            <div>
                <el-button size="small" @click="reloadData" :loading="loading">刷新</el-button>
                <el-button type="primary" size="small" @click="saveConfig" :loading="saving" :disabled="!statusInfo.isFake">保存配置</el-button>
            </div>
        </div>
        <div class="content" v-loading="loading">
            <div v-if="!statusInfo.isFake && !loading" class="empty-box">当前不是仿真模式,仿真参数不可编辑。</div>
            <template v-else>
                <el-tabs v-model="activeTab">
                    <el-tab-pane label="基础参数" name="basic">
                        <div class="group-card" v-for="group in basicGroups" :key="group.key">
                            <div class="group-head">
                                <div>
                                    <div class="group-title">{{ group.title }}</div>
                                    <div class="group-desc">{{ group.desc }}</div>
                                </div>
                            </div>
                            <div class="config-grid">
                                <div class="config-item" v-for="item in group.items" :key="item.code">
                                    <div class="config-name">{{ item.name }}</div>
                                    <div class="config-desc">{{ item.desc }}</div>
                                    <div class="config-control">
                                        <el-switch
                                            v-if="item.type === 'boolean'"
                                            v-model="form[item.code]"
                                            active-text="是"
                                            inactive-text="否">
                                        </el-switch>
                                        <el-input-number
                                            v-else
                                            v-model="form[item.code]"
                                            :min="item.min"
                                            :max="item.max"
                                            :step="item.step || 1"
                                            controls-position="right">
                                        </el-input-number>
                                    </div>
                                    <div class="config-meta">
                                        <span class="meta-chip">{{ item.code }}</span>
                                        <span class="meta-chip" v-if="item.unit">单位:{{ item.unit }}</span>
                                        <span class="meta-chip" v-if="item.type !== 'boolean'">范围:{{ item.min }} - {{ item.max }}</span>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </el-tab-pane>
                    <el-tab-pane label="高级参数" name="advanced">
                        <div class="group-card" v-for="group in advancedGroups" :key="group.key">
                            <div class="group-head">
                                <div>
                                    <div class="group-title">{{ group.title }}</div>
                                    <div class="group-desc">{{ group.desc }}</div>
                                </div>
                            </div>
                            <div class="config-grid">
                                <div class="config-item" v-for="item in group.items" :key="item.code">
                                    <div class="config-name">{{ item.name }}</div>
                                    <div class="config-desc">{{ item.desc }}</div>
                                    <div class="config-control">
                                        <el-input-number
                                            v-model="form[item.code]"
                                            :min="item.min"
                                            :max="item.max"
                                            :step="item.step || 1"
                                            controls-position="right">
                                        </el-input-number>
                                    </div>
                                    <div class="config-meta">
                                        <span class="meta-chip">{{ item.code }}</span>
                                        <span class="meta-chip" v-if="item.unit">单位:{{ item.unit }}</span>
                                        <span class="meta-chip">范围:{{ item.min }} - {{ item.max }}</span>
                                    </div>
                                </div>
                            </div>
                            <div class="section-note" v-if="group.note">{{ group.note }}</div>
                        </div>
                    </el-tab-pane>
                </el-tabs>
            </template>
        </div>
    </div>
</div>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/watch/fakeOperationConfig.js" charset="utf-8"></script>
</body>
</html>