7 changed files with 699 additions and 4 deletions
-
6build.gradle
-
1src/main/java/com/gaotao/config/ShiroConfig.java
-
68src/main/java/com/gaotao/config/WebSocketConfig.java
-
39src/main/java/com/gaotao/modules/dashboard/dao/DashboardDao.java
-
206src/main/java/com/gaotao/modules/dashboard/service/DashboardWebSocketService.java
-
352src/main/java/com/gaotao/modules/dashboard/task/DashboardPushTask.java
-
31src/main/resources/mapper/dashboard/DashboardDao.xml
@ -0,0 +1,68 @@ |
|||||
|
package com.gaotao.config; |
||||
|
|
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.messaging.simp.config.MessageBrokerRegistry; |
||||
|
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; |
||||
|
import org.springframework.web.socket.config.annotation.StompEndpointRegistry; |
||||
|
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; |
||||
|
|
||||
|
/** |
||||
|
* WebSocket 配置类 |
||||
|
* |
||||
|
* <p><b>功能说明:</b></p> |
||||
|
* <ul> |
||||
|
* <li>启用STOMP协议的WebSocket消息代理</li> |
||||
|
* <li>配置消息端点和订阅前缀</li> |
||||
|
* <li>支持跨域访问</li> |
||||
|
* <li>提供SockJS降级支持</li> |
||||
|
* </ul> |
||||
|
* |
||||
|
* <p><b>订阅主题说明:</b></p> |
||||
|
* <ul> |
||||
|
* <li>/topic/dashboard/manual-picking - 人工拣选看板</li> |
||||
|
* <li>/topic/dashboard/robot-picking - 机械臂拣选看板</li> |
||||
|
* <li>/topic/dashboard/slitting-board - 分切区看板</li> |
||||
|
* <li>/topic/dashboard/inventory-board - 库存分析看板</li> |
||||
|
* </ul> |
||||
|
* |
||||
|
* @author System |
||||
|
* @since 2025-01-23 |
||||
|
*/ |
||||
|
@Configuration |
||||
|
@EnableWebSocketMessageBroker |
||||
|
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { |
||||
|
|
||||
|
/** |
||||
|
* 配置消息代理 |
||||
|
* |
||||
|
* @param config 消息代理注册器 |
||||
|
*/ |
||||
|
@Override |
||||
|
public void configureMessageBroker(MessageBrokerRegistry config) { |
||||
|
// 启用简单消息代理,用于向客户端推送消息 |
||||
|
// /topic - 公共广播(一对多) |
||||
|
// /queue - 点对点(一对一) |
||||
|
config.enableSimpleBroker("/topic", "/queue"); |
||||
|
|
||||
|
// 客户端发送消息的目的地前缀 |
||||
|
config.setApplicationDestinationPrefixes("/app"); |
||||
|
|
||||
|
// 点对点消息的用户前缀 |
||||
|
config.setUserDestinationPrefix("/user"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 注册STOMP端点 |
||||
|
* |
||||
|
* @param registry STOMP端点注册器 |
||||
|
*/ |
||||
|
@Override |
||||
|
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
|
// 注册WebSocket端点:/ws/dashboard |
||||
|
// 前端通过此端点建立WebSocket连接 |
||||
|
registry.addEndpoint("/ws/dashboard") |
||||
|
.setAllowedOriginPatterns("*") // 允许所有跨域访问 |
||||
|
.withSockJS(); // 启用SockJS降级支持(当浏览器不支持WebSocket时) |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,39 @@ |
|||||
|
package com.gaotao.modules.dashboard.dao; |
||||
|
|
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* 看板数据访问接口 |
||||
|
* |
||||
|
* <p><b>功能说明:</b>从数据库视图获取看板实时数据</p> |
||||
|
* |
||||
|
* <p><b>数据来源:</b></p> |
||||
|
* <ul> |
||||
|
* <li>view_board_slitting_assist_arm - 分切区助力臂数据</li> |
||||
|
* <li>view_board_slitting_inbound - 分切区入库数据</li> |
||||
|
* </ul> |
||||
|
* |
||||
|
* @author System |
||||
|
* @since 2025-01-23 |
||||
|
*/ |
||||
|
@Mapper |
||||
|
public interface DashboardDao { |
||||
|
|
||||
|
/** |
||||
|
* 查询分切区助力臂数据 |
||||
|
* |
||||
|
* @return 助力臂区实时数据 |
||||
|
*/ |
||||
|
List<Map<String, Object>> querySlittingAssistArmData(); |
||||
|
|
||||
|
/** |
||||
|
* 查询分切区入库数据 |
||||
|
* |
||||
|
* @return 分切入库区实时数据 |
||||
|
*/ |
||||
|
List<Map<String, Object>> querySlittingInboundData(); |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,206 @@ |
|||||
|
package com.gaotao.modules.dashboard.service; |
||||
|
|
||||
|
import com.gaotao.common.utils.R; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.messaging.simp.SimpMessagingTemplate; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* 看板WebSocket推送服务 |
||||
|
* |
||||
|
* <p><b>功能说明:</b></p> |
||||
|
* <ul> |
||||
|
* <li>向订阅的前端看板推送实时数据</li> |
||||
|
* <li>支持按看板类型定向推送</li> |
||||
|
* <li>支持广播推送到所有看板</li> |
||||
|
* </ul> |
||||
|
* |
||||
|
* <p><b>订阅主题列表:</b></p> |
||||
|
* <ul> |
||||
|
* <li>/topic/dashboard/manual-picking - 人工拣选看板</li> |
||||
|
* <li>/topic/dashboard/robot-picking - 机械臂拣选看板</li> |
||||
|
* <li>/topic/dashboard/slitting-board - 分切区看板</li> |
||||
|
* <li>/topic/dashboard/inventory-board - 库存分析看板</li> |
||||
|
* </ul> |
||||
|
* |
||||
|
* @author System |
||||
|
* @since 2025-01-23 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Service |
||||
|
public class DashboardWebSocketService { |
||||
|
|
||||
|
@Autowired |
||||
|
private SimpMessagingTemplate messagingTemplate; |
||||
|
|
||||
|
/** |
||||
|
* 推送人工拣选看板数据 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void pushManualPickingData(Map<String, Object> data) { |
||||
|
log.debug("推送人工拣选看板数据"); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/manual-picking", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送人工拣选看板数据失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 推送机械臂拣选看板数据 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void pushRobotPickingData(Map<String, Object> data) { |
||||
|
log.debug("推送机械臂拣选看板数据: 周转箱{}条, 原材{}条", |
||||
|
data.get("containerList") != null ? ((java.util.List<?>)data.get("containerList")).size() : 0, |
||||
|
data.get("materialList") != null ? ((java.util.List<?>)data.get("materialList")).size() : 0); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/robot-picking", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送机械臂拣选看板数据失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 推送分切区看板数据 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void pushSlittingBoardData(Map<String, Object> data) { |
||||
|
log.debug("推送分切区看板数据"); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/slitting-board", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送分切区看板数据失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 推送库存分析看板数据 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void pushInventoryBoardData(Map<String, Object> data) { |
||||
|
log.debug("推送库存分析看板数据"); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/inventory-board", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送库存分析看板数据失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 推送成品入库出库区看板数据 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void pushFinishedProductBoardData(Map<String, Object> data) { |
||||
|
log.debug("推送成品入库出库区看板数据"); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/finished-product-board", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送成品入库出库区看板数据失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 推送原材收货区看板数据 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void pushMaterialReceivingBoardData(Map<String, Object> data) { |
||||
|
log.debug("推送原材收货区看板数据"); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/material-receiving-board", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送原材收货区看板数据失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 推送缓存区看板数据 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void pushBufferBoardData(Map<String, Object> data) { |
||||
|
log.debug("推送缓存区看板数据"); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/buffer-board", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送缓存区看板数据失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 推送车间AGV放料区看板数据 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void pushWorkshopFeedingBoardData(Map<String, Object> data) { |
||||
|
log.debug("推送车间AGV放料区看板数据"); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/workshop-feeding-board", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送车间AGV放料区看板数据失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 推送异常处理区看板数据 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void pushExceptionBoardData(Map<String, Object> data) { |
||||
|
log.debug("推送异常处理区看板数据"); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/exception-board", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送异常处理区看板数据失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 广播推送到所有看板 |
||||
|
* |
||||
|
* @param data 看板数据 |
||||
|
*/ |
||||
|
public void broadcastToAllDashboards(Map<String, Object> data) { |
||||
|
log.info("广播推送到所有看板"); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend("/topic/dashboard/broadcast", |
||||
|
R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("广播推送到所有看板失败: {}", e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 推送到指定主题 |
||||
|
* |
||||
|
* @param topic 主题名称 |
||||
|
* @param data 数据 |
||||
|
*/ |
||||
|
public void pushToTopic(String topic, Object data) { |
||||
|
log.debug("推送数据到主题: {}", topic); |
||||
|
try { |
||||
|
messagingTemplate.convertAndSend(topic, R.ok().put("data", data)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("推送数据到主题{}失败: {}", topic, e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,352 @@ |
|||||
|
package com.gaotao.modules.dashboard.task; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import com.gaotao.common.utils.HttpUtils; |
||||
|
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.*; |
||||
|
|
||||
|
/** |
||||
|
* 看板数据推送定时任务 |
||||
|
* |
||||
|
* <p><b>功能说明:</b></p> |
||||
|
* <ul> |
||||
|
* <li>定时从WCS Board API获取最新数据</li> |
||||
|
* <li>检测到数据变更后通过WebSocket推送到前端</li> |
||||
|
* <li>相比轮询,只在数据变更时推送,减少无效传输</li> |
||||
|
* </ul> |
||||
|
* |
||||
|
* <p><b>推送策略:</b></p> |
||||
|
* <ul> |
||||
|
* <li>每5秒检查一次机械臂拣选数据</li> |
||||
|
* <li>数据有变化时立即推送</li> |
||||
|
* <li>推送失败时记录日志,不影响下次推送</li> |
||||
|
* </ul> |
||||
|
* |
||||
|
* @author System |
||||
|
* @since 2025-01-23 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class DashboardPushTask { |
||||
|
|
||||
|
@Autowired |
||||
|
private DashboardWebSocketService webSocketService; |
||||
|
|
||||
|
@Value("${custom.wcs-board-api}") |
||||
|
private String wcsBoardApi; |
||||
|
|
||||
|
/** |
||||
|
* 上次推送的数据哈希值(用于检测数据变更) |
||||
|
*/ |
||||
|
private Map<String, Integer> lastDataHash = new HashMap<>(); |
||||
|
|
||||
|
@Autowired |
||||
|
private com.gaotao.modules.dashboard.dao.DashboardDao dashboardDao; |
||||
|
|
||||
|
/** |
||||
|
* 每5秒检查机械臂拣选数据并推送 |
||||
|
* |
||||
|
* <p>注意:这个间隔可以根据实际需求调整</p> |
||||
|
* <ul> |
||||
|
* <li>如果数据变化频繁,可以缩短间隔(如2-3秒)</li> |
||||
|
* <li>如果数据变化不频繁,可以延长间隔(如10-15秒)</li> |
||||
|
* </ul> |
||||
|
*/ |
||||
|
@Scheduled(fixedRate = 5000) |
||||
|
public void pushRobotPickingData() { |
||||
|
try { |
||||
|
// 从WCS Board API获取机械臂拣选数据 |
||||
|
Map<String, Object> data = getRobotPickingDataFromWcs(); |
||||
|
|
||||
|
// 如果返回null,转换为空数据(避免前端显示过期数据) |
||||
|
if (data == null) { |
||||
|
data = createEmptyData(); |
||||
|
} |
||||
|
|
||||
|
// 计算数据哈希值 |
||||
|
int currentHash = data.hashCode(); |
||||
|
int lastHash = lastDataHash.getOrDefault("robot-picking", 0); |
||||
|
|
||||
|
// 只在数据变更时推送(包括从有数据变为空数据) |
||||
|
if (currentHash != lastHash) { |
||||
|
boolean isEmpty = isDataEmpty(data); |
||||
|
if (isEmpty) { |
||||
|
log.info("=== 机械臂拣选数据为空,推送空数据清空前端列表 ==="); |
||||
|
} else { |
||||
|
int containerCount = ((List<?>) data.get("containerList")).size(); |
||||
|
int materialCount = ((List<?>) data.get("materialList")).size(); |
||||
|
log.info("=== 检测到机械臂拣选数据变更,推送到前端(周转箱:{}条,原材:{}条)===", |
||||
|
containerCount, materialCount); |
||||
|
} |
||||
|
webSocketService.pushRobotPickingData(data); |
||||
|
lastDataHash.put("robot-picking", currentHash); |
||||
|
} else { |
||||
|
log.debug("机械臂拣选数据无变化,跳过推送"); |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.error("推送机械臂拣选数据失败,推送空数据清空前端列表: {}", e.getMessage(), e); |
||||
|
// 异常时推送空数据,避免前端显示过期数据 |
||||
|
try { |
||||
|
Map<String, Object> emptyData = createEmptyData(); |
||||
|
webSocketService.pushRobotPickingData(emptyData); |
||||
|
lastDataHash.put("robot-picking", emptyData.hashCode()); |
||||
|
} catch (Exception ex) { |
||||
|
log.error("推送空数据失败: {}", ex.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 从WCS Board API获取机械臂拣选数据 |
||||
|
* |
||||
|
* @return 机械臂拣选数据 |
||||
|
*/ |
||||
|
private Map<String, Object> 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<Map<String, Object>> containerList = new ArrayList<>(); // 1071-周转箱 |
||||
|
List<Map<String, Object>> 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<String, Object> item = convertMaterialToItem(material, sortingStation); |
||||
|
|
||||
|
// 根据工作站分类 |
||||
|
if ("1071".equals(sortingStation)) { |
||||
|
containerList.add(item); |
||||
|
} else if ("1060".equals(sortingStation)) { |
||||
|
materialList.add(item); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 构造返回数据 |
||||
|
Map<String, Object> 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<String, Object> convertMaterialToItem(JsonNode material, String sortingStation) { |
||||
|
Map<String, Object> 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<String, Object> createEmptyData() { |
||||
|
Map<String, Object> emptyData = new HashMap<>(); |
||||
|
emptyData.put("containerList", new ArrayList<>()); |
||||
|
emptyData.put("materialList", new ArrayList<>()); |
||||
|
return emptyData; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断数据是否为空 |
||||
|
* |
||||
|
* @param data 待检查的数据 |
||||
|
* @return true=数据为空,false=数据不为空 |
||||
|
*/ |
||||
|
private boolean isDataEmpty(Map<String, Object> 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秒检查分切区看板数据并推送 |
||||
|
* |
||||
|
* <p><b>数据来源:</b></p> |
||||
|
* <ul> |
||||
|
* <li>view_board_slitting_assist_arm - 助力臂区数据</li> |
||||
|
* <li>view_board_slitting_inbound - 分切入库区数据</li> |
||||
|
* </ul> |
||||
|
*/ |
||||
|
@Scheduled(fixedRate = 5000) |
||||
|
public void pushSlittingBoardData() { |
||||
|
try { |
||||
|
// 从数据库视图获取分切区数据 |
||||
|
Map<String, Object> data = getSlittingBoardDataFromDb(); |
||||
|
|
||||
|
// 如果返回null,转换为空数据 |
||||
|
if (data == null) { |
||||
|
data = createEmptySlittingData(); |
||||
|
} |
||||
|
|
||||
|
// 计算数据哈希值 |
||||
|
int currentHash = data.hashCode(); |
||||
|
int lastHash = lastDataHash.getOrDefault("slitting-board", 0); |
||||
|
|
||||
|
// 只在数据变更时推送 |
||||
|
if (currentHash != lastHash) { |
||||
|
boolean isEmpty = isSlittingDataEmpty(data); |
||||
|
if (isEmpty) { |
||||
|
log.info("=== 分切区看板数据为空,推送空数据清空前端列表 ==="); |
||||
|
} else { |
||||
|
int assistArmCount = ((List<?>) data.get("assistArmList")).size(); |
||||
|
int inboundCount = ((List<?>) data.get("slittingInboundList")).size(); |
||||
|
log.info("=== 检测到分切区看板数据变更,推送到前端(助力臂:{}条,入库:{}条)===", |
||||
|
assistArmCount, inboundCount); |
||||
|
} |
||||
|
webSocketService.pushSlittingBoardData(data); |
||||
|
lastDataHash.put("slitting-board", currentHash); |
||||
|
} else { |
||||
|
log.debug("分切区看板数据无变化,跳过推送"); |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.error("推送分切区看板数据失败,推送空数据清空前端列表: {}", e.getMessage(), e); |
||||
|
// 异常时推送空数据,避免前端显示过期数据 |
||||
|
try { |
||||
|
Map<String, Object> emptyData = createEmptySlittingData(); |
||||
|
webSocketService.pushSlittingBoardData(emptyData); |
||||
|
lastDataHash.put("slitting-board", emptyData.hashCode()); |
||||
|
} catch (Exception ex) { |
||||
|
log.error("推送空数据失败: {}", ex.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 从数据库视图获取分切区看板数据 |
||||
|
* |
||||
|
* @return 分切区看板数据 |
||||
|
*/ |
||||
|
private Map<String, Object> getSlittingBoardDataFromDb() { |
||||
|
try { |
||||
|
// 查询助力臂区数据 |
||||
|
List<Map<String, Object>> assistArmList = dashboardDao.querySlittingAssistArmData(); |
||||
|
log.debug("查询到助力臂区数据: {}条", assistArmList != null ? assistArmList.size() : 0); |
||||
|
|
||||
|
// 查询分切入库区数据 |
||||
|
List<Map<String, Object>> inboundList = dashboardDao.querySlittingInboundData(); |
||||
|
log.debug("查询到分切入库区数据: {}条", inboundList != null ? inboundList.size() : 0); |
||||
|
|
||||
|
// 构造返回数据 |
||||
|
Map<String, Object> 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<String, Object> createEmptySlittingData() { |
||||
|
Map<String, Object> emptyData = new HashMap<>(); |
||||
|
emptyData.put("assistArmList", new ArrayList<>()); |
||||
|
emptyData.put("slittingInboundList", new ArrayList<>()); |
||||
|
return emptyData; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 判断分切区数据是否为空 |
||||
|
* |
||||
|
* @param data 待检查的数据 |
||||
|
* @return true=数据为空,false=数据不为空 |
||||
|
*/ |
||||
|
private boolean isSlittingDataEmpty(Map<String, Object> 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()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,31 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
|
||||
|
<mapper namespace="com.gaotao.modules.dashboard.dao.DashboardDao"> |
||||
|
|
||||
|
<!-- 查询分切区助力臂数据 --> |
||||
|
<select id="querySlittingAssistArmData" resultType="map"> |
||||
|
SELECT |
||||
|
storage_location AS storageLocation, |
||||
|
pallet_code AS palletCode, |
||||
|
picking_location AS pickingLocation, |
||||
|
material_name AS materialName, |
||||
|
quantity, |
||||
|
status |
||||
|
FROM view_board_slitting_assist_arm |
||||
|
ORDER BY picking_location ASC |
||||
|
</select> |
||||
|
|
||||
|
<!-- 查询分切区入库数据 --> |
||||
|
<select id="querySlittingInboundData" resultType="map"> |
||||
|
SELECT |
||||
|
storage_location AS storageLocation, |
||||
|
pallet_code AS palletCode, |
||||
|
task_type AS taskType, |
||||
|
status |
||||
|
FROM view_board_slitting_inbound |
||||
|
ORDER BY storage_location ASC |
||||
|
</select> |
||||
|
|
||||
|
</mapper> |
||||
|
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue