diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/controller/QgcExportController.java b/backend/src/main/java/com/yfd/platform/qgc_export/controller/QgcExportController.java index f879110f..cf225a43 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/controller/QgcExportController.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/controller/QgcExportController.java @@ -54,9 +54,9 @@ public class QgcExportController { List monthList = parseCsv(months); List stationList = parseCsv(stcd); - if (dataFields.isEmpty()) { - dataFields = Arrays.asList("v", "q", "z"); - } +// if (dataFields.isEmpty()) { +// dataFields = Arrays.asList("v", "q", "z"); +// } if (tmDimension == null || tmDimension.isEmpty()) { tmDimension = "month"; } diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdWqR.java b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdWqR.java new file mode 100644 index 00000000..f97d85f7 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdWqR.java @@ -0,0 +1,51 @@ +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_WQ_R + */ +@Data +@TableName("SD_WQ_R") +public class SdWqR implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.INPUT) + private String id; + + /** 站码 */ + private String stcd; + + /** 采样时间 */ + private Date tm; + + /** 水温 (℃) */ + private BigDecimal wtmp; + + /** PH */ + private BigDecimal ph; + + /** 溶解氧 (mg/L) */ + private BigDecimal dox; + + /** 电导率 (μS/cm) */ + private BigDecimal cond; + + /** 浊度 (NTU) */ + private BigDecimal tu; + + /** 是否达标: 0=不达标 1=达标 */ + private Integer sfdb; + + /** 是否已删除 */ + private Integer isDeleted; +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdWqdayS.java b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdWqdayS.java new file mode 100644 index 00000000..0db66633 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/domain/SdWqdayS.java @@ -0,0 +1,51 @@ +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_WQDAY_S + */ +@Data +@TableName("SD_WQDAY_S") +public class SdWqdayS implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.INPUT) + private String id; + + /** 站码 */ + private String stcd; + + /** 时间 */ + private Date dt; + + /** 水温 (℃) */ + private BigDecimal wtmp; + + /** PH */ + private BigDecimal ph; + + /** 溶解氧 (mg/L) */ + private BigDecimal dox; + + /** 电导率 (μS/cm) */ + private BigDecimal cond; + + /** 浊度 (NTU) */ + private BigDecimal tu; + + /** 是否达标 */ + private Integer sfdb; + + /** 是否已删除 */ + private Integer isDeleted; +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdWqRMapper.java b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdWqRMapper.java new file mode 100644 index 00000000..12f6f401 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdWqRMapper.java @@ -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.SdWqR; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * SD_WQ_R 水质小时数据 Mapper + */ +public interface SdWqRMapper extends BaseMapper { + + /** + * 批量查询测站在指定时间范围内的原始小时数据(含达标标志) + * + * @param stcdList 测站编码列表 + * @param fieldName 查询字段名(WTMP/PH/DOX/COND/TU) + * @param startTime 起始时间 + * @param endTime 结束时间 + * @return [STCD, TM, VALUE, SFDB] + */ + List> selectRawData(@Param("stcdList") List stcdList, + @Param("fieldName") String fieldName, + @Param("startTime") String startTime, + @Param("endTime") String endTime); +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdWqdaySMapper.java b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdWqdaySMapper.java new file mode 100644 index 00000000..2a2dd55e --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/mapper/SdWqdaySMapper.java @@ -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.SdWqdayS; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * SD_WQDAY_S 水质日数据 Mapper + */ +public interface SdWqdaySMapper extends BaseMapper { + + /** + * 批量查询测站在指定时间范围内的原始日数据 + * + * @param stcdList 测站编码列表 + * @param fieldName 查询字段名(WTMP/PH/DOX/COND/TU) + * @param startTime 起始时间 + * @param endTime 结束时间 + * @return [STCD, DT, VALUE] + */ + List> selectRawData(@Param("stcdList") List stcdList, + @Param("fieldName") String fieldName, + @Param("startTime") String startTime, + @Param("endTime") String endTime); +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/CondProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/CondProcessor.java new file mode 100644 index 00000000..f79e6921 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/CondProcessor.java @@ -0,0 +1,30 @@ +package com.yfd.platform.qgc_export.processor; + +import org.springframework.stereotype.Component; + +/** + * 电导率(COND) 指标处理器 + */ +@Component +public class CondProcessor extends WqIndicatorProcessor { + + @Override + public String getFieldName() { + return "COND"; + } + + @Override + public String getSheetName() { + return "电导率(μS/cm)"; + } + + @Override + public int getDecimalPlaces() { + return 2; + } + + @Override + public String getSttpType() { + return "WQ"; + } +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/DoxProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/DoxProcessor.java new file mode 100644 index 00000000..61adcc37 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/DoxProcessor.java @@ -0,0 +1,30 @@ +package com.yfd.platform.qgc_export.processor; + +import org.springframework.stereotype.Component; + +/** + * 溶解氧(DOX) 指标处理器 + */ +@Component +public class DoxProcessor extends WqIndicatorProcessor { + + @Override + public String getFieldName() { + return "DOX"; + } + + @Override + public String getSheetName() { + return "溶解氧(mg/L)"; + } + + @Override + public int getDecimalPlaces() { + return 2; + } + + @Override + public String getSttpType() { + return "WQ"; + } +} 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 f86de229..ac7db929 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 @@ -24,7 +24,8 @@ public interface ExportIndicatorProcessor { * 测站类型(STTP),用于区分同一电站下不同用途的测站。 *
    *
  • V/Q/Z 河道水情 → "ZQ"
  • - *
  • QEC 生态流量 → 待定(如 "SQ")
  • + *
  • WQ 水质 → null(不区分类型,取第一个测站)
  • + *
  • QEC 生态流量 → 待定
  • *
* 返回 null 表示不按 STTP 过滤,取该电站的第一个测站。 */ @@ -32,6 +33,20 @@ public interface ExportIndicatorProcessor { return null; } + /** + * 数值保留的小数位数,默认 3 位 + */ + default int getDecimalPlaces() { + return 3; + } + + /** + * 是否有小时达标率指标(水质指标返回 true,在月均下方追加一行"小时达标率") + */ + default boolean hasComplianceRate() { + return false; + } + /** * 查询原始数据 * diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/IndicatorDataRow.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/IndicatorDataRow.java index 828c5b7c..eca691d9 100644 --- a/backend/src/main/java/com/yfd/platform/qgc_export/processor/IndicatorDataRow.java +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/IndicatorDataRow.java @@ -13,6 +13,8 @@ public class IndicatorDataRow { private Date tm; /** 指标值 */ private BigDecimal value; + /** 是否达标(仅水质指标使用,0=不达标 1=达标) */ + private Integer sfdb; public IndicatorDataRow() {} @@ -28,4 +30,6 @@ public class IndicatorDataRow { public void setTm(Date tm) { this.tm = tm; } public BigDecimal getValue() { return value; } public void setValue(BigDecimal value) { this.value = value; } + public Integer getSfdb() { return sfdb; } + public void setSfdb(Integer sfdb) { this.sfdb = sfdb; } } diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/PhProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/PhProcessor.java new file mode 100644 index 00000000..1eca08f7 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/PhProcessor.java @@ -0,0 +1,30 @@ +package com.yfd.platform.qgc_export.processor; + +import org.springframework.stereotype.Component; + +/** + * PH 指标处理器 + */ +@Component +public class PhProcessor extends WqIndicatorProcessor { + + @Override + public String getFieldName() { + return "PH"; + } + + @Override + public String getSheetName() { + return "PH"; + } + + @Override + public int getDecimalPlaces() { + return 2; + } + + @Override + public String getSttpType() { + return "WQ"; + } +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/TuProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/TuProcessor.java new file mode 100644 index 00000000..f92a3b0d --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/TuProcessor.java @@ -0,0 +1,30 @@ +package com.yfd.platform.qgc_export.processor; + +import org.springframework.stereotype.Component; + +/** + * 浊度(TU) 指标处理器 + */ +@Component +public class TuProcessor extends WqIndicatorProcessor { + + @Override + public String getFieldName() { + return "TU"; + } + + @Override + public String getSheetName() { + return "浊度(NTU)"; + } + + @Override + public int getDecimalPlaces() { + return 2; + } + + @Override + public String getSttpType() { + return "WQ"; + } +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/WqIndicatorProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/WqIndicatorProcessor.java new file mode 100644 index 00000000..c0bde5df --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/WqIndicatorProcessor.java @@ -0,0 +1,77 @@ +package com.yfd.platform.qgc_export.processor; + +import com.yfd.platform.qgc_export.mapper.SdWqRMapper; +import com.yfd.platform.qgc_export.mapper.SdWqdaySMapper; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * 水质指标处理器基类 — 5个水质指标(WTMP/PH/DOX/COND/TU)共用查询逻辑 + *

+ * 小时表查询额外提取 SFDB(是否达标)字段,用于计算小时达标率。 + * 子类需标注 @Component 并提供 getFieldName/getSheetName/getDecimalPlaces。 + */ +public abstract class WqIndicatorProcessor implements ExportIndicatorProcessor { + + protected SdWqRMapper sdWqRMapper; + protected SdWqdaySMapper sdWqdaySMapper; + + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public void setMappers(SdWqRMapper rMapper, SdWqdaySMapper sMapper) { + this.sdWqRMapper = rMapper; + this.sdWqdaySMapper = sMapper; + } + + @Override + public String getSttpType() { + return null; // 水质不按STTP过滤,取第一个测站 + } + + @Override + public boolean hasComplianceRate() { + return true; + } + + @Override + public List queryRawData(List stcds, Date startTime, Date endTime, boolean isDaily) { + String startStr = SDF.format(startTime); + String endStr = SDF.format(endTime); + + List> rawList; + if (isDaily) { + rawList = sdWqdaySMapper.selectRawData(stcds, getFieldName(), startStr, endStr); + } else { + rawList = sdWqRMapper.selectRawData(stcds, getFieldName(), startStr, endStr); + } + + List rows = new ArrayList<>(); + if (rawList != null) { + for (Map 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) { + IndicatorDataRow row = new IndicatorDataRow(stcd, tm, value); + // 小时表额外携带 SFDB(达标标志) + if (!isDaily) { + Object sfdbObj = map.get("SFDB"); + if (sfdbObj instanceof Number) { + row.setSfdb(((Number) sfdbObj).intValue()); + } + } + rows.add(row); + } + } + } + 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; + } +} diff --git a/backend/src/main/java/com/yfd/platform/qgc_export/processor/WtmpProcessor.java b/backend/src/main/java/com/yfd/platform/qgc_export/processor/WtmpProcessor.java new file mode 100644 index 00000000..7a136d59 --- /dev/null +++ b/backend/src/main/java/com/yfd/platform/qgc_export/processor/WtmpProcessor.java @@ -0,0 +1,31 @@ +package com.yfd.platform.qgc_export.processor; + +import org.springframework.stereotype.Component; + +/** + * 水温(WTMP) 指标处理器 + */ +@Component +public class WtmpProcessor extends WqIndicatorProcessor { + + @Override + public String getFieldName() { + return "WTMP"; + } + + @Override + public String getSheetName() { + return "水温(℃)"; + } + + @Override + public int getDecimalPlaces() { + return 3; + } + + @Override + public String getSttpType() { + return "WQ"; + } + +} 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 5ad37af1..af085dc1 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 @@ -3,13 +3,10 @@ package com.yfd.platform.qgc_export.service.impl; import com.yfd.platform.qgc_export.domain.StationMapping; import com.yfd.platform.qgc_export.mapper.SdRiverRMapper; import com.yfd.platform.qgc_export.mapper.SdRiverdaySMapper; +import com.yfd.platform.qgc_export.mapper.SdWqRMapper; +import com.yfd.platform.qgc_export.mapper.SdWqdaySMapper; import com.yfd.platform.qgc_export.mapper.StationMappingMapper; -import com.yfd.platform.qgc_export.processor.ExportIndicatorProcessor; -import com.yfd.platform.qgc_export.processor.IndicatorDataRow; -import com.yfd.platform.qgc_export.processor.RiverIndicatorProcessor; -import com.yfd.platform.qgc_export.processor.VProcessor; -import com.yfd.platform.qgc_export.processor.QProcessor; -import com.yfd.platform.qgc_export.processor.ZProcessor; +import com.yfd.platform.qgc_export.processor.*; import com.yfd.platform.qgc_export.service.IQgcExportService; import com.yfd.platform.utils.ExportZipUtil; @@ -30,27 +27,23 @@ import java.util.stream.Collectors; * 综合导出服务实现(月度报表) *

* 格式:每月一个 Excel 文件 → ZIP 下载。 - * 每个 Excel 含多个 Sheet(每个指标一个 Sheet)。 + * 每个 Excel 含多个 Sheet,顺序:水温→PH→溶解氧→电导率→浊度→流速→流量→水位。 * *

Sheet 结构

*
- *     日期        | 班多      | 羊曲       ← 表头(Row 0:第一列=日期,后面列=测站名称)
- *   01           | 2757.711  | 2709.839  ← 每日数据(Row 1~31)
+ *     日期        | 班多      | 羊曲       ← 表头(Row 0)
+ *   01           | 2757.711  | 2709.839  ← 日数据(Row 1~31)
  *   02           | 2757.806  | 2709.794
  *   ...
- *   31           | 2757.751  | 2709.714
- *   日均最低      | 2757.549  | 2709.565  ← 汇总指标(7行)
+ *   日均最低      | 2757.549  | 2709.565  ← 汇总指标(7~8行)
  *   日均最高      | 2757.879  | 2709.903
  *   月内最低      | 2756.979  | 2709.463
  *   月内最低时间  | 31日 08时  | 27日 08时
  *   月内最高      | 2758.003  | 2709.983
  *   月内最高时间  | 12日 16时  | 24日 16时
  *   月均         | 2757.751  | 2757.751
+ *   小时达标率    | 95.2%     | 93.8%     ← 仅水质指标有此行
  * 
- * - *

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

- * 同一电站(RSTCD)在 V_MS_STBPRP_T 视图中可能有多个测站(STCD), - * 不同类型的指标需要不同类型的测站数据。通过 {@link ExportIndicatorProcessor#getSttpType()} 区分。 */ @Service public class QgcExportServiceImpl implements IQgcExportService { @@ -60,11 +53,11 @@ public class QgcExportServiceImpl implements IQgcExportService { @Resource private StationMappingMapper stationMappingMapper; + // V/Q/Z 相关 @Resource private SdRiverRMapper sdRiverRMapper; @Resource private SdRiverdaySMapper sdRiverdaySMapper; - @Resource private VProcessor vProcessor; @Resource @@ -72,12 +65,29 @@ public class QgcExportServiceImpl implements IQgcExportService { @Resource private ZProcessor zProcessor; - /** 时间格式:dd日 HH时(如 "31日 08时") */ + // WQ 相关 + @Resource + private SdWqRMapper sdWqRMapper; + @Resource + private SdWqdaySMapper sdWqdaySMapper; + @Resource + private WtmpProcessor wtmpProcessor; + @Resource + private PhProcessor phProcessor; + @Resource + private DoxProcessor doxProcessor; + @Resource + private CondProcessor condProcessor; + @Resource + private TuProcessor tuProcessor; + private static final SimpleDateFormat SDF_TIME = new SimpleDateFormat("dd日 HH时"); - /** 汇总指标行标签(7个) */ - private static final List INDICATOR_LABELS = Collections.unmodifiableList( + /** 基础汇总指标(7个) */ + private static final List BASE_LABELS = Collections.unmodifiableList( Arrays.asList("日均最低", "日均最高", "月内最低", "月内最低时间", "月内最高", "月内最高时间", "月均")); + /** 小时达标率标签 */ + private static final String LABEL_COMPLIANCE = "小时达标率"; @Override public void exportData(List dataFields, List months, @@ -96,7 +106,7 @@ public class QgcExportServiceImpl implements IQgcExportService { .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); - // 3. 电站 → 测站映射(按 STTP 类型分别获取) + // 3. 电站 → 测站映射 StationResolution stationRes = resolveStations(stationCodes, sttpTypes); if (stationRes.displayNames.isEmpty()) { log.warn("未找到任何电站映射数据,导出取消"); @@ -142,18 +152,12 @@ public class QgcExportServiceImpl implements IQgcExportService { // ======================== 构建 Sheet 数据 ======================== - /** - * 查询一个月的数据并组装为一个 Sheet 的全部行: - *
    - *
  1. 日数据行(01~31):每天每电站一个值
  2. - *
  3. 汇总指标行(7行):日均最低、日均最高、月内最低、月内最低时间、月内最高、月内最高时间、月均
  4. - *
- */ private List> buildSheetRows( ExportIndicatorProcessor processor, MonthRange mr, List stationOrder, StationResolution stationRes) { String sttp = processor.getSttpType(); + int decimals = processor.getDecimalPlaces(); // 构建该处理器对应的 RSTCD→STCD 映射 Map rstcdToStcd = new LinkedHashMap<>(); @@ -171,16 +175,22 @@ public class QgcExportServiceImpl implements IQgcExportService { List uniqueStcds = rstcdToStcd.values().stream().distinct().toList(); - // 查询日表 + 小时表数据 + // 查询日表 + 小时表 List dailyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), true); List hourlyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), false); - // 汇总聚合 - Map aggMap = aggregate(dailyRows, hourlyRows, rstcdToStcd); + // 汇总聚合(含达标率) + Map aggMap = aggregate(dailyRows, hourlyRows, rstcdToStcd, processor.hasComplianceRate()); - // 日数据按 (stcd, day) 索引 + // 日数据索引 Map> dailyByStcd = indexDailyByDay(dailyRows); + // 指标标签 + List labels = new ArrayList<>(BASE_LABELS); + if (processor.hasComplianceRate()) { + labels.add(LABEL_COMPLIANCE); + } + List> rows = new ArrayList<>(); // ---- 日数据行 ---- @@ -192,28 +202,29 @@ public class QgcExportServiceImpl implements IQgcExportService { String stcd = rstcdToStcd.get(rstcd); Map dayMap = stcd != null ? dailyByStcd.get(stcd) : null; BigDecimal val = dayMap != null ? dayMap.get(d) : null; - row.add(formatOrEmpty(val)); + row.add(formatOrEmpty(val, decimals)); } rows.add(row); } // ---- 汇总指标行 ---- - for (int i = 0; i < 7; i++) { + for (int i = 0; i < labels.size(); i++) { List row = new ArrayList<>(); - row.add(INDICATOR_LABELS.get(i)); + row.add(labels.get(i)); for (String rstcd : stationOrder) { Aggregation agg = aggMap.get(rstcd); switch (i) { - case 0: row.add(formatOrEmpty(agg != null ? agg.getDailyMin() : null)); break; - case 1: row.add(formatOrEmpty(agg != null ? agg.getDailyMax() : null)); break; - case 2: row.add(formatOrEmpty(agg != null ? agg.getExtremeMin() : null)); break; + case 0: row.add(formatOrEmpty(agg != null ? agg.getDailyMin() : null, decimals)); break; + case 1: row.add(formatOrEmpty(agg != null ? agg.getDailyMax() : null, decimals)); break; + case 2: row.add(formatOrEmpty(agg != null ? agg.getExtremeMin() : null, decimals)); break; case 3: row.add(agg != null && agg.getExtremeMinTime() != null ? SDF_TIME.format(agg.getExtremeMinTime()) : ""); break; - case 4: row.add(formatOrEmpty(agg != null ? agg.getExtremeMax() : null)); break; + case 4: row.add(formatOrEmpty(agg != null ? agg.getExtremeMax() : null, decimals)); break; case 5: row.add(agg != null && agg.getExtremeMaxTime() != null ? SDF_TIME.format(agg.getExtremeMaxTime()) : ""); break; - case 6: row.add(formatOrEmpty(agg != null ? agg.getMonthlyAvg() : null)); break; + case 6: row.add(formatOrEmpty(agg != null ? agg.getMonthlyAvg() : null, decimals)); break; + case 7: row.add(formatComplianceRate(agg != null ? agg.getComplianceRate() : null)); break; } } rows.add(row); @@ -224,8 +235,19 @@ public class QgcExportServiceImpl implements IQgcExportService { // ======================== 指标处理器匹配 ======================== + /** + * 注册顺序决定 Sheet 在 Excel 中的顺序: + * 水质指标(水温→PH→溶解氧→电导率→浊度)→ 河道水情(流速→流量→水位) + */ private List resolveProcessors(List dataFields) { Map processorMap = new LinkedHashMap<>(); + // WQ 在前 + processorMap.put("wtmp", wtmpProcessor); + processorMap.put("ph", phProcessor); + processorMap.put("dox", doxProcessor); + processorMap.put("cond", condProcessor); + processorMap.put("tu", tuProcessor); + // V/Q/Z 在后 processorMap.put("v", vProcessor); processorMap.put("q", qProcessor); processorMap.put("z", zProcessor); @@ -237,6 +259,8 @@ public class QgcExportServiceImpl implements IQgcExportService { if (p != null) { if (p instanceof RiverIndicatorProcessor) { ((RiverIndicatorProcessor) p).setMappers(sdRiverRMapper, sdRiverdaySMapper); + } else if (p instanceof WqIndicatorProcessor) { + ((WqIndicatorProcessor) p).setMappers(sdWqRMapper, sdWqdaySMapper); } result.add(p); } @@ -244,7 +268,7 @@ public class QgcExportServiceImpl implements IQgcExportService { return result; } - // ======================== 电站映射(按 STTP 类型区分 + 测站名称) ======================== + // ======================== 电站映射 ======================== private StationResolution resolveStations(List rstcds, Set sttpTypes) { StationResolution result = new StationResolution(); @@ -267,7 +291,7 @@ public class QgcExportServiceImpl implements IQgcExportService { continue; } - // 测站名称:优先 ZQ 类型的 STNM → 任意 STNM → ENNM → RSTCD + // 测站名称:优先 ZQ 类型 STNM → 任意 STNM → ENNM → RSTCD String displayName = null; for (StationMapping m : rows) { if ("ZQ".equals(m.getSttp()) && m.getStnm() != null) { @@ -288,7 +312,7 @@ public class QgcExportServiceImpl implements IQgcExportService { } result.displayNames.put(rstcd, displayName); - // STTP → STCD 映射:每种 STTP 取第一个 + // STTP → STCD 映射 Map sttpMap = new LinkedHashMap<>(); for (StationMapping m : rows) { String sttp = m.getSttp(); @@ -327,7 +351,6 @@ public class QgcExportServiceImpl implements IQgcExportService { } } - /** 计算月份的天数 */ private int getDaysInMonth(MonthRange mr) { Calendar cal = Calendar.getInstance(); cal.setTime(mr.getEnd()); @@ -339,7 +362,7 @@ public class QgcExportServiceImpl implements IQgcExportService { private Map aggregate( List dailyRows, List hourlyRows, - Map rstcdToStcd) { + Map rstcdToStcd, boolean calcCompliance) { Map result = new LinkedHashMap<>(); @@ -347,7 +370,7 @@ public class QgcExportServiceImpl implements IQgcExportService { String stcd = rstcdToStcd.get(rstcd); Aggregation agg = new Aggregation(); - // 日均最低/最高/月均:筛选该测站的所有日数据 + // 日均最低/最高/月均 List dailyValues = dailyRows.stream() .filter(r -> Objects.equals(r.getStcd(), stcd) && r.getValue() != null) .map(IndicatorDataRow::getValue) @@ -355,12 +378,11 @@ public class QgcExportServiceImpl implements IQgcExportService { if (!dailyValues.isEmpty()) { agg.setDailyMin(Collections.min(dailyValues)); agg.setDailyMax(Collections.max(dailyValues)); - // 月均 = 日均值的平均值 BigDecimal sum = dailyValues.stream().reduce(BigDecimal.ZERO, BigDecimal::add); agg.setMonthlyAvg(sum.divide(BigDecimal.valueOf(dailyValues.size()), 3, RoundingMode.HALF_UP)); } - // 月内最低/最高及时间:筛选该测站的所有小时数据 + // 月内最低/最高及时间 List hourlyStationData = hourlyRows.stream() .filter(r -> Objects.equals(r.getStcd(), stcd) && r.getValue() != null) .collect(Collectors.toList()); @@ -375,14 +397,22 @@ public class QgcExportServiceImpl implements IQgcExportService { agg.setExtremeMaxTime(maxRow.getTm()); } + // 小时达标率(仅水质指标) + if (calcCompliance) { + List sfdbRows = hourlyStationData.stream() + .filter(r -> r.getSfdb() != null) + .collect(Collectors.toList()); + if (!sfdbRows.isEmpty()) { + long compliant = sfdbRows.stream().filter(r -> r.getSfdb() == 1).count(); + agg.setComplianceRate(BigDecimal.valueOf(compliant * 100.0 / sfdbRows.size())); + } + } + result.put(rstcd, agg); } return result; } - /** - * 将日表数据按 (stcd → day → value) 索引,供日数据行快速查找 - */ private Map> indexDailyByDay(List dailyRows) { Map> result = new LinkedHashMap<>(); for (IndicatorDataRow row : dailyRows) { @@ -395,9 +425,14 @@ public class QgcExportServiceImpl implements IQgcExportService { return result; } - private static String formatOrEmpty(BigDecimal value) { + private static String formatOrEmpty(BigDecimal value, int decimals) { if (value == null) return ""; - return value.setScale(3, RoundingMode.HALF_UP).toPlainString(); + return value.setScale(decimals, RoundingMode.HALF_UP).toPlainString(); + } + + private static String formatComplianceRate(BigDecimal value) { + if (value == null) return ""; + return value.setScale(1, RoundingMode.HALF_UP).toPlainString() + "%"; } private static String getFileNamePrefix(String tmDimension) { @@ -413,19 +448,22 @@ public class QgcExportServiceImpl implements IQgcExportService { // ======================== 内部数据类 ======================== private static class StationResolution { - /** RSTCD → 测站名称(列标题显示用) */ final Map displayNames = new LinkedHashMap<>(); - /** RSTCD → (STTP → STCD) */ final Map> sttpStcdMap = new LinkedHashMap<>(); String getDisplayName(String rstcd) { return displayNames.getOrDefault(rstcd, rstcd); } + /** + * 获取指定 STTP 的测站编码。sttp 为 null 时返回第一个可用的 STCD。 + */ String getStcd(String rstcd, String sttp) { Map inner = sttpStcdMap.get(rstcd); - if (inner == null || sttp == null) return null; - return inner.get(sttp); + if (inner == null || inner.isEmpty()) return null; + if (sttp != null) return inner.get(sttp); + // sttp 为 null:返回第一个可用的 STCD + return inner.values().iterator().next(); } } @@ -441,12 +479,11 @@ public class QgcExportServiceImpl implements IQgcExportService { private BigDecimal dailyMin; private BigDecimal dailyMax; private BigDecimal extremeMin; - /** 月内最低发生时间 */ private Date extremeMinTime; private BigDecimal extremeMax; - /** 月内最高发生时间 */ private Date extremeMaxTime; - /** 月均(所有日值的平均值) */ private BigDecimal monthlyAvg; + /** 小时达标率(百分比,仅水质指标使用) */ + private BigDecimal complianceRate; } } diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java b/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java index 06e5d6e9..dc4dda21 100644 --- a/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java +++ b/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java @@ -12,10 +12,7 @@ import com.yfd.platform.qgc_base.domain.SdEngInfoBH; import com.yfd.platform.qgc_base.service.ISdEngInfoBHService; import com.yfd.platform.qgc_base.service.ISdHbrvDicService; import com.yfd.platform.system.domain.*; -import com.yfd.platform.system.mapper.SysMenuMapper; -import com.yfd.platform.system.mapper.SysOrganizationMapper; -import com.yfd.platform.system.mapper.SysRoleMapper; -import com.yfd.platform.system.mapper.SysUserTenantMapper; +import com.yfd.platform.system.mapper.*; import com.yfd.platform.system.service.ISmsVerifyCodeService; import com.yfd.platform.system.service.ISysLogService; import com.yfd.platform.system.service.IUserService; @@ -85,6 +82,10 @@ public class SmsVerifyCodeController { @Resource private ISdHbrvDicService hbrvDicService; + @Resource + private SysUserMapper sysUserMapper; + + @Value("${rsa.private_key}") private String privateKey; private static final String URGE_CONTENT = "根据生态环境部要求,请贵电站尽快完成过鱼数据的报送工作,感谢支持。"; @@ -478,7 +479,7 @@ public class SmsVerifyCodeController { return ResponseResult.error("验证码错误或已过期"); } - SysUser user = userService.getUserByPhone(phone, tenantId); + SysUser user = userService.getUserByPhone(phone, null); if (user == null) { return ResponseResult.error("该手机号未注册"); } @@ -500,19 +501,43 @@ public class SmsVerifyCodeController { Authentication authenticate = new UsernamePasswordAuthenticationToken(user, null, authenticationToken.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticate); - LoginUser loginUser = new LoginUser(); - loginUser.setUser(user); - loginUser.setUsername(user.getUsername()); - //Todo 根据用户查询权限信息 添加到LoginUser中 + // 判断是否为超级管理员(LEVEL=1) + boolean isSuperAdmin = (isSuperAdmin(user.getId())||user.getUsertype()==0); + + // 查询用户可访问的门户列表 + List accessibleTenantIds = sysUserTenantMapper.getTenantIdsByUserId(user.getId()); + + // 非超级管理员必须指定门户,且必须在可访问列表中 + if (!isSuperAdmin) { + if (StrUtil.isBlank(tenantId)) { + throw new RuntimeException("用户无权限访问该平台"); + } + if (accessibleTenantIds == null || !accessibleTenantIds.contains(tenantId)) { + throw new RuntimeException("用户无权限访问该平台"); + } + } + // 加载权限:超级管理员加载全部权限,普通用户按门户过滤 - boolean isSuperAdmin = userService.isSuperAdmin(user.getId()); List permissions; if (isSuperAdmin) { permissions = sysMenuMapper.selectPermsByUserId(user.getId(), null); } else { permissions = sysMenuMapper.selectPermsByUserId(user.getId(), tenantId); } - loginUser.setPermissions(permissions); + + LoginUser loginUser = new LoginUser(user, permissions, tenantId, accessibleTenantIds, isSuperAdmin); + +// LoginUser loginUser = new LoginUser(); +// loginUser.setUser(user); +// loginUser.setUsername(user.getUsername()); +// boolean isSuperAdmin = userService.isSuperAdmin(user.getId()); +// List permissions; +// if (isSuperAdmin) { +// permissions = sysMenuMapper.selectPermsByUserId(user.getId(), null); +// } else { +// permissions = sysMenuMapper.selectPermsByUserId(user.getId(), tenantId); +// } +// loginUser.setPermissions(permissions); HttpServletRequest request = RequestHolder.getHttpServletRequest(); SysLog sysLog = new SysLog(); sysLog.setUsercode(user.getUsername()); @@ -549,4 +574,12 @@ public class SmsVerifyCodeController { return ResponseResult.successData(map); } + + /** + * 判断用户是否为超级管理员(拥有LEVEL=1的角色) + */ + private boolean isSuperAdmin(String userId) { + String maxLevel = sysUserMapper.getMaxLevel(userId); + return "1".equals(maxLevel); + } } \ No newline at end of file diff --git a/backend/src/main/resources/mapper/qgc_export/SdWqRMapper.xml b/backend/src/main/resources/mapper/qgc_export/SdWqRMapper.xml new file mode 100644 index 00000000..3026c9fb --- /dev/null +++ b/backend/src/main/resources/mapper/qgc_export/SdWqRMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/backend/src/main/resources/mapper/qgc_export/SdWqdaySMapper.xml b/backend/src/main/resources/mapper/qgc_export/SdWqdaySMapper.xml new file mode 100644 index 00000000..4541f84e --- /dev/null +++ b/backend/src/main/resources/mapper/qgc_export/SdWqdaySMapper.xml @@ -0,0 +1,26 @@ + + + + + + + +