#
Junjie
3 天以前 877b2519157cea762b1e63e9c57c09614216d684
#
18个文件已修改
4个文件已添加
1563 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/ShuttleController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/model/NavigateNode.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/model/enums/NavigationMapType.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/utils/NavigateMapData.java 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/utils/NavigateMapUtils.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/utils/NavigatePositionConvert.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/utils/NavigateUtils.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/utils/ShuttleOperaUtils.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/ServerBootstrap.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/action/ShuttleAction.java 584 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/cache/MessageQueue.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/RedisKeyType.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/enums/SlaveType.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/TrafficControlDataModel.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/command/ShuttleAssignCommand.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/command/ShuttleCommand.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/model/protocol/ShuttleProtocol.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/ShuttleThread.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/TrafficControlThread.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/NyShuttleThread.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/thread/impl/TrafficControlImplThread.java 575 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/TrafficControlUtils.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/ShuttleController.java
@@ -35,6 +35,7 @@
import com.zy.core.model.param.ShuttleMoveLocParam;
import com.zy.core.model.protocol.ShuttleProtocol;
import com.zy.core.thread.ShuttleThread;
import com.zy.core.thread.TrafficControlThread;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -346,6 +347,9 @@
            assignCommand.setCommands(commands);
        } else if (shuttleTaskModeType == ShuttleTaskModeType.RESET) {
            //复位
            TrafficControlThread trafficControlThread = (TrafficControlThread) SlaveConnection.get(SlaveType.TrafficControl, 1);
            trafficControlThread.cancelTrafficControl(shuttleProtocol.getShuttleNo(), shuttleProtocol.getTaskNo());
            shuttleThread.setSyncTaskNo(0);//工作号清空
            shuttleThread.setProtocolStatus(ShuttleProtocolStatusType.IDLE);//任务状态-空闲
            shuttleThread.setPakMk(true);//作业标记复位
src/main/java/com/zy/common/model/NavigateNode.java
@@ -22,6 +22,7 @@
    private Integer moveDistance;//行走距离
    private Integer nodeValue;//节点数据
    private Boolean linePartAllowGo = false;//直线段部分,允许直接行走
    private Long linePartFlag;//直线段数据标识
    public NavigateNode(int x, int y) {
        this.x = x;
src/main/java/com/zy/common/model/enums/NavigationMapType.java
@@ -11,6 +11,7 @@
    SHUTTLE(3, "地图携带小车"),
    LIFT(4, "地图携带提升机"),
    PATH_LOCK(5, "路径锁"),
    TRAFFIC_CONTROL(6, "交通管制"),
    ;
    public Integer id;
@@ -46,11 +47,11 @@
    }
    public static List<NavigationMapType> getDfxWithDevice() {
        return getMapTypes(DFX, SHUTTLE, LIFT, PATH_LOCK);
        return getMapTypes(DFX, SHUTTLE, LIFT, PATH_LOCK, TRAFFIC_CONTROL);
    }
    public static List<NavigationMapType> getNormalWithDevice() {
        return getMapTypes(NORMAL, SHUTTLE, LIFT, PATH_LOCK);
        return getMapTypes(NORMAL, SHUTTLE, LIFT, PATH_LOCK, TRAFFIC_CONTROL);
    }
    public static List<NavigationMapType> getMapTypes(NavigationMapType... types) {
src/main/java/com/zy/common/utils/NavigateMapData.java
@@ -3,6 +3,8 @@
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.SpringUtils;
import com.core.exception.CoolException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zy.asrs.entity.BasMap;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.asrs.entity.LocMast;
@@ -11,16 +13,24 @@
import com.zy.asrs.service.LocMastService;
import com.zy.asrs.utils.Utils;
import com.zy.common.model.MapNode;
import com.zy.common.model.NavigateNode;
import com.zy.common.model.enums.NavigationMapType;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.MapNodeType;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.ShuttleCommandModeType;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.TrafficControlDataModel;
import com.zy.core.model.command.ShuttleAssignCommand;
import com.zy.core.model.command.ShuttleCommand;
import com.zy.core.model.command.ShuttleRedisCommand;
import com.zy.core.model.protocol.ForkLiftStaProtocol;
import com.zy.core.thread.ForkLiftThread;
import com.zy.core.thread.TrafficControlThread;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -38,6 +48,8 @@
    private DeviceConfigService deviceConfigService;
    @Autowired
    private BasMapService basMapService;
    @Autowired
    private ObjectMapper objectMapper;
    public int[][] getData(Integer lev, List<NavigationMapType> mapTypes, List<int[]> whitePoints, List<int[]> shuttlePoints) {
        try {
@@ -188,6 +200,11 @@
            if(mapType.equals(NavigationMapType.PATH_LOCK)) {
                //加载路径锁
                lists = loadPathLock(lists, lev);
            }
            if(mapType.equals(NavigationMapType.TRAFFIC_CONTROL)) {
                //加载交通管制
                lists = loadTrafficControl(lists, lev);
            }
        }
@@ -396,4 +413,112 @@
        return lists;
    }
    //加载交通管制
    public List<List<MapNode>> loadTrafficControl(List<List<MapNode>> lists, Integer lev) {
        try {
            TrafficControlThread trafficControlThread = (TrafficControlThread) SlaveConnection.get(SlaveType.TrafficControl, 1);
            if (trafficControlThread == null) {
                throw new CoolException("init traffic control thread error");
            }
            List<TrafficControlDataModel> trafficList = trafficControlThread.getAllTrafficControl();
            for (TrafficControlDataModel trafficControlDataModel : trafficList) {
                List<NavigateNode> totalNodeList = trafficControlDataModel.getTotalNodeList();
                NavigateNode startNode = totalNodeList.get(0);
                if (startNode.getZ() != lev) {
                    continue;
                }
                for (NavigateNode node : totalNodeList) {
                    int row = node.getX();
                    int bay = node.getY();
                    List<MapNode> list = lists.get(row);
                    MapNode mapNode = list.get(bay);
                    //节点设置成Disable
                    mapNode.setValue(MapNodeType.DISABLE.id);
                    //更新list
                    list.set(bay, mapNode);
                    lists.set(row, list);
                }
            }
//            HashMap<String, Integer> deviceMap = trafficControlThread.getDeviceMap();
//            if(deviceMap == null) {
//                throw new CoolException("get traffic control device map error");
//            }
//
//            for (Map.Entry<String, Integer> entry : deviceMap.entrySet()) {
//                String key = entry.getKey();
//                Integer shuttleNo = entry.getValue();
//                if (shuttleNo <= 0) {
//                    continue;
//                }
//
//                Object object = redisUtil.get(RedisKeyType.SHUTTLE_FLAG.key + shuttleNo);
//                if (object == null) {
//                    continue;
//                }
//
//                int taskNo = Integer.parseInt(String.valueOf(object));
//
//                Object obj = redisUtil.get(RedisKeyType.SHUTTLE_WORK_FLAG.key + taskNo);
//                if (obj == null) {
//                    continue;
//                }
//
//                ShuttleRedisCommand redisCommand = null;
//                try {
//                    redisCommand = objectMapper.readValue(String.valueOf(obj), ShuttleRedisCommand.class);
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }
//
//                if (redisCommand == null) {
//                    continue;
//                }
//
//                ShuttleAssignCommand assignCommand = redisCommand.getAssignCommand();
//                List<ShuttleCommand> commands = assignCommand.getCommands();
//
//                List<NavigateNode> controlLocList = new ArrayList<>();
//                for (ShuttleCommand command : commands) {
//                    if (command.getComplete()) {
//                        continue;
//                    }
//
//                    if (command.getMode() == ShuttleCommandModeType.MOVE.id) {
//                        List<NavigateNode> nodes = command.getNodes();
//                        for (NavigateNode node : nodes) {
//                            if(!controlLocList.contains(node)) {
//                                controlLocList.add(node);
//                            }
//                        }
//                    }
//                }
//
//                for (NavigateNode node : controlLocList) {
//                    int row = node.getX();
//                    int bay = node.getY();
//
//                    List<MapNode> list = lists.get(row);
//                    MapNode mapNode = list.get(bay);
//
//                    //节点设置成Disable
//                    mapNode.setValue(MapNodeType.DISABLE.id);
//
//                    //更新list
//                    list.set(bay, mapNode);
//                    lists.set(row, list);
//                }
//
//            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return lists;
    }
}
src/main/java/com/zy/common/utils/NavigateMapUtils.java
@@ -58,7 +58,10 @@
            //判断节点是否被锁定
            if(lockMap.containsKey(key)) {
                return false;
                Integer deviceNo = (Integer) lockMap.get(key);
                if (!shuttleNo.equals(deviceNo)) {//等待锁定的节点已被当前小车锁定,认定锁定成功
                    return false;
                }
            }
            tmpMap.put(key, shuttleNo);
src/main/java/com/zy/common/utils/NavigatePositionConvert.java
@@ -100,4 +100,10 @@
        return new int[]{col, row};
    }
    public static boolean equalsNode(NavigateNode node1, NavigateNode node2) {
        if(node1.getX() ==  node2.getX() && node1.getY() == node2.getY() && node1.getZ() == node2.getZ()) {
            return true;
        }
        return false;
    }
}
src/main/java/com/zy/common/utils/NavigateUtils.java
@@ -2,6 +2,8 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.SnowflakeIdWorker;
import com.core.exception.CoolException;
import com.zy.asrs.utils.Utils;
import com.zy.common.model.MapNode;
@@ -10,6 +12,8 @@
import com.zy.core.News;
import com.zy.core.enums.MapNodeType;
import com.zy.core.model.PythonSimilarityResult;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -30,6 +34,10 @@
    private String pythonCalcSimilarity;
    @Autowired
    private NavigateMapData navigateMapData;
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
    @Autowired
    private ConfigService configService;
    public List<NavigateNode> calc(String startPoint, String endPoint, List<NavigationMapType> mapTypes, List<int[]> shuttlePoints, List<int[]> whites) {
        return calcJava(startPoint, endPoint, mapTypes, shuttlePoints, whites);
@@ -315,15 +323,23 @@
        //将最后一段数据添加进入
        list.add(data);
        //分段路径-处理超长直线段路径
        List<List<NavigateNode>> paths = getSectionPathToSplitOverLength(list);
        return paths;
    }
    //分段路径-处理超长直线段路径
    public List<List<NavigateNode>> getSectionPathToSplitOverLength(List<List<NavigateNode>> list) {
        int overLength = 9;//默认9节
        Config shuttleMoveOverLengthConfig = configService.selectOne(new EntityWrapper<Config>().eq("code", "shuttleMoveOverLength"));
        if (shuttleMoveOverLengthConfig != null) {
            overLength = Integer.parseInt(shuttleMoveOverLengthConfig.getValue());
        }
        List<List<NavigateNode>> paths = new ArrayList<>();
        int overLength = 9;
        for (List<NavigateNode> nodes : list) {
            long nextId = snowflakeIdWorker.nextId();
            if (nodes.size() > overLength) {
                List<NavigateNode> copy = JSON.parseArray(JSON.toJSONString(nodes), NavigateNode.class);
                List<NavigateNode> tmp = new ArrayList<>();
@@ -337,10 +353,12 @@
                        if (lastNode == null) {
                            NavigateNode startNode = tmp.get(0);
                            startNode.setLinePartAllowGo(true);//直线段超长部分允许直接行走
                            startNode.setLinePartFlag(nextId);//直线段数据标识
                            tmp.set(0, startNode);
                        }
                        NavigateNode targetNode = tmp.get(tmp.size() - 1);
                        targetNode.setLinePartAllowGo(true);//直线段超长部分允许直接行走
                        targetNode.setLinePartFlag(nextId);//直线段数据标识
                        if (lastNode != null) {
                            tmp.add(0, lastNode);
                        }
@@ -357,6 +375,10 @@
                    paths.add(tmp);
                }
            }else {
                NavigateNode startNode = nodes.get(0);
                startNode.setLinePartAllowGo(true);//直线段超长部分允许直接行走
                startNode.setLinePartFlag(nextId);//直线段数据标识
                nodes.set(0, startNode);
                paths.add(nodes);
            }
        }
src/main/java/com/zy/common/utils/ShuttleOperaUtils.java
@@ -116,12 +116,15 @@
        assignCommand.setNodes(allNode);//当前任务所占用的节点list
        boolean result = navigateMapUtils.writeNavigateNodeToRedisMap(Utils.getLev(startLocNo), shuttleNo, lockNode, true);//锁定路径
        if (!result) {
            News.error("{} dash {} can't lock path!", startLocNo, endLocNo);
            shuttleThread.offerSystemMsg("{} dash {} can't lock path!", startLocNo, endLocNo);
            return null;//路径锁定失败
        //小车移动连续下发指令
        boolean shuttleMoveCommandsContinuously = false;
        Config shuttleMoveCommandsContinuouslyConfig = configService.selectOne(new EntityWrapper<Config>().eq("code", "shuttleMoveCommandsContinuously"));
        if (shuttleMoveCommandsContinuouslyConfig != null) {
            if (shuttleMoveCommandsContinuouslyConfig.getValue().equals("Y")) {
                shuttleMoveCommandsContinuously = true;
            }
        }
        assignCommand.setShuttleMoveCommandsContinuously(shuttleMoveCommandsContinuously);
        return commands;
    }
src/main/java/com/zy/core/ServerBootstrap.java
@@ -11,7 +11,9 @@
import com.zy.core.cache.MessageQueue;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.SlaveType;
import com.zy.core.thread.TrafficControlThread;
import com.zy.core.thread.impl.NyShuttleThread;
import com.zy.core.thread.impl.TrafficControlImplThread;
import com.zy.core.thread.impl.ZyForkLiftThread;
import com.zy.core.utils.DeviceMsgUtils;
import lombok.extern.slf4j.Slf4j;
@@ -61,14 +63,16 @@
        List<DeviceConfig> forkLiftList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>()
                .eq("device_type", String.valueOf(SlaveType.ForkLift)));
        for (DeviceConfig forkLift : forkLiftList) {
            MessageQueue.init(SlaveType.ForkLift, forkLift);
            MessageQueue.init(SlaveType.ForkLift, forkLift.getDeviceNo());
        }
        // 初始化四向穿梭车mq
        List<DeviceConfig> shuttleList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>()
                .eq("device_type", String.valueOf(SlaveType.Shuttle)));
        for (DeviceConfig shuttle : shuttleList) {
            MessageQueue.init(SlaveType.Shuttle, shuttle);
            MessageQueue.init(SlaveType.Shuttle, shuttle.getDeviceNo());
        }
        MessageQueue.init(SlaveType.TrafficControl, 1);
    }
    private void initThread(){
@@ -128,6 +132,10 @@
        //设备初始化完毕
        deviceMsgUtils.sendDeviceConfig(JSON.toJSONString(allDevices));
        deviceMsgUtils.sendFakeDeviceConfig(JSON.toJSONString(fakeDevices));
        TrafficControlThread trafficControlThread = new TrafficControlImplThread(redisUtil);
        new Thread(trafficControlThread).start();
        SlaveConnection.put(SlaveType.TrafficControl, 1, trafficControlThread);
    }
src/main/java/com/zy/core/action/ShuttleAction.java
@@ -13,31 +13,30 @@
import com.zy.asrs.service.LocMastService;
import com.zy.asrs.service.WrkMastService;
import com.zy.asrs.utils.Utils;
import com.zy.common.ExecuteSupport;
import com.zy.common.model.NavigateNode;
import com.zy.common.service.CommonService;
import com.zy.common.utils.NavigateMapUtils;
import com.zy.common.utils.NavigatePositionConvert;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.dispatcher.ShuttleDispatchUtils;
import com.zy.core.enums.*;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.TrafficControlDataModel;
import com.zy.core.model.command.ShuttleAssignCommand;
import com.zy.core.model.command.ShuttleCommand;
import com.zy.core.model.command.ShuttleRedisCommand;
import com.zy.core.model.protocol.ShuttleProtocol;
import com.zy.core.thread.ShuttleThread;
import com.zy.core.thread.TrafficControlThread;
import com.zy.system.entity.Config;
import com.zy.system.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.*;
@Component
public class ShuttleAction {
@@ -65,6 +64,17 @@
        ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, shuttleNo);
        if (shuttleThread == null) {
            return false;
        }
        TrafficControlThread trafficControlThread = (TrafficControlThread) SlaveConnection.get(SlaveType.TrafficControl, 1);
        if(trafficControlThread == null){
            return false;
        }
        //清空下发索引
        Set<String> keys = redisUtil.searchKeys(RedisKeyType.SHUTTLE_SEND_COMMAND_INDEX.key + shuttleNo);
        for (String key : keys) {
            redisUtil.del(key);
        }
        ShuttleRedisCommand redisCommand = new ShuttleRedisCommand();
@@ -113,139 +123,69 @@
            return false;
        }
        List<ShuttleCommand> commands = redisCommand.getAssignCommand().getCommands();
        if (commands.isEmpty()) {
        //检测命令
        int checked = checkCommand(redisCommand, shuttleNo);
        if (checked == 0) {
            return false;
        }
        ShuttleAssignCommand assignCommand = redisCommand.getAssignCommand();
        int commandStep = redisCommand.getCommandStep();
        Integer mode = -1;
        if(commandStep < commands.size()) {
            //取出命令
            ShuttleCommand currentCommand = commands.get(commandStep);
            mode = currentCommand.getMode();
        }
        //判断设备是否空闲
        Integer finalMode = mode;
        if (!shuttleThread.isDeviceIdle(new ExecuteSupport() {
            @Override
            public Boolean judgement() {
                if (ShuttleCommandModeType.CHARGE_CLOSE.id.equals(finalMode)) {//关闭充电
                    return false;//不需要判断状态
                }
                return true;//需要判断状态
            }
        })) {
        List<ShuttleCommand> commands = assignCommand.getCommands();
        if (commands.isEmpty()) {
            return false;
        }
        // 完结上一条命令
        boolean updateCommand = false;
        if (commandStep != 0) {
            ShuttleCommand command = commands.get(commandStep - 1);
            if (command.getMode() == ShuttleCommandModeType.MOVE.id) {
                // 正常移动
                if (command.getTargetLocNo().equals(shuttleProtocol.getCurrentLocNo())) {
                    command.setComplete(true);
                    updateCommand = true;
                    //解锁锁定路径,上一条路径
                    List<NavigateNode> nodes = null;
                    try {
                        String nodesStr = objectMapper.writeValueAsString(command.getNodes());
                        nodes = objectMapper.readValue(nodesStr, new TypeReference<List<NavigateNode>>() {
                        });
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    if (nodes != null) {
                        NavigateNode targetNode = assignCommand.getNodes().get(assignCommand.getNodes().size() - 1);//最终节点
                        NavigateNode node = nodes.get(nodes.size() - 1);
                        if (!(targetNode.getX() == node.getX() && targetNode.getY() == node.getY())) {
                            nodes.remove(nodes.size() - 1);//剔除尾节点
                        }
                        boolean result = navigateMapUtils.writeNavigateNodeToRedisMap(Utils.getLev(shuttleProtocol.getCurrentLocNo()), shuttleProtocol.getShuttleNo(), nodes, false);//解锁路径
                        if (!result) {
                            return false;//解锁失败
                        }
                    }
                }
            } else if (command.getMode() == ShuttleCommandModeType.PALLET_LIFT.id) {
                // 托盘顶升
                //判断是否顶升到位
                if (shuttleProtocol.getHasLift()) {
                    command.setComplete(true);
                    updateCommand = true;
//                    //判断是否有物
//                    if (shuttleProtocol.getHasPallet()) {
//                        command.setComplete(true);
//                    }
                }
            } else if (command.getMode() == ShuttleCommandModeType.PALLET_DOWN.id) {
                // 托盘下降命令
                // 判断是否下降到位
                if (!shuttleProtocol.getHasLift()) {
                    command.setComplete(true);
                    updateCommand = true;
                }
            } else if (command.getMode() == ShuttleCommandModeType.CHARGE_OPEN.id) {
                // 充电开
                //判断小车充电状态
                if (shuttleProtocol.getHasCharge()) {
                    command.setComplete(true);
                    updateCommand = true;
                }
            }else {
                command.setComplete(true);//其他命令默认认为完成
                updateCommand = true;
            }
            if(updateCommand) {
                // 更新redis数据
                redisUtil.set(RedisKeyType.SHUTTLE_WORK_FLAG.key + redisCommand.getWrkNo(), JSON.toJSONString(redisCommand, SerializerFeature.DisableCircularReferenceDetect));
            }
            if (!command.getComplete()) {
                return false;
            }
            //判断是否为最后一条命令且命令执行完成,抛出等待确认状态
            ShuttleCommand endCommand = commands.get(commands.size() - 1);
            if (endCommand.getComplete()) {
                News.info("四向穿梭车任务执行下发完成等待执行结束,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(commands));
                // 系统任务
                if (assignCommand.getAuto()) {
                    if (!assignCommand.getCharge()) {
                        //对主线程抛出等待确认状态waiting
                        shuttleThread.setProtocolStatus(ShuttleProtocolStatusType.WAITING);
                    }else {
                        shuttleThread.setProtocolStatus(ShuttleProtocolStatusType.CHARGING_WAITING);
                    }
                    News.info("四向穿梭车任务执行下发完成等待执行结束,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(command));
                    // 手动任务
                } else {
                    //手动模式不抛出等待状态,直接复位空闲状态
                    shuttleThread.setProtocolStatus(ShuttleProtocolStatusType.IDLE);
                    //任务号清零
                    shuttleThread.setSyncTaskNo(0);
                    //标记复位
                    shuttleThread.setPakMk(true);
                    News.info("四向穿梭车手动任务执行完成,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(command));
                }
                //删除redis
                redisUtil.del(RedisKeyType.SHUTTLE_WORK_FLAG.key + redisCommand.getWrkNo());
                return false;//禁止再下发命令
            }
        int commandStep = redisCommand.getCommandStep();
        if(commandStep >= commands.size()){
            return false;
        }
        //取出命令
        ShuttleCommand command = commands.get(commandStep);
        ShuttleCommand command = null;
        if (checked == 1) {
            //非连续指令,需要检测上一条指令是否完成
            if (commandStep > 0) {
                ShuttleCommand lastCommand = commands.get(commandStep - 1);
                if (!lastCommand.getComplete()) {
                    return false;//指令未完成
                }
            }
            command = commands.get(commandStep);
        } else if (checked == 2) {
            //连续指令直接取数据
            command = commands.get(commandStep);
        }
        if(command == null){
            return false;
        }
        //移动命令,锁定路径
        if (command.getMode() == ShuttleCommandModeType.MOVE.id) {
            List<NavigateNode> nodes = JSON.parseArray(JSON.toJSONString(command.getNodes()), NavigateNode.class);
            //申请管制
            boolean apply = applyTrafficControl(commands, nodes, shuttleNo, taskNo);
            if(!apply){
                return false;//申请失败
            }
//            //检测路径是否冲突
//            int conflict = searchShuttlePathConflict(nodes, shuttleNo);
//            if(conflict == 2){
//                return false;//检测后有冲突
//            }
            if (checked == 2) {
                nodes.remove(0);
            }
            boolean result = navigateMapUtils.writeNavigateNodeToRedisMap(Utils.getLev(command.getTargetLocNo()), shuttleNo, nodes, true);//锁定路径
            if (!result) {
                News.error("{} device can't lock path!", shuttleNo);
                shuttleThread.offerSystemMsg("{} device can't lock path!", shuttleNo);
                return false;//路径锁定失败
            }
        }
        // 下发命令
        CommandResponse response = write(command, shuttleNo);
@@ -273,14 +213,400 @@
        }
        shuttleThread.setProtocolStatus(ShuttleProtocolStatusType.WORKING);
        commandStep++;
        //存储下发指令索引
        redisUtil.set(RedisKeyType.SHUTTLE_SEND_COMMAND_INDEX.key + shuttleNo + "_" + taskNo + "_" + commandStep, commandStep);
        commandStep += 1;
        //更新redis数据
        redisCommand.setCommandStep(commandStep);
        //任务数据保存到redis
        redisUtil.set(RedisKeyType.SHUTTLE_WORK_FLAG.key + redisCommand.getWrkNo(), JSON.toJSONString(redisCommand));
        return true;
    }
    /**
     * 检测命令
     * 0:未通过 1:通过 2:通过且可提前下发指令
     */
    private int checkCommand(ShuttleRedisCommand redisCommand, Integer shuttleNo) {
        TrafficControlThread trafficControlThread = (TrafficControlThread) SlaveConnection.get(SlaveType.TrafficControl, 1);
        if(trafficControlThread == null){
            return 0;
        }
        ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, shuttleNo);
        if (shuttleThread == null) {
            return 0;
        }
        ShuttleProtocol shuttleProtocol = shuttleThread.getStatus();
        if (shuttleProtocol == null) {
            return 0;
        }
        int commandStep = redisCommand.getCommandStep();
        if (commandStep == 0) {
            return 1;//第一条命令无需检测
        }
        ShuttleAssignCommand assignCommand = redisCommand.getAssignCommand();
        List<ShuttleCommand> commands = assignCommand.getCommands();
        if (commands.isEmpty()) {
            return 0;
        }
        //上一条指令
        String searchKey = RedisKeyType.SHUTTLE_SEND_COMMAND_INDEX.key + shuttleNo + "_" + redisCommand.getWrkNo() + "_";
        Set<String> keys = redisUtil.searchKeys(searchKey);
        TreeSet<Integer> treeSet = new TreeSet<>();
        for (String key : keys) {
            String[] split = key.split(searchKey);
            treeSet.add(Integer.parseInt(split[1]));
        }
        if (treeSet.isEmpty()) {
            return 1;
        }
        String firstKey = searchKey + treeSet.first();
        Integer lastCommandIdx = (Integer) redisUtil.get(firstKey);
        ShuttleCommand lastCommand = commands.get(lastCommandIdx);
        if (!lastCommand.getComplete()) {
            //检测更新命令完成
            boolean checked = updateCommandComplete(lastCommandIdx, commands, shuttleNo);
            if (checked) {
                //删除索引
                redisUtil.del(firstKey);
                // 更新redis数据
                redisUtil.set(RedisKeyType.SHUTTLE_WORK_FLAG.key + redisCommand.getWrkNo(), JSON.toJSONString(redisCommand, SerializerFeature.DisableCircularReferenceDetect));
            }else {
                //小车移动连续下发指令
                if (assignCommand.getShuttleMoveCommandsContinuously()) {
                    if (treeSet.size() <= 1) {
                        if(commandStep >= commands.size()) {
                            return 0;
                        }
                        //移动指令
                        if(lastCommand.getMode() != ShuttleCommandModeType.MOVE.id) {
                            return 0;
                        }
                        List<NavigateNode> nodes = lastCommand.getNodes();
                        NavigateNode startNode = nodes.get(0);
                        if (!startNode.getLinePartAllowGo()) {//直线段部分,允许直接行走
                            return 0;
                        }
                        //直线段数据标识
                        Long linePartFlag = startNode.getLinePartFlag();
                        //取指令
                        ShuttleCommand currentCommand = commands.get(commandStep);
                        if(currentCommand.getMode() != ShuttleCommandModeType.MOVE.id) {
                            return 0;
                        }
                        List<NavigateNode> currentNodes = currentCommand.getNodes();
                        NavigateNode currentStartNode = currentNodes.get(0);
                        if(!currentStartNode.getLinePartAllowGo()) {//直线段部分,允许直接行走
                            return 0;
                        }
                        if(currentStartNode.getLinePartFlag().equals(linePartFlag)) {
                            //数据标识一致
                            return 2;//允许小车移动连续下发指令
                        }
                    }
                }
                return 0;
            }
        }
        //判断是否为最后一条命令且命令执行完成,抛出等待确认状态
        ShuttleCommand endCommand = commands.get(commands.size() - 1);
        if (endCommand.getComplete()) {
            News.info("四向穿梭车任务执行下发完成等待执行结束,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(commands));
            // 系统任务
            if (assignCommand.getAuto()) {
                if (!assignCommand.getCharge()) {
                    //对主线程抛出等待确认状态waiting
                    shuttleThread.setProtocolStatus(ShuttleProtocolStatusType.WAITING);
                } else {
                    shuttleThread.setProtocolStatus(ShuttleProtocolStatusType.CHARGING_WAITING);
                }
                News.info("四向穿梭车任务执行下发完成等待执行结束,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(endCommand));
                // 手动任务
            } else {
                //手动模式不抛出等待状态,直接复位空闲状态
                shuttleThread.setProtocolStatus(ShuttleProtocolStatusType.IDLE);
                //任务号清零
                shuttleThread.setSyncTaskNo(0);
                //标记复位
                shuttleThread.setPakMk(true);
                News.info("四向穿梭车手动任务执行完成,穿梭车号={},任务数据={}", shuttleProtocol.getShuttleNo(), JSON.toJSON(endCommand));
            }
            //申请取消管制
            trafficControlThread.cancelTrafficControl(shuttleNo, shuttleProtocol.getTaskNo());
            //删除redis
            redisUtil.del(RedisKeyType.SHUTTLE_WORK_FLAG.key + redisCommand.getWrkNo());
            return 0;//禁止再下发命令
        }
        return 1;
    }
    //检测更新命令完成
    private boolean updateCommandComplete(Integer commandIdx, List<ShuttleCommand> commands, Integer shuttleNo) {
        ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, shuttleNo);
        if (shuttleThread == null) {
            return false;
        }
        ShuttleProtocol shuttleProtocol = shuttleThread.getStatus();
        if (shuttleProtocol == null) {
            return false;
        }
        //判断设备是否空闲
        boolean deviceIdle = shuttleThread.isDeviceIdle();
        ShuttleCommand command = commands.get(commandIdx);
        if (command.getMode() == ShuttleCommandModeType.MOVE.id) {
            // 正常移动
            List<String> targetPoints = new ArrayList<>();
            //解锁锁定路径,上一条路径
            List<NavigateNode> nodes = null;
            try {
                String nodesStr = objectMapper.writeValueAsString(command.getNodes());
                nodes = objectMapper.readValue(nodesStr, new TypeReference<List<NavigateNode>>() {
                });
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            NavigateNode startNode = nodes.get(0);
            Long linePartFlag = startNode.getLinePartFlag();
            NavigateNode targetNode = nodes.get(nodes.size() - 1);
            targetPoints.add(targetNode.getX() + "-" + targetNode.getY());
            for (int i = commandIdx + 1; i < commands.size(); i++) {
                ShuttleCommand nextCommand = commands.get(i);
                if (nextCommand.getMode() == ShuttleCommandModeType.MOVE.id) {
                    List<NavigateNode> nextCommandNodes = nextCommand.getNodes();
                    NavigateNode nextStartNode = nextCommandNodes.get(0);
                    Long nextLinePartFlag = nextStartNode.getLinePartFlag();
                    if(nextLinePartFlag.equals(linePartFlag)) {
                        for (NavigateNode node : nextCommandNodes) {
                            String key = node.getX() + "-" + node.getY();
                            if(!targetPoints.contains(key)) {
                                targetPoints.add(key);
                            }
                        }
                    }
                }
            }
            TrafficControlThread trafficControlThread = (TrafficControlThread) SlaveConnection.get(SlaveType.TrafficControl, 1);
            if (trafficControlThread == null) {
                return false;
            }
            TrafficControlDataModel trafficControlDataModel = trafficControlThread.queryTrafficControl(shuttleNo);
            if (trafficControlDataModel != null) {
                //有管制信息,进行检测
                if (!trafficControlDataModel.getTaskNo().equals(shuttleProtocol.getTaskNo())) {
                    return false;//任务不一致
                }
                //检测是否到终点
                List<NavigateNode> totalNodeList = trafficControlDataModel.getTotalNodeList();
                NavigateNode trafficTargetNode = totalNodeList.get(totalNodeList.size() - 1);
                String trafficTargetLoc = Utils.getLocNo(trafficTargetNode.getX(), trafficTargetNode.getY(), trafficTargetNode.getZ());
                //判断小车是否到终点
                if(shuttleProtocol.getCurrentLocNo().equals(trafficTargetLoc)) {
                    //上报交管
                    trafficControlThread.trafficReport(command.getNodesDeepCopy(), shuttleNo, shuttleProtocol.getTaskNo());
                }
            }
            String currentLocNo = shuttleProtocol.getCurrentLocNo();
            if (targetPoints.contains(Utils.getRow(currentLocNo) + "-" + Utils.getBay(currentLocNo))) {
                command.setComplete(true);
                boolean result = navigateMapUtils.writeNavigateNodeToRedisMap(Utils.getLev(shuttleProtocol.getCurrentLocNo()), shuttleProtocol.getShuttleNo(), nodes, false);//解锁路径
                if (!result) {
                    return false;//解锁失败
                }
            }else {
                return false;
            }
        } else if (command.getMode() == ShuttleCommandModeType.PALLET_LIFT.id) {
            // 托盘顶升
            if (!deviceIdle) {
                return false;//设备不空闲
            }
            //判断是否顶升到位
            if (shuttleProtocol.getHasLift()) {
                command.setComplete(true);
//                    //判断是否有物
//                    if (shuttleProtocol.getHasPallet()) {
//                        command.setComplete(true);
//                    }
            }else {
                return false;
            }
        } else if (command.getMode() == ShuttleCommandModeType.PALLET_DOWN.id) {
            // 托盘下降命令
            if (!deviceIdle) {
                return false;//设备不空闲
            }
            // 判断是否下降到位
            if (!shuttleProtocol.getHasLift()) {
                command.setComplete(true);
            }else {
                return false;
            }
        } else if (command.getMode() == ShuttleCommandModeType.CHARGE_OPEN.id) {
            // 充电开
            //判断小车充电状态
            if (shuttleProtocol.getHasCharge()) {
                command.setComplete(true);
            }else {
                return false;
            }
        } else if (command.getMode() == ShuttleCommandModeType.CHARGE_CLOSE.id) {
            //关闭充电
            command.setComplete(true);
        } else {
            command.setComplete(true);//其他命令默认认为完成
        }
        return true;
    }
    //申请管制
    public boolean applyTrafficControl(List<ShuttleCommand> commands, List<NavigateNode> nodeList, Integer shuttleNo, Integer taskNo) {
        TrafficControlThread trafficControlThread = (TrafficControlThread) SlaveConnection.get(SlaveType.TrafficControl, 1);
        if (trafficControlThread == null) {
            return false;
        }
        NavigateNode startNode = nodeList.get(0);
        Long linePartFlag = startNode.getLinePartFlag();
        List<NavigateNode> totalNodeList = new ArrayList<>();
        for (ShuttleCommand command : commands) {
            if (command.getMode() == ShuttleCommandModeType.MOVE.id) {
                NavigateNode node = command.getNodes().get(0);
                Long nodeLinePartFlag = node.getLinePartFlag();
                if (nodeLinePartFlag.equals(linePartFlag)) {
                    List<NavigateNode> deepCopy = command.getNodesDeepCopy();
                    if (deepCopy != null) {
                        totalNodeList.addAll(deepCopy);
                    }
                }
            }
        }
        return trafficControlThread.applyTrafficControl(totalNodeList, nodeList, shuttleNo, taskNo);
    }
//    /**
//     * 搜索小车路径是否存在冲突
//     * 0:未检测 1:检测无冲突 2:检测有冲突
//     */
//    public int searchShuttlePathConflict(List<NavigateNode> nodeList, Integer shuttleNo) {
//        ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, shuttleNo);
//        if (shuttleThread == null) {
//            return 0;
//        }
//
//        ShuttleProtocol shuttleProtocol = shuttleThread.getStatus();
//        if (shuttleProtocol == null) {
//            return 0;
//        }
//
//        int lev = Utils.getLev(shuttleProtocol.getCurrentLocNo());
//
//        TrafficControlThread trafficControlThread = (TrafficControlThread) SlaveConnection.get(SlaveType.TrafficControl, 1);
//        if (trafficControlThread == null) {
//            return 2;
//        }
//        HashMap<String, List<Integer>> nodeMap = trafficControlThread.getNodesMapByLev(lev);
//        if (nodeMap == null || nodeMap.isEmpty()) {
//            return 2;
//        }
//
//        List<String> conflictLocList = new ArrayList<>();
//        for (NavigateNode node : nodeList) {
//            String locNo = Utils.getLocNo(node.getX(), node.getY(), lev);
//            if(!nodeMap.containsKey(locNo)) {
//                return 2;
//            }
//
//            List<Integer> shuttleNos = nodeMap.get(locNo);
//            if (shuttleNos.size() > 1) {
//                //路径存在多车,冲突
//                conflictLocList.add(locNo);
//            }
//        }
//
//        if (conflictLocList.isEmpty()) {
//            //无冲突,解除交通管制
//            shuttleThread.setTrafficControl(false, null);
//            return 1;//检测后无冲突
//        }
//
//        //路径存在冲突,检测可执行车辆是否为当前小车
//        //上报小车状态-交通管制中
//        shuttleThread.setTrafficControl(true, nodeList);
//
//        HashMap<String, Integer> deviceMap = trafficControlThread.getDeviceMap();
//        if(deviceMap == null) {
//            return 2;
//        }
//
//        boolean detected = false;
////            for (Map.Entry<String, Integer> entry : deviceMap.entrySet()) {
////                List<String> mainList = JSON.parseArray(entry.getKey(), String.class);
////                Integer device = entry.getValue();
////                if(result) {
////                    //判断管制车辆是否匹配
////                    if(shuttleNo.equals(device)) {
////                        detected = true;
////                        break;
////                    }
////                }
////            }
//
//        for (Map.Entry<String, Integer> entry : deviceMap.entrySet()) {
//            String key = entry.getKey();
//            Integer value = entry.getValue();
//            if(shuttleNo.equals(value)) {
//                //判断管制车辆是否匹配
//                detected = true;
//                break;
//            }
//        }
//
//        if (detected) {
//            return 1;//检测后无冲突,交通管制已允许此小车运行
//        }
//
//        return 2;//检测后有冲突
//    }
    //演示模式
    public synchronized void demo(Integer shuttleNo) {
        ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, shuttleNo);
src/main/java/com/zy/core/cache/MessageQueue.java
@@ -1,6 +1,5 @@
package com.zy.core.cache;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.Task;
@@ -22,23 +21,28 @@
    private static final Map<Integer, ConcurrentLinkedQueue<Task>> FORK_LIFT_EXCHANGE = new ConcurrentHashMap<>();
    //货叉提升机Master mq交换机
    private static final Map<Integer, ConcurrentLinkedQueue<Task>> FORK_LIFT_MASTER_EXCHANGE = new ConcurrentHashMap<>();
    //Traffic Control mq交换机
    private static final Map<Integer, ConcurrentLinkedQueue<Task>> TRAFFIC_CONTROL_EXCHANGE = new ConcurrentHashMap<>();
    /**
     * mq 交换机初始化
     */
    public static void init(SlaveType type, DeviceConfig deviceConfig) {
    public static void init(SlaveType type, Integer id) {
        switch (type) {
            case Shuttle:
                SHUTTLE_EXCHANGE.put(deviceConfig.getDeviceNo(), new ConcurrentLinkedQueue<>());
                SHUTTLE_EXCHANGE.put(id, new ConcurrentLinkedQueue<>());
                break;
            case ForkLift:
                FORK_LIFT_EXCHANGE.put(deviceConfig.getDeviceNo(), new ConcurrentLinkedQueue<>());
                FORK_LIFT_EXCHANGE.put(id, new ConcurrentLinkedQueue<>());
                break;
            case ForkLiftMaster:
                FORK_LIFT_MASTER_EXCHANGE.put(deviceConfig.getDeviceNo(), new ConcurrentLinkedQueue<>());
                FORK_LIFT_MASTER_EXCHANGE.put(id, new ConcurrentLinkedQueue<>());
                break;
            case Lift:
                LIFT_EXCHANGE.put(deviceConfig.getDeviceNo(), new ConcurrentLinkedQueue<>());
                LIFT_EXCHANGE.put(id, new ConcurrentLinkedQueue<>());
                break;
            case TrafficControl:
                TRAFFIC_CONTROL_EXCHANGE.put(id, new ConcurrentLinkedQueue<>());
                break;
            default:
                break;
@@ -59,6 +63,8 @@
                return FORK_LIFT_MASTER_EXCHANGE.get(id).offer(task);
            case Lift:
                return LIFT_EXCHANGE.get(id).offer(task);
            case TrafficControl:
                return TRAFFIC_CONTROL_EXCHANGE.get(id).offer(task);
            default:
                return false;
        }
@@ -78,6 +84,8 @@
                return FORK_LIFT_MASTER_EXCHANGE.get(id).poll();
            case Lift:
                return LIFT_EXCHANGE.get(id).poll();
            case TrafficControl:
                return TRAFFIC_CONTROL_EXCHANGE.get(id).poll();
            default:
                return null;
        }
@@ -96,6 +104,8 @@
                return FORK_LIFT_MASTER_EXCHANGE.get(id).peek();
            case Lift:
                return LIFT_EXCHANGE.get(id).peek();
            case TrafficControl:
                return TRAFFIC_CONTROL_EXCHANGE.get(id).peek();
            default:
                return null;
        }
@@ -115,6 +125,9 @@
            case Lift:
                LIFT_EXCHANGE.get(id).clear();
                break;
            case TrafficControl:
                TRAFFIC_CONTROL_EXCHANGE.get(id).clear();
                break;
            default:
                break;
        }
src/main/java/com/zy/core/enums/RedisKeyType.java
@@ -14,6 +14,12 @@
    FORK_LIFT_PUT_COMPLETE("fork_lift_put_complete_"),
    OUT_TASK_PREVIEW_DISPATCH_FORKLIFT("out_task_preview_dispatch_forklift_"),
    //小车下发指令索引
    SHUTTLE_SEND_COMMAND_INDEX("shuttle_send_command_index_"),
    //任务堵塞可用设备地图
    TASK_BLOCK_ENABLE_DEVICE_MAP("task_block_enable_device_map"),
    //地图锁定节点
    LOCK_MAP_NODES("lock_map_nodes_"),
src/main/java/com/zy/core/enums/SlaveType.java
@@ -6,6 +6,7 @@
    Lift,
    ForkLift,
    ForkLiftMaster,
    TrafficControl,
    ;
    public static SlaveType findInstance(String s){
src/main/java/com/zy/core/model/TrafficControlDataModel.java
New file
@@ -0,0 +1,19 @@
package com.zy.core.model;
import com.zy.common.model.NavigateNode;
import lombok.Data;
import java.util.List;
@Data
public class TrafficControlDataModel {
    private Integer shuttleNo;
    private Integer taskNo;
    private List<NavigateNode> nodeList;
    private List<NavigateNode> totalNodeList;
}
src/main/java/com/zy/core/model/command/ShuttleAssignCommand.java
@@ -77,6 +77,9 @@
     */
    private List<NavigateNode> nodes;
    //小车移动连续下发指令
    private Boolean shuttleMoveCommandsContinuously;
    public List<NavigateNode> getNodesDeepCopy() {
        if (this.nodes == null) {
            return null;
src/main/java/com/zy/core/model/command/ShuttleCommand.java
@@ -3,6 +3,8 @@
import com.zy.common.model.NavigateNode;
import com.zy.core.enums.ShuttleCommandModeType;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
@@ -50,7 +52,18 @@
        if (this.mode == null) {
            return null;
        }
        return ShuttleCommandModeType.get(this.mode).desc;
    }
    public List<NavigateNode> getNodesDeepCopy() {
        if (this.nodes == null) {
            return null;
        }
        List<NavigateNode> navigateNodes = new ArrayList<>();
        for (NavigateNode node : nodes) {
            navigateNodes.add(node.clone());
        }
        return navigateNodes;
    }
}
src/main/java/com/zy/core/model/protocol/ShuttleProtocol.java
@@ -6,6 +6,7 @@
import com.zy.asrs.entity.LocMast;
import com.zy.asrs.service.BasShuttleErrService;
import com.zy.asrs.service.LocMastService;
import com.zy.common.model.NavigateNode;
import com.zy.common.utils.RedisUtil;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.ShuttleProtocolStatusType;
@@ -13,6 +14,7 @@
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
/**
 * 四向穿梭车
@@ -184,6 +186,11 @@
    private Long lastOnlineTime = System.currentTimeMillis();
    /**
     * 小车空闲时间
     */
    private Long idleTime = System.currentTimeMillis();
    /**
     * 扩展字段
     */
    private Object extend;
@@ -193,6 +200,16 @@
     */
    private String systemMsg;
    /**
     * 交通管制
     */
    private Boolean trafficControl = false;
    /**
     * 交通管制Nodes
     */
    private List<NavigateNode> trafficControlNodes = null;
    public String getProtocolStatus$() {
        if (this.protocolStatusType == null) {
            return "";
src/main/java/com/zy/core/thread/ShuttleThread.java
@@ -70,6 +70,8 @@
    boolean offerSystemMsg(String format, Object... arguments);
    boolean setTrafficControl(boolean enable, List<NavigateNode> nodeList);
    void updateDeviceDataLogTime(long time);
    JSONObject parseStatusToMsg(ShuttleProtocol shuttleProtocol);
src/main/java/com/zy/core/thread/TrafficControlThread.java
New file
@@ -0,0 +1,30 @@
package com.zy.core.thread;
import com.zy.common.model.NavigateNode;
import com.zy.core.ThreadHandler;
import com.zy.core.model.TrafficControlDataModel;
import java.util.HashMap;
import java.util.List;
public interface TrafficControlThread extends ThreadHandler {
//    boolean getDetecting();
//
//    void updateDetect();
//
//    boolean addNodes(Integer shuttleNo, Integer taskNo, List<NavigateNode> nodeList);
//
//    boolean removeNodes(Integer shuttleNo, Integer taskNo);
    boolean applyTrafficControl(List<NavigateNode> totalNodeList, List<NavigateNode> nodeList, Integer shuttleNo, Integer taskNo);
    boolean trafficReport(List<NavigateNode> nodeList, Integer shuttleNo, Integer taskNo);
    boolean cancelTrafficControl(Integer shuttleNo, Integer taskNo);
    TrafficControlDataModel queryTrafficControl(Integer shuttleNo);
    List<TrafficControlDataModel> getAllTrafficControl();
}
src/main/java/com/zy/core/thread/impl/NyShuttleThread.java
@@ -752,6 +752,9 @@
    @Override
    public synchronized boolean setProtocolStatus(ShuttleProtocolStatusType status) {
        if (status.equals(ShuttleProtocolStatusType.IDLE)) {
            this.shuttleProtocol.setIdleTime(System.currentTimeMillis());
        }
        this.shuttleProtocol.setProtocolStatus(status);
        return true;
    }
@@ -821,6 +824,13 @@
    }
    @Override
    public boolean setTrafficControl(boolean enable, List<NavigateNode> nodeList) {
        shuttleProtocol.setTrafficControl(enable);
        shuttleProtocol.setTrafficControlNodes(nodeList);
        return true;
    }
    @Override
    public void updateDeviceDataLogTime(long time) {
        shuttleProtocol.setDeviceDataLog(time);
    }
src/main/java/com/zy/core/thread/impl/TrafficControlImplThread.java
New file
@@ -0,0 +1,575 @@
package com.zy.core.thread.impl;
import com.alibaba.fastjson.JSON;
import com.core.common.SpringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zy.asrs.utils.Utils;
import com.zy.common.model.NavigateNode;
import com.zy.common.model.enums.NavigationMapType;
import com.zy.common.utils.NavigatePositionConvert;
import com.zy.common.utils.NavigateUtils;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.SlaveConnection;
import com.zy.core.enums.RedisKeyType;
import com.zy.core.enums.SlaveType;
import com.zy.core.model.TrafficControlDataModel;
import com.zy.core.model.command.ShuttleAssignCommand;
import com.zy.core.model.command.ShuttleRedisCommand;
import com.zy.core.model.protocol.ShuttleProtocol;
import com.zy.core.thread.ShuttleThread;
import com.zy.core.thread.TrafficControlThread;
import com.zy.core.utils.TrafficControlUtils;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
public class TrafficControlImplThread implements TrafficControlThread {
    private RedisUtil redisUtil;
    private boolean detecting = false;
    private boolean updateDetect = false;
    private Long detectTime = System.currentTimeMillis();
    private HashMap<String, Integer> deviceMap = null;
    private HashMap<Integer, HashMap<String, List<Integer>>> levNodesMap = null;
    private HashMap<String, List<NavigateNode>> taskNodesMap = new HashMap<>();
    private List<TrafficControlDataModel> trafficControlDataList = new ArrayList<>();
    public TrafficControlImplThread(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }
    @Override
    public void run() {
//        List<Integer> shuttleNoList = new ArrayList<>();
//        while (true) {
//            try {
//                DeviceConfigService deviceConfigService = null;
//                try {
//                    deviceConfigService = SpringUtils.getBean(DeviceConfigService.class);
//                }catch (Exception e){}
//
//                if(deviceConfigService == null){
//                    continue;
//                }
//
//                if(shuttleNoList.isEmpty()){
//                    List<DeviceConfig> shuttleList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>()
//                            .eq("device_type", String.valueOf(SlaveType.Shuttle)));
//                    for (DeviceConfig deviceConfig : shuttleList) {
//                        shuttleNoList.add(deviceConfig.getDeviceNo());
//                    }
//                }
//
//                if((updateDetect) || ((System.currentTimeMillis() - detectTime) > 1000 * 2)) {
//                    detect(shuttleNoList);
//                }
//            }catch (Exception e){
//                e.printStackTrace();
//            }
//        }
    }
    public synchronized void detect(List<Integer> shuttleNoList) {
        detecting = true;
        updateDetect = false;
        ObjectMapper objectMapper = null;
        try {
            objectMapper = SpringUtils.getBean(ObjectMapper.class);
        }catch (Exception e){}
        if(objectMapper == null){
            return;
        }
        HashMap<String, List<NavigateNode>> tmpTaskNodesMap = new HashMap<>();
        for (Integer shuttleNo : shuttleNoList) {
            ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, shuttleNo);
            if (shuttleThread == null) {
                continue;
            }
            ShuttleProtocol shuttleProtocol = shuttleThread.getStatus();
            if (shuttleProtocol == null) {
                continue;
            }
            if (shuttleProtocol.getTaskNo() == 0) {
                continue;
            }
            if (shuttleProtocol.getCurrentLocNo() == null) {
                continue;
            }
            int lev = Utils.getLev(shuttleProtocol.getCurrentLocNo());
            Object obj = redisUtil.get(RedisKeyType.SHUTTLE_WORK_FLAG.key + shuttleProtocol.getTaskNo());
            if (obj == null) {
                continue;
            }
            ShuttleRedisCommand redisCommand = null;
            try {
                redisCommand = objectMapper.readValue(String.valueOf(obj), ShuttleRedisCommand.class);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            if (redisCommand == null) {
                continue;
            }
            ShuttleAssignCommand assignCommand = redisCommand.getAssignCommand();
            if (assignCommand == null) {
                continue;
            }
            List<NavigateNode> nodeList = assignCommand.getNodesDeepCopy();
            if (nodeList == null || nodeList.isEmpty()) {
                continue;
            }
            tmpTaskNodesMap.put(shuttleProtocol.getTaskNo() + "-" + shuttleNo, nodeList);
        }
        this.taskNodesMap = tmpTaskNodesMap;
        HashMap<Integer, HashMap<String, List<Integer>>> levNodesMap = calcNodeList();
        HashMap<Integer, HashMap<String, List<Integer>>> levBlockMap = new HashMap<>();
        //过滤不需要管制节点
        for (Map.Entry<Integer, HashMap<String, List<Integer>>> entry : levNodesMap.entrySet()) {
            Integer lev = entry.getKey();
            HashMap<String, List<Integer>> value = entry.getValue();
            HashMap<String, List<Integer>> blockMap = new HashMap<>();
            for (Map.Entry<String, List<Integer>> listEntry : value.entrySet()) {
                String locNo = listEntry.getKey();
                List<Integer> shuttleNos = listEntry.getValue();
                if (shuttleNos.size() <= 1) {
                    continue;
                }
                blockMap.put(locNo, shuttleNos);
            }
            levBlockMap.put(lev, blockMap);
        }
        //计算堵塞范围
        List<List<String>> allLocList = new ArrayList<>();
        List<List<Integer>> allDeviceNodes = new ArrayList<>();
        for (Map.Entry<Integer, HashMap<String, List<Integer>>> entry : levBlockMap.entrySet()) {
            Integer lev = entry.getKey();
            HashMap<String, List<Integer>> nodes = entry.getValue();
            Set<String> sets = new HashSet<>();
            for (Map.Entry<String, List<Integer>> val : nodes.entrySet()) {
                String locNo = val.getKey();
                sets.add(locNo);
            }
            List<List<String>> locList = TrafficControlUtils.groupNodes(sets);
            List<List<Integer>> deviceNodes = new ArrayList<>();
            //get devices
            for (List<String> list : locList) {
                List<List<Integer>> tmpDeviceNodes = new ArrayList<>();
                for (String loc : list) {
                    List<Integer> shuttleNos = nodes.get(loc);
                    if(!tmpDeviceNodes.contains(shuttleNos)) {
                        tmpDeviceNodes.add(shuttleNos);
                    }
                }
                //节点并集-获取堵塞设备编号
                List<List<Integer>> deviceList = mergeConnectedComponents(tmpDeviceNodes);
                deviceNodes.addAll(deviceList);
            }
            allLocList.addAll(locList);
            allDeviceNodes.addAll(deviceNodes);
        }
        System.out.println(JSON.toJSONString(allLocList));
        System.out.println(JSON.toJSONString(allDeviceNodes));
//        //分配堵塞节点可执行设备
//        findDeviceByBlockList(allLocList, allDeviceNodes);
        detecting = false;
        detectTime = System.currentTimeMillis();
        //发布堵塞节点可用设备编号
        redisUtil.set(RedisKeyType.TASK_BLOCK_ENABLE_DEVICE_MAP.key, trafficControlDataList);
    }
    //分配堵塞节点可执行设备
    public void findDeviceByBlockList(List<List<String>> allLocList, List<List<Integer>> blockNodes) {
        HashMap<String, Integer> map = new HashMap<>();
        if (deviceMap == null) {
            Object object = redisUtil.get(RedisKeyType.TASK_BLOCK_ENABLE_DEVICE_MAP.key);
            if (object != null) {
                map = (HashMap<String, Integer>) object;
            }
        } else {
            map = deviceMap;
        }
        HashMap<String, Integer> newMap = new HashMap<>();
        for (int i = 0; i < blockNodes.size(); i++) {
            List<Integer> blockNode = blockNodes.get(i);
            List<String> locs = allLocList.get(i);
            String key = JSON.toJSONString(locs);
            Integer value = -1;
            if (map.containsKey(key)) {
                value = map.get(key);
                map.remove(key);
                if (value > 0) {
                    ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, value);
                    if (shuttleThread == null) {
                        continue;
                    }
                    ShuttleProtocol shuttleProtocol = shuttleThread.getStatus();
                    if (shuttleProtocol == null) {
                        continue;
                    }
                    if (shuttleProtocol.getTaskNo() == 0) {
                        value = searchDevice(locs, blockNode, newMap);
                    }
                    if (!shuttleProtocol.getTrafficControl()) {
                        value = searchDevice(locs, blockNode, newMap);
                    }
                }else {
                    value = searchDevice(locs, blockNode, newMap);
                }
            } else {
                value = searchDevice(locs, blockNode, newMap);
            }
            newMap.put(key, value);
        }
        deviceMap = newMap;
        //发布堵塞节点可用设备编号
        redisUtil.set(RedisKeyType.TASK_BLOCK_ENABLE_DEVICE_MAP.key, newMap);
    }
    public Integer searchDevice(List<String> locs, List<Integer> blockNode, HashMap<String, Integer> deviceMap) {
        NavigateUtils navigateUtils = null;
        try {
            navigateUtils = SpringUtils.getBean(NavigateUtils.class);
        }catch (Exception e){}
        if(navigateUtils == null){
            return -1;
        }
        Integer value = -1;
        for (Integer shuttleNo : blockNode) {
            ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, shuttleNo);
            if (shuttleThread == null) {
                continue;
            }
            ShuttleProtocol shuttleProtocol = shuttleThread.getStatus();
            if (shuttleProtocol == null) {
                continue;
            }
            if (!shuttleThread.isDeviceIdle()) {
                continue;
            }
            if (shuttleProtocol.getTaskNo() == 0) {
                continue;
            }
            if (!shuttleProtocol.getTrafficControl()) {
                continue;//小车未申请交通管制
            }
            //检测小车是否已经分配
            if (deviceMap.containsValue(shuttleNo)) {
                continue;
            }
            List<NavigateNode> trafficControlNodes = shuttleProtocol.getTrafficControlNodes();
            List<String> trafficControlLocs = new ArrayList<>();
            for (NavigateNode node : trafficControlNodes) {
                trafficControlLocs.add(Utils.getLocNo(node.getX(), node.getY(), node.getZ()));
            }
            //检测当前小车节点是否匹配交通管制节点
            boolean result = false;
            for (String loc : locs) {
                if (trafficControlLocs.contains(loc)) {
                    result = true;
                    break;
                }
            }
            if (!result) {
                continue;
            }
            //check path
            String currentLocNo = shuttleProtocol.getCurrentLocNo();
            for (String loc : locs) {
                if (loc.equals(currentLocNo)) {
                    continue;
                }
                List<NavigateNode> nodeList = navigateUtils.calc(currentLocNo, loc, NavigationMapType.getNormalWithDevice(), Utils.getShuttlePoints(shuttleNo, Utils.getLev(loc)), null);
                if (nodeList == null) {
                    break;
                }
            }
            value = shuttleNo;
            break;
        }
        return value;
    }
    //节点并集
    public List<List<Integer>> mergeConnectedComponents(List<List<Integer>> lists) {
        // 1. 收集所有唯一元素
        Set<Integer> allElements = new HashSet<>();
        for (List<Integer> list : lists) {
            allElements.addAll(list);
        }
        // 2. 初始化并查集
        Map<Integer, Integer> parent = new HashMap<>();
        for (Integer element : allElements) {
            parent.put(element, element);
        }
        // 3. 定义查找根节点的函数(带路径压缩)
        Function<Integer, Integer> find = x -> {
            int root = x;
            while (parent.get(root) != root) {
                root = parent.get(root);
            }
            // 路径压缩
            int current = x;
            while (parent.get(current) != root) {
                int next = parent.get(current);
                parent.put(current, root);
                current = next;
            }
            return root;
        };
        // 4. 遍历每个列表并合并元素
        for (List<Integer> list : lists) {
            if (list.isEmpty()) continue;
            int first = list.get(0);
            for (int i = 1; i < list.size(); i++) {
                int a = first;
                int b = list.get(i);
                int rootA = find.apply(a);
                int rootB = find.apply(b);
                if (rootA != rootB) {
                    parent.put(rootB, rootA); // 合并集合
                }
            }
        }
        // 5. 按根节点分组
        Map<Integer, Set<Integer>> components = new HashMap<>();
        for (Integer element : allElements) {
            int root = find.apply(element);
            components.computeIfAbsent(root, k -> new TreeSet<>()).add(element);
        }
        // 6. 转换为有序列表
        List<List<Integer>> result = new ArrayList<>();
        for (Set<Integer> set : components.values()) {
            result.add(new ArrayList<>(set));
        }
        return result;
    }
    private HashMap<Integer, HashMap<String, List<Integer>>> calcNodeList() {
        HashMap<Integer, HashMap<String, List<Integer>>> levNodesMap = new HashMap<>();
        for (Map.Entry<String, List<NavigateNode>> entry : taskNodesMap.entrySet()) {
            String key = entry.getKey();
            String[] split = key.split("-");
            Integer taskNo = Integer.parseInt(split[0]);
            Integer shuttleNo = Integer.parseInt(split[1]);
            List<NavigateNode> nodeList = entry.getValue();
            NavigateNode node1 = nodeList.get(0);
            int lev = node1.getZ();
            HashMap<String, List<Integer>> nodeMap = new HashMap<>();
            if(levNodesMap.containsKey(lev)) {
                nodeMap = levNodesMap.get(lev);
            }
            for (NavigateNode node : nodeList) {
                String locNo = Utils.getLocNo(node.getX(), node.getY(), lev);
                List<Integer> shuttleNos = new ArrayList<>();
                if (nodeMap.containsKey(locNo)) {
                    shuttleNos = nodeMap.get(locNo);
                }
                if (!shuttleNos.contains(shuttleNo)) {
                    shuttleNos.add(shuttleNo);
                }
                nodeMap.put(locNo, shuttleNos);
            }
            levNodesMap.put(lev, nodeMap);
        }
        this.levNodesMap = levNodesMap;
        return levNodesMap;
    }
    @Override
    public synchronized boolean applyTrafficControl(List<NavigateNode> totalNodeList, List<NavigateNode> nodeList, Integer shuttleNo, Integer taskNo) {
        //发布堵塞节点可用设备编号
        redisUtil.set(RedisKeyType.TASK_BLOCK_ENABLE_DEVICE_MAP.key, trafficControlDataList);
        //检测车子是否存在管制
        for (int i = 0; i < trafficControlDataList.size(); i++) {
            TrafficControlDataModel controlDataModel = trafficControlDataList.get(i);
            if(shuttleNo.equals(controlDataModel.getShuttleNo())) {
                //存在管制
                if(!controlDataModel.getTaskNo().equals(taskNo)) {
                    return false;
                }
                //任务总数量不一致
                if (totalNodeList.size() != controlDataModel.getTotalNodeList().size()) {
                    return false;
                }
                int startIdx = 0;
                int targetIdx = totalNodeList.size() - 1;
                NavigateNode applyStartNode = totalNodeList.get(startIdx);
                NavigateNode applyTargetNode = totalNodeList.get(targetIdx);
                NavigateNode controlStartNode = controlDataModel.getTotalNodeList().get(startIdx);
                NavigateNode controlTargetNode = controlDataModel.getTotalNodeList().get(targetIdx);
                //起点不同
                if(!NavigatePositionConvert.equalsNode(applyStartNode, controlStartNode)) {
                    return false;
                }
                //终点不同
                if(!NavigatePositionConvert.equalsNode(applyTargetNode, controlTargetNode)) {
                    return false;
                }
                News.info("traffic running {},{}", shuttleNo, taskNo);
                return true;//已经管制允许执行
            }
        }
        NavigateNode startNode = totalNodeList.get(0);
        List<int[]> shuttlePoints = Utils.getShuttlePoints(shuttleNo, startNode.getZ());
        List<String> shuttleLocList = new ArrayList<>();
        for (int[] shuttlePoint : shuttlePoints) {
            String locNo = Utils.getLocNo(shuttlePoint[0], shuttlePoint[1], startNode.getZ());
            shuttleLocList.add(locNo);
        }
        for (NavigateNode node : totalNodeList) {
            String locNo = Utils.getLocNo(node.getX(), node.getY(), node.getZ());
            if(shuttleLocList.contains(locNo)) {
               return false;//node has shuttle
            }
        }
        //检测节点是否被使用
        for (TrafficControlDataModel controlDataModel : trafficControlDataList) {
            List<NavigateNode> list = controlDataModel.getTotalNodeList();
            for (NavigateNode node1 : list) {
                for (NavigateNode node2 : totalNodeList) {
                    if (NavigatePositionConvert.equalsNode(node1, node2)) {
                        return false;
                    }
                }
            }
        }
        //交管接收
        TrafficControlDataModel model = new TrafficControlDataModel();
        model.setShuttleNo(shuttleNo);
        model.setTaskNo(taskNo);
        model.setNodeList(nodeList);
        model.setTotalNodeList(totalNodeList);
        trafficControlDataList.add(model);
        News.info("receipt traffic {},{}", shuttleNo, taskNo);
        return true;
    }
    @Override
    public boolean trafficReport(List<NavigateNode> nodeList, Integer shuttleNo, Integer taskNo) {
        //检测车子是否存在管制
        for (int i = 0; i < trafficControlDataList.size(); i++) {
            TrafficControlDataModel controlDataModel = trafficControlDataList.get(i);
            if(shuttleNo.equals(controlDataModel.getShuttleNo())) {
                if(controlDataModel.getTaskNo().equals(taskNo)) {
                    List<NavigateNode> totalNodeList = controlDataModel.getTotalNodeList();
                    totalNodeList.removeAll(nodeList);
                    controlDataModel.setTotalNodeList(totalNodeList);
                    trafficControlDataList.set(i, controlDataModel);
                    return true;
                }
            }
        }
        return false;
    }
    @Override
    public synchronized boolean cancelTrafficControl(Integer shuttleNo, Integer taskNo) {
        //检测车子是否存在管制
        for (int i = 0; i < trafficControlDataList.size(); i++) {
            TrafficControlDataModel controlDataModel = trafficControlDataList.get(i);
            if(shuttleNo.equals(controlDataModel.getShuttleNo())) {
                if(controlDataModel.getTaskNo().equals(taskNo)) {
                    trafficControlDataList.remove(i);//取消管制
                    return true;
                }
            }
        }
        return false;
    }
    @Override
    public TrafficControlDataModel queryTrafficControl(Integer shuttleNo) {
        //检测车子是否存在管制
        for (int i = 0; i < trafficControlDataList.size(); i++) {
            TrafficControlDataModel controlDataModel = trafficControlDataList.get(i);
            if(shuttleNo.equals(controlDataModel.getShuttleNo())) {
                return controlDataModel;
            }
        }
        return null;
    }
    @Override
    public List<TrafficControlDataModel> getAllTrafficControl() {
        return trafficControlDataList;
    }
    @Override
    public boolean connect() {
        return false;
    }
    @Override
    public void close() {
    }
}
src/main/java/com/zy/core/utils/TrafficControlUtils.java
New file
@@ -0,0 +1,81 @@
package com.zy.core.utils;
import java.util.*;
public class TrafficControlUtils {
    public static List<List<String>> groupNodes(Collection<String> keys) {
        // 1. 按位置聚类:Map<位置键, 节点列表>
        Map<String, List<String>> clusterMap = new HashMap<>();
        for (String key : keys) {
            String rowStr = key.substring(0, 2);
            String colStr = key.substring(2, 5);
            String posKey = rowStr + "_" + colStr; // 位置键格式: "RR_CCC"
            clusterMap.computeIfAbsent(posKey, k -> new ArrayList<>()).add(key);
        }
        // 2. 初始化并查集
        Map<String, String> parentMap = new HashMap<>();
        Map<String, Integer> rankMap = new HashMap<>();
        for (String posKey : clusterMap.keySet()) {
            parentMap.put(posKey, posKey); // 初始父节点指向自己
            rankMap.put(posKey, 0);
        }
        // 3. 遍历所有位置键,合并相邻簇
        for (String posKey : clusterMap.keySet()) {
            String[] parts = posKey.split("_");
            int row = Integer.parseInt(parts[0]);
            int col = Integer.parseInt(parts[1]);
            // 检查四个方向:左、右、上、下
            int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // {dRow, dCol}
            for (int[] dir : directions) {
                int newRow = row + dir[0];
                int newCol = col + dir[1];
                String neighborKey = String.format("%02d_%03d", newRow, newCol);
                if (parentMap.containsKey(neighborKey)) {
                    union(posKey, neighborKey, parentMap, rankMap);
                }
            }
        }
        // 4. 生成分组结果
        Map<String, List<String>> groupMap = new HashMap<>();
        for (String posKey : clusterMap.keySet()) {
            String root = find(posKey, parentMap);
            groupMap.computeIfAbsent(root, k -> new ArrayList<>())
                    .addAll(clusterMap.get(posKey));
        }
        return new ArrayList<>(groupMap.values());
    }
    // 并查集:查找根节点(带路径压缩)
    private static String find(String x, Map<String, String> parentMap) {
        if (!parentMap.get(x).equals(x)) {
            parentMap.put(x, find(parentMap.get(x), parentMap));
        }
        return parentMap.get(x);
    }
    // 并查集:按秩合并
    private static void union(String x, String y, Map<String, String> parentMap, Map<String, Integer> rankMap) {
        String rootX = find(x, parentMap);
        String rootY = find(y, parentMap);
        if (rootX.equals(rootY)) return;
        int rankX = rankMap.get(rootX);
        int rankY = rankMap.get(rootY);
        if (rankX < rankY) {
            parentMap.put(rootX, rootY);
        } else if (rankX > rankY) {
            parentMap.put(rootY, rootX);
        } else {
            parentMap.put(rootY, rootX);
            rankMap.put(rootX, rankX + 1);
        }
    }
}