From b3fb3db3341ea98dac914f62dc94e59fe37e6b3f Mon Sep 17 00:00:00 2001
From: cl <1442464845@qq.com>
Date: 星期一, 13 四月 2026 09:00:30 +0800
Subject: [PATCH] 推送报警预写

---
 rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/RcsTvCallbackController.java |   70 ++++++++++++++
 rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/RcsTvCallbackService.java       |  146 +++++++++++++++++++++++++++++
 rsf-open-api/src/main/java/com/vincent/rsf/openApi/tv/TvMonitorRedisKeys.java              |   16 +++
 rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/TvMonitorRedisConfig.java        |   43 ++++++++
 4 files changed, 275 insertions(+), 0 deletions(-)

diff --git a/rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/TvMonitorRedisConfig.java b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/TvMonitorRedisConfig.java
new file mode 100644
index 0000000..1e67bd6
--- /dev/null
+++ b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/TvMonitorRedisConfig.java
@@ -0,0 +1,43 @@
+package com.vincent.rsf.openApi.config;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+/**
+ * 鐢佃鏈哄悗鍙颁娇鐢ㄧ嫭绔� Redis DB 鏃讹紝姝ゅ涓� {@code tv-monitor.redis.database} 瀵归綈锛堥粯璁� 0锛屼笌 zy-monitor-admin 涓�鑷达級
+ */
+@Configuration
+public class TvMonitorRedisConfig {
+
+    @Value("${redis.host:127.0.0.1}")
+    private String host;
+    @Value("${redis.port:6379}")
+    private int port;
+    @Value("${redis.password:}")
+    private String password;
+    @Value("${tv-monitor.redis.database:0}")
+    private int database;
+
+    @Bean(name = "tvMonitorRedisConnectionFactory")
+    public LettuceConnectionFactory tvMonitorRedisConnectionFactory() {
+        RedisStandaloneConfiguration cfg = new RedisStandaloneConfiguration(host, port);
+        cfg.setDatabase(database);
+        if (password != null && !password.isEmpty()) {
+            cfg.setPassword(password);
+        }
+        return new LettuceConnectionFactory(cfg);
+    }
+
+    @Bean(name = "tvMonitorStringRedisTemplate")
+    public StringRedisTemplate tvMonitorStringRedisTemplate(
+            @Qualifier("tvMonitorRedisConnectionFactory") LettuceConnectionFactory tvMonitorRedisConnectionFactory) {
+        StringRedisTemplate t = new StringRedisTemplate();
+        t.setConnectionFactory(tvMonitorRedisConnectionFactory);
+        return t;
+    }
+}
diff --git a/rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/RcsTvCallbackController.java b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/RcsTvCallbackController.java
new file mode 100644
index 0000000..f134d70
--- /dev/null
+++ b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/RcsTvCallbackController.java
@@ -0,0 +1,70 @@
+package com.vincent.rsf.openApi.controller;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.vincent.rsf.openApi.service.RcsTvCallbackService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * RCS 鍚� WMS 鎺ㄩ�侊細杈撻�佺嚎寮傚父銆佺珯鐐逛换鍔″彿锛涘啓鍏ヤ笌鐢佃鏈哄悗鍙扮浉鍚岀殑 Redis锛堢數瑙嗘満涓� zy-monitor-admin 涓嶆敼浠g爜锛�
+ */
+@Slf4j
+@RestController
+@Api(tags = "RCS鐢佃鏈哄洖璋�")
+@RequestMapping("/rcs/callback/tv")
+public class RcsTvCallbackController {
+
+    @Resource
+    private RcsTvCallbackService rcsTvCallbackService;
+
+    @PostMapping("/station/error")
+    @ApiOperation("杈撻�佺嚎寮傚父鎺ㄩ�侊細鍐欏叆 Redis tvManualErrorMsg")
+    public Map<String, Object> stationError(
+            @RequestHeader(value = "X-Rcs-Token", required = false) String rcsToken,
+            @RequestBody(required = false) JsonNode body) {
+        try {
+            rcsTvCallbackService.assertToken(rcsToken);
+            return rcsTvCallbackService.handleStationError(body);
+        } catch (IllegalArgumentException e) {
+            return rcsFail(500, e.getMessage());
+        } catch (Exception e) {
+            log.error("RCS station/error 澶勭悊澶辫触", e);
+            return rcsFail(500, e.getMessage() != null ? e.getMessage() : "澶勭悊澶辫触");
+        }
+    }
+
+    @PostMapping("/station/taskNo")
+    @ApiOperation("杈撻�佺嚎浠诲姟鍙锋帹閫侊細鍐欏叆 Redis Hash tvRcsStationTaskNo")
+    public Map<String, Object> stationTaskNo(
+            @RequestHeader(value = "X-Rcs-Token", required = false) String rcsToken,
+            @RequestBody(required = false) JsonNode body) {
+        try {
+            rcsTvCallbackService.assertToken(rcsToken);
+            return rcsTvCallbackService.handleStationTaskNo(body);
+        } catch (IllegalArgumentException e) {
+            return rcsFail(500, e.getMessage());
+        } catch (Exception e) {
+            log.error("RCS station/taskNo 澶勭悊澶辫触", e);
+            return rcsFail(500, e.getMessage() != null ? e.getMessage() : "澶勭悊澶辫触");
+        }
+    }
+
+    private static Map<String, Object> rcsFail(int code, String message) {
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("code", code);
+        m.put("message", message != null ? message : "澶辫触");
+        m.put("timestamp", System.currentTimeMillis());
+        m.put("data", null);
+        return m;
+    }
+}
diff --git a/rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/RcsTvCallbackService.java b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/RcsTvCallbackService.java
new file mode 100644
index 0000000..e796aa6
--- /dev/null
+++ b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/RcsTvCallbackService.java
@@ -0,0 +1,146 @@
+package com.vincent.rsf.openApi.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.vincent.rsf.openApi.tv.TvMonitorRedisKeys;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * RCS 鎺ㄩ�侊細鍐欏叆涓庣數瑙嗘満鍚庡彴鐩稿悓鐨� Redis 鏍煎紡锛堜笉淇敼 zy-monitor-admin / 鐢佃鏈猴級
+ */
+@Slf4j
+@Service
+public class RcsTvCallbackService {
+
+    @Resource
+    @Qualifier("tvMonitorStringRedisTemplate")
+    private StringRedisTemplate tvMonitorStringRedisTemplate;
+
+    @Value("${tv-monitor.rcs-callback-token:}")
+    private String rcsCallbackToken;
+
+    public void assertToken(String headerToken) {
+        if (!StringUtils.hasText(rcsCallbackToken)) {
+            return;
+        }
+        if (!rcsCallbackToken.equals(headerToken)) {
+            throw new IllegalArgumentException("X-Rcs-Token 鏃犳晥");
+        }
+    }
+
+    /**
+     * 瑙f瀽杈撻�佺嚎寮傚父鍒楄〃锛屽啓鍏� {@link TvMonitorRedisKeys#TV_MANUAL_ERROR_MSG}锛堝垎鍙锋嫾鎺ワ紝涓庢帹閫佺 join 椋庢牸涓�鑷达級
+     */
+    public Map<String, Object> handleStationError(JsonNode body) {
+        if (body != null && body.isObject() && body.has("clear") && body.get("clear").asBoolean(false)) {
+            tvMonitorStringRedisTemplate.delete(TvMonitorRedisKeys.TV_MANUAL_ERROR_MSG);
+            return rcsOk(null);
+        }
+        JsonNode arr = resolveErrorArray(body);
+        if (arr == null || !arr.isArray() || arr.size() == 0) {
+            tvMonitorStringRedisTemplate.delete(TvMonitorRedisKeys.TV_MANUAL_ERROR_MSG);
+            return rcsOk(null);
+        }
+        List<String> parts = new ArrayList<>();
+        for (JsonNode item : arr) {
+            if (item == null || !item.isObject()) {
+                continue;
+            }
+            String staNo = text(item.get("staNo"));
+            String err = text(item.get("error"));
+            String plc = text(item.get("plcDesc"));
+            String msg = StringUtils.hasText(err) ? err : plc;
+            if (!StringUtils.hasText(msg)) {
+                continue;
+            }
+            if (StringUtils.hasText(staNo)) {
+                parts.add("[" + staNo + "]" + msg);
+            } else {
+                parts.add(msg);
+            }
+        }
+        if (parts.isEmpty()) {
+            tvMonitorStringRedisTemplate.delete(TvMonitorRedisKeys.TV_MANUAL_ERROR_MSG);
+            return rcsOk(null);
+        }
+        String value = String.join("锛�", parts);
+        tvMonitorStringRedisTemplate.opsForValue().set(TvMonitorRedisKeys.TV_MANUAL_ERROR_MSG, value);
+        log.info("RCS 鎶ヨ宸插啓鍏� Redis tvManualErrorMsg锛屾潯鏁�={}", parts.size());
+        return rcsOk(value);
+    }
+
+    private static JsonNode resolveErrorArray(JsonNode body) {
+        if (body == null || body.isNull()) {
+            return null;
+        }
+        if (body.isArray()) {
+            return body;
+        }
+        if (body.isObject()) {
+            if (body.has("data") && body.get("data").isArray()) {
+                return body.get("data");
+            }
+            if (body.has("errors") && body.get("errors").isArray()) {
+                return body.get("errors");
+            }
+        }
+        return null;
+    }
+
+    private static String text(JsonNode n) {
+        if (n == null || n.isNull()) {
+            return "";
+        }
+        String s = n.asText("");
+        return s == null ? "" : s.trim();
+    }
+
+    /**
+     * 绔欑偣浠诲姟鍙峰啓鍏� Redis Hash锛屼緵鍚庣画鎵╁睍锛涚數瑙嗘満 led 浠诲姟鍙蜂粛鏉ヨ嚜 WCS 绔欑偣杞
+     */
+    public Map<String, Object> handleStationTaskNo(JsonNode body) {
+        if (body == null || body.isNull() || !body.isObject()) {
+            throw new IllegalArgumentException("body 椤讳负 JSON 瀵硅薄");
+        }
+        JsonNode dataNode = body.get("data");
+        JsonNode src = (dataNode != null && dataNode.isObject()) ? dataNode : body;
+        String staNo = text(body.get("staNo"));
+        if (!StringUtils.hasText(staNo)) {
+            staNo = text(src.get("staNo"));
+        }
+        String taskNo = text(src.get("taskNo"));
+        if (!StringUtils.hasText(staNo)) {
+            throw new IllegalArgumentException("staNo 涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(taskNo)) {
+            tvMonitorStringRedisTemplate.opsForHash().delete(TvMonitorRedisKeys.TV_RCS_STATION_TASK_NO, staNo);
+        } else {
+            tvMonitorStringRedisTemplate.opsForHash()
+                    .put(TvMonitorRedisKeys.TV_RCS_STATION_TASK_NO, staNo, taskNo);
+        }
+        log.info("RCS 浠诲姟鍙峰凡鍐欏叆 Redis Hash staNo={} taskNo={}", staNo, taskNo);
+        Map<String, Object> payload = new LinkedHashMap<>();
+        payload.put("staNo", staNo);
+        payload.put("taskNo", taskNo);
+        return rcsOk(payload);
+    }
+
+    private Map<String, Object> rcsOk(Object data) {
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("code", 200);
+        m.put("message", "璇锋眰鎴愬姛");
+        m.put("timestamp", System.currentTimeMillis());
+        m.put("data", data);
+        return m;
+    }
+}
diff --git a/rsf-open-api/src/main/java/com/vincent/rsf/openApi/tv/TvMonitorRedisKeys.java b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/tv/TvMonitorRedisKeys.java
new file mode 100644
index 0000000..dc337ae
--- /dev/null
+++ b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/tv/TvMonitorRedisKeys.java
@@ -0,0 +1,16 @@
+package com.vincent.rsf.openApi.tv;
+
+/**
+ * 涓� zy-monitor-admin {@code RedisKeyType} 涓� key 瀛楃涓蹭繚鎸佷竴鑷达紝渚涚數瑙嗘満鍚庡彴鍘熸牱璇诲彇
+ */
+public final class TvMonitorRedisKeys {
+
+    /** 涓� {@code RedisKeyType.TV_MANUAL_ERROR_MSG} 涓�鑷达紝{@code TvDataPushService} 鐢ㄥ瓧绗︿覆鎺ㄩ�� error */
+    public static final String TV_MANUAL_ERROR_MSG = "tvManualErrorMsg";
+
+    /** RCS 绔欑偣浠诲姟鍙风紦瀛橈紙Hash锛歠ield=staNo, value=taskNo锛夛紱鐢佃鏈� led 浠诲姟鍙蜂粛渚濊禆 WCS 杞 stationMap锛屾湰閿緵鎵╁睍 */
+    public static final String TV_RCS_STATION_TASK_NO = "tvRcsStationTaskNo";
+
+    private TvMonitorRedisKeys() {
+    }
+}

--
Gitblit v1.9.1