| New file |
| | |
| | | package com.zy.asrs.service.impl; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.mapper.EntityWrapper; |
| | | import com.zy.asrs.domain.vo.StationCycleCapacityVo; |
| | | import com.zy.asrs.domain.vo.StationCycleLoopVo; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.DeviceConfig; |
| | | import com.zy.asrs.service.BasMapService; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.service.DeviceConfigService; |
| | | import com.zy.asrs.service.StationCycleCapacityService; |
| | | import com.zy.common.model.NavigateNode; |
| | | import com.zy.common.utils.NavigateSolution; |
| | | import com.zy.core.cache.SlaveConnection; |
| | | import com.zy.core.enums.SlaveType; |
| | | import com.zy.core.model.StationObjModel; |
| | | import com.zy.core.model.protocol.StationProtocol; |
| | | import com.zy.core.thread.StationThread; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.ArrayDeque; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.Deque; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.concurrent.atomic.AtomicReference; |
| | | |
| | | @Service("stationCycleCapacityService") |
| | | @Slf4j |
| | | public class StationCycleCapacityServiceImpl implements StationCycleCapacityService { |
| | | |
| | | @Autowired |
| | | private BasMapService basMapService; |
| | | @Autowired |
| | | private DeviceConfigService deviceConfigService; |
| | | @Autowired |
| | | private BasDevpService basDevpService; |
| | | |
| | | private final AtomicReference<StationCycleCapacityVo> snapshotRef = new AtomicReference<>(new StationCycleCapacityVo()); |
| | | |
| | | @Override |
| | | public synchronized void refreshSnapshot() { |
| | | try { |
| | | StationCycleCapacityVo snapshot = buildSnapshot(); |
| | | snapshotRef.set(snapshot); |
| | | } catch (Exception e) { |
| | | log.error("刷新循环圈承载量失败", e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public StationCycleCapacityVo getLatestSnapshot() { |
| | | StationCycleCapacityVo snapshot = snapshotRef.get(); |
| | | if (snapshot == null || snapshot.getRefreshTime() == null) { |
| | | refreshSnapshot(); |
| | | snapshot = snapshotRef.get(); |
| | | } |
| | | return snapshot == null ? new StationCycleCapacityVo() : snapshot; |
| | | } |
| | | |
| | | private StationCycleCapacityVo buildSnapshot() { |
| | | GraphContext context = buildStationGraph(); |
| | | Map<Integer, Integer> workNoMap = buildStationWorkNoMap(); |
| | | |
| | | Set<Integer> availableStationSet = new HashSet<>(context.graph.keySet()); |
| | | availableStationSet.removeAll(context.excludeStationSet); |
| | | |
| | | Map<Integer, Set<Integer>> filteredGraph = new HashMap<>(); |
| | | for (Integer stationId : availableStationSet) { |
| | | Set<Integer> nextSet = context.graph.getOrDefault(stationId, Collections.emptySet()); |
| | | Set<Integer> filteredNext = new HashSet<>(); |
| | | for (Integer nextId : nextSet) { |
| | | if (availableStationSet.contains(nextId)) { |
| | | filteredNext.add(nextId); |
| | | } |
| | | } |
| | | filteredGraph.put(stationId, filteredNext); |
| | | } |
| | | |
| | | List<Set<Integer>> sccList = findStrongConnectedComponents(filteredGraph); |
| | | List<StationCycleLoopVo> loopList = new ArrayList<>(); |
| | | |
| | | int loopNo = 1; |
| | | int totalStationCount = 0; |
| | | int taskStationCount = 0; |
| | | |
| | | for (Set<Integer> scc : sccList) { |
| | | if (!isCycleScc(scc, filteredGraph)) { |
| | | continue; |
| | | } |
| | | |
| | | // 对 SCC 再做一次“环核心”剥离,剔除枝杈/死胡同节点 |
| | | List<Set<Integer>> coreLoopList = extractCoreLoopComponents(scc, filteredGraph); |
| | | for (Set<Integer> coreLoop : coreLoopList) { |
| | | List<Integer> stationIdList = new ArrayList<>(coreLoop); |
| | | Collections.sort(stationIdList); |
| | | |
| | | List<Integer> workNoList = new ArrayList<>(); |
| | | int currentLoopTaskCount = 0; |
| | | for (Integer stationId : stationIdList) { |
| | | Integer workNo = workNoMap.get(stationId); |
| | | if (workNo != null && workNo > 0) { |
| | | workNoList.add(workNo); |
| | | currentLoopTaskCount++; |
| | | } |
| | | } |
| | | |
| | | StationCycleLoopVo loopVo = new StationCycleLoopVo(); |
| | | loopVo.setLoopNo(loopNo++); |
| | | loopVo.setStationIdList(stationIdList); |
| | | loopVo.setWorkNoList(workNoList); |
| | | loopVo.setStationCount(stationIdList.size()); |
| | | loopVo.setTaskCount(currentLoopTaskCount); |
| | | loopVo.setCurrentLoad(calcCurrentLoad(currentLoopTaskCount, stationIdList.size())); |
| | | loopList.add(loopVo); |
| | | |
| | | totalStationCount += stationIdList.size(); |
| | | taskStationCount += currentLoopTaskCount; |
| | | } |
| | | } |
| | | |
| | | StationCycleCapacityVo vo = new StationCycleCapacityVo(); |
| | | vo.setLoopList(loopList); |
| | | vo.setLoopCount(loopList.size()); |
| | | vo.setTotalStationCount(totalStationCount); |
| | | vo.setTaskStationCount(taskStationCount); |
| | | vo.setCurrentLoad(calcCurrentLoad(taskStationCount, totalStationCount)); |
| | | vo.setRefreshTime(new Date()); |
| | | return vo; |
| | | } |
| | | |
| | | private double calcCurrentLoad(int taskCount, int stationCount) { |
| | | if (stationCount <= 0 || taskCount <= 0) { |
| | | return 0.0; |
| | | } |
| | | double value = (double) taskCount / (double) stationCount; |
| | | if (value < 0.0) { |
| | | return 0.0; |
| | | } |
| | | if (value > 1.0) { |
| | | return 1.0; |
| | | } |
| | | return value; |
| | | } |
| | | |
| | | private GraphContext buildStationGraph() { |
| | | GraphContext context = new GraphContext(); |
| | | List<Integer> levList = basMapService.getLevList(); |
| | | if (levList == null || levList.isEmpty()) { |
| | | return context; |
| | | } |
| | | |
| | | NavigateSolution navigateSolution = new NavigateSolution(); |
| | | List<Integer> sortedLevList = new ArrayList<>(levList); |
| | | sortedLevList = new ArrayList<>(new HashSet<>(sortedLevList)); |
| | | Collections.sort(sortedLevList); |
| | | |
| | | for (Integer lev : sortedLevList) { |
| | | List<List<NavigateNode>> stationMap; |
| | | try { |
| | | stationMap = navigateSolution.getStationMap(lev); |
| | | } catch (Exception e) { |
| | | log.warn("加载楼层{}地图失败,跳过循环圈计算", lev); |
| | | continue; |
| | | } |
| | | if (stationMap == null || stationMap.isEmpty()) { |
| | | continue; |
| | | } |
| | | |
| | | for (List<NavigateNode> row : stationMap) { |
| | | for (NavigateNode node : row) { |
| | | JSONObject valueObj = parseNodeValue(node.getNodeValue()); |
| | | if (valueObj == null) { |
| | | continue; |
| | | } |
| | | Integer stationId = valueObj.getInteger("stationId"); |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | |
| | | context.graph.computeIfAbsent(stationId, k -> new HashSet<>()); |
| | | if (isExcludeStation(valueObj)) { |
| | | context.excludeStationSet.add(stationId); |
| | | } |
| | | |
| | | List<NavigateNode> nextNodeList = navigateSolution.extend_current_node(stationMap, node); |
| | | if (nextNodeList == null || nextNodeList.isEmpty()) { |
| | | continue; |
| | | } |
| | | for (NavigateNode nextNode : nextNodeList) { |
| | | JSONObject nextValueObj = parseNodeValue(nextNode.getNodeValue()); |
| | | if (nextValueObj == null) { |
| | | continue; |
| | | } |
| | | Integer nextStationId = nextValueObj.getInteger("stationId"); |
| | | if (nextStationId == null || stationId.equals(nextStationId)) { |
| | | continue; |
| | | } |
| | | |
| | | context.graph.computeIfAbsent(nextStationId, k -> new HashSet<>()); |
| | | context.graph.get(stationId).add(nextStationId); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | appendExcludeStationsFromDeviceConfig(context.excludeStationSet); |
| | | |
| | | return context; |
| | | } |
| | | |
| | | private void appendExcludeStationsFromDeviceConfig(Set<Integer> excludeStationSet) { |
| | | List<BasDevp> basDevpList = basDevpService.selectList(new EntityWrapper<>()); |
| | | if (basDevpList == null || basDevpList.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | for (BasDevp basDevp : basDevpList) { |
| | | List<StationObjModel> inStationList = basDevp.getInStationList$(); |
| | | for (StationObjModel stationObjModel : inStationList) { |
| | | if (stationObjModel != null && stationObjModel.getStationId() != null) { |
| | | excludeStationSet.add(stationObjModel.getStationId()); |
| | | } |
| | | } |
| | | |
| | | List<StationObjModel> barcodeStationList = basDevp.getBarcodeStationList$(); |
| | | for (StationObjModel stationObjModel : barcodeStationList) { |
| | | if (stationObjModel != null && stationObjModel.getStationId() != null) { |
| | | excludeStationSet.add(stationObjModel.getStationId()); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private JSONObject parseNodeValue(String nodeValue) { |
| | | if (nodeValue == null || nodeValue.trim().isEmpty()) { |
| | | return null; |
| | | } |
| | | try { |
| | | return JSON.parseObject(nodeValue); |
| | | } catch (Exception ignore) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private boolean isExcludeStation(JSONObject valueObj) { |
| | | Integer isInStation = valueObj.getInteger("isInStation"); |
| | | Integer isBarcodeStation = valueObj.getInteger("isBarcodeStation"); |
| | | return (isInStation != null && isInStation == 1) |
| | | || (isBarcodeStation != null && isBarcodeStation == 1); |
| | | } |
| | | |
| | | private Map<Integer, Integer> buildStationWorkNoMap() { |
| | | Map<Integer, Integer> workNoMap = new HashMap<>(); |
| | | List<DeviceConfig> devpList = deviceConfigService.selectList(new EntityWrapper<DeviceConfig>() |
| | | .eq("device_type", String.valueOf(SlaveType.Devp))); |
| | | if (devpList == null || devpList.isEmpty()) { |
| | | return workNoMap; |
| | | } |
| | | |
| | | for (DeviceConfig deviceConfig : devpList) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, deviceConfig.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap(); |
| | | if (statusMap == null || statusMap.isEmpty()) { |
| | | continue; |
| | | } |
| | | |
| | | for (StationProtocol protocol : statusMap.values()) { |
| | | if (protocol == null || protocol.getStationId() == null) { |
| | | continue; |
| | | } |
| | | Integer taskNo = protocol.getTaskNo(); |
| | | if (taskNo != null && taskNo > 0) { |
| | | workNoMap.put(protocol.getStationId(), taskNo); |
| | | } |
| | | } |
| | | } |
| | | return workNoMap; |
| | | } |
| | | |
| | | private List<Set<Integer>> findStrongConnectedComponents(Map<Integer, Set<Integer>> graph) { |
| | | List<Set<Integer>> result = new ArrayList<>(); |
| | | if (graph == null || graph.isEmpty()) { |
| | | return result; |
| | | } |
| | | |
| | | Map<Integer, Integer> indexMap = new HashMap<>(); |
| | | Map<Integer, Integer> lowLinkMap = new HashMap<>(); |
| | | Deque<Integer> stack = new ArrayDeque<>(); |
| | | Set<Integer> inStack = new HashSet<>(); |
| | | int[] index = new int[]{0}; |
| | | |
| | | List<Integer> nodeList = new ArrayList<>(graph.keySet()); |
| | | Collections.sort(nodeList); |
| | | for (Integer node : nodeList) { |
| | | if (!indexMap.containsKey(node)) { |
| | | strongConnect(node, graph, indexMap, lowLinkMap, stack, inStack, index, result); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private void strongConnect(Integer node, |
| | | Map<Integer, Set<Integer>> graph, |
| | | Map<Integer, Integer> indexMap, |
| | | Map<Integer, Integer> lowLinkMap, |
| | | Deque<Integer> stack, |
| | | Set<Integer> inStack, |
| | | int[] index, |
| | | List<Set<Integer>> result) { |
| | | indexMap.put(node, index[0]); |
| | | lowLinkMap.put(node, index[0]); |
| | | index[0]++; |
| | | |
| | | stack.push(node); |
| | | inStack.add(node); |
| | | |
| | | List<Integer> nextList = new ArrayList<>(graph.getOrDefault(node, Collections.emptySet())); |
| | | Collections.sort(nextList); |
| | | for (Integer next : nextList) { |
| | | if (!indexMap.containsKey(next)) { |
| | | strongConnect(next, graph, indexMap, lowLinkMap, stack, inStack, index, result); |
| | | lowLinkMap.put(node, Math.min(lowLinkMap.get(node), lowLinkMap.get(next))); |
| | | } else if (inStack.contains(next)) { |
| | | lowLinkMap.put(node, Math.min(lowLinkMap.get(node), indexMap.get(next))); |
| | | } |
| | | } |
| | | |
| | | if (!lowLinkMap.get(node).equals(indexMap.get(node))) { |
| | | return; |
| | | } |
| | | |
| | | Set<Integer> scc = new HashSet<>(); |
| | | while (!stack.isEmpty()) { |
| | | Integer top = stack.pop(); |
| | | inStack.remove(top); |
| | | scc.add(top); |
| | | if (top.equals(node)) { |
| | | break; |
| | | } |
| | | } |
| | | result.add(scc); |
| | | } |
| | | |
| | | private boolean isCycleScc(Set<Integer> scc, Map<Integer, Set<Integer>> graph) { |
| | | if (scc == null || scc.isEmpty()) { |
| | | return false; |
| | | } |
| | | if (scc.size() > 1) { |
| | | return true; |
| | | } |
| | | Integer onlyNode = scc.iterator().next(); |
| | | Set<Integer> nextSet = graph.getOrDefault(onlyNode, Collections.emptySet()); |
| | | return nextSet.contains(onlyNode); |
| | | } |
| | | |
| | | /** |
| | | * 从 SCC 中提取循环核心: |
| | | * 1) 转无向图 |
| | | * 2) 递归剥离度数<2的节点(2-core) |
| | | * 3) 将剩余节点拆成连通分量,每个分量>=3才认定为循环圈 |
| | | */ |
| | | private List<Set<Integer>> extractCoreLoopComponents(Set<Integer> scc, Map<Integer, Set<Integer>> graph) { |
| | | List<Set<Integer>> result = new ArrayList<>(); |
| | | if (scc == null || scc.isEmpty()) { |
| | | return result; |
| | | } |
| | | |
| | | // 构建 SCC 内无向邻接 |
| | | Map<Integer, Set<Integer>> undirectedMap = new HashMap<>(); |
| | | for (Integer node : scc) { |
| | | undirectedMap.put(node, new HashSet<>()); |
| | | } |
| | | for (Integer from : scc) { |
| | | Set<Integer> nextSet = graph.getOrDefault(from, Collections.emptySet()); |
| | | for (Integer to : nextSet) { |
| | | if (!scc.contains(to) || from.equals(to)) { |
| | | continue; |
| | | } |
| | | undirectedMap.get(from).add(to); |
| | | undirectedMap.get(to).add(from); |
| | | } |
| | | } |
| | | |
| | | // 2-core 剥离 |
| | | Set<Integer> alive = new HashSet<>(scc); |
| | | Map<Integer, Integer> degreeMap = new HashMap<>(); |
| | | ArrayDeque<Integer> queue = new ArrayDeque<>(); |
| | | for (Integer node : scc) { |
| | | int degree = undirectedMap.getOrDefault(node, Collections.emptySet()).size(); |
| | | degreeMap.put(node, degree); |
| | | if (degree < 2) { |
| | | queue.offer(node); |
| | | } |
| | | } |
| | | |
| | | while (!queue.isEmpty()) { |
| | | Integer node = queue.poll(); |
| | | if (!alive.remove(node)) { |
| | | continue; |
| | | } |
| | | for (Integer next : undirectedMap.getOrDefault(node, Collections.emptySet())) { |
| | | if (!alive.contains(next)) { |
| | | continue; |
| | | } |
| | | int newDegree = degreeMap.getOrDefault(next, 0) - 1; |
| | | degreeMap.put(next, newDegree); |
| | | if (newDegree < 2) { |
| | | queue.offer(next); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (alive.size() < 3) { |
| | | return result; |
| | | } |
| | | |
| | | // 拆分连通分量 |
| | | Set<Integer> visited = new HashSet<>(); |
| | | List<Integer> sortedAlive = new ArrayList<>(alive); |
| | | Collections.sort(sortedAlive); |
| | | for (Integer start : sortedAlive) { |
| | | if (!visited.add(start)) { |
| | | continue; |
| | | } |
| | | Set<Integer> component = new HashSet<>(); |
| | | ArrayDeque<Integer> bfs = new ArrayDeque<>(); |
| | | bfs.offer(start); |
| | | component.add(start); |
| | | while (!bfs.isEmpty()) { |
| | | Integer node = bfs.poll(); |
| | | for (Integer next : undirectedMap.getOrDefault(node, Collections.emptySet())) { |
| | | if (!alive.contains(next) || !visited.add(next)) { |
| | | continue; |
| | | } |
| | | component.add(next); |
| | | bfs.offer(next); |
| | | } |
| | | } |
| | | |
| | | // 至少3个点才认为是真正“圈” |
| | | if (component.size() >= 3) { |
| | | result.add(component); |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | private static class GraphContext { |
| | | private final Map<Integer, Set<Integer>> graph = new HashMap<>(); |
| | | private final Set<Integer> excludeStationSet = new HashSet<>(); |
| | | } |
| | | } |