| rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/TvMonitorRedisConfig.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/RcsTvCallbackController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/RcsTvCallbackService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rsf-open-api/src/main/java/com/vincent/rsf/openApi/tv/TvMonitorRedisKeys.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/TvMonitorRedisConfig.java
New file @@ -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; } } rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/RcsTvCallbackController.java
New file @@ -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 不改代码) */ @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; } } rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/RcsTvCallbackService.java
New file @@ -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 无效"); } } /** * 解析输送线异常列表,写入 {@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; } } rsf-open-api/src/main/java/com/vincent/rsf/openApi/tv/TvMonitorRedisKeys.java
New file @@ -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:field=staNo, value=taskNo);电视机 led 任务号仍依赖 WCS 轮询 stationMap,本键供扩展 */ public static final String TV_RCS_STATION_TASK_NO = "tvRcsStationTaskNo"; private TvMonitorRedisKeys() { } }