Przeglądaj źródła

2.36.0 /api/channels 一致(9/17): 首个 Java 原生实现端点(契约 YAML via snakeyaml + ch_cn 由 AST 抽出); 修注解绝对路径转义问题

zhouyang.xie 1 tydzień temu
rodzic
commit
646f53a6d8

+ 189 - 0
app_backEnd/backend-java/src/main/java/com/guanlan/backend/web/ChannelController.java

@@ -0,0 +1,189 @@
+package com.guanlan.backend.web;
+
+import com.guanlan.backend.redact.Redact;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.yaml.snakeyaml.Yaml;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * SCADA 通道清单(Java 实现)——契约 {@code /api/channels}。
+ *
+ * <p>与 Python `channel_list()`(serve.py:1974-1980)**逐字对齐**:
+ * 读契约 YAML → 取每组里 {@code liveness == '活'} 的列 → {@code {code, cn: ch_cn(col, with_tag=False) + " · " + col}}。
+ * 这里**不代理算法服务**:列名中文化是后端展示口径,且 Python 后端最终要删除,故用 Java 实现。
+ */
+@Api(tags = "channels")
+@RestController
+public class ChannelController {
+
+    private static final Map<String, String> STEM = new LinkedHashMap<>();
+    static {
+        STEM.put("MBearHTm", "前主轴承温度(轮毂侧)");
+        STEM.put("MBearGTm", "后主轴承温度(齿轮箱侧)");
+        STEM.put("GeOilTmp", "齿轮箱油温");
+        STEM.put("IMSGenTm", "齿轮箱中速轴温度(发电机侧)");
+        STEM.put("IMSRotTm", "齿轮箱中速轴温度(转子侧)");
+        STEM.put("HSGenTmp", "齿轮箱高速轴温度(发电机侧)");
+        STEM.put("HSRotTmp", "齿轮箱高速轴温度(转子侧)");
+        STEM.put("GFilB1Tm", "齿轮箱滤芯加热带B1温度");
+        STEM.put("GFilB2Tm", "齿轮箱滤芯加热带B2温度");
+        STEM.put("GFilB3Tm", "齿轮箱滤芯加热带B3温度");
+        STEM.put("BrkTmpGn", "机械刹车温度(发电机侧)");
+        STEM.put("BrkTmpGr", "机械刹车温度(齿轮箱侧)");
+        STEM.put("GenBeGTm", "发电机非驱动端(NDE)轴承温度");
+        STEM.put("GenBeRTm", "发电机驱动端(DE)轴承温度");
+        STEM.put("Gen1U1Tm", "发电机定子绕组温度U相");
+        STEM.put("Gen1V1Tm", "发电机定子绕组温度V相");
+        STEM.put("Gen1W1Tm", "发电机定子绕组温度W相");
+        STEM.put("ConvWTmp", "变流器冷却水温度");
+        STEM.put("TrafoTMa", "变压器绕组最高温度");
+        STEM.put("TraOilTF", "变压器油温");
+        STEM.put("TraRooTF", "变压器室温度");
+        STEM.put("A1ExtTmp", "A1柜排风温度");
+        STEM.put("A21IntTm", "A21柜内温度");
+        STEM.put("A3LefTmp", "A3柜左侧温度");
+        STEM.put("A3RigTmp", "A3柜右侧温度");
+        STEM.put("NacelTmp", "机舱温度");
+        STEM.put("HubTemp", "轮毂温度");
+        STEM.put("AmbieTmp", "环境温度");
+        STEM.put("HydOilTm", "液压油温");
+        STEM.put("HydPress_band", "变桨液压压力摆幅");
+        STEM.put("HydPress", "变桨液压系统压力");
+        STEM.put("YawPress", "偏航制动压力");
+        STEM.put("YawLubPu", "偏航润滑泵");
+        STEM.put("Y1LubPis", "偏航润滑柱塞");
+        STEM.put("MBLubPis", "主轴承润滑柱塞");
+        STEM.put("GBLubPis", "齿轮箱润滑柱塞");
+        STEM.put("GenLubPu", "发电机润滑泵");
+        STEM.put("HubLubPu", "轮毂润滑泵(变桨轴承)");
+        STEM.put("PitchPum", "变桨液压泵使能");
+        STEM.put("LubPistA", "变桨润滑柱塞A");
+        STEM.put("LubPistB", "变桨润滑柱塞B");
+        STEM.put("LubPistC", "变桨润滑柱塞C");
+        STEM.put("HydLevel", "液压油位开关");
+        STEM.put("HydrFilt", "液压滤芯开关");
+        STEM.put("ScYawOpe", "偏航动作(运行标志)");
+        STEM.put("TORYaw", "扭缆正常位");
+        STEM.put("Windvane", "风向标信号");
+        STEM.put("WindFauS", "风传感故障计数S路");
+        STEM.put("WindFauT", "风传感故障计数T路");
+        STEM.put("PriAnemo", "一次风速计");
+        STEM.put("SecAnemo", "二次风速计(判别用)");
+        STEM.put("ActualWindDirection", "风向(合成口径)");
+        STEM.put("MainSRpm", "主轴转速");
+        STEM.put("GenRpm", "发电机转速");
+        STEM.put("ActPower", "有功功率");
+        STEM.put("PowerRef", "功率给定");
+        STEM.put("PowerRed", "功率削减位");
+        STEM.put("PitcPosA", "桨叶A桨距角");
+        STEM.put("PitcPosB", "桨叶B桨距角");
+        STEM.put("PitcPosC", "桨叶C桨距角");
+        STEM.put("PitchRef_BladeA", "桨叶A桨距角给定");
+        STEM.put("PitchRef_BladeB", "桨叶B桨距角给定");
+        STEM.put("PitchRef_BladeC", "桨叶C桨距角给定");
+        STEM.put("kWG1TotE", "发电量计数(出口)");
+        STEM.put("kWG1TotI", "受电量计数");
+        STEM.put("kWG1Tot", "发电量计数");
+        STEM.put("ScComErr", "主控通讯错误计数");
+        STEM.put("ScEnvErr", "主控环境错误计数");
+        STEM.put("ScGrdErr", "主控电网错误计数");
+        STEM.put("ScTurErr", "主控机组错误计数");
+    }
+
+    private static final Map<String, String> SUFFIX = new LinkedHashMap<>();
+    static {
+        SUFFIX.put("_mean", "均值");
+        SUFFIX.put("_max", "最大");
+        SUFFIX.put("_min", "最小");
+        SUFFIX.put("_stddev", "标准差");
+        SUFFIX.put("_endvalue", "末值");
+        SUFFIX.put("_accum", "累计");
+        SUFFIX.put("_counts", "次数");
+        SUFFIX.put("_timeon", "接通时长");
+    }
+
+    @Value("${guanlan.contract-yaml:reference/rudong/windscada_contract.yaml}")
+    private String contractYaml;
+
+    private Path contractFile() {
+        Path p = Paths.get(contractYaml);
+        if (p.isAbsolute() && Files.exists(p)) {
+            return p;
+        }
+        Path cwd = Paths.get("").toAbsolutePath();
+        for (Path base : new Path[]{cwd, cwd.resolve("..").resolve("..")}) {
+            Path f = base.resolve(contractYaml);
+            if (Files.exists(f)) {
+                return f;
+            }
+        }
+        return cwd.resolve(contractYaml);
+    }
+
+    /** 旧 `i18n.ch_cn(col, with_tag=False)` */
+    static String chCn(String col) {
+        String base = col;
+        for (String pre : new String[]{"tmp_wtc_", "prs_wtc_", "dot_wtc_", "din_wtc_", "cnt_wtc_",
+                                       "flg_wtc_", "tur_wtc_", "grd_wtc_"}) {
+            base = base.replace(pre, "");
+        }
+        String suf = "";
+        for (Map.Entry<String, String> e : SUFFIX.entrySet()) {
+            if (base.endsWith(e.getKey())) {
+                base = base.substring(0, base.length() - e.getKey().length());
+                suf = e.getValue();
+                break;
+            }
+        }
+        String cn = STEM.get(base);
+        if (cn == null) {
+            return col;                                   // 查不到就回原列名(与旧实现一致)
+        }
+        return cn + (suf.isEmpty() ? "" : "·" + suf);
+    }
+
+    @ApiOperation("SCADA 通道清单(按契约分组,只列活列)")
+    @GetMapping(value = "/api/channels", produces = "application/json; charset=utf-8")
+    @SuppressWarnings("unchecked")
+    public ResponseEntity<Map<String, Object>> channels() {
+        Map<String, Object> out = new LinkedHashMap<>();
+        try {
+            String text = new String(Files.readAllBytes(contractFile()), StandardCharsets.UTF_8);
+            Map<String, Object> c = new Yaml().load(text);
+            Map<String, Object> groups = (Map<String, Object>) c.get("groups");
+            for (Map.Entry<String, Object> g : groups.entrySet()) {
+                List<Map<String, Object>> cols = new ArrayList<>();
+                Map<String, Object> chan = (Map<String, Object>) g.getValue();
+                for (Map.Entry<String, Object> col : chan.entrySet()) {
+                    Map<String, Object> meta = (Map<String, Object>) col.getValue();
+                    if (!"活".equals(String.valueOf(meta.get("liveness")))) {
+                        continue;
+                    }
+                    Map<String, Object> item = new LinkedHashMap<>();
+                    item.put("code", col.getKey());
+                    item.put("cn", chCn(col.getKey()) + " · " + col.getKey());
+                    cols.add(item);
+                }
+                out.put(g.getKey(), cols);
+            }
+        } catch (Exception e) {
+            out.clear();
+            out.put("err", ("契约读取失败: " + e.getMessage()).substring(0, Math.min(200, 20 + String.valueOf(e.getMessage()).length())));
+        }
+        return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body((Map<String, Object>) Redact.apply(out));
+    }
+}

+ 2 - 0
app_backEnd/backend-java/src/main/resources/application.yml

@@ -46,3 +46,5 @@ logging:
     com.guanlan: INFO
   # ★ P12:数据可信度 findings 读**同一份**对象库产物
   objects-json: outputs/rudong/ontology/objects.json
+  # ★ P12:SCADA 通道契约(与 Python `farm()["contract"]` 同一份)
+  contract-yaml: reference/rudong/windscada_contract.yaml