fix: 优化excel综合导出流量、水位、流速方法
This commit is contained in:
parent
06684578f8
commit
d6d8c87b19
@ -23,4 +23,8 @@ public class StationMapping implements Serializable {
|
||||
|
||||
@TableField("ENNM")
|
||||
private String ennm;
|
||||
|
||||
/** 测站类型(如 ZQ=河道水情, XX=生态流量等),用于区分同一电站下不同类型的测站 */
|
||||
@TableField("STTP")
|
||||
private String sttp;
|
||||
}
|
||||
|
||||
@ -4,14 +4,14 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 指标处理器接口 — 每种指标(v/q/z/wq/dwt 等)实现此接口
|
||||
* 指标处理器接口 — 每种指标(v/q/z/wq/dwt/qec 等)实现此接口
|
||||
* <p>
|
||||
* 新增指标时:实现本接口 + 在 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),用于区分同一电站下不同用途的测站。
|
||||
* <ul>
|
||||
* <li>V/Q/Z 河道水情 → "ZQ"</li>
|
||||
* <li>QEC 生态流量 → 待定(如 "SQ")</li>
|
||||
* </ul>
|
||||
* 返回 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<IndicatorDataRow> queryRawData(List<String> stcds, Date startTime, Date endTime, boolean isDaily);
|
||||
|
||||
@ -17,4 +17,9 @@ public class QProcessor extends RiverIndicatorProcessor {
|
||||
public String getSheetName() {
|
||||
return "流量(m³/s)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSttpType() {
|
||||
return "ZQ";
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,4 +17,9 @@ public class VProcessor extends RiverIndicatorProcessor {
|
||||
public String getSheetName() {
|
||||
return "流速(m/s)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSttpType() {
|
||||
return "ZQ";
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,4 +17,9 @@ public class ZProcessor extends RiverIndicatorProcessor {
|
||||
public String getSheetName() {
|
||||
return "水位(m)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSttpType() {
|
||||
return "ZQ";
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,16 @@ import java.util.stream.Collectors;
|
||||
* <p>
|
||||
* 格式:每月一个 Excel 文件 → ZIP 下载。
|
||||
* 每个 Excel 含多个 Sheet(每个指标一个 Sheet),每行是一个统计指标。
|
||||
*
|
||||
* <h3>电站→测站映射(STTP 类型区分)</h3>
|
||||
* 同一电站(RSTCD)在 V_MS_STBPRP_T 视图中可能有多个测站(STCD),
|
||||
* 不同类型的指标需要不同类型的测站数据。通过 {@link ExportIndicatorProcessor#getSttpType()}
|
||||
* 区分:
|
||||
* <ul>
|
||||
* <li>V/Q/Z 河道水情 → STTP="ZQ"</li>
|
||||
* <li>QEC 生态流量 → STTP=待定</li>
|
||||
* </ul>
|
||||
*
|
||||
* <pre>
|
||||
* 日期 | 电站A | 电站B
|
||||
* 日均最低 | 0.523 | 0.612
|
||||
@ -71,39 +81,32 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
public void exportData(List<String> dataFields, List<String> months,
|
||||
List<String> stationCodes, boolean isDailyDimension,
|
||||
String tmDimension, HttpServletResponse response) {
|
||||
// 1. 电站 → 测站映射 + 电站名称
|
||||
Map<String, StationInfo> stationInfoMap = resolveStations(stationCodes);
|
||||
if (stationInfoMap.isEmpty()) {
|
||||
log.warn("未找到任何电站映射数据,导出取消");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 指标处理器匹配
|
||||
// 1. 指标处理器匹配
|
||||
List<ExportIndicatorProcessor> processors = resolveProcessors(dataFields);
|
||||
if (processors.isEmpty()) {
|
||||
log.warn("未找到匹配的指标处理器,dataFields={}", dataFields);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 收集电站名称 + 测站列表(保持顺序)
|
||||
List<String> stationOrder = new ArrayList<>(stationInfoMap.keySet());
|
||||
List<String> stationNames = stationOrder.stream()
|
||||
.map(k -> stationInfoMap.get(k).getStationName())
|
||||
.toList();
|
||||
List<String> stcds = stationInfoMap.values().stream()
|
||||
.map(StationInfo::getStcd)
|
||||
// 2. 收集所有需要的 STTP 类型
|
||||
Set<String> sttpTypes = processors.stream()
|
||||
.map(ExportIndicatorProcessor::getSttpType)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
|
||||
// 4. rstcd → stcd 映射(用于聚合时找数据)
|
||||
Map<String, String> rstcdToStcd = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, StationInfo> 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<String> stationOrder = new ArrayList<>(stationRes.stationNames.keySet());
|
||||
List<String> stationNames = stationOrder.stream()
|
||||
.map(stationRes::getStationName)
|
||||
.toList();
|
||||
|
||||
// 5. 每月一个 ExcelData
|
||||
List<ExportZipUtil.ExcelData> excelList = new ArrayList<>();
|
||||
for (String month : months) {
|
||||
@ -112,9 +115,9 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
|
||||
List<ExportZipUtil.SheetData> sheets = new ArrayList<>();
|
||||
for (ExportIndicatorProcessor processor : processors) {
|
||||
// 查询 + 聚合这一个月的数据
|
||||
// 查询 + 聚合这一个月的数据(使用该处理器对应的 STTP 测站)
|
||||
List<List<Object>> rows = buildIndicatorRows(
|
||||
processor, mr, stcds, rstcdToStcd, stationOrder);
|
||||
processor, mr, stationOrder, stationRes);
|
||||
if (rows != null) {
|
||||
List<String> 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
|
||||
* <p>
|
||||
* 返回的每一行 = [指标名, station1值, station2值, ...]
|
||||
* 使用 processor.getSttpType() 映射到正确的测站编码。
|
||||
*/
|
||||
private List<List<Object>> buildIndicatorRows(
|
||||
ExportIndicatorProcessor processor, MonthRange mr,
|
||||
List<String> stcds, Map<String, String> rstcdToStcd,
|
||||
List<String> stationOrder) {
|
||||
List<String> stationOrder, StationResolution stationRes) {
|
||||
|
||||
if (stcds.isEmpty()) return null;
|
||||
String sttp = processor.getSttpType();
|
||||
|
||||
// 构建该处理器对应的 RSTCD→STCD 映射
|
||||
Map<String, String> 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<String> uniqueStcds = rstcdToStcd.values().stream().distinct().toList();
|
||||
|
||||
// 查询日表数据(用于日均最低/最高)
|
||||
List<IndicatorDataRow> dailyRows = processor.queryRawData(stcds, mr.getStart(), mr.getEnd(), true);
|
||||
List<IndicatorDataRow> dailyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), true);
|
||||
// 查询小时表数据(用于月内最低/最高)
|
||||
List<IndicatorDataRow> hourlyRows = processor.queryRawData(stcds, mr.getStart(), mr.getEnd(), false);
|
||||
List<IndicatorDataRow> hourlyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), false);
|
||||
|
||||
// 按电站聚合
|
||||
Map<String, Aggregation> aggMap = aggregate(dailyRows, hourlyRows, rstcdToStcd);
|
||||
@ -184,42 +215,88 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
|
||||
// ======================== 指标处理器匹配 ========================
|
||||
|
||||
/**
|
||||
* 根据前端 dataField 匹配处理器。
|
||||
* <p>
|
||||
* 新增指标时只需在这里注册一行即可,Controller 无需改动。
|
||||
* <ul>
|
||||
* <li>{@link RiverIndicatorProcessor} 子类需要调用 setMappers() 注入 Mapper</li>
|
||||
* <li>独立实现的 {@link ExportIndicatorProcessor}(如 QEC)自行管理 Mapper</li>
|
||||
* </ul>
|
||||
*/
|
||||
private List<ExportIndicatorProcessor> resolveProcessors(List<String> dataFields) {
|
||||
Map<String, RiverIndicatorProcessor> riverMap = new LinkedHashMap<>();
|
||||
riverMap.put("v", vProcessor);
|
||||
riverMap.put("q", qProcessor);
|
||||
riverMap.put("z", zProcessor);
|
||||
Map<String, ExportIndicatorProcessor> processorMap = new LinkedHashMap<>();
|
||||
processorMap.put("v", vProcessor);
|
||||
processorMap.put("q", qProcessor);
|
||||
processorMap.put("z", zProcessor);
|
||||
// 未来新增: processorMap.put("qec", qecProcessor);
|
||||
|
||||
List<ExportIndicatorProcessor> 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<String, StationInfo> resolveStations(List<String> rstcds) {
|
||||
Map<String, StationInfo> result = new LinkedHashMap<>();
|
||||
/**
|
||||
* 查询 V_MS_STBPRP_T 视图,同时获取:
|
||||
* <ol>
|
||||
* <li>电站中文名(ENNM):首次出现时记录,所有 STTP 共用</li>
|
||||
* <li>STTP → STCD 映射:每个 RSTCD 下按 STTP 类型取第一个测站编码</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param rstcds 前端传入的电站编码列表
|
||||
* @param sttpTypes 需要的测站类型集合(从 processors 收集)
|
||||
*/
|
||||
private StationResolution resolveStations(List<String> rstcds, Set<String> sttpTypes) {
|
||||
StationResolution result = new StationResolution();
|
||||
if (rstcds == null || rstcds.isEmpty()) return result;
|
||||
|
||||
List<StationMapping> mappings = stationMappingMapper.selectList(
|
||||
new LambdaQueryWrapper<StationMapping>().in(StationMapping::getRstcd, rstcds));
|
||||
|
||||
// 按 RSTCD 分组
|
||||
Map<String, List<StationMapping>> 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<StationMapping> 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<String, String> 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<String, String> stationNames = new LinkedHashMap<>();
|
||||
/** RSTCD → (STTP → STCD) */
|
||||
final Map<String, Map<String, String>> sttpStcdMap = new LinkedHashMap<>();
|
||||
|
||||
String getStationName(String rstcd) {
|
||||
return stationNames.getOrDefault(rstcd, rstcd);
|
||||
}
|
||||
|
||||
String getStcd(String rstcd, String sttp) {
|
||||
Map<String, String> inner = sttpStcdMap.get(rstcd);
|
||||
if (inner == null || sttp == null) return null;
|
||||
return inner.get(sttp);
|
||||
}
|
||||
}
|
||||
|
||||
@lombok.Data
|
||||
|
||||
Loading…
Reference in New Issue
Block a user