package com.gaotao.modules.dashboard.task; import com.beust.ah.A; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.gaotao.common.utils.HttpUtils; import com.gaotao.modules.automatedWarehouse.entity.tusk.AgvStatus; import com.gaotao.modules.automatedWarehouse.entity.tusk.TuskResponse; import com.gaotao.modules.automatedWarehouse.service.TuskClientService; import com.gaotao.modules.dashboard.service.DashboardWebSocketService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.*; /** * 看板数据推送定时任务 * *

功能说明:

* * *

推送策略:

* * * @author System * @since 2025-01-23 */ @Slf4j @Component public class DashboardPushTask { @Autowired private DashboardWebSocketService webSocketService; @Value("${custom.wcs-board-api}") private String wcsBoardApi; // 看板推送任务开关配置(所有看板共用一个开关) @Value("${dashboard.push.enabled:true}") private boolean dashboardPushEnabled; /** * 上次推送的数据哈希值(用于检测数据变更) */ private Map lastDataHash = new HashMap<>(); @Autowired private com.gaotao.modules.dashboard.dao.DashboardDao dashboardDao; @Autowired(required = false) private TuskClientService tuskClientService; @Value("${custom.wcs-url}") private String wcsUrl; /** * 每10秒检查人工拣选数据并推送 * *

数据来源:WCS系统 - /api/wms/query-auto-sorting-info

* *

数据说明:

* * *

配置开关:

* */ @Scheduled(fixedRate = 5000) public void pushManualPickingData() { // 检查总开关 if (!dashboardPushEnabled) { log.trace("看板推送已禁用"); return; } try { log.debug("=== 开始获取人工拣选看板数据 ==="); // 从WCS获取人工拣选数据 Map data = getManualPickingDataFromWcs(); // 如果返回null,转换为空数据 if (data == null) { data = createEmptyManualPickingData(); } // 计算数据哈希值并推送 int currentHash = data.hashCode(); webSocketService.pushManualPickingData(data); lastDataHash.put("manual-picking", currentHash); log.debug("=== 人工拣选数据推送完成 ==="); } catch (Exception e) { log.error("推送人工拣选数据失败,推送空数据清空前端列表: {}", e.getMessage(), e); // 异常时推送空数据,避免前端显示过期数据 try { Map emptyData = createEmptyManualPickingData(); webSocketService.pushManualPickingData(emptyData); lastDataHash.put("manual-picking", emptyData.hashCode()); } catch (Exception ex) { log.error("推送空数据失败: {}", ex.getMessage()); } } } /** * 从WCS获取人工拣选数据 * * @return 人工拣选数据 */ private Map getManualPickingDataFromWcs() { try { String url = wcsUrl + "query-auto-sorting-info"; ObjectMapper mapper = new ObjectMapper(); // 请求1043站数据 log.info("调用WCS人工拣选API (1043站): {}", url); List> station1043List = getStationData(url, "1043", mapper); log.info("1043站数据获取完成: {}条", station1043List.size()); // 请求1028站数据 log.info("调用WCS人工拣选API (1028站): {}", url); List> station1028List = getStationData(url, "1028", mapper); log.info("1028站数据获取完成: {}条", station1028List.size()); // 构造返回数据 Map resultData = new HashMap<>(); resultData.put("station1043List", station1043List); resultData.put("station1028List", station1028List); log.info("=== 人工拣选数据处理完成 === 1043站:{}条, 1028站:{}条", station1043List.size(), station1028List.size()); return resultData; } catch (Exception e) { log.error("从WCS获取人工拣选数据失败: {}", e.getMessage(), e); return null; } } /** * 获取指定工作站的数据 * * @param url WCS API地址 * @param devCode 设备编码(工作站号) * @param mapper JSON解析器 * @return 工作站数据列表 */ private List> getStationData(String url, String devCode, ObjectMapper mapper) { List> stationList = new ArrayList<>(); try { // 构造POST请求参数 - 直接传devCode字符串 String jsonBody = "\"" + devCode + "\""; String wcsResponse = HttpUtils.doPost(url, jsonBody, null); log.debug("WCS API返回数据 ({}站): {}", devCode, wcsResponse); // 解析JSON数据 JsonNode rootNode = mapper.readTree(wcsResponse); // 获取sortingStations数组 if (!rootNode.has("sortingStations")) { log.warn("WCS返回数据中没有sortingStations ({}站)", devCode); return stationList; } JsonNode sortingStations = rootNode.get("sortingStations"); // 处理返回的数据 for (JsonNode station : sortingStations) { String sortingStation = station.get("sortingStation").asText(); JsonNode materials = station.get("materials"); // 只处理匹配的工作站数据 if (devCode.equals(sortingStation) && materials != null && materials.isArray()) { for (JsonNode material : materials) { Map item = convertWcsMaterialToPickingItem(material); stationList.add(item); } } } } catch (Exception e) { log.error("获取{}站数据失败: {}", devCode, e.getMessage(), e); } return stationList; } /** * 将WCS返回的material数据转换为人工拣选前端需要的格式 * * @param material WCS返回的物料数据 * @return 前端表格数据格式 */ private Map convertWcsMaterialToPickingItem(JsonNode material) { Map item = new HashMap<>(); // 拣选托盘码 (源托盘) item.put("pickingBatchNo", material.has("sourcePalletCode") ? material.get("sourcePalletCode").asText() : ""); // 拣选物料名称 (SKU) item.put("pickingMaterialName", material.has("sku") ? material.get("sku").asText() : ""); // RFID条码 item.put("rfidBarcode", material.has("rfidBarcode") ? material.get("rfidBarcode").asText() : ""); // 状态 (根据isCompleted判断) boolean isCompleted = material.has("isCompleted") && material.get("isCompleted").asBoolean(); item.put("status", isCompleted ? "完成" : "等待分拣"); // 存放位置 (工作站编号) item.put("storageLocation", material.has("sortingStation") ? material.get("sortingStation").asText() : ""); return item; } /** * 创建空的人工拣选数据 * * @return 空的人工拣选数据结构 */ private Map createEmptyManualPickingData() { Map emptyData = new HashMap<>(); emptyData.put("station1043List", new ArrayList<>()); emptyData.put("station1028List", new ArrayList<>()); return emptyData; } /** * 每5秒检查机械臂拣选数据并推送 * *

注意:这个间隔可以根据实际需求调整

*
    *
  • 如果数据变化频繁,可以缩短间隔(如2-3秒)
  • *
  • 如果数据变化不频繁,可以延长间隔(如10-15秒)
  • *
* *

配置开关:

*
    *
  • dashboard.push.enabled - 看板推送总开关
  • *
*/ @Scheduled(fixedRate = 10000) public void pushRobotPickingData() { // 检查总开关 if (!dashboardPushEnabled) { log.trace("看板推送已禁用"); return; } try { // 从WCS Board API获取机械臂拣选数据 Map data = getRobotPickingDataFromWcs(); // 如果返回null,转换为空数据(避免前端显示过期数据) if (data == null) { data = createEmptyData(); } // 计算数据哈希值 int currentHash = data.hashCode(); webSocketService.pushRobotPickingData(data); lastDataHash.put("robot-picking", currentHash); } catch (Exception e) { log.error("推送机械臂拣选数据失败,推送空数据清空前端列表: {}", e.getMessage(), e); // 异常时推送空数据,避免前端显示过期数据 try { Map emptyData = createEmptyData(); webSocketService.pushRobotPickingData(emptyData); lastDataHash.put("robot-picking", emptyData.hashCode()); } catch (Exception ex) { log.error("推送空数据失败: {}", ex.getMessage()); } } } /** * 从WCS Board API获取机械臂拣选数据 * * @return 机械臂拣选数据 */ private Map getRobotPickingDataFromWcs() { try { // 调用WCS Board API String url = wcsBoardApi + "WmsDashboard/auto-sorting-info"; String wcsResponse = HttpUtils.doGet(url, null, null); // 解析JSON数据 ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(wcsResponse); // 检查返回码 int resCode = rootNode.get("resCode").asInt(); if (resCode != 200) { log.warn("WCS API返回错误: code={}", resCode); return null; } // 获取sortingStations数组 JsonNode resData = rootNode.get("resData"); if (resData == null || !resData.has("sortingStations")) { log.warn("WCS返回数据中没有sortingStations"); return createEmptyData(); } JsonNode sortingStations = resData.get("sortingStations"); // 按照sortingStation分类处理数据 List> containerList = new ArrayList<>(); // 1071-周转箱 List> materialList = new ArrayList<>(); // 1060-原材 for (JsonNode station : sortingStations) { String sortingStation = station.get("sortingStation").asText(); JsonNode materials = station.get("materials"); if (materials != null && materials.isArray()) { for (JsonNode material : materials) { Map item = convertMaterialToItem(material, sortingStation); // 根据工作站分类 if ("1071".equals(sortingStation)) { containerList.add(item); } else if ("1060".equals(sortingStation)) { materialList.add(item); } } } } // 构造返回数据 Map resultData = new HashMap<>(); resultData.put("containerList", containerList); resultData.put("materialList", materialList); return resultData; } catch (Exception e) { log.error("从WCS获取机械臂拣选数据失败: {}", e.getMessage()); return null; } } /** * 将WCS返回的material数据转换为前端需要的格式 * * @param material WCS返回的物料数据 * @param sortingStation 工作站编号 * @return 前端表格数据格式 */ private Map convertMaterialToItem(JsonNode material, String sortingStation) { Map item = new HashMap<>(); // 拣选托盘码 (源托盘) item.put("pickingBatchNo", material.has("sourcePalletCode") ? material.get("sourcePalletCode").asText() : ""); // 拣选物料名称 (SKU) item.put("pickingMaterialName", material.has("sku") ? material.get("sku").asText() : ""); // RFID条码 item.put("rfidBarcode", material.has("rfidBarcode") ? material.get("rfidBarcode").asText() : ""); // 状态 (根据isCompleted判断) boolean isCompleted = material.has("isCompleted") && material.get("isCompleted").asBoolean(); item.put("status", isCompleted ? "完成" : "等待分拣"); // 存放托盘码 (目标托盘) item.put("storageBatchNo", material.has("targetPalletCode") ? material.get("targetPalletCode").asText() : ""); // 存放位置 (工作站编号) item.put("storageLocation", material.has("sortingStation") ? material.get("sortingStation").asText() : sortingStation); return item; } /** * 创建空数据 * * @return 空的数据结构 */ private Map createEmptyData() { Map emptyData = new HashMap<>(); emptyData.put("containerList", new ArrayList<>()); emptyData.put("materialList", new ArrayList<>()); return emptyData; } /** * 判断数据是否为空 * * @param data 待检查的数据 * @return true=数据为空,false=数据不为空 */ private boolean isDataEmpty(Map data) { if (data == null || data.isEmpty()) { return true; } List containerList = (List) data.get("containerList"); List materialList = (List) data.get("materialList"); return (containerList == null || containerList.isEmpty()) && (materialList == null || materialList.isEmpty()); } /** * 每5秒检查分切区看板数据并推送 * *

数据来源:

*
    *
  • view_board_slitting_assist_arm - 助力臂区数据
  • *
  • view_board_slitting_inbound - 分切入库区数据
  • *
*/ @Scheduled(fixedRate = 10000) public void pushSlittingBoardData() { // 检查总开关 if (!dashboardPushEnabled) { log.trace("看板推送已禁用"); return; } try { // 从数据库视图获取分切区数据 Map data = getSlittingBoardDataFromDb(); // 如果返回null,转换为空数据 if (data == null) { data = createEmptySlittingData(); } // 计算数据哈希值 int currentHash = data.hashCode(); webSocketService.pushSlittingBoardData(data); lastDataHash.put("slitting-board", currentHash); } catch (Exception e) { log.error("推送分切区看板数据失败,推送空数据清空前端列表: {}", e.getMessage(), e); // 异常时推送空数据,避免前端显示过期数据 try { Map emptyData = createEmptySlittingData(); webSocketService.pushSlittingBoardData(emptyData); lastDataHash.put("slitting-board", emptyData.hashCode()); } catch (Exception ex) { log.error("推送空数据失败: {}", ex.getMessage()); } } } /** * 从数据库视图获取分切区看板数据 * * @return 分切区看板数据 */ private Map getSlittingBoardDataFromDb() { try { // 查询助力臂区数据 List> assistArmList = dashboardDao.querySlittingAssistArmData(); log.debug("查询到助力臂区数据: {}条", assistArmList != null ? assistArmList.size() : 0); // 查询分切入库区数据 List> inboundList = dashboardDao.querySlittingInboundData(); log.debug("查询到分切入库区数据: {}条", inboundList != null ? inboundList.size() : 0); // 构造返回数据 Map resultData = new HashMap<>(); resultData.put("assistArmList", assistArmList != null ? assistArmList : new ArrayList<>()); resultData.put("slittingInboundList", inboundList != null ? inboundList : new ArrayList<>()); return resultData; } catch (Exception e) { log.error("从数据库获取分切区看板数据失败: {}", e.getMessage(), e); return null; } } /** * 创建空的分切区数据 * * @return 空的分切区数据结构 */ private Map createEmptySlittingData() { Map emptyData = new HashMap<>(); emptyData.put("assistArmList", new ArrayList<>()); emptyData.put("slittingInboundList", new ArrayList<>()); return emptyData; } /** * 判断分切区数据是否为空 * * @param data 待检查的数据 * @return true=数据为空,false=数据不为空 */ private boolean isSlittingDataEmpty(Map data) { if (data == null || data.isEmpty()) { return true; } List assistArmList = (List) data.get("assistArmList"); List inboundList = (List) data.get("slittingInboundList"); return (assistArmList == null || assistArmList.isEmpty()) && (inboundList == null || inboundList.isEmpty()); } /** * 每5秒检查智能立体仓库看板数据并推送 * *

数据来源:

*
    *
  • 任务统计数据 - queryWarehouseTaskStats
  • *
  • 库位利用率 - queryWarehouseStorageUtilization
  • *
  • 机器人状态 - queryWarehouseRobotStatus
  • *
  • AGV状态 - queryWarehouseAgvStatus
  • *
  • 领料申请单统计 - queryWarehouseMaterialRequestStats
  • *
  • 发货统计 - queryWarehouseShipmentStats
  • *
*/ @Scheduled(fixedRate = 10000) public void pushWarehouse3dBoardData() { // 检查总开关 if (!dashboardPushEnabled) { log.trace("看板推送已禁用"); return; } try { // 从数据库获取立体仓库看板数据 Map data = getWarehouse3dBoardDataFromDb(); // 如果返回null,转换为空数据 if (data == null) { data = createEmptyWarehouse3dData(); } // 计算数据哈希值 int currentHash = data.hashCode(); webSocketService.pushWarehouse3dBoardData(data); lastDataHash.put("warehouse-3d", currentHash); } catch (Exception e) { log.error("推送智能立体仓库看板数据失败,推送空数据: {}", e.getMessage(), e); // 异常时推送空数据,避免前端显示过期数据 try { Map emptyData = createEmptyWarehouse3dData(); webSocketService.pushWarehouse3dBoardData(emptyData); lastDataHash.put("warehouse-3d", emptyData.hashCode()); } catch (Exception ex) { log.error("推送空数据失败: {}", ex.getMessage()); } } } /** * 从数据库获取智能立体仓库看板数据 * * @return 智能立体仓库看板数据 */ private Map getWarehouse3dBoardDataFromDb() { try { log.debug("开始从数据库获取智能立体仓库看板数据"); // 查询任务统计数据 Map taskStats = dashboardDao.queryWarehouseTaskStats(); log.debug("任务统计数据: {}", taskStats); // 查询库位利用率数据(从WCS Board API获取) Map storageUtilization = getInventoryStatsFromWcs(); log.debug("库位利用率数据: {}", storageUtilization); // 查询机器人状态数据(从WMS Dashboard API获取) List> robotStatus = getRobotSortingInfoFromWms(); log.debug("查询到机器人状态数据: {}条", robotStatus != null ? robotStatus.size() : 0); // 查询AGV状态数据(从TUSK系统获取) List> agvStatus = getAgvStatusFromTusk(); log.debug("查询到AGV状态数据: {}条", agvStatus != null ? agvStatus.size() : 0); // 查询领料申请单统计 //Map materialRequestStats = dashboardDao.queryWarehouseMaterialRequestStats(); Map materialRequestStats = new HashMap<>(); log.debug("领料申请单统计: {}", materialRequestStats); // 查询发货统计 //Map shipmentStats = dashboardDao.queryWarehouseShipmentStats(); Map shipmentStats = new HashMap<>(); log.debug("发货统计: {}", shipmentStats); // 查询库存趋势数据 List> rawMaterialTrend = dashboardDao.queryRawMaterialInventoryTrend(); log.debug("查询到原材料库存趋势数据: {}条", rawMaterialTrend != null ? rawMaterialTrend.size() : 0); List> specifiedMaterialTrend = dashboardDao.querySpecifiedMaterialInventoryTrend(); log.debug("查询到规格料库存趋势数据: {}条", specifiedMaterialTrend != null ? specifiedMaterialTrend.size() : 0); List> finishedGoodsTrend = dashboardDao.queryFinishedGoodsInventoryTrend(); log.debug("查询到产成品库存趋势数据: {}条", finishedGoodsTrend != null ? finishedGoodsTrend.size() : 0); // 构造返回数据 Map resultData = new HashMap<>(); resultData.put("taskData", taskStats != null ? taskStats : new HashMap<>()); resultData.put("storageData", storageUtilization != null ? storageUtilization : new HashMap<>()); resultData.put("robotData", robotStatus != null ? robotStatus : new ArrayList<>()); resultData.put("agvData", agvStatus != null ? agvStatus : new ArrayList<>()); resultData.put("materialRequestData", materialRequestStats != null ? materialRequestStats : new HashMap<>()); resultData.put("shipmentData", shipmentStats != null ? shipmentStats : new HashMap<>()); // 添加库存趋势数据 resultData.put("rawMaterialTrend", rawMaterialTrend != null ? rawMaterialTrend : new ArrayList<>()); resultData.put("specifiedMaterialTrend", specifiedMaterialTrend != null ? specifiedMaterialTrend : new ArrayList<>()); resultData.put("finishedGoodsTrend", finishedGoodsTrend != null ? finishedGoodsTrend : new ArrayList<>()); log.debug("智能立体仓库看板数据组装完成"); return resultData; } catch (Exception e) { log.error("从数据库获取智能立体仓库看板数据失败: {}", e.getMessage(), e); return null; } } /** * 从WCS Board API获取库存统计数据 * *

API说明:

*
    *
  • 接口地址: /api/WmsDashboard/inventory-stats
  • *
  • 返回托盘库存统计:平托、框架托、钢托
  • *
  • 返回空容器库存数量
  • *
* *

数据转换:

*
    *
  • flatPallet(平托) -> flatPallet
  • *
  • framePallet(框架托) -> guardPallet(围挡托盘)
  • *
  • steelPallet(钢托) -> steelPallet
  • *
  • emptyContainerInventory -> otherPallet(其他)
  • *
* * @return 库存统计数据 */ private Map getInventoryStatsFromWcs() { try { // 调用WCS Board API String url = wcsBoardApi + "WmsDashboard/inventory-stats"; log.debug("调用WCS库存统计API: {}", url); String wcsResponse = HttpUtils.doGet(url, null, null); log.debug("WCS API返回数据: {}", wcsResponse); // 解析JSON数据 ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(wcsResponse); // 检查返回码 int resCode = rootNode.get("resCode").asInt(); if (resCode != 200) { String resMsg = rootNode.has("resMsg") ? rootNode.get("resMsg").asText() : "未知错误"; log.warn("WCS API返回错误: code={}, msg={}", resCode, resMsg); return createEmptyStorageData(); } // 获取resData数据 JsonNode resData = rootNode.get("resData"); if (resData == null || resData.isNull()) { log.warn("WCS返回数据中没有resData"); return createEmptyStorageData(); } // 获取materialInventory(物料库存) JsonNode materialInventory = resData.get("materialInventory"); if (materialInventory == null || materialInventory.isNull()) { log.warn("WCS返回数据中没有materialInventory"); return createEmptyStorageData(); } // 提取物料盘各类托盘数量 int materialFlatPallet = materialInventory.has("flatPallet") ? materialInventory.get("flatPallet").asInt() : 0; int materialFramePallet = materialInventory.has("framePallet") ? materialInventory.get("framePallet").asInt() : 0; int materialSteelPallet = materialInventory.has("steelPallet") ? materialInventory.get("steelPallet").asInt() : 0; // 提取空容器各类托盘数量 JsonNode emptyContainerInventory = resData.get("emptyContainerInventory"); int emptyFlatPallet = 0; int emptyFramePallet = 0; int emptySteelPallet = 0; if (emptyContainerInventory != null && !emptyContainerInventory.isNull()) { emptyFlatPallet = emptyContainerInventory.has("flatPallet") ? emptyContainerInventory.get("flatPallet").asInt() : 0; emptyFramePallet = emptyContainerInventory.has("framePallet") ? emptyContainerInventory.get("framePallet").asInt() : 0; emptySteelPallet = emptyContainerInventory.has("steelPallet") ? emptyContainerInventory.get("steelPallet").asInt() : 0; } // 计算总使用库位和利用率(物料盘+空盘) int usedSlots = materialFlatPallet + materialFramePallet + materialSteelPallet + emptyFlatPallet + emptyFramePallet + emptySteelPallet; int totalSlots = 1960; // 固定值 double utilizationRate = totalSlots > 0 ? Math.round((double) usedSlots / totalSlots * 1000.0) / 10.0 : 0.0; // 构造物料盘库存数据 Map materialInventoryData = new HashMap<>(); materialInventoryData.put("flatPallet", materialFlatPallet+emptyFlatPallet); // 物料盘-平托 materialInventoryData.put("framePallet", materialFramePallet+emptyFramePallet); // 物料盘-围框托盘 materialInventoryData.put("steelPallet", materialSteelPallet+emptySteelPallet); // 物料盘-钢底托 // 构造空盘库存数据 Map emptyContainerInventoryData = new HashMap<>(); emptyContainerInventoryData.put("flatPallet", emptyFlatPallet); // 空盘-平托 emptyContainerInventoryData.put("framePallet", emptyFramePallet); // 空盘-围框托盘 emptyContainerInventoryData.put("steelPallet", emptySteelPallet); // 空盘-钢底托 // 构造返回数据 Map storageData = new HashMap<>(); storageData.put("totalSlots", totalSlots); // 总库位数 storageData.put("usedSlots", usedSlots); // 已使用库位数 storageData.put("utilizationRate", utilizationRate); // 利用率 storageData.put("materialInventory", materialInventoryData); // 物料盘库存 storageData.put("emptyContainerInventory", emptyContainerInventoryData); // 空盘库存 log.info("库存统计数据获取成功 - 物料盘[平托:{}, 框架托:{}, 钢托:{}], 空盘[平托:{}, 框架托:{}, 钢托:{}], 总使用:{}, 利用率:{}%", materialFlatPallet, materialFramePallet, materialSteelPallet, emptyFlatPallet, emptyFramePallet, emptySteelPallet, usedSlots, utilizationRate); return storageData; } catch (Exception e) { log.error("从WCS获取库存统计数据失败: {}", e.getMessage(), e); return createEmptyStorageData(); } } /** * 创建空的库存统计数据 * * @return 空的库存统计数据结构 */ private Map createEmptyStorageData() { // 构造空的物料盘库存数据 Map emptyMaterialInventory = new HashMap<>(); emptyMaterialInventory.put("flatPallet", 0); emptyMaterialInventory.put("framePallet", 0); emptyMaterialInventory.put("steelPallet", 0); // 构造空的空盘库存数据 Map emptyContainerInventory = new HashMap<>(); emptyContainerInventory.put("flatPallet", 0); emptyContainerInventory.put("framePallet", 0); emptyContainerInventory.put("steelPallet", 0); // 构造返回数据 Map emptyData = new HashMap<>(); emptyData.put("totalSlots", 1960); emptyData.put("usedSlots", 0); emptyData.put("utilizationRate", 0.0); emptyData.put("materialInventory", emptyMaterialInventory); emptyData.put("emptyContainerInventory", emptyContainerInventory); return emptyData; } /** * 创建空的智能立体仓库数据 * * @return 空的智能立体仓库数据结构 */ private Map createEmptyWarehouse3dData() { Map emptyData = new HashMap<>(); emptyData.put("taskData", new HashMap<>()); emptyData.put("storageData", new HashMap<>()); emptyData.put("robotData", new ArrayList<>()); emptyData.put("agvData", new ArrayList<>()); emptyData.put("materialRequestData", new HashMap<>()); emptyData.put("shipmentData", new HashMap<>()); return emptyData; } /** * 判断智能立体仓库数据是否为空 * * @param data 待检查的数据 * @return true=数据为空,false=数据不为空 */ private boolean isWarehouse3dDataEmpty(Map data) { if (data == null || data.isEmpty()) { return true; } Map taskData = (Map) data.get("taskData"); Map storageData = (Map) data.get("storageData"); List robotData = (List) data.get("robotData"); List agvData = (List) data.get("agvData"); return (taskData == null || taskData.isEmpty()) && (storageData == null || storageData.isEmpty()) && (robotData == null || robotData.isEmpty()) && (agvData == null || agvData.isEmpty()); } /** * 从TUSK系统获取AGV状态数据 * *

数据转换说明:

*
    *
  • 从TUSK获取原始AGV状态
  • *
  • 转换为看板需要的格式
  • *
  • 映射状态码为状态文本
  • *
* * @return AGV状态列表 */ private List> getAgvStatusFromTusk() { List> agvList = new ArrayList<>(); try { // 如果TUSK客户端服务未配置,返回空列表 if (tuskClientService == null) { log.debug("TUSK客户端服务未配置,跳过AGV状态查询"); return agvList; } // 调用TUSK接口获取在线AGV列表 TuskResponse> response = tuskClientService.getOnlineRobots(); if (!response.isSuccess() || response.getData() == null) { log.warn("从TUSK获取AGV状态失败: {}", response.getMsg()); return agvList; } // 转换TUSK数据为看板格式 List tuskAgvList = response.getData(); for (AgvStatus agvStatus : tuskAgvList) { Map agv = new HashMap<>(); // AGV编号 agv.put("id", agvStatus.getId()); agv.put("name", "AGV#" + agvStatus.getId()); // 状态转换 String status = convertAgvStatus(agvStatus.getAgvStat()); agv.put("status", status.toLowerCase()); // working/idle/charging/error agv.put("statusText", getAgvStatusText(agvStatus.getAgvStat())); // 电量 agv.put("battery", agvStatus.getSoc()); // 当前任务数(根据状态判断:运行中为1,否则为0) int tasks = (agvStatus.getAgvStat() >= 1 && agvStatus.getAgvStat() <= 12) ? 1 : 0; agv.put("tasks", tasks); agvList.add(agv); } log.debug("成功从TUSK获取{}个AGV状态", agvList.size()); } catch (Exception e) { log.error("从TUSK获取AGV状态异常: {}", e.getMessage(), e); } return agvList; } /** * 转换AGV状态码为标准状态 * * @param agvStat TUSK系统的AGV状态码 * @return 标准状态 (working/idle/charging/error) */ private String convertAgvStatus(Integer agvStat) { if (agvStat == null) { return "idle"; } if (agvStat == 0) { return "idle"; // 空闲 } else if (agvStat >= 1 && agvStat <= 12) { return "working"; // 运行中 } else if (agvStat == 13) { return "charging"; // 充电中 } else if (agvStat >= 128) { return "error"; // 异常状态 } return "idle"; } /** * 获取AGV状态文本(中文) * * @param agvStat TUSK系统的AGV状态码 * @return 状态文本 */ private String getAgvStatusText(Integer agvStat) { if (agvStat == null) { return "空闲"; } if (agvStat == 0) { return "空闲"; } else if (agvStat == 1) { return "运行中"; } else if (agvStat == 2) { return "直线运动中"; } else if (agvStat == 3) { return "旋转中"; } else if (agvStat == 13) { return "充电中"; } else if (agvStat == 23) { return "暂停"; } else if (agvStat == 128) { return "异常状态"; } else if (agvStat == 129) { return "急停"; } else if (agvStat == 130) { return "碰撞告警"; } else if (agvStat == 131) { return "告警"; } else if (agvStat >= 1 && agvStat <= 12) { return "运行中"; } else if (agvStat >= 128) { return "异常"; } return "未知状态"; } /** * 每5秒检查成品入库出库区看板数据并推送 * *

数据来源:

*
    *
  • view_board_finish_package - 成品包装区数据
  • *
  • view_board_finish_inbound - 成品入库区数据
  • *
*/ @Scheduled(fixedRate = 10000) public void pushFinishedProductBoardData() { // 检查总开关 if (!dashboardPushEnabled) { log.trace("看板推送已禁用"); return; } try { // 从数据库视图获取成品区数据 Map data = getFinishedProductBoardDataFromDb(); // 如果返回null,转换为空数据 if (data == null) { data = createEmptyFinishedProductData(); } // 计算数据哈希值 int currentHash = data.hashCode(); webSocketService.pushFinishedProductBoardData(data); lastDataHash.put("finished-product", currentHash); } catch (Exception e) { log.error("推送成品入库出库区看板数据失败,推送空数据清空前端列表: {}", e.getMessage(), e); // 异常时推送空数据,避免前端显示过期数据 try { Map emptyData = createEmptyFinishedProductData(); webSocketService.pushFinishedProductBoardData(emptyData); lastDataHash.put("finished-product", emptyData.hashCode()); } catch (Exception ex) { log.error("推送空数据失败: {}", ex.getMessage()); } } } /** * 从数据库视图获取成品入库出库区看板数据 * * @return 成品入库出库区看板数据 */ private Map getFinishedProductBoardDataFromDb() { try { // 查询成品包装区数据 List> packageList = dashboardDao.queryFinishPackageData(); log.debug("查询到成品包装区数据: {}条", packageList != null ? packageList.size() : 0); // 查询成品入库区数据 List> inboundList = dashboardDao.queryFinishInboundData(); log.debug("查询到成品入库区数据: {}条", inboundList != null ? inboundList.size() : 0); // 构造返回数据 Map resultData = new HashMap<>(); resultData.put("packagingList", packageList != null ? packageList : new ArrayList<>()); resultData.put("inboundList", inboundList != null ? inboundList : new ArrayList<>()); return resultData; } catch (Exception e) { log.error("从数据库获取成品入库出库区看板数据失败: {}", e.getMessage(), e); return null; } } /** * 创建空的成品入库出库区数据 * * @return 空的成品入库出库区数据结构 */ private Map createEmptyFinishedProductData() { Map emptyData = new HashMap<>(); emptyData.put("packagingList", new ArrayList<>()); emptyData.put("inboundList", new ArrayList<>()); return emptyData; } /** * 判断成品入库出库区数据是否为空 * * @param data 待检查的数据 * @return true=数据为空,false=数据不为空 */ private boolean isFinishedProductDataEmpty(Map data) { if (data == null || data.isEmpty()) { return true; } List packageList = (List) data.get("packagingList"); List inboundList = (List) data.get("inboundList"); return (packageList == null || packageList.isEmpty()) && (inboundList == null || inboundList.isEmpty()); } /** * 每5秒检查原材收货区看板数据并推送 * *

数据来源:

*
    *
  • view_board_receiving_receive - 原材收货区数据
  • *
  • view_board_receiving_inbound - 原材入库区数据
  • *
*/ @Scheduled(fixedRate = 10000) public void pushMaterialReceivingBoardData() { // 检查总开关 if (!dashboardPushEnabled) { log.trace("看板推送已禁用"); return; } try { // 从数据库视图获取原材收货区数据 Map data = getMaterialReceivingBoardDataFromDb(); // 如果返回null,转换为空数据 if (data == null) { data = createEmptyMaterialReceivingData(); } // 计算数据哈希值 int currentHash = data.hashCode(); webSocketService.pushMaterialReceivingBoardData(data); lastDataHash.put("material-receiving", currentHash); } catch (Exception e) { log.error("推送原材收货区看板数据失败,推送空数据清空前端列表: {}", e.getMessage(), e); // 异常时推送空数据,避免前端显示过期数据 try { Map emptyData = createEmptyMaterialReceivingData(); webSocketService.pushMaterialReceivingBoardData(emptyData); lastDataHash.put("material-receiving", emptyData.hashCode()); } catch (Exception ex) { log.error("推送空数据失败: {}", ex.getMessage()); } } } /** * 从数据库视图获取原材收货区看板数据 * * @return 原材收货区看板数据 */ private Map getMaterialReceivingBoardDataFromDb() { try { // 查询原材收货区数据 List> receiveList = dashboardDao.queryReceivingReceiveData(); log.debug("查询到原材收货区数据: {}条", receiveList != null ? receiveList.size() : 0); // 查询原材入库区数据 List> inboundList = dashboardDao.queryReceivingInboundData(); log.debug("查询到原材入库区数据: {}条", inboundList != null ? inboundList.size() : 0); // 构造返回数据 Map resultData = new HashMap<>(); resultData.put("receivingList", receiveList != null ? receiveList : new ArrayList<>()); resultData.put("inboundList", inboundList != null ? inboundList : new ArrayList<>()); return resultData; } catch (Exception e) { log.error("从数据库获取原材收货区看板数据失败: {}", e.getMessage(), e); return null; } } /** * 创建空的原材收货区数据 * * @return 空的原材收货区数据结构 */ private Map createEmptyMaterialReceivingData() { Map emptyData = new HashMap<>(); emptyData.put("receivingList", new ArrayList<>()); emptyData.put("inboundList", new ArrayList<>()); return emptyData; } /** * 判断原材收货区数据是否为空 * * @param data 待检查的数据 * @return true=数据为空,false=数据不为空 */ private boolean isMaterialReceivingDataEmpty(Map data) { if (data == null || data.isEmpty()) { return true; } List receiveList = (List) data.get("receivingList"); List inboundList = (List) data.get("inboundList"); return (receiveList == null || receiveList.isEmpty()) && (inboundList == null || inboundList.isEmpty()); } /** * 从WMS Dashboard API获取机械臂分拣信息 * *

数据转换说明:

*
    *
  • 从WMS获取原始机械臂分拣状态
  • *
  • 转换为看板需要的格式
  • *
  • 映射工作模式和设备状态枚举值
  • *
  • 1071=机械手1, 1060=机械手2
  • *
* * @return 机械臂分拣状态列表 */ private List> getRobotSortingInfoFromWms() { List> robotList = new ArrayList<>(); try { // 调用WMS Dashboard API(使用配置文件中的URL) String url = wcsBoardApi + "WmsDashboard/robot-sorting-info"; log.debug("调用WMS机械臂分拣API: {}", url); String wmsResponse = HttpUtils.doGet(url, null, null); log.debug("WMS API返回数据: {}", wmsResponse); // 解析JSON数据 ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(wmsResponse); // 检查返回码 int resCode = rootNode.get("resCode").asInt(); if (resCode != 200) { String resMsg = rootNode.has("resMsg") ? rootNode.get("resMsg").asText() : "未知错误"; log.warn("WMS机械臂分拣API返回错误: code={}, msg={}", resCode, resMsg); return robotList; } // 获取resData.sortingStations数组 if (!rootNode.has("resData") || !rootNode.get("resData").has("sortingStations")) { log.warn("WMS返回数据中没有sortingStations"); return robotList; } JsonNode sortingStations = rootNode.get("resData").get("sortingStations"); if (!sortingStations.isArray()) { log.warn("sortingStations不是数组格式"); return robotList; } // 转换数据为看板格式 for (JsonNode station : sortingStations) { Map robot = new HashMap<>(); // 分拣站编号 String sortingStation = station.has("sortingStation") ? station.get("sortingStation").asText() : ""; robot.put("id", sortingStation); // 机械手名称映射:1071=机械手1, 1060=机械手2 String robotName; if ("1071".equals(sortingStation)) { robotName = "机械手 1"; } else if ("1060".equals(sortingStation)) { robotName = "机械手 2"; } else { robotName = "机械手 " + sortingStation; } robot.put("name", robotName); // 待处理物料数量(任务数) int pendingMaterialCount = station.has("pendingMaterialCount") ? station.get("pendingMaterialCount").asInt() : 0; robot.put("tasks", pendingMaterialCount); // 工作模式(OperationMode枚举) int workModel = station.has("workModel") ? station.get("workModel").asInt() : 0; robot.put("workModel", workModel); robot.put("workModelText", getWorkModelText(workModel)); // 设备状态(DeviceStatus枚举) int workStatus = station.has("workStatus") ? station.get("workStatus").asInt() : 0; robot.put("workStatus", workStatus); robot.put("workStatusText", getWorkStatusText(workStatus)); // 转换为标准状态(working/idle/error/maintenance) String status = convertRobotStatus(workModel, workStatus); robot.put("status", status); robot.put("statusText", getWorkStatusText(workStatus)); robotList.add(robot); } log.info("从WMS获取到{}个机械臂分拣站状态", robotList.size()); } catch (Exception e) { log.error("从WMS获取机械臂分拣数据失败", e); } return robotList; } /** * 获取工作模式文本 * * @param workModel 工作模式枚举值 * @return 工作模式文本 */ private String getWorkModelText(int workModel) { switch (workModel) { case 1: return "自动模式"; case 2: return "半自动模式"; case 3: return "手动模式"; case 4: return "报警模式"; case 5: return "维护模式"; default: return "未定义"; } } /** * 获取设备状态文本 * * @param workStatus 设备状态枚举值 * @return 设备状态文本 */ private String getWorkStatusText(int workStatus) { switch (workStatus) { case 1: return "空闲"; case 2: return "取货中"; case 3: return "RFID检测中"; case 4: return "放货中"; case 5: return "等待上位反馈完成"; case 6: return "等待流线托盘到位"; default: return "未定义"; } } /** * 转换机械臂状态为标准状态 * * @param workModel 工作模式 * @param workStatus 设备状态 * @return 标准状态 (working/idle/error/maintenance) */ private String convertRobotStatus(int workModel, int workStatus) { // 报警模式 if (workModel == 4) { return "error"; } // 维护模式 if (workModel == 5) { return "maintenance"; } // 根据设备状态判断 if (workStatus == 1) { return "idle"; // 空闲 } else if (workStatus >= 2 && workStatus <= 6) { return "working"; // 工作中 } return "idle"; } }