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.RedisUtil; import com.zy.common.utils.NavigateSolution; import com.zy.core.cache.SlaveConnection; import com.zy.core.enums.RedisKeyType; 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 { private static final long LOOP_LOAD_RESERVE_EXPIRE_MILLIS = 120_000L; @Autowired private BasMapService basMapService; @Autowired private DeviceConfigService deviceConfigService; @Autowired private BasDevpService basDevpService; @Autowired private RedisUtil redisUtil; private final AtomicReference 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 workNoMap = buildStationWorkNoMap(); Set availableStationSet = new HashSet<>(context.graph.keySet()); availableStationSet.removeAll(context.excludeStationSet); Map> filteredGraph = new HashMap<>(); for (Integer stationId : availableStationSet) { Set nextSet = context.graph.getOrDefault(stationId, Collections.emptySet()); Set filteredNext = new HashSet<>(); for (Integer nextId : nextSet) { if (availableStationSet.contains(nextId)) { filteredNext.add(nextId); } } filteredGraph.put(stationId, filteredNext); } List> sccList = findStrongConnectedComponents(filteredGraph); List loopList = new ArrayList<>(); int loopNo = 1; int totalStationCount = 0; int taskStationCount = 0; Set actualWorkNoSet = new HashSet<>(); for (Set scc : sccList) { if (!isCycleScc(scc, filteredGraph)) { continue; } // 对 SCC 再做一次“环核心”剥离,剔除枝杈/死胡同节点 List> coreLoopList = extractCoreLoopComponents(scc, filteredGraph); for (Set coreLoop : coreLoopList) { List stationIdList = new ArrayList<>(coreLoop); Collections.sort(stationIdList); List workNoList = new ArrayList<>(); int currentLoopTaskCount = 0; for (Integer stationId : stationIdList) { Integer workNo = workNoMap.get(stationId); if (workNo != null && workNo > 0) { workNoList.add(workNo); currentLoopTaskCount++; actualWorkNoSet.add(workNo); } } 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; } } int reserveTaskCount = mergeReserveTaskCount(loopList, actualWorkNoSet); taskStationCount += reserveTaskCount; 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 int mergeReserveTaskCount(List loopList, Set actualWorkNoSet) { if (loopList == null || loopList.isEmpty()) { return 0; } Map reserveMap = redisUtil.hmget(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key); if (reserveMap == null || reserveMap.isEmpty()) { return 0; } Map loopMap = new HashMap<>(); Map stationLoopMap = new HashMap<>(); for (StationCycleLoopVo loopVo : loopList) { if (loopVo != null && loopVo.getLoopNo() != null) { loopMap.put(loopVo.getLoopNo(), loopVo); } if (loopVo == null || loopVo.getStationIdList() == null) { continue; } for (Integer stationId : loopVo.getStationIdList()) { if (stationId != null) { stationLoopMap.put(stationId, loopVo); } } } long now = System.currentTimeMillis(); int mergedCount = 0; List removeFieldList = new ArrayList<>(); for (Map.Entry entry : reserveMap.entrySet()) { ReserveRecord record = parseReserveRecord(entry.getKey(), entry.getValue()); if (record == null) { removeFieldList.add(entry.getKey()); continue; } if (actualWorkNoSet.contains(record.wrkNo)) { removeFieldList.add(entry.getKey()); continue; } if (record.createTime <= 0 || now - record.createTime > LOOP_LOAD_RESERVE_EXPIRE_MILLIS) { removeFieldList.add(entry.getKey()); continue; } StationCycleLoopVo loopVo = loopMap.get(record.loopNo); if (loopVo == null && record.hitStationId != null) { loopVo = stationLoopMap.get(record.hitStationId); } if (loopVo == null) { removeFieldList.add(entry.getKey()); continue; } List workNoList = loopVo.getWorkNoList(); if (workNoList == null) { workNoList = new ArrayList<>(); loopVo.setWorkNoList(workNoList); } if (workNoList.contains(record.wrkNo)) { continue; } workNoList.add(record.wrkNo); Collections.sort(workNoList); int mergedTaskCount = toNonNegative(loopVo.getTaskCount()) + 1; loopVo.setTaskCount(mergedTaskCount); loopVo.setCurrentLoad(calcCurrentLoad(mergedTaskCount, toNonNegative(loopVo.getStationCount()))); mergedCount++; } if (!removeFieldList.isEmpty()) { redisUtil.hdel(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, removeFieldList.toArray()); } return mergedCount; } private ReserveRecord parseReserveRecord(Object fieldObj, Object valueObj) { if (fieldObj == null || valueObj == null) { return null; } Integer fieldWrkNo = parseInteger(String.valueOf(fieldObj)); if (fieldWrkNo == null || fieldWrkNo <= 0) { return null; } JSONObject jsonObject; try { jsonObject = JSON.parseObject(String.valueOf(valueObj)); } catch (Exception e) { return null; } if (jsonObject == null) { return null; } Integer wrkNo = jsonObject.getInteger("wrkNo"); Integer loopNo = jsonObject.getInteger("loopNo"); Integer hitStationId = jsonObject.getInteger("hitStationId"); Long createTime = jsonObject.getLong("createTime"); if (wrkNo == null || wrkNo <= 0) { wrkNo = fieldWrkNo; } if ((loopNo == null || loopNo <= 0) && (hitStationId == null || hitStationId <= 0)) { return null; } if (createTime == null || createTime <= 0) { return null; } ReserveRecord record = new ReserveRecord(); record.wrkNo = wrkNo; record.loopNo = loopNo; record.hitStationId = hitStationId; record.createTime = createTime; return record; } private Integer parseInteger(String value) { if (value == null || value.trim().isEmpty()) { return null; } try { return Integer.parseInt(value.trim()); } catch (Exception e) { return null; } } private int toNonNegative(Integer value) { if (value == null || value < 0) { return 0; } return value; } private static class ReserveRecord { private Integer wrkNo; private Integer loopNo; private Integer hitStationId; private Long createTime; } 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 levList = basMapService.getLevList(); if (levList == null || levList.isEmpty()) { return context; } NavigateSolution navigateSolution = new NavigateSolution(); List sortedLevList = new ArrayList<>(levList); sortedLevList = new ArrayList<>(new HashSet<>(sortedLevList)); Collections.sort(sortedLevList); for (Integer lev : sortedLevList) { List> stationMap; try { stationMap = navigateSolution.getStationMap(lev); } catch (Exception e) { log.warn("加载楼层{}地图失败,跳过循环圈计算", lev); continue; } if (stationMap == null || stationMap.isEmpty()) { continue; } for (List 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 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 excludeStationSet) { List basDevpList = basDevpService.selectList(new EntityWrapper<>()); if (basDevpList == null || basDevpList.isEmpty()) { return; } for (BasDevp basDevp : basDevpList) { List inStationList = basDevp.getInStationList$(); for (StationObjModel stationObjModel : inStationList) { if (stationObjModel != null && stationObjModel.getStationId() != null) { excludeStationSet.add(stationObjModel.getStationId()); } } List 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 buildStationWorkNoMap() { Map workNoMap = new HashMap<>(); List devpList = deviceConfigService.selectList(new EntityWrapper() .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 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> findStrongConnectedComponents(Map> graph) { List> result = new ArrayList<>(); if (graph == null || graph.isEmpty()) { return result; } Map indexMap = new HashMap<>(); Map lowLinkMap = new HashMap<>(); Deque stack = new ArrayDeque<>(); Set inStack = new HashSet<>(); int[] index = new int[]{0}; List 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> graph, Map indexMap, Map lowLinkMap, Deque stack, Set inStack, int[] index, List> result) { indexMap.put(node, index[0]); lowLinkMap.put(node, index[0]); index[0]++; stack.push(node); inStack.add(node); List 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 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 scc, Map> graph) { if (scc == null || scc.isEmpty()) { return false; } if (scc.size() > 1) { return true; } Integer onlyNode = scc.iterator().next(); Set nextSet = graph.getOrDefault(onlyNode, Collections.emptySet()); return nextSet.contains(onlyNode); } /** * 从 SCC 中提取循环核心: * 1) 转无向图 * 2) 递归剥离度数<2的节点(2-core) * 3) 将剩余节点拆成连通分量,每个分量>=3才认定为循环圈 */ private List> extractCoreLoopComponents(Set scc, Map> graph) { List> result = new ArrayList<>(); if (scc == null || scc.isEmpty()) { return result; } // 构建 SCC 内无向邻接 Map> undirectedMap = new HashMap<>(); for (Integer node : scc) { undirectedMap.put(node, new HashSet<>()); } for (Integer from : scc) { Set 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 alive = new HashSet<>(scc); Map degreeMap = new HashMap<>(); ArrayDeque 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 visited = new HashSet<>(); List sortedAlive = new ArrayList<>(alive); Collections.sort(sortedAlive); for (Integer start : sortedAlive) { if (!visited.add(start)) { continue; } Set component = new HashSet<>(); ArrayDeque 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> graph = new HashMap<>(); private final Set excludeStationSet = new HashSet<>(); } }