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<>();
|
}
|
}
|