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),用于区分同一电站下不同用途的测站。 + *
* 格式:每月一个 Excel 文件 → ZIP 下载。 * 每个 Excel 含多个 Sheet(每个指标一个 Sheet),每行是一个统计指标。 + * + *
* 日期 | 电站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 视图,同时获取:
+ *
+ * - 电站中文名(ENNM):首次出现时记录,所有 STTP 共用
+ * - STTP → STCD 映射:每个 RSTCD 下按 STTP 类型取第一个测站编码
+ *
+ *
+ * @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