fix: 优化excel综合导出水质方法
This commit is contained in:
parent
3d952315a5
commit
616c1fbcba
@ -54,9 +54,9 @@ public class QgcExportController {
|
||||
List<String> monthList = parseCsv(months);
|
||||
List<String> 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";
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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<SdWqR> {
|
||||
|
||||
/**
|
||||
* 批量查询测站在指定时间范围内的原始小时数据(含达标标志)
|
||||
*
|
||||
* @param stcdList 测站编码列表
|
||||
* @param fieldName 查询字段名(WTMP/PH/DOX/COND/TU)
|
||||
* @param startTime 起始时间
|
||||
* @param endTime 结束时间
|
||||
* @return [STCD, TM, VALUE, SFDB]
|
||||
*/
|
||||
List<Map<String, Object>> selectRawData(@Param("stcdList") List<String> stcdList,
|
||||
@Param("fieldName") String fieldName,
|
||||
@Param("startTime") String startTime,
|
||||
@Param("endTime") String endTime);
|
||||
}
|
||||
@ -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<SdWqdayS> {
|
||||
|
||||
/**
|
||||
* 批量查询测站在指定时间范围内的原始日数据
|
||||
*
|
||||
* @param stcdList 测站编码列表
|
||||
* @param fieldName 查询字段名(WTMP/PH/DOX/COND/TU)
|
||||
* @param startTime 起始时间
|
||||
* @param endTime 结束时间
|
||||
* @return [STCD, DT, VALUE]
|
||||
*/
|
||||
List<Map<String, Object>> selectRawData(@Param("stcdList") List<String> stcdList,
|
||||
@Param("fieldName") String fieldName,
|
||||
@Param("startTime") String startTime,
|
||||
@Param("endTime") String endTime);
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,8 @@ public interface ExportIndicatorProcessor {
|
||||
* 测站类型(STTP),用于区分同一电站下不同用途的测站。
|
||||
* <ul>
|
||||
* <li>V/Q/Z 河道水情 → "ZQ"</li>
|
||||
* <li>QEC 生态流量 → 待定(如 "SQ")</li>
|
||||
* <li>WQ 水质 → null(不区分类型,取第一个测站)</li>
|
||||
* <li>QEC 生态流量 → 待定</li>
|
||||
* </ul>
|
||||
* 返回 null 表示不按 STTP 过滤,取该电站的第一个测站。
|
||||
*/
|
||||
@ -32,6 +33,20 @@ public interface ExportIndicatorProcessor {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数值保留的小数位数,默认 3 位
|
||||
*/
|
||||
default int getDecimalPlaces() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有小时达标率指标(水质指标返回 true,在月均下方追加一行"小时达标率")
|
||||
*/
|
||||
default boolean hasComplianceRate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询原始数据
|
||||
*
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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)共用查询逻辑
|
||||
* <p>
|
||||
* 小时表查询额外提取 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<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 = sdWqdaySMapper.selectRawData(stcds, getFieldName(), startStr, endStr);
|
||||
} else {
|
||||
rawList = sdWqRMapper.selectRawData(stcds, getFieldName(), 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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
* 综合导出服务实现(月度报表)
|
||||
* <p>
|
||||
* 格式:每月一个 Excel 文件 → ZIP 下载。
|
||||
* 每个 Excel 含多个 Sheet(每个指标一个 Sheet)。
|
||||
* 每个 Excel 含多个 Sheet,顺序:水温→PH→溶解氧→电导率→浊度→流速→流量→水位。
|
||||
*
|
||||
* <h3>Sheet 结构</h3>
|
||||
* <pre>
|
||||
* 日期 | 班多 | 羊曲 ← 表头(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% ← 仅水质指标有此行
|
||||
* </pre>
|
||||
*
|
||||
* <h3>电站→测站映射(STTP 类型区分)</h3>
|
||||
* 同一电站(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<String> INDICATOR_LABELS = Collections.unmodifiableList(
|
||||
/** 基础汇总指标(7个) */
|
||||
private static final List<String> BASE_LABELS = Collections.unmodifiableList(
|
||||
Arrays.asList("日均最低", "日均最高", "月内最低", "月内最低时间", "月内最高", "月内最高时间", "月均"));
|
||||
/** 小时达标率标签 */
|
||||
private static final String LABEL_COMPLIANCE = "小时达标率";
|
||||
|
||||
@Override
|
||||
public void exportData(List<String> dataFields, List<String> 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 的全部行:
|
||||
* <ol>
|
||||
* <li>日数据行(01~31):每天每电站一个值</li>
|
||||
* <li>汇总指标行(7行):日均最低、日均最高、月内最低、月内最低时间、月内最高、月内最高时间、月均</li>
|
||||
* </ol>
|
||||
*/
|
||||
private List<List<Object>> buildSheetRows(
|
||||
ExportIndicatorProcessor processor, MonthRange mr,
|
||||
List<String> stationOrder, StationResolution stationRes) {
|
||||
|
||||
String sttp = processor.getSttpType();
|
||||
int decimals = processor.getDecimalPlaces();
|
||||
|
||||
// 构建该处理器对应的 RSTCD→STCD 映射
|
||||
Map<String, String> rstcdToStcd = new LinkedHashMap<>();
|
||||
@ -171,16 +175,22 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
|
||||
List<String> uniqueStcds = rstcdToStcd.values().stream().distinct().toList();
|
||||
|
||||
// 查询日表 + 小时表数据
|
||||
// 查询日表 + 小时表
|
||||
List<IndicatorDataRow> dailyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), true);
|
||||
List<IndicatorDataRow> hourlyRows = processor.queryRawData(uniqueStcds, mr.getStart(), mr.getEnd(), false);
|
||||
|
||||
// 汇总聚合
|
||||
Map<String, Aggregation> aggMap = aggregate(dailyRows, hourlyRows, rstcdToStcd);
|
||||
// 汇总聚合(含达标率)
|
||||
Map<String, Aggregation> aggMap = aggregate(dailyRows, hourlyRows, rstcdToStcd, processor.hasComplianceRate());
|
||||
|
||||
// 日数据按 (stcd, day) 索引
|
||||
// 日数据索引
|
||||
Map<String, Map<Integer, BigDecimal>> dailyByStcd = indexDailyByDay(dailyRows);
|
||||
|
||||
// 指标标签
|
||||
List<String> labels = new ArrayList<>(BASE_LABELS);
|
||||
if (processor.hasComplianceRate()) {
|
||||
labels.add(LABEL_COMPLIANCE);
|
||||
}
|
||||
|
||||
List<List<Object>> rows = new ArrayList<>();
|
||||
|
||||
// ---- 日数据行 ----
|
||||
@ -192,28 +202,29 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
String stcd = rstcdToStcd.get(rstcd);
|
||||
Map<Integer, BigDecimal> 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<Object> 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<ExportIndicatorProcessor> resolveProcessors(List<String> dataFields) {
|
||||
Map<String, ExportIndicatorProcessor> 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<String> rstcds, Set<String> 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<String, String> 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<String, Aggregation> aggregate(
|
||||
List<IndicatorDataRow> dailyRows, List<IndicatorDataRow> hourlyRows,
|
||||
Map<String, String> rstcdToStcd) {
|
||||
Map<String, String> rstcdToStcd, boolean calcCompliance) {
|
||||
|
||||
Map<String, Aggregation> result = new LinkedHashMap<>();
|
||||
|
||||
@ -347,7 +370,7 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
String stcd = rstcdToStcd.get(rstcd);
|
||||
Aggregation agg = new Aggregation();
|
||||
|
||||
// 日均最低/最高/月均:筛选该测站的所有日数据
|
||||
// 日均最低/最高/月均
|
||||
List<BigDecimal> 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<IndicatorDataRow> 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<IndicatorDataRow> 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<String, Map<Integer, BigDecimal>> indexDailyByDay(List<IndicatorDataRow> dailyRows) {
|
||||
Map<String, Map<Integer, BigDecimal>> 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<String, String> displayNames = new LinkedHashMap<>();
|
||||
/** RSTCD → (STTP → STCD) */
|
||||
final Map<String, Map<String, String>> sttpStcdMap = new LinkedHashMap<>();
|
||||
|
||||
String getDisplayName(String rstcd) {
|
||||
return displayNames.getOrDefault(rstcd, rstcd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 STTP 的测站编码。sttp 为 null 时返回第一个可用的 STCD。
|
||||
*/
|
||||
String getStcd(String rstcd, String sttp) {
|
||||
Map<String, String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String> 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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
27
backend/src/main/resources/mapper/qgc_export/SdWqRMapper.xml
Normal file
27
backend/src/main/resources/mapper/qgc_export/SdWqRMapper.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?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.SdWqRMapper">
|
||||
|
||||
<!--
|
||||
水质小时数据动态字段查询
|
||||
利用 STCD + TM + IS_DELETED 复合索引
|
||||
-->
|
||||
<select id="selectRawData" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
STCD,
|
||||
TM,
|
||||
${fieldName} AS VALUE,
|
||||
SFDB
|
||||
FROM SD_WQ_R
|
||||
WHERE IS_DELETED = 0
|
||||
AND STCD IN
|
||||
<foreach collection="stcdList" item="stcd" open="(" separator="," close=")">
|
||||
#{stcd}
|
||||
</foreach>
|
||||
AND TM >= TO_DATE(#{startTime}, 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND TM < TO_DATE(#{endTime}, 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND ${fieldName} IS NOT NULL
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@ -0,0 +1,26 @@
|
||||
<?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.SdWqdaySMapper">
|
||||
|
||||
<!--
|
||||
水质日数据动态字段查询
|
||||
利用 STCD + DT + IS_DELETED 复合索引
|
||||
-->
|
||||
<select id="selectRawData" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
STCD,
|
||||
DT,
|
||||
${fieldName} AS VALUE
|
||||
FROM SD_WQDAY_S
|
||||
WHERE IS_DELETED = 0
|
||||
AND STCD IN
|
||||
<foreach collection="stcdList" item="stcd" open="(" separator="," close=")">
|
||||
#{stcd}
|
||||
</foreach>
|
||||
AND DT >= TO_DATE(#{startTime}, 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND DT < TO_DATE(#{endTime}, 'YYYY-MM-DD HH24:MI:SS')
|
||||
AND ${fieldName} IS NOT NULL
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
Loading…
Reference in New Issue
Block a user