cl
14 小时以前 b3fb3db3341ea98dac914f62dc94e59fe37e6b3f
推送报警预写
4个文件已添加
275 ■■■■■ 已修改文件
rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/TvMonitorRedisConfig.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/controller/RcsTvCallbackController.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/RcsTvCallbackService.java 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/tv/TvMonitorRedisKeys.java 16 ●●●●● 补丁 | 查看 | 原始文档 | 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() {
    }
}