fix: 增加excel综合导出出库水温数据

This commit is contained in:
tangwei 2026-08-05 16:41:05 +08:00
parent 356c4f89ca
commit 9d45d85fc6
12 changed files with 465 additions and 5 deletions

View File

@ -0,0 +1,63 @@
package com.yfd.platform.qgc_export.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 河道水温监测数据表小时级
* 对应 Oracle : QGC_REFA.SD_WTRV_R
*/
@Data
@TableName("SD_WTRV_R")
public class SdWtrvR implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.INPUT)
private String id;
/** 水温站编码 */
private String stcd;
/** 数据时间 */
private Date tm;
/** 水温 (℃) */
private BigDecimal wt;
/** 备注 */
private String remark;
/** 附件ID */
private String fid;
/** 所属水生生态调查数据表ID */
private String werId;
/** 创建人 */
private String recordUser;
/** 创建时间 */
private Date recordTime;
/** 更新人 */
private String modifyUser;
/** 更新时间 */
private Date modifyTime;
/** 是否已删除 */
private Integer isDeleted;
/** 删除人 */
private String deleteUser;
/** 删除时间 */
private Date deleteTime;
}

View File

@ -0,0 +1,54 @@
package com.yfd.platform.qgc_export.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 河道水温监测数据日统计表
* 对应 Oracle : QGC_REFA.SD_WTRVDAY_S
*/
@Data
@TableName("SD_WTRVDAY_S")
public class SdWtrvdayS implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.INPUT)
private String id;
/** 水温站编码 */
private String stcd;
/** 数据时间 */
private Date dt;
/** 水温 (℃) */
private BigDecimal wt;
/** 创建人 */
private String recordUser;
/** 创建时间 */
private Date recordTime;
/** 更新人 */
private String modifyUser;
/** 更新时间 */
private Date modifyTime;
/** 是否已删除 */
private Integer isDeleted;
/** 删除人 */
private String deleteUser;
/** 删除时间 */
private Date deleteTime;
}

View File

@ -0,0 +1,26 @@
package com.yfd.platform.qgc_export.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yfd.platform.qgc_export.domain.SdWtrvR;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* SD_WTRV_R 河道水温小时数据 Mapper
*/
public interface SdWtrvRMapper extends BaseMapper<SdWtrvR> {
/**
* 批量查询测站在指定时间范围内的原始小时数据
*
* @param stcdList 测站编码列表
* @param startTime 起始时间
* @param endTime 结束时间
* @return [STCD, TM, VALUE]
*/
List<Map<String, Object>> selectRawData(@Param("stcdList") List<String> stcdList,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
}

View File

@ -0,0 +1,26 @@
package com.yfd.platform.qgc_export.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yfd.platform.qgc_export.domain.SdWtrvdayS;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* SD_WTRVDAY_S 河道水温日数据 Mapper
*/
public interface SdWtrvdaySMapper extends BaseMapper<SdWtrvdayS> {
/**
* 批量查询测站在指定时间范围内的原始日数据
*
* @param stcdList 测站编码列表
* @param startTime 起始时间
* @param endTime 结束时间
* @return [STCD, DT, VALUE]
*/
List<Map<String, Object>> selectRawData(@Param("stcdList") List<String> stcdList,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
}

View File

@ -0,0 +1,22 @@
package com.yfd.platform.qgc_export.processor;
import org.springframework.stereotype.Component;
/**
* 出库水温(DWT) 标准格式处理器
* <p>
* Sheet 格式与水情指标一致日数据(01-31) + 7个汇总指标
*/
@Component
public class DwtProcessor extends WtrvIndicatorProcessor {
@Override
public String getFieldName() {
return "DWT";
}
@Override
public String getSheetName() {
return "出库水温";
}
}

View File

@ -0,0 +1,27 @@
package com.yfd.platform.qgc_export.processor;
import org.springframework.stereotype.Component;
/**
* 出入库水温日变幅 处理器
* <p>
* 日变幅格式日数据行展示每日变幅当日最大-最小汇总指标为"日变幅均值""日变幅最大值"
*/
@Component
public class DwtVariationProcessor extends WtrvIndicatorProcessor {
@Override
public String getFieldName() {
return "DWT";
}
@Override
public String getSheetName() {
return "出入库水温日变幅";
}
@Override
public boolean isVariation() {
return true;
}
}

View File

@ -25,6 +25,7 @@ public interface ExportIndicatorProcessor {
* <ul> * <ul>
* <li>V/Q/Z 河道水情 "ZQ"</li> * <li>V/Q/Z 河道水情 "ZQ"</li>
* <li>WQ 水质 "WQ"</li> * <li>WQ 水质 "WQ"</li>
* <li>DWT 出库水温 "WTRV"</li>
* <li>QEC 生态流量 待定</li> * <li>QEC 生态流量 待定</li>
* </ul> * </ul>
*/ */
@ -46,6 +47,14 @@ public interface ExportIndicatorProcessor {
return false; return false;
} }
/**
* 是否为日变幅格式出库水温日变幅 Sheet
* 日变幅格式日数据行展示每日变幅(=当日最大-最小)汇总指标为"日变幅均值""日变幅最大值"
*/
default boolean isVariation() {
return false;
}
/** /**
* 查询原始数据 * 查询原始数据
* *

View File

@ -0,0 +1,68 @@
package com.yfd.platform.qgc_export.processor;
import com.yfd.platform.qgc_export.mapper.SdWtrvRMapper;
import com.yfd.platform.qgc_export.mapper.SdWtrvdaySMapper;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 河道水温指标处理器基类 DWT出库水温使用 SD_WTRV_R / SD_WTRVDAY_S
* <p>
* 子类标注 @ComponentgetSttpType() 固定返回 "WTRV"
*/
public abstract class WtrvIndicatorProcessor implements ExportIndicatorProcessor {
protected SdWtrvRMapper sdWtrvRMapper;
protected SdWtrvdaySMapper sdWtrvdaySMapper;
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public void setMappers(SdWtrvRMapper rMapper, SdWtrvdaySMapper sMapper) {
this.sdWtrvRMapper = rMapper;
this.sdWtrvdaySMapper = sMapper;
}
@Override
public String getSttpType() {
return "WTRV";
}
@Override
public int getDecimalPlaces() {
return 3;
}
@Override
public List<IndicatorDataRow> queryRawData(List<String> stcds, Date startTime, Date endTime, boolean isDaily) {
String startStr = SDF.format(startTime);
String endStr = SDF.format(endTime);
List<Map<String, Object>> rawList;
if (isDaily) {
rawList = sdWtrvdaySMapper.selectRawData(stcds, startStr, endStr);
} else {
rawList = sdWtrvRMapper.selectRawData(stcds, startStr, endStr);
}
List<IndicatorDataRow> rows = new ArrayList<>();
if (rawList != null) {
for (Map<String, Object> map : rawList) {
String stcd = (String) map.get("STCD");
Date tm = (Date) map.get(isDaily ? "DT" : "TM");
BigDecimal value = toBigDecimal(map.get("VALUE"));
if (stcd != null && tm != null && value != null) {
rows.add(new IndicatorDataRow(stcd, tm, value));
}
}
}
return rows;
}
private BigDecimal toBigDecimal(Object val) {
if (val instanceof BigDecimal) return (BigDecimal) val;
if (val instanceof Number) return BigDecimal.valueOf(((Number) val).doubleValue());
return null;
}
}

View File

@ -5,6 +5,8 @@ import com.yfd.platform.qgc_export.mapper.SdRiverRMapper;
import com.yfd.platform.qgc_export.mapper.SdRiverdaySMapper; import com.yfd.platform.qgc_export.mapper.SdRiverdaySMapper;
import com.yfd.platform.qgc_export.mapper.SdWqRMapper; import com.yfd.platform.qgc_export.mapper.SdWqRMapper;
import com.yfd.platform.qgc_export.mapper.SdWqdaySMapper; import com.yfd.platform.qgc_export.mapper.SdWqdaySMapper;
import com.yfd.platform.qgc_export.mapper.SdWtrvRMapper;
import com.yfd.platform.qgc_export.mapper.SdWtrvdaySMapper;
import com.yfd.platform.qgc_export.mapper.StationMappingMapper; import com.yfd.platform.qgc_export.mapper.StationMappingMapper;
import com.yfd.platform.qgc_export.processor.*; import com.yfd.platform.qgc_export.processor.*;
import com.yfd.platform.qgc_export.service.IQgcExportService; import com.yfd.platform.qgc_export.service.IQgcExportService;
@ -81,6 +83,16 @@ public class QgcExportServiceImpl implements IQgcExportService {
@Resource @Resource
private TuProcessor tuProcessor; private TuProcessor tuProcessor;
// DWT 出库水温相关
@Resource
private SdWtrvRMapper sdWtrvRMapper;
@Resource
private SdWtrvdaySMapper sdWtrvdaySMapper;
@Resource
private DwtProcessor dwtProcessor;
@Resource
private DwtVariationProcessor dwtVariationProcessor;
private static final SimpleDateFormat SDF_TIME = new SimpleDateFormat("dd日 HH时"); private static final SimpleDateFormat SDF_TIME = new SimpleDateFormat("dd日 HH时");
/** 基础汇总指标7个 */ /** 基础汇总指标7个 */
@ -175,7 +187,12 @@ public class QgcExportServiceImpl implements IQgcExportService {
List<String> uniqueStcds = rstcdToStcd.values().stream().distinct().toList(); List<String> uniqueStcds = rstcdToStcd.values().stream().distinct().toList();
// 查询日表 + 小时表 // ---- 日变幅格式出入库水温日变幅 ----
if (processor.isVariation()) {
return buildVariationRows(processor, mr, stationOrder, rstcdToStcd, uniqueStcds, decimals);
}
// ---- 标准格式 ----
List<IndicatorDataRow> dailyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), true); List<IndicatorDataRow> dailyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), true);
List<IndicatorDataRow> hourlyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), false); List<IndicatorDataRow> hourlyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), false);
@ -240,6 +257,7 @@ public class QgcExportServiceImpl implements IQgcExportService {
* 水质指标水温PH溶解氧电导率浊度 河道水情流速流量水位 * 水质指标水温PH溶解氧电导率浊度 河道水情流速流量水位
* <p> * <p>
* 支持别名前端传 "wq" 等价于传 "wtmp,ph,dox,cond,tu" * 支持别名前端传 "wq" 等价于传 "wtmp,ph,dox,cond,tu"
* 前端传 "dwt" 展开为出库水温 + 出入库水温日变幅两个 Sheet
*/ */
private List<ExportIndicatorProcessor> resolveProcessors(List<String> dataFields) { private List<ExportIndicatorProcessor> resolveProcessors(List<String> dataFields) {
Map<String, ExportIndicatorProcessor> processorMap = new LinkedHashMap<>(); Map<String, ExportIndicatorProcessor> processorMap = new LinkedHashMap<>();
@ -267,6 +285,15 @@ public class QgcExportServiceImpl implements IQgcExportService {
List<ExportIndicatorProcessor> result = new ArrayList<>(); List<ExportIndicatorProcessor> result = new ArrayList<>();
for (String field : expanded) { for (String field : expanded) {
// DWT 出库水温 展开为标准格式 + 日变幅两个 Sheet
if ("dwt".equals(field)) {
dwtProcessor.setMappers(sdWtrvRMapper, sdWtrvdaySMapper);
dwtVariationProcessor.setMappers(sdWtrvRMapper, sdWtrvdaySMapper);
result.add(dwtProcessor);
result.add(dwtVariationProcessor);
continue;
}
ExportIndicatorProcessor p = processorMap.get(field); ExportIndicatorProcessor p = processorMap.get(field);
if (p != null) { if (p != null) {
if (p instanceof RiverIndicatorProcessor) { if (p instanceof RiverIndicatorProcessor) {
@ -450,6 +477,98 @@ public class QgcExportServiceImpl implements IQgcExportService {
return result; return result;
} }
// ======================== 日变幅格式出入库水温日变幅 ========================
/** 日变幅汇总标签 */
private static final List<String> VARIATION_LABELS = List.of("日变幅均值", "日变幅最大值");
/**
* 构建日变幅格式的 Sheet 数据
* <p>
* 日数据行从小时表计算每日变幅当日最大 - 最小
* 汇总行日变幅均值日变幅最大值
*/
private List<List<Object>> buildVariationRows(
ExportIndicatorProcessor processor, MonthRange mr,
List<String> stationOrder, Map<String, String> rstcdToStcd,
List<String> uniqueStcds, int decimals) {
// 仅查询小时表按天分组计算日变幅当日最大-最小
List<IndicatorDataRow> hourlyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), false);
// STCD {day 日变幅}
Map<String, Map<Integer, BigDecimal>> variationByStcd = new LinkedHashMap<>();
// STCD {day List<BigDecimal>} 临时分组
Map<String, Map<Integer, List<BigDecimal>>> hourlyByStcdDay = new LinkedHashMap<>();
for (IndicatorDataRow row : hourlyRows) {
if (row.getStcd() == null || row.getTm() == null || row.getValue() == null) continue;
Calendar cal = Calendar.getInstance();
cal.setTime(row.getTm());
int day = cal.get(Calendar.DAY_OF_MONTH);
hourlyByStcdDay
.computeIfAbsent(row.getStcd(), k -> new LinkedHashMap<>())
.computeIfAbsent(day, k -> new ArrayList<>())
.add(row.getValue());
}
// 计算每日变幅 = max - min
for (Map.Entry<String, Map<Integer, List<BigDecimal>>> stcdEntry : hourlyByStcdDay.entrySet()) {
Map<Integer, BigDecimal> dayVariation = new LinkedHashMap<>();
for (Map.Entry<Integer, List<BigDecimal>> dayEntry : stcdEntry.getValue().entrySet()) {
List<BigDecimal> values = dayEntry.getValue();
if (values.size() >= 2) {
BigDecimal max = Collections.max(values);
BigDecimal min = Collections.min(values);
dayVariation.put(dayEntry.getKey(), max.subtract(min));
}
}
if (!dayVariation.isEmpty()) {
variationByStcd.put(stcdEntry.getKey(), dayVariation);
}
}
// RSTCD [日变幅均值, 日变幅最大值]
Map<String, BigDecimal[]> variationAgg = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : rstcdToStcd.entrySet()) {
Map<Integer, BigDecimal> dayMap = variationByStcd.get(entry.getValue());
if (dayMap != null && !dayMap.isEmpty()) {
List<BigDecimal> values = new ArrayList<>(dayMap.values());
BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal avg = sum.divide(BigDecimal.valueOf(values.size()), 3, RoundingMode.HALF_UP);
BigDecimal max = Collections.max(values);
variationAgg.put(entry.getKey(), new BigDecimal[]{avg, max});
}
}
List<List<Object>> rows = new ArrayList<>();
// ---- 日数据行日变幅 ----
int daysInMonth = getDaysInMonth(mr);
for (int d = 1; d <= daysInMonth; d++) {
List<Object> row = new ArrayList<>();
row.add(String.format("%02d", d));
for (String rstcd : stationOrder) {
String stcd = rstcdToStcd.get(rstcd);
Map<Integer, BigDecimal> dayMap = stcd != null ? variationByStcd.get(stcd) : null;
BigDecimal val = dayMap != null ? dayMap.get(d) : null;
row.add(formatOrEmpty(val, decimals));
}
rows.add(row);
}
// ---- 汇总行 ----
for (int i = 0; i < VARIATION_LABELS.size(); i++) {
List<Object> row = new ArrayList<>();
row.add(VARIATION_LABELS.get(i));
for (String rstcd : stationOrder) {
BigDecimal[] agg = variationAgg.get(rstcd);
row.add(formatOrEmpty(agg != null ? agg[i] : null, decimals));
}
rows.add(row);
}
return rows;
}
private static String formatOrEmpty(BigDecimal value, int decimals) { private static String formatOrEmpty(BigDecimal value, int decimals) {
if (value == null) return ""; if (value == null) return "";
return value.setScale(decimals, RoundingMode.HALF_UP).toPlainString(); return value.setScale(decimals, RoundingMode.HALF_UP).toPlainString();

View File

@ -13,8 +13,8 @@ spring:
# ==================== 达梦(DM) 数据源 ==================== # ==================== 达梦(DM) 数据源 ====================
dm-master: dm-master:
driverClassName: dm.jdbc.driver.DmDriver driverClassName: dm.jdbc.driver.DmDriver
url: "${DB_DM_MASTER_URL:jdbc:dm://172.16.21.143:5236/QGC_REFA}" url: "${DB_DM_MASTER_URL:jdbc:dm://172.16.21.143:5236/QGC_REFA_TEST}"
username: "${DB_DM_MASTER_USERNAME:QGC_REFA}" username: "${DB_DM_MASTER_USERNAME:QGC_REFA_TEST}"
password: "${DB_DM_MASTER_PASSWORD:Y4M4K1oCkL8U}" password: "${DB_DM_MASTER_PASSWORD:Y4M4K1oCkL8U}"
initial-size: 5 initial-size: 5
min-idle: 5 min-idle: 5
@ -42,8 +42,8 @@ spring:
max-pool-prepared-statement-per-connection-size: 100 max-pool-prepared-statement-per-connection-size: 100
dm-slave: dm-slave:
driverClassName: dm.jdbc.driver.DmDriver driverClassName: dm.jdbc.driver.DmDriver
url: "${DB_DM_SLAVE_URL:jdbc:dm://172.16.21.143:5236/QGC_REFA}" url: "${DB_DM_SLAVE_URL:jdbc:dm://172.16.21.143:5236/QGC_REFA_TEST}"
username: "${DB_DM_SLAVE_USERNAME:QGC_REFA}" username: "${DB_DM_SLAVE_USERNAME:QGC_REFA_TEST}"
password: "${DB_DM_SLAVE_PASSWORD:Y4M4K1oCkL8U}" password: "${DB_DM_SLAVE_PASSWORD:Y4M4K1oCkL8U}"
initial-size: 5 initial-size: 5
min-idle: 5 min-idle: 5

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.qgc_export.mapper.SdWtrvRMapper">
<!-- 河道水温小时数据查询 -->
<select id="selectRawData" resultType="java.util.HashMap">
SELECT
STCD,
TM,
WT AS VALUE
FROM SD_WTRV_R
WHERE IS_DELETED = 0
AND STCD IN
<foreach collection="stcdList" item="stcd" open="(" separator="," close=")">
#{stcd}
</foreach>
AND TM &gt;= TO_DATE(#{startTime}, 'YYYY-MM-DD HH24:MI:SS')
AND TM &lt; TO_DATE(#{endTime}, 'YYYY-MM-DD HH24:MI:SS')
AND WT IS NOT NULL
</select>
</mapper>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.qgc_export.mapper.SdWtrvdaySMapper">
<!-- 河道水温日数据查询 -->
<select id="selectRawData" resultType="java.util.HashMap">
SELECT
STCD,
DT,
WT AS VALUE
FROM SD_WTRVDAY_S
WHERE IS_DELETED = 0
AND STCD IN
<foreach collection="stcdList" item="stcd" open="(" separator="," close=")">
#{stcd}
</foreach>
AND DT &gt;= TO_DATE(#{startTime}, 'YYYY-MM-DD HH24:MI:SS')
AND DT &lt; TO_DATE(#{endTime}, 'YYYY-MM-DD HH24:MI:SS')
AND WT IS NOT NULL
</select>
</mapper>