diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/domain/StationMapping.java b/backend/src/main/java/com/yfd/platform/qgc_export/domain/StationMapping.java index 6b6990c8..61af0b13 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/domain/StationMapping.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/domain/StationMapping.java @@ -23,4 +23,8 @@ public class StationMapping implements Serializable { @TableField("ENNM") private String ennm; + + /** 测站类型(如 ZQ=河道水情, XX=生态流量等),用于区分同一电站下不同类型的测站 */ + @TableField("STTP") + private String sttp; } diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/ExportIndicatorProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/ExportIndicatorProcessor.java index 83ac09a3..f86de229 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/processor/ExportIndicatorProcessor.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/ExportIndicatorProcessor.java @@ -4,14 +4,14 @@ import java.util.Date; import java.util.List; /** - * 指标处理器接口 — 每种指标(v/q/z/wq/dwt 等)实现此接口 + * 指标处理器接口 — 每种指标(v/q/z/wq/dwt/qec 等)实现此接口 *

- * 新增指标时:实现本接口 + 在 QgcExportServiceImpl 中注入即可。 + * 新增指标时:实现本接口 + 在 QgcExportServiceImpl.resolveProcessors() 中注入即可。 */ public interface ExportIndicatorProcessor { /** - * 指标字段名,与前端 dataField 中的值对应(如 "v", "q", "z") + * 指标字段名,与前端 dataField 中的值对应(如 "v", "q", "z", "qec") */ String getFieldName(); @@ -20,13 +20,25 @@ public interface ExportIndicatorProcessor { */ String getSheetName(); + /** + * 测站类型(STTP),用于区分同一电站下不同用途的测站。 + *

+ * 返回 null 表示不按 STTP 过滤,取该电站的第一个测站。 + */ + default String getSttpType() { + return null; + } + /** * 查询原始数据 * - * @param stcds 测站编码列表(已从电站编码转换) + * @param stcds 测站编码列表(已从电站编码转换,且已按 STTP 过滤) * @param startTime 起始时间 * @param endTime 结束时间 - * @param isDaily 是否查询日表(true=SD_RIVERDAY_S, false=SD_RIVER_R) + * @param isDaily 是否查询日表(true=日表, false=小时表) * @return 原始数据行列表 */ List queryRawData(List stcds, Date startTime, Date endTime, boolean isDaily); diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/QProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/QProcessor.java index 81680f10..031d7361 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/processor/QProcessor.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/QProcessor.java @@ -17,4 +17,9 @@ public class QProcessor extends RiverIndicatorProcessor { public String getSheetName() { return "流量(m³/s)"; } + + @Override + public String getSttpType() { + return "ZQ"; + } } diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/VProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/VProcessor.java index 3897e376..74438880 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/processor/VProcessor.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/VProcessor.java @@ -17,4 +17,9 @@ public class VProcessor extends RiverIndicatorProcessor { public String getSheetName() { return "流速(m/s)"; } + + @Override + public String getSttpType() { + return "ZQ"; + } } diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/ZProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/ZProcessor.java index 6aeb9903..32e5e8b3 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/processor/ZProcessor.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/ZProcessor.java @@ -17,4 +17,9 @@ public class ZProcessor extends RiverIndicatorProcessor { public String getSheetName() { return "水位(m)"; } + + @Override + public String getSttpType() { + return "ZQ"; + } } diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/service/impl/QgcExportServiceImpl.java b/backend/src/main/java/com/yfd/platform/qgc_export/service/impl/QgcExportServiceImpl.java index 24d2949d..ea60381c 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/service/impl/QgcExportServiceImpl.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/service/impl/QgcExportServiceImpl.java @@ -31,6 +31,16 @@ import java.util.stream.Collectors; *

* 格式:每月一个 Excel 文件 → ZIP 下载。 * 每个 Excel 含多个 Sheet(每个指标一个 Sheet),每行是一个统计指标。 + * + *

电站→测站映射(STTP 类型区分)

+ * 同一电站(RSTCD)在 V_MS_STBPRP_T 视图中可能有多个测站(STCD), + * 不同类型的指标需要不同类型的测站数据。通过 {@link ExportIndicatorProcessor#getSttpType()} + * 区分: + * + * *
  *     日期        | 电站A    | 电站B
  *   日均最低      | 0.523    | 0.612
@@ -71,39 +81,32 @@ public class QgcExportServiceImpl implements IQgcExportService {
     public void exportData(List dataFields, List months,
                            List stationCodes, boolean isDailyDimension,
                            String tmDimension, HttpServletResponse response) {
-        // 1. 电站 → 测站映射 + 电站名称
-        Map stationInfoMap = resolveStations(stationCodes);
-        if (stationInfoMap.isEmpty()) {
-            log.warn("未找到任何电站映射数据,导出取消");
-            return;
-        }
-
-        // 2. 指标处理器匹配
+        // 1. 指标处理器匹配
         List processors = resolveProcessors(dataFields);
         if (processors.isEmpty()) {
             log.warn("未找到匹配的指标处理器,dataFields={}", dataFields);
             return;
         }
 
-        // 3. 收集电站名称 + 测站列表(保持顺序)
-        List stationOrder = new ArrayList<>(stationInfoMap.keySet());
-        List stationNames = stationOrder.stream()
-                .map(k -> stationInfoMap.get(k).getStationName())
-                .toList();
-        List stcds = stationInfoMap.values().stream()
-                .map(StationInfo::getStcd)
+        // 2. 收集所有需要的 STTP 类型
+        Set sttpTypes = processors.stream()
+                .map(ExportIndicatorProcessor::getSttpType)
                 .filter(Objects::nonNull)
-                .distinct()
-                .collect(Collectors.toList());
+                .collect(Collectors.toCollection(LinkedHashSet::new));
 
-        // 4. rstcd → stcd 映射(用于聚合时找数据)
-        Map rstcdToStcd = new LinkedHashMap<>();
-        for (Map.Entry e : stationInfoMap.entrySet()) {
-            if (e.getValue().getStcd() != null) {
-                rstcdToStcd.put(e.getKey(), e.getValue().getStcd());
-            }
+        // 3. 电站 → 测站映射(按 STTP 类型分别获取)
+        StationResolution stationRes = resolveStations(stationCodes, sttpTypes);
+        if (stationRes.stationNames.isEmpty()) {
+            log.warn("未找到任何电站映射数据,导出取消");
+            return;
         }
 
+        // 4. 电站顺序 + 名称
+        List stationOrder = new ArrayList<>(stationRes.stationNames.keySet());
+        List stationNames = stationOrder.stream()
+                .map(stationRes::getStationName)
+                .toList();
+
         // 5. 每月一个 ExcelData
         List excelList = new ArrayList<>();
         for (String month : months) {
@@ -112,9 +115,9 @@ public class QgcExportServiceImpl implements IQgcExportService {
 
             List sheets = new ArrayList<>();
             for (ExportIndicatorProcessor processor : processors) {
-                // 查询 + 聚合这一个月的数据
+                // 查询 + 聚合这一个月的数据(使用该处理器对应的 STTP 测站)
                 List> rows = buildIndicatorRows(
-                        processor, mr, stcds, rstcdToStcd, stationOrder);
+                        processor, mr, stationOrder, stationRes);
                 if (rows != null) {
                     List headers = new ArrayList<>();
                     headers.add("日期"); // 表头第一列 = 日期
@@ -132,30 +135,58 @@ public class QgcExportServiceImpl implements IQgcExportService {
             log.warn("没有可导出的数据");
             return;
         }
-
+        String fileNamePrefix = getFileNamePrefix(tmDimension);
         // 6. 委托 ExportZipUtil 导出 ZIP
-        String zipFileName = "综合数据导出_" + System.currentTimeMillis() + ".zip";
+        String zipFileName = fileNamePrefix + ".zip";
+//        String zipFileName = fileNamePrefix + System.currentTimeMillis() + ".zip";
         ExportZipUtil.exportToResponse(response, zipFileName, excelList);
     }
 
+    // 根据维度类型返回文件名称前缀
+    private static String getFileNamePrefix(String tmDimension) {
+        return switch (tmDimension) {
+            case "year" -> "年度报表";
+            case "month" -> "月度报表";
+            case "day" -> "日度报表";
+            case "hour" -> "小时报表";
+            default -> "未知";
+        };
+    }
+
     // ======================== 构建单个指标的 Sheet 数据 ========================
 
     /**
      * 查询并聚合一个月的数据,组装成 SheetData 的 rows
      * 

- * 返回的每一行 = [指标名, station1值, station2值, ...] + * 使用 processor.getSttpType() 映射到正确的测站编码。 */ private List> buildIndicatorRows( ExportIndicatorProcessor processor, MonthRange mr, - List stcds, Map rstcdToStcd, - List stationOrder) { + List stationOrder, StationResolution stationRes) { - if (stcds.isEmpty()) return null; + String sttp = processor.getSttpType(); + + // 构建该处理器对应的 RSTCD→STCD 映射 + Map rstcdToStcd = new LinkedHashMap<>(); + for (String rstcd : stationOrder) { + String stcd = stationRes.getStcd(rstcd, sttp); + if (stcd != null) { + rstcdToStcd.put(rstcd, stcd); + } + } + + if (rstcdToStcd.isEmpty()) { + log.warn("指标 {} 未找到 STTP={} 的测站数据", processor.getFieldName(), sttp); + return null; + } + + // 去重 STCD 用于查询 + List uniqueStcds = rstcdToStcd.values().stream().distinct().toList(); // 查询日表数据(用于日均最低/最高) - List dailyRows = processor.queryRawData(stcds, mr.getStart(), mr.getEnd(), true); + List dailyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), true); // 查询小时表数据(用于月内最低/最高) - List hourlyRows = processor.queryRawData(stcds, mr.getStart(), mr.getEnd(), false); + List hourlyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), false); // 按电站聚合 Map aggMap = aggregate(dailyRows, hourlyRows, rstcdToStcd); @@ -184,42 +215,88 @@ public class QgcExportServiceImpl implements IQgcExportService { // ======================== 指标处理器匹配 ======================== + /** + * 根据前端 dataField 匹配处理器。 + *

+ * 新增指标时只需在这里注册一行即可,Controller 无需改动。 + *

    + *
  • {@link RiverIndicatorProcessor} 子类需要调用 setMappers() 注入 Mapper
  • + *
  • 独立实现的 {@link ExportIndicatorProcessor}(如 QEC)自行管理 Mapper
  • + *
+ */ private List resolveProcessors(List dataFields) { - Map riverMap = new LinkedHashMap<>(); - riverMap.put("v", vProcessor); - riverMap.put("q", qProcessor); - riverMap.put("z", zProcessor); + Map processorMap = new LinkedHashMap<>(); + processorMap.put("v", vProcessor); + processorMap.put("q", qProcessor); + processorMap.put("z", zProcessor); + // 未来新增: processorMap.put("qec", qecProcessor); List result = new ArrayList<>(); for (String field : dataFields) { String lower = field.trim().toLowerCase(); - RiverIndicatorProcessor p = riverMap.get(lower); + ExportIndicatorProcessor p = processorMap.get(lower); if (p != null) { - p.setMappers(sdRiverRMapper, sdRiverdaySMapper); + // RiverIndicatorProcessor 需要注入 Mapper(非 Spring Bean,手动设置) + if (p instanceof RiverIndicatorProcessor) { + ((RiverIndicatorProcessor) p).setMappers(sdRiverRMapper, sdRiverdaySMapper); + } result.add(p); } } return result; } - // ======================== 电站映射 ======================== + // ======================== 电站映射(按 STTP 类型区分) ======================== - private Map resolveStations(List rstcds) { - Map result = new LinkedHashMap<>(); + /** + * 查询 V_MS_STBPRP_T 视图,同时获取: + *
    + *
  1. 电站中文名(ENNM):首次出现时记录,所有 STTP 共用
  2. + *
  3. STTP → STCD 映射:每个 RSTCD 下按 STTP 类型取第一个测站编码
  4. + *
+ * + * @param rstcds 前端传入的电站编码列表 + * @param sttpTypes 需要的测站类型集合(从 processors 收集) + */ + private StationResolution resolveStations(List rstcds, Set sttpTypes) { + StationResolution result = new StationResolution(); if (rstcds == null || rstcds.isEmpty()) return result; List mappings = stationMappingMapper.selectList( new LambdaQueryWrapper().in(StationMapping::getRstcd, rstcds)); + // 按 RSTCD 分组 + Map> byRstcd = new LinkedHashMap<>(); for (StationMapping m : mappings) { - String rstcd = m.getRstcd(); - if (rstcd == null || result.containsKey(rstcd)) continue; - result.put(rstcd, new StationInfo(m.getStcd(), - m.getEnnm() != null ? m.getEnnm() : rstcd)); + if (m.getRstcd() == null) continue; + byRstcd.computeIfAbsent(m.getRstcd(), k -> new ArrayList<>()).add(m); } + for (String rstcd : rstcds) { - result.putIfAbsent(rstcd, new StationInfo(null, rstcd)); + List rows = byRstcd.get(rstcd); + if (rows == null || rows.isEmpty()) { + // 无映射数据,电站名回退为编码 + result.stationNames.put(rstcd, rstcd); + result.sttpStcdMap.put(rstcd, new LinkedHashMap<>()); + continue; + } + + // 电站中文名:取第一条记录(所有 STTP 共用同一个名称) + String ennm = rows.get(0).getEnnm(); + result.stationNames.put(rstcd, ennm != null ? ennm : rstcd); + + // STTP → STCD 映射:每种 STTP 取第一个 + Map sttpMap = new LinkedHashMap<>(); + for (StationMapping m : rows) { + String sttp = m.getSttp(); + if (sttp != null && !sttpMap.containsKey(sttp)) { + sttpMap.put(sttp, m.getStcd()); + } + } + result.sttpStcdMap.put(rstcd, sttpMap); } + + log.info("电站映射解析完成,RSTCD数量={}, STTP类型={}", result.stationNames.size(), sttpTypes); return result; } @@ -296,10 +373,24 @@ public class QgcExportServiceImpl implements IQgcExportService { // ======================== 内部数据类 ======================== - @lombok.Data - private static class StationInfo { - private final String stcd; - private final String stationName; + /** + * 电站→测站映射解析结果 + */ + private static class StationResolution { + /** RSTCD → 电站中文名 */ + final Map stationNames = new LinkedHashMap<>(); + /** RSTCD → (STTP → STCD) */ + final Map> sttpStcdMap = new LinkedHashMap<>(); + + String getStationName(String rstcd) { + return stationNames.getOrDefault(rstcd, rstcd); + } + + String getStcd(String rstcd, String sttp) { + Map inner = sttpStcdMap.get(rstcd); + if (inner == null || sttp == null) return null; + return inner.get(sttp); + } } @lombok.Data