package com.vincent.rsf.openApi.service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.vincent.rsf.openApi.tv.TvMonitorRedisKeys; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; /** * WCS 站点状态:与 zy-wcs-gsl {@code OpenController#getStationStatus} 请求字段一致(stationIds); * 查询结果字段与 zy-monitor {@code WcsStationTimer} 解析一致(data 数组含 superTaskNo、autoing、loading 等) */ @Slf4j @Service public class WcsStationStatusService { @Resource @Qualifier("tvMonitorStringRedisTemplate") private StringRedisTemplate tvMonitorStringRedisTemplate; @Resource private ObjectMapper objectMapper; public Map handle(JsonNode body) { if (body == null || body.isNull()) { return okEmptyData(); } // 与 zy-wcs-gsl OpenController#getStationStatus(GetStationStatusParam) 一致:stationIds 为 null 或空则返回全部 if (body.has("stationIds")) { JsonNode sidArr = body.get("stationIds"); if (sidArr.isNull()) { return okQueryAll(); } if (!sidArr.isArray()) { return fail("stationIds 须为数组"); } if (sidArr.size() == 0) { return okQueryAll(); } return okQuery(sidArr); } JsonNode data = body.get("data"); if (data == null && body.isArray()) { data = body; } if (data != null && data.isArray()) { savePush(data); return okMsg("已写入站点状态"); } // 未带 stationIds、data 上报时按全量查询,与 stationIds 空数组一致 return okQueryAll(); } private Map okEmptyData() { Map m = new LinkedHashMap<>(); m.put("code", 200); m.put("data", objectMapper.createArrayNode()); return m; } /** Redis 中全部站点,对应 WCS 侧 stationIds 为空时 stationThread.getStatus() 全量 */ private Map okQueryAll() { ArrayNode out = objectMapper.createArrayNode(); Set keys = tvMonitorStringRedisTemplate.opsForHash() .keys(TvMonitorRedisKeys.TV_WCS_STATION_STATUS); for (Object k : keys) { if (k == null) { continue; } String raw = (String) tvMonitorStringRedisTemplate.opsForHash() .get(TvMonitorRedisKeys.TV_WCS_STATION_STATUS, k.toString()); if (!StringUtils.hasText(raw)) { continue; } try { int sid = Integer.parseInt(k.toString()); JsonNode stored = objectMapper.readTree(raw); out.add(toMonitorRow(sid, stored)); } catch (Exception e) { log.warn("站点 {} Redis 解析失败: {}", k, e.getMessage()); } } Map m = new LinkedHashMap<>(); m.put("code", 200); m.put("data", out); return m; } private Map okQuery(JsonNode stationIds) { ArrayNode out = objectMapper.createArrayNode(); for (JsonNode idNode : stationIds) { if (idNode == null || !idNode.isNumber()) { continue; } int sid = idNode.asInt(); String raw = (String) tvMonitorStringRedisTemplate.opsForHash() .get(TvMonitorRedisKeys.TV_WCS_STATION_STATUS, String.valueOf(sid)); if (!StringUtils.hasText(raw)) { continue; } try { JsonNode stored = objectMapper.readTree(raw); out.add(toMonitorRow(sid, stored)); } catch (Exception e) { log.warn("站点 {} Redis JSON 解析失败: {}", sid, e.getMessage()); } } Map m = new LinkedHashMap<>(); m.put("code", 200); m.put("data", out); return m; } private ObjectNode toMonitorRow(int stationId, JsonNode stored) { ObjectNode row = objectMapper.createObjectNode(); row.put("stationId", stationId); String taskNo = textFirst(stored, "taskNo", "superTaskNo"); row.put("superTaskNo", taskNo != null ? taskNo : ""); row.put("autoing", truthy(stored, "auto", "autoing")); row.put("loading", truthy(stored, "loading")); row.put("barcode", text(stored, "barcode")); row.put("errorMsg", text(stored, "errorMsg")); row.put("systemWarning", text(stored, "systemWarning")); if (stored.has("wrkDetls") && !stored.get("wrkDetls").isNull()) { row.set("wrkDetls", stored.get("wrkDetls")); } if (stored.has("ioType") && !stored.get("ioType").isNull()) { row.set("ioType", stored.get("ioType")); } if (stored.has("orderNo") && !stored.get("orderNo").isNull()) { row.put("orderNo", text(stored, "orderNo")); } return row; } private void savePush(JsonNode data) { for (JsonNode item : data) { if (item == null || !item.isObject()) { continue; } JsonNode sidNode = item.get("stationId"); if (sidNode == null || !sidNode.isNumber()) { continue; } int sid = sidNode.asInt(); ObjectNode norm = normalizeForStore(item); try { tvMonitorStringRedisTemplate.opsForHash().put( TvMonitorRedisKeys.TV_WCS_STATION_STATUS, String.valueOf(sid), objectMapper.writeValueAsString(norm)); } catch (Exception e) { log.warn("写入站点 {} 失败: {}", sid, e.getMessage()); } } log.info("WCS 站点状态已写入 Redis,条数={}", data.size()); } /** 与 zy-monitor WcsStationDto 字段对齐,统一 taskNo/auto 数值 */ private ObjectNode normalizeForStore(JsonNode item) { ObjectNode n = objectMapper.createObjectNode(); if (item.has("stationId")) { n.put("stationId", item.get("stationId").asInt()); } String taskNo = textFirst(item, "taskNo", "superTaskNo"); if (StringUtils.hasText(taskNo)) { n.put("taskNo", taskNo); } n.put("auto", truthy(item, "auto", "autoing") ? 1 : 0); n.put("loading", truthy(item, "loading") ? 1 : 0); if (item.has("barcode")) { n.put("barcode", text(item, "barcode")); } if (item.has("errorMsg")) { n.put("errorMsg", text(item, "errorMsg")); } if (item.has("systemWarning")) { n.put("systemWarning", text(item, "systemWarning")); } if (item.has("wrkDetls")) { n.set("wrkDetls", item.get("wrkDetls")); } if (item.has("ioType")) { n.set("ioType", item.get("ioType")); } if (item.has("orderNo")) { n.put("orderNo", text(item, "orderNo")); } if (item.has("outboundSeq")) { n.put("outboundSeq", text(item, "outboundSeq")); } return n; } private static String text(JsonNode n, String field) { JsonNode v = n.get(field); if (v == null || v.isNull()) { return ""; } return v.asText(""); } private static String textFirst(JsonNode n, String... fields) { for (String f : fields) { if (n.hasNonNull(f)) { String s = n.get(f).asText(""); if (StringUtils.hasText(s)) { return s; } } } return null; } private static boolean truthy(JsonNode n, String... fields) { for (String f : fields) { if (!n.has(f) || n.get(f).isNull()) { continue; } JsonNode v = n.get(f); if (v.isBoolean()) { return v.booleanValue(); } if (v.isNumber()) { return v.asInt() != 0; } if (v.isTextual()) { return "1".equals(v.asText()) || "true".equalsIgnoreCase(v.asText()); } } return false; } private static Map okMsg(String msg) { Map m = new LinkedHashMap<>(); m.put("code", 200); m.put("message", msg); return m; } private static Map fail(String msg) { Map m = new LinkedHashMap<>(); m.put("code", 400); m.put("message", msg); return m; } /** * RCS 轮询得到任务号后,合并 WMS queryTask(料箱号、ioType、wrkDetls 物料明细)到电视机 Redis */ public void upsertFromRcsPoll(String stationId, String taskNo, Map wmsData) { if (!StringUtils.hasText(stationId)) { return; } ObjectNode n = objectMapper.createObjectNode(); try { n.put("stationId", Integer.parseInt(stationId.trim())); } catch (NumberFormatException e) { n.put("stationId", stationId); } String tn = taskNo != null ? taskNo.trim() : ""; n.put("taskNo", tn); n.put("superTaskNo", tn); n.put("auto", 1); n.put("loading", StringUtils.hasText(tn) && !"0".equals(tn) ? 1 : 0); if (wmsData != null) { if (wmsData.get("barcode") != null) { n.put("barcode", String.valueOf(wmsData.get("barcode"))); } if (wmsData.get("ioType") != null) { n.set("ioType", objectMapper.valueToTree(wmsData.get("ioType"))); } if (wmsData.get("wrkDetls") != null) { n.set("wrkDetls", objectMapper.valueToTree(wmsData.get("wrkDetls"))); } } try { tvMonitorStringRedisTemplate.opsForHash().put( TvMonitorRedisKeys.TV_WCS_STATION_STATUS, stationId, objectMapper.writeValueAsString(n)); } catch (Exception e) { log.warn("tvWcsStationStatus stationId={} 写入失败: {}", stationId, e.getMessage()); } } public void clearStationSnapshot(String stationId) { if (!StringUtils.hasText(stationId)) { return; } tvMonitorStringRedisTemplate.opsForHash().delete(TvMonitorRedisKeys.TV_WCS_STATION_STATUS, stationId); } }