fix: 增加垂向水温导出sheet功能

This commit is contained in:
tangwei 2026-08-06 17:50:06 +08:00
parent 97982109ff
commit f2564ceca9
6 changed files with 401 additions and 0 deletions

View File

@ -7,6 +7,7 @@ import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
/**
* 垂向水温年份数据Mapper
@ -28,4 +29,20 @@ public interface SdWtvtRMapper extends BaseMapper<SdWtvtYearVo> {
"ORDER BY STCD, TO_CHAR(TM, 'yyyy') DESC" +
"</script>")
List<SdWtvtYearVo> getWtrvDefaultYear(@Param("stcd") String stcd);
/**
* 查询各测站水深对应的高程HGT STCDWTHG 分组取最大值
*/
@Select("<script>" +
"SELECT STCD, WTHG, MAX(HGT) AS HGT " +
"FROM SD_WTVT_R " +
"WHERE IS_DELETED = 0 " +
"AND HGT IS NOT NULL " +
"AND STCD IN " +
"<foreach collection='stcdList' item='stcd' open='(' separator=',' close=')'>" +
"#{stcd}" +
"</foreach>" +
"GROUP BY STCD, WTHG" +
"</script>")
List<Map<String, Object>> selectDepthElevation(@Param("stcdList") List<String> stcdList);
}

View File

@ -0,0 +1,27 @@
package com.yfd.platform.qgc_export.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* SD_WTVTDAY_S 垂向水温日统计表
*/
@Data
@TableName("SD_WTVTDAY_S")
public class SdWtvtdayS implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String stcd;
private Date dt;
private BigDecimal wthg; // 水深
private BigDecimal vwt; // 水温
private String recordUser;
private Date recordTime;
private Date modifyTime;
private Integer isDeleted;
}

View File

@ -0,0 +1,28 @@
package com.yfd.platform.qgc_export.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yfd.platform.qgc_export.domain.SdWtvtdayS;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* SD_WTVTDAY_S 垂向水温日数据 Mapper
*/
public interface SdWtvtdaySMapper extends BaseMapper<SdWtvtdayS> {
/**
* 查询月内每日垂向水温数据 EXTRACT(DAY FROM DT)用于按指定日期筛选
*/
List<Map<String, Object>> selectDailyData(@Param("stcdList") List<String> stcdList,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
/**
* 查询月均垂向水温 STCDWTHG 分组
*/
List<Map<String, Object>> selectMonthlyAvg(@Param("stcdList") List<String> stcdList,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
}

View File

@ -0,0 +1,60 @@
package com.yfd.platform.qgc_export.processor;
import com.yfd.platform.qgc_env.wt.mapper.SdWtvtRMapper;
import com.yfd.platform.qgc_export.mapper.SdWtvtdaySMapper;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* 垂向水温(RWT) 处理器 双表头格式按水深分层展示
* <p>
* STTP: WTVT, 使用 SD_WTVT_R / SD_WTVTDAY_S
*/
@Component
public class RwtProcessor implements ExportIndicatorProcessor {
private SdWtvtRMapper sdWtvtRMapper;
private SdWtvtdaySMapper sdWtvtdaySMapper;
public void setMappers(SdWtvtRMapper rMapper, SdWtvtdaySMapper sMapper) {
this.sdWtvtRMapper = rMapper;
this.sdWtvtdaySMapper = sMapper;
}
public SdWtvtRMapper getSdWtvtRMapper() { return sdWtvtRMapper; }
public SdWtvtdaySMapper getSdWtvtdaySMapper() { return sdWtvtdaySMapper; }
@Override
public String getFieldName() {
return "rwt";
}
@Override
public String getSheetName() {
return "垂向水温";
}
@Override
public String getSttpType() {
return "WTVT";
}
@Override
public int getDecimalPlaces() {
return 2;
}
@Override
public int getOrder() {
return 11;
}
@Override
public List<IndicatorDataRow> queryRawData(List<String> stcds, Date startTime, Date endTime, boolean isDaily) {
// RWT 使用自定义 Sheet 构建不走标准数据查询流程
return Collections.emptyList();
}
}

View File

@ -13,6 +13,9 @@ import com.yfd.platform.qgc_export.mapper.SdHydropwdaySMapper;
import com.yfd.platform.qgc_export.mapper.SdQecRMapper;
import com.yfd.platform.qgc_export.mapper.SdQecdaySMapper;
import com.yfd.platform.qgc_export.mapper.StationMappingMapper;
import com.yfd.platform.qgc_export.mapper.MsAlongdetBMapper;
import com.yfd.platform.qgc_env.wt.mapper.SdWtvtRMapper;
import com.yfd.platform.qgc_export.mapper.SdWtvtdaySMapper;
import com.yfd.platform.qgc_export.processor.*;
import com.yfd.platform.qgc_export.service.IQgcExportService;
import com.yfd.platform.qgc_eng.eq.entity.vo.EngEqRuleVo;
@ -142,6 +145,17 @@ public class QgcExportServiceImpl implements IQgcExportService {
@Resource
private EngEqDataService engEqDataService;
@Resource
private MsAlongdetBMapper msAlongdetBMapper;
@Resource
private SdWtvtRMapper sdWtvtRMapper;
@Resource
private SdWtvtdaySMapper sdWtvtdaySMapper;
@Resource
private RwtProcessor rwtProcessor;
private static final SimpleDateFormat SDF_TIME = new SimpleDateFormat("dd日 HH时");
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@ -192,6 +206,8 @@ public class QgcExportServiceImpl implements IQgcExportService {
// QEC 自定义三栏达标率格式
if (processor instanceof QecProcessor) {
rows = buildQecSheetRows(mr, stationOrder, stationRes);
} else if (processor instanceof RwtProcessor) {
rows = buildRwtSheetRows(mr, stationOrder, stationRes);
} else {
rows = buildSheetRows(processor, mr, stationOrder, stationRes);
}
@ -206,6 +222,15 @@ public class QgcExportServiceImpl implements IQgcExportService {
headers.add(title);
for (int i = 1; i < 6; i++) headers.add("");
}
} else if (processor instanceof RwtProcessor) {
// 垂向水温双表头测站名称 | 水深 | 5日 | | 15日 | | 25日 | | 月均 |
headers = new ArrayList<>();
headers.add("测站名称");
headers.add("水深(m)");
headers.add("5日"); headers.add("");
headers.add("15日"); headers.add("");
headers.add("25日"); headers.add("");
headers.add("月均"); headers.add("");
} else {
// 仅包含当前处理器 STTP 类型有对应测站的电站
headers = new ArrayList<>();
@ -228,6 +253,31 @@ public class QgcExportServiceImpl implements IQgcExportService {
new int[]{0, 0, 6, 11}, // 水利部
new int[]{0, 0, 12, 17} // 多年平均
));
} else if (processor instanceof RwtProcessor) {
// 垂向水温双表头合并单元格
sd.setHeaderRowCount(1);
List<int[]> regions = new ArrayList<>(Arrays.asList(
new int[]{0, 1, 0, 0}, // 测站名称 header
new int[]{0, 1, 1, 1}, // 水深(m) header
new int[]{0, 0, 2, 3}, // 5日
new int[]{0, 0, 4, 5}, // 15日
new int[]{0, 0, 6, 7}, // 25日
new int[]{0, 0, 8, 9} // 月均
));
// 数据行测站名称相同合并rows[0] 是子标题行数据从 rows[1] 开始sheet 行号 = 2 + i
int dataStartRow = 2; // headers(1) + subheader(1)
for (int i = 1; i < rows.size(); ) {
String name = rows.get(i).get(0).toString();
int j = i + 1;
while (j < rows.size() && name.equals(rows.get(j).get(0).toString())) {
j++;
}
if (j - i > 1) {
regions.add(new int[]{dataStartRow + i - 1, dataStartRow + j - 2, 0, 0});
}
i = j;
}
sd.setMergedRegions(regions);
}
sheets.add(sd);
if ("WQ".equals(sttp)) {
@ -494,6 +544,179 @@ public class QgcExportServiceImpl implements IQgcExportService {
* <p>
* 返回的 rows[0] 是子标题行|达标数|总数|达标率(%)|达标数|总数|达标率(%)|后续为电站数据行
*/
// ======================== RWT 垂向水温 ========================
/**
* 构建垂向水温(RWT) Sheet 数据 双表头格式按测站和水深分层
*/
private List<List<Object>> buildRwtSheetRows(MonthRange mr, List<String> stationOrder,
StationResolution stationRes) {
List<String> stcdList = new ArrayList<>();
// WTVT 测站显示名 映射
Map<String, String> stcdDisplayMap = new LinkedHashMap<>();
for (String rstcd : stationOrder) {
String stcd = stationRes.getStcd(rstcd, "WTVT");
if (stcd != null) {
stcdList.add(stcd);
String displayName = stationRes.getDisplayName(rstcd, "WTVT");
stcdDisplayMap.put(stcd, StrUtil.isNotBlank(displayName) ? displayName : stcd);
}
}
if (stcdList.isEmpty()) return null;
String startStr = SDF.format(mr.getStart());
String endStr = SDF.format(mr.getEnd());
// 1. 查询日数据 EXTRACT(DAY FROM DT)
List<Map<String, Object>> dailyData = sdWtvtdaySMapper.selectDailyData(stcdList, startStr, endStr);
// 2. 查询月均
List<Map<String, Object>> monthlyData = sdWtvtdaySMapper.selectMonthlyAvg(stcdList, startStr, endStr);
// 3. 查询高程
List<Map<String, Object>> elevationData = sdWtvtRMapper.selectDepthElevation(stcdList);
// (STCD, WTHG) 构建日水温索引
Map<String, Map<String, BigDecimal>> dayVwtMap = new LinkedHashMap<>();
Map<String, BigDecimal> wthgOrderKey = new LinkedHashMap<>();
for (Map<String, Object> row : dailyData) {
String stcd = (String) row.get("STCD");
Object wthgObj = row.get("WTHG");
Object vwtObj = row.get("VWT");
Object dayNumObj = row.get("DAY_NUM");
if (stcd == null || wthgObj == null || vwtObj == null || dayNumObj == null) continue;
String wthg = formatNumber(wthgObj);
int dayNum = ((Number) dayNumObj).intValue();
BigDecimal vwt = toBigDecimal(vwtObj);
String key = stcd + "|" + wthg;
dayVwtMap.computeIfAbsent(key, k -> new LinkedHashMap<>())
.putIfAbsent(String.valueOf(dayNum), vwt);
wthgOrderKey.putIfAbsent(wthg, toBigDecimal(wthgObj));
}
// 月均索引
Map<String, BigDecimal> monthlyAvgMap = new LinkedHashMap<>();
for (Map<String, Object> row : monthlyData) {
String stcd = (String) row.get("STCD");
Object wthgObj = row.get("WTHG");
Object vwtObj = row.get("VWT");
if (stcd == null || wthgObj == null || vwtObj == null) continue;
String wthg = formatNumber(wthgObj);
String key = stcd + "|" + wthg;
monthlyAvgMap.put(key, toBigDecimal(vwtObj));
wthgOrderKey.putIfAbsent(wthg, toBigDecimal(wthgObj));
}
// 高程索引
Map<String, BigDecimal> elevationMap = new LinkedHashMap<>();
for (Map<String, Object> row : elevationData) {
String stcd = (String) row.get("STCD");
Object wthgObj = row.get("WTHG");
Object hgtObj = row.get("HGT");
if (stcd == null || wthgObj == null || hgtObj == null) continue;
String wthg = formatNumber(wthgObj);
elevationMap.put(stcd + "|" + wthg, toBigDecimal(hgtObj));
}
// 收集所有 (STCD, WTHG) 组合按测站顺序和 WTHG 升序排列
Set<String> allKeys = new LinkedHashSet<>();
allKeys.addAll(dayVwtMap.keySet());
allKeys.addAll(monthlyAvgMap.keySet());
List<String> sortedKeys = new ArrayList<>();
for (String stcd : stcdList) {
String prefix = stcd + "|";
List<String> stcdKeys = new ArrayList<>();
for (String key : allKeys) {
if (key.startsWith(prefix)) {
stcdKeys.add(key.substring(prefix.length()));
}
}
stcdKeys.sort(Comparator.comparing(k -> wthgOrderKey.getOrDefault(k, BigDecimal.ZERO)));
for (String wthg : stcdKeys) {
sortedKeys.add(prefix + wthg);
}
}
// 构建数据行
List<List<Object>> rows = new ArrayList<>();
// 子标题行Row 0
List<Object> subHeaderRow = new ArrayList<>();
subHeaderRow.add(""); subHeaderRow.add("");
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)");
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)");
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)");
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)");
rows.add(subHeaderRow);
for (String key : sortedKeys) {
int sep = key.indexOf('|');
String stcd = key.substring(0, sep);
String wthg = key.substring(sep + 1);
Map<String, BigDecimal> dayMap = dayVwtMap.getOrDefault(key, Collections.emptyMap());
BigDecimal elev = elevationMap.get(key);
BigDecimal monthlyAvg = monthlyAvgMap.get(key);
List<Object> row = new ArrayList<>();
row.add(stcdDisplayMap.getOrDefault(stcd, stcd));
row.add(formatNumberCell(wthg));
row.add(formatTemp(dayMap.get("5")));
row.add(formatElev(elev));
row.add(formatTemp(dayMap.get("15")));
row.add(formatElev(elev));
row.add(formatTemp(dayMap.get("25")));
row.add(formatElev(elev));
row.add(formatTemp(monthlyAvg));
row.add(formatElev(elev));
rows.add(row);
}
return rows;
}
private String formatNumberCell(String numStr) {
BigDecimal val = toBigDecimal(numStr);
return val != null ? stripTrailingZeros(val) : numStr;
}
private String formatTemp(BigDecimal val) {
if (val == null) return "";
return val.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString();
}
private String formatElev(BigDecimal val) {
if (val == null) return "";
return stripTrailingZeros(val);
}
private String formatNumber(Object obj) {
if (obj instanceof BigDecimal) return stripTrailingZeros((BigDecimal) obj);
if (obj instanceof Number) return String.valueOf(obj);
return obj.toString();
}
private BigDecimal toBigDecimal(Object val) {
if (val instanceof BigDecimal) return (BigDecimal) val;
if (val instanceof Number) return BigDecimal.valueOf(((Number) val).doubleValue());
if (val instanceof String) {
try {
return new BigDecimal((String) val);
} catch (NumberFormatException e) {
return null;
}
}
return null;
}
private String stripTrailingZeros(BigDecimal val) {
if (val == null) return "";
return val.stripTrailingZeros().toPlainString();
}
private List<List<Object>> buildWqComplianceSheet(
MonthRange mr, List<String> stationOrder, StationResolution stationRes) {
@ -659,6 +882,8 @@ public class QgcExportServiceImpl implements IQgcExportService {
processorMap.put("qo", qoProcessor);
// 生态流量STTP=ENG仅标准格式无日变幅
processorMap.put("qec", qecProcessor);
// 垂向水温STTP=WTVT, 双表头分层格式
processorMap.put("rwt", rwtProcessor);
// WQ 别名展开为全部水质指标
List<String> expanded = new ArrayList<>();
@ -721,6 +946,8 @@ public class QgcExportServiceImpl implements IQgcExportService {
((HydropwIndicatorProcessor) p).setMappers(sdHydropwRMapper, sdHydropwdaySMapper);
} else if (p instanceof QecProcessor) {
((QecProcessor) p).setMappers(sdQecRMapper, sdQecdaySMapper);
} else if (p instanceof RwtProcessor) {
((RwtProcessor) p).setMappers(sdWtvtRMapper, sdWtvtdaySMapper);
}
result.add(p);
}

View File

@ -0,0 +1,42 @@
<?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.SdWtvtdaySMapper">
<!-- 查询月内每日垂向水温数据 -->
<select id="selectDailyData" resultType="java.util.HashMap">
SELECT
STCD,
WTHG,
VWT,
EXTRACT(DAY FROM DT) AS DAY_NUM
FROM SD_WTVTDAY_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 VWT IS NOT NULL
</select>
<!-- 查询月均垂向水温 -->
<select id="selectMonthlyAvg" resultType="java.util.HashMap">
SELECT
STCD,
WTHG,
AVG(VWT) AS VWT
FROM SD_WTVTDAY_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 VWT IS NOT NULL
GROUP BY STCD, WTHG
</select>
</mapper>