From 53332ecb0edc651bae91f1e7a74a795d76d87cb2 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 04 三月 2026 17:18:07 +0800
Subject: [PATCH] #
---
src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java | 623 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 623 insertions(+), 0 deletions(-)
diff --git a/src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java b/src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java
new file mode 100644
index 0000000..493238d
--- /dev/null
+++ b/src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java
@@ -0,0 +1,623 @@
+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<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;
+ Set<Integer> actualWorkNoSet = new HashSet<>();
+
+ 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++;
+ 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<StationCycleLoopVo> loopList, Set<Integer> actualWorkNoSet) {
+ if (loopList == null || loopList.isEmpty()) {
+ return 0;
+ }
+
+ Map<Object, Object> reserveMap = redisUtil.hmget(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key);
+ if (reserveMap == null || reserveMap.isEmpty()) {
+ return 0;
+ }
+
+ Map<Integer, StationCycleLoopVo> loopMap = new HashMap<>();
+ Map<Integer, StationCycleLoopVo> 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<Object> removeFieldList = new ArrayList<>();
+
+ for (Map.Entry<Object, Object> 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<Integer> 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<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<>();
+ }
+}
--
Gitblit v1.9.1