fix: 优化excel综合导出方法
This commit is contained in:
parent
0b8ca9063c
commit
06684578f8
@ -68,7 +68,8 @@ public class SecurityConfig {
|
||||
.requestMatchers("/dict/cache/**").permitAll()
|
||||
.requestMatchers("/sys/psbmodulelbb/**").permitAll()
|
||||
.requestMatchers("/base/operationLog/**").permitAll()
|
||||
.requestMatchers("/system/**").permitAll()
|
||||
// .requestMatchers("/system/**").permitAll()
|
||||
.requestMatchers("/qgcExport/**").permitAll()
|
||||
// .requestMatchers("/eng/**").permitAll()
|
||||
// .requestMatchers("/eq/**").permitAll()
|
||||
// .requestMatchers("/env/**").permitAll()
|
||||
|
||||
@ -299,4 +299,12 @@ public class SwaggerConfig {
|
||||
.packagesToScan("com.yfd.platform.qgc_lygk.along.controller")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi groupExportApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("7. 综合导出")
|
||||
.packagesToScan("com.yfd.platform.qgc_export.controller")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.yfd.platform.qgc_export.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.yfd.platform.annotation.Log;
|
||||
import com.yfd.platform.qgc_export.service.IQgcExportService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 综合导出控制器
|
||||
* <p>
|
||||
* 前端传参示例:
|
||||
* <pre>{@code
|
||||
* POST /qgcExport/exportData
|
||||
* {
|
||||
* "tmDimension": "month",
|
||||
* "dataField": "v,q,z",
|
||||
* "months": "2026-08,2026-07",
|
||||
* "stcd": "00001,00002",
|
||||
* "sysId": "qgc",
|
||||
* "isDailyDimension": true,
|
||||
* "isCalcQec": true
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/qgcExport")
|
||||
@Tag(name = "综合导出")
|
||||
public class QgcExportController {
|
||||
|
||||
@Resource
|
||||
private IQgcExportService qgcExportService;
|
||||
|
||||
@PostMapping("/exportData")
|
||||
@Operation(summary = "综合数据导出(ZIP 含多 Sheet Excel)")
|
||||
public void exportData(@RequestParam(required = false) String tmDimension,
|
||||
@RequestParam(required = false) String dataField,
|
||||
@RequestParam(required = false) String months,
|
||||
@RequestParam(required = false) String stcd,
|
||||
@RequestParam(required = false) String sysId,
|
||||
@RequestParam(defaultValue = "true") boolean isDailyDimension,
|
||||
@RequestParam(defaultValue = "false") boolean isCalcQec,
|
||||
HttpServletResponse response) {
|
||||
|
||||
// 解析逗号分隔参数
|
||||
List<String> dataFields = parseCsv(dataField);
|
||||
List<String> monthList = parseCsv(months);
|
||||
List<String> stationList = parseCsv(stcd);
|
||||
|
||||
if (dataFields.isEmpty()) {
|
||||
dataFields = Arrays.asList("v", "q", "z");
|
||||
}
|
||||
if (tmDimension == null || tmDimension.isEmpty()) {
|
||||
tmDimension = "month";
|
||||
}
|
||||
|
||||
qgcExportService.exportData(dataFields, monthList, stationList,
|
||||
isDailyDimension, tmDimension, response);
|
||||
}
|
||||
|
||||
private List<String> parseCsv(String value) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.stream(value.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
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_RIVER_R
|
||||
*/
|
||||
@Data
|
||||
@TableName("SD_RIVER_R")
|
||||
public class SdRiverR implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private String id;
|
||||
|
||||
/** 站码 */
|
||||
private String stcd;
|
||||
|
||||
/** 时间 */
|
||||
private Date tm;
|
||||
|
||||
/** 水位 (m) */
|
||||
private BigDecimal z;
|
||||
|
||||
/** 流量 (m³/s) */
|
||||
private BigDecimal q;
|
||||
|
||||
/** 流速 (m/s) */
|
||||
private BigDecimal v;
|
||||
|
||||
/** 测流方法 */
|
||||
private String msqmt;
|
||||
|
||||
/** 创建人 */
|
||||
private String recordUser;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date recordTime;
|
||||
|
||||
/** 更新人 */
|
||||
private String modifyUser;
|
||||
|
||||
/** 更新时间 */
|
||||
private Date modifyTime;
|
||||
|
||||
/** 是否已删除: 0=未删除 1=已删除 */
|
||||
private Integer isDeleted;
|
||||
|
||||
/** 删除人 */
|
||||
private String deleteUser;
|
||||
|
||||
/** 删除时间 */
|
||||
private Date deleteTime;
|
||||
|
||||
/** 附件ID */
|
||||
private String fid;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
}
|
||||
@ -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_RIVERDAY_S
|
||||
*/
|
||||
@Data
|
||||
@TableName("SD_RIVERDAY_S")
|
||||
public class SdRiverdayS implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private String id;
|
||||
|
||||
/** 站码 */
|
||||
private String stcd;
|
||||
|
||||
/** 时间 */
|
||||
private Date dt;
|
||||
|
||||
/** 水位 (m) */
|
||||
private BigDecimal z;
|
||||
|
||||
/** 流量 (m³/s) */
|
||||
private BigDecimal q;
|
||||
|
||||
/** 流速 (m/s) */
|
||||
private BigDecimal v;
|
||||
|
||||
/** 测流方法 */
|
||||
private String msqmt;
|
||||
|
||||
/** 创建人 */
|
||||
private String recordUser;
|
||||
|
||||
/** 创建时间 */
|
||||
private Date recordTime;
|
||||
|
||||
/** 更新人 */
|
||||
private String modifyUser;
|
||||
|
||||
/** 更新时间 */
|
||||
private Date modifyTime;
|
||||
|
||||
/** 是否已删除: 0=未删除 1=已删除 */
|
||||
private Integer isDeleted;
|
||||
|
||||
/** 删除人 */
|
||||
private String deleteUser;
|
||||
|
||||
/** 删除时间 */
|
||||
private Date deleteTime;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.yfd.platform.qgc_export.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* V_MS_STBPRP_T 视图 — 仅用于电站→测站映射
|
||||
*/
|
||||
@Data
|
||||
@TableName("V_MS_STBPRP_T")
|
||||
public class StationMapping implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableField("STCD")
|
||||
private String stcd;
|
||||
|
||||
@TableField("RSTCD")
|
||||
private String rstcd;
|
||||
|
||||
@TableField("ENNM")
|
||||
private String ennm;
|
||||
}
|
||||
@ -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.SdRiverR;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SD_RIVER_R 河道水情小时数据 Mapper
|
||||
*/
|
||||
public interface SdRiverRMapper extends BaseMapper<SdRiverR> {
|
||||
|
||||
/**
|
||||
* 批量查询站点在指定时间范围内的原始小时数据
|
||||
*
|
||||
* @param stcdList 测站编码列表
|
||||
* @param fieldName 查询字段名(V/Q/Z)
|
||||
* @param startTime 起始时间
|
||||
* @param endTime 结束时间
|
||||
* @return [STCD, TM, 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,28 @@
|
||||
package com.yfd.platform.qgc_export.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.yfd.platform.qgc_export.domain.SdRiverdayS;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SD_RIVERDAY_S 河道水情日数据 Mapper
|
||||
*/
|
||||
public interface SdRiverdaySMapper extends BaseMapper<SdRiverdayS> {
|
||||
|
||||
/**
|
||||
* 批量查询站点在指定时间范围内的原始日数据
|
||||
*
|
||||
* @param stcdList 测站编码列表
|
||||
* @param fieldName 查询字段名(V/Q/Z)
|
||||
* @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,10 @@
|
||||
package com.yfd.platform.qgc_export.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.yfd.platform.qgc_export.domain.StationMapping;
|
||||
|
||||
/**
|
||||
* V_MS_STBPRP_T 视图 Mapper — 用于电站编码→测站编码转换
|
||||
*/
|
||||
public interface StationMappingMapper extends BaseMapper<StationMapping> {
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.yfd.platform.qgc_export.processor;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 指标处理器接口 — 每种指标(v/q/z/wq/dwt 等)实现此接口
|
||||
* <p>
|
||||
* 新增指标时:实现本接口 + 在 QgcExportServiceImpl 中注入即可。
|
||||
*/
|
||||
public interface ExportIndicatorProcessor {
|
||||
|
||||
/**
|
||||
* 指标字段名,与前端 dataField 中的值对应(如 "v", "q", "z")
|
||||
*/
|
||||
String getFieldName();
|
||||
|
||||
/**
|
||||
* Excel Sheet 页中文标题(如 "流速(m/s)")
|
||||
*/
|
||||
String getSheetName();
|
||||
|
||||
/**
|
||||
* 查询原始数据
|
||||
*
|
||||
* @param stcds 测站编码列表(已从电站编码转换)
|
||||
* @param startTime 起始时间
|
||||
* @param endTime 结束时间
|
||||
* @param isDaily 是否查询日表(true=SD_RIVERDAY_S, false=SD_RIVER_R)
|
||||
* @return 原始数据行列表
|
||||
*/
|
||||
List<IndicatorDataRow> queryRawData(List<String> stcds, Date startTime, Date endTime, boolean isDaily);
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.yfd.platform.qgc_export.processor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 指标数据行 — 从数据库查询后的原始数据封装
|
||||
*/
|
||||
public class IndicatorDataRow {
|
||||
/** 测站编码 */
|
||||
private String stcd;
|
||||
/** 数据时间 */
|
||||
private Date tm;
|
||||
/** 指标值 */
|
||||
private BigDecimal value;
|
||||
|
||||
public IndicatorDataRow() {}
|
||||
|
||||
public IndicatorDataRow(String stcd, Date tm, BigDecimal value) {
|
||||
this.stcd = stcd;
|
||||
this.tm = tm;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getStcd() { return stcd; }
|
||||
public void setStcd(String stcd) { this.stcd = stcd; }
|
||||
public Date getTm() { return tm; }
|
||||
public void setTm(Date tm) { this.tm = tm; }
|
||||
public BigDecimal getValue() { return value; }
|
||||
public void setValue(BigDecimal value) { this.value = value; }
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.yfd.platform.qgc_export.processor;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 流量(Q) 指标处理器
|
||||
*/
|
||||
@Component
|
||||
public class QProcessor extends RiverIndicatorProcessor {
|
||||
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
return "Q";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSheetName() {
|
||||
return "流量(m³/s)";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.yfd.platform.qgc_export.processor;
|
||||
|
||||
import com.yfd.platform.qgc_export.mapper.SdRiverRMapper;
|
||||
import com.yfd.platform.qgc_export.mapper.SdRiverdaySMapper;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 河道水情指标处理器基类 — V/Q/Z 三个指标共用查询逻辑
|
||||
* <p>
|
||||
* 子类(VProcessor/QProcessor/ZProcessor)需标注 @Component 并提供 getFieldName/getSheetName
|
||||
*/
|
||||
public abstract class RiverIndicatorProcessor implements ExportIndicatorProcessor {
|
||||
|
||||
protected SdRiverRMapper sdRiverRMapper;
|
||||
protected SdRiverdaySMapper sdRiverdaySMapper;
|
||||
|
||||
private static final SimpleDateFormat SDF_DATETIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public void setMappers(SdRiverRMapper rMapper, SdRiverdaySMapper sMapper) {
|
||||
this.sdRiverRMapper = rMapper;
|
||||
this.sdRiverdaySMapper = sMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IndicatorDataRow> queryRawData(List<String> stcds, Date startTime, Date endTime, boolean isDaily) {
|
||||
String startStr = SDF_DATETIME.format(startTime);
|
||||
String endStr = SDF_DATETIME.format(endTime);
|
||||
|
||||
List<Map<String, Object>> rawList;
|
||||
if (isDaily) {
|
||||
rawList = sdRiverdaySMapper.selectRawData(stcds, getFieldName(), startStr, endStr);
|
||||
} else {
|
||||
rawList = sdRiverRMapper.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 = null;
|
||||
Object val = map.get("VALUE");
|
||||
if (val instanceof BigDecimal) {
|
||||
value = (BigDecimal) val;
|
||||
} else if (val instanceof Number) {
|
||||
value = BigDecimal.valueOf(((Number) val).doubleValue());
|
||||
}
|
||||
if (stcd != null && tm != null && value != null) {
|
||||
rows.add(new IndicatorDataRow(stcd, tm, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.yfd.platform.qgc_export.processor;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 流速(V) 指标处理器
|
||||
*/
|
||||
@Component
|
||||
public class VProcessor extends RiverIndicatorProcessor {
|
||||
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
return "V";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSheetName() {
|
||||
return "流速(m/s)";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.yfd.platform.qgc_export.processor;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 水位(Z) 指标处理器
|
||||
*/
|
||||
@Component
|
||||
public class ZProcessor extends RiverIndicatorProcessor {
|
||||
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
return "Z";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSheetName() {
|
||||
return "水位(m)";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.yfd.platform.qgc_export.service;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 综合导出服务接口
|
||||
*/
|
||||
public interface IQgcExportService {
|
||||
|
||||
/**
|
||||
* 执行综合导出,生成 ZIP 并写入 HttpServletResponse
|
||||
*
|
||||
* @param dataFields 指标字段列表(如 ["v","q","z"])
|
||||
* @param months 月份列表(如 ["2026-08","2026-07"])
|
||||
* @param stationCodes 电站编码列表(RSTCD,前端传入)
|
||||
* @param isDailyDimension 是否查日表
|
||||
* @param tmDimension 时间维度 (hour/day/month/year)
|
||||
* @param response HttpServletResponse
|
||||
*/
|
||||
void exportData(List<String> dataFields,
|
||||
List<String> months,
|
||||
List<String> stationCodes,
|
||||
boolean isDailyDimension,
|
||||
String tmDimension,
|
||||
HttpServletResponse response);
|
||||
}
|
||||
@ -0,0 +1,321 @@
|
||||
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.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.service.IQgcExportService;
|
||||
import com.yfd.platform.utils.ExportZipUtil;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 综合导出服务实现
|
||||
* <p>
|
||||
* 格式:每月一个 Excel 文件 → ZIP 下载。
|
||||
* 每个 Excel 含多个 Sheet(每个指标一个 Sheet),每行是一个统计指标。
|
||||
* <pre>
|
||||
* 日期 | 电站A | 电站B
|
||||
* 日均最低 | 0.523 | 0.612
|
||||
* 日均最高 | 1.234 | 1.456
|
||||
* 月内最低 | 0.412 | 0.523
|
||||
* 月内最低时间 | 2026-08-15 03:00:00 | 2026-08-14 05:30:00
|
||||
* 月内最高 | 2.156 | 2.345
|
||||
* 月内最高时间 | 2026-08-03 12:00:00 | 2026-08-20 18:45:00
|
||||
* </pre>
|
||||
*/
|
||||
@Service
|
||||
public class QgcExportServiceImpl implements IQgcExportService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(QgcExportServiceImpl.class);
|
||||
|
||||
@Resource
|
||||
private StationMappingMapper stationMappingMapper;
|
||||
|
||||
@Resource
|
||||
private SdRiverRMapper sdRiverRMapper;
|
||||
@Resource
|
||||
private SdRiverdaySMapper sdRiverdaySMapper;
|
||||
|
||||
@Resource
|
||||
private VProcessor vProcessor;
|
||||
@Resource
|
||||
private QProcessor qProcessor;
|
||||
@Resource
|
||||
private ZProcessor zProcessor;
|
||||
|
||||
private static final SimpleDateFormat SDF_DATETIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/** 行标题:6 个统计指标 */
|
||||
private static final List<String> INDICATOR_LABELS = Collections.unmodifiableList(
|
||||
Arrays.asList("日均最低", "日均最高", "月内最低", "月内最低时间", "月内最高", "月内最高时间"));
|
||||
|
||||
@Override
|
||||
public void exportData(List<String> dataFields, List<String> months,
|
||||
List<String> stationCodes, boolean isDailyDimension,
|
||||
String tmDimension, HttpServletResponse response) {
|
||||
// 1. 电站 → 测站映射 + 电站名称
|
||||
Map<String, StationInfo> stationInfoMap = resolveStations(stationCodes);
|
||||
if (stationInfoMap.isEmpty()) {
|
||||
log.warn("未找到任何电站映射数据,导出取消");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 指标处理器匹配
|
||||
List<ExportIndicatorProcessor> processors = resolveProcessors(dataFields);
|
||||
if (processors.isEmpty()) {
|
||||
log.warn("未找到匹配的指标处理器,dataFields={}", dataFields);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 收集电站名称 + 测站列表(保持顺序)
|
||||
List<String> stationOrder = new ArrayList<>(stationInfoMap.keySet());
|
||||
List<String> stationNames = stationOrder.stream()
|
||||
.map(k -> stationInfoMap.get(k).getStationName())
|
||||
.toList();
|
||||
List<String> stcds = stationInfoMap.values().stream()
|
||||
.map(StationInfo::getStcd)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 4. rstcd → stcd 映射(用于聚合时找数据)
|
||||
Map<String, String> rstcdToStcd = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, StationInfo> e : stationInfoMap.entrySet()) {
|
||||
if (e.getValue().getStcd() != null) {
|
||||
rstcdToStcd.put(e.getKey(), e.getValue().getStcd());
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 每月一个 ExcelData
|
||||
List<ExportZipUtil.ExcelData> excelList = new ArrayList<>();
|
||||
for (String month : months) {
|
||||
MonthRange mr = buildMonthRange(month);
|
||||
if (mr == null) continue;
|
||||
|
||||
List<ExportZipUtil.SheetData> sheets = new ArrayList<>();
|
||||
for (ExportIndicatorProcessor processor : processors) {
|
||||
// 查询 + 聚合这一个月的数据
|
||||
List<List<Object>> rows = buildIndicatorRows(
|
||||
processor, mr, stcds, rstcdToStcd, stationOrder);
|
||||
if (rows != null) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
headers.add("日期"); // 表头第一列 = 日期
|
||||
headers.addAll(stationNames);
|
||||
sheets.add(new ExportZipUtil.SheetData(processor.getSheetName(), headers, rows));
|
||||
}
|
||||
}
|
||||
|
||||
if (!sheets.isEmpty()) {
|
||||
excelList.add(new ExportZipUtil.ExcelData(month, sheets));
|
||||
}
|
||||
}
|
||||
|
||||
if (excelList.isEmpty()) {
|
||||
log.warn("没有可导出的数据");
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. 委托 ExportZipUtil 导出 ZIP
|
||||
String zipFileName = "综合数据导出_" + System.currentTimeMillis() + ".zip";
|
||||
ExportZipUtil.exportToResponse(response, zipFileName, excelList);
|
||||
}
|
||||
|
||||
// ======================== 构建单个指标的 Sheet 数据 ========================
|
||||
|
||||
/**
|
||||
* 查询并聚合一个月的数据,组装成 SheetData 的 rows
|
||||
* <p>
|
||||
* 返回的每一行 = [指标名, station1值, station2值, ...]
|
||||
*/
|
||||
private List<List<Object>> buildIndicatorRows(
|
||||
ExportIndicatorProcessor processor, MonthRange mr,
|
||||
List<String> stcds, Map<String, String> rstcdToStcd,
|
||||
List<String> stationOrder) {
|
||||
|
||||
if (stcds.isEmpty()) return null;
|
||||
|
||||
// 查询日表数据(用于日均最低/最高)
|
||||
List<IndicatorDataRow> dailyRows = processor.queryRawData(stcds, mr.getStart(), mr.getEnd(), true);
|
||||
// 查询小时表数据(用于月内最低/最高)
|
||||
List<IndicatorDataRow> hourlyRows = processor.queryRawData(stcds, mr.getStart(), mr.getEnd(), false);
|
||||
|
||||
// 按电站聚合
|
||||
Map<String, Aggregation> aggMap = aggregate(dailyRows, hourlyRows, rstcdToStcd);
|
||||
|
||||
// 组装 6 行
|
||||
List<List<Object>> rows = new ArrayList<>(6);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
List<Object> row = new ArrayList<>();
|
||||
row.add(INDICATOR_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 3: row.add(agg != null && agg.getExtremeMinTime() != null ? agg.getExtremeMinTime() : ""); break;
|
||||
case 4: row.add(formatOrEmpty(agg != null ? agg.getExtremeMax() : null)); break;
|
||||
case 5: row.add(agg != null && agg.getExtremeMaxTime() != null ? agg.getExtremeMaxTime() : ""); break;
|
||||
}
|
||||
}
|
||||
rows.add(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
// ======================== 指标处理器匹配 ========================
|
||||
|
||||
private List<ExportIndicatorProcessor> resolveProcessors(List<String> dataFields) {
|
||||
Map<String, RiverIndicatorProcessor> riverMap = new LinkedHashMap<>();
|
||||
riverMap.put("v", vProcessor);
|
||||
riverMap.put("q", qProcessor);
|
||||
riverMap.put("z", zProcessor);
|
||||
|
||||
List<ExportIndicatorProcessor> result = new ArrayList<>();
|
||||
for (String field : dataFields) {
|
||||
String lower = field.trim().toLowerCase();
|
||||
RiverIndicatorProcessor p = riverMap.get(lower);
|
||||
if (p != null) {
|
||||
p.setMappers(sdRiverRMapper, sdRiverdaySMapper);
|
||||
result.add(p);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ======================== 电站映射 ========================
|
||||
|
||||
private Map<String, StationInfo> resolveStations(List<String> rstcds) {
|
||||
Map<String, StationInfo> result = new LinkedHashMap<>();
|
||||
if (rstcds == null || rstcds.isEmpty()) return result;
|
||||
|
||||
List<StationMapping> mappings = stationMappingMapper.selectList(
|
||||
new LambdaQueryWrapper<StationMapping>().in(StationMapping::getRstcd, rstcds));
|
||||
|
||||
for (StationMapping m : mappings) {
|
||||
String rstcd = m.getRstcd();
|
||||
if (rstcd == null || result.containsKey(rstcd)) continue;
|
||||
result.put(rstcd, new StationInfo(m.getStcd(),
|
||||
m.getEnnm() != null ? m.getEnnm() : rstcd));
|
||||
}
|
||||
for (String rstcd : rstcds) {
|
||||
result.putIfAbsent(rstcd, new StationInfo(null, rstcd));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ======================== 月份范围 ========================
|
||||
|
||||
private MonthRange buildMonthRange(String month) {
|
||||
try {
|
||||
String[] parts = month.split("-");
|
||||
int year = Integer.parseInt(parts[0]);
|
||||
int mon = Integer.parseInt(parts[1]);
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(year, mon - 1, 1, 0, 0, 0);
|
||||
cal.set(Calendar.MILLISECOND, 0);
|
||||
Date start = cal.getTime();
|
||||
|
||||
cal.set(year, mon, 1, 0, 0, 0);
|
||||
cal.set(Calendar.MILLISECOND, 0);
|
||||
Date end = cal.getTime();
|
||||
|
||||
return new MonthRange(month, start, end);
|
||||
} catch (Exception e) {
|
||||
log.warn("解析月份失败: {}", month, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 数据聚合 ========================
|
||||
|
||||
private Map<String, Aggregation> aggregate(
|
||||
List<IndicatorDataRow> dailyRows, List<IndicatorDataRow> hourlyRows,
|
||||
Map<String, String> rstcdToStcd) {
|
||||
|
||||
Map<String, Aggregation> result = new LinkedHashMap<>();
|
||||
|
||||
for (String rstcd : rstcdToStcd.keySet()) {
|
||||
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)
|
||||
.collect(Collectors.toList());
|
||||
if (!dailyValues.isEmpty()) {
|
||||
agg.setDailyMin(Collections.min(dailyValues));
|
||||
agg.setDailyMax(Collections.max(dailyValues));
|
||||
}
|
||||
|
||||
// 月内最低/最高:筛选该测站的所有小时数据
|
||||
List<IndicatorDataRow> hourlyStationData = hourlyRows.stream()
|
||||
.filter(r -> Objects.equals(r.getStcd(), stcd) && r.getValue() != null)
|
||||
.collect(Collectors.toList());
|
||||
if (!hourlyStationData.isEmpty()) {
|
||||
IndicatorDataRow minRow = Collections.min(hourlyStationData,
|
||||
Comparator.comparing(IndicatorDataRow::getValue));
|
||||
IndicatorDataRow maxRow = Collections.max(hourlyStationData,
|
||||
Comparator.comparing(IndicatorDataRow::getValue));
|
||||
agg.setExtremeMin(minRow.getValue());
|
||||
agg.setExtremeMinTime(SDF_DATETIME.format(minRow.getTm()));
|
||||
agg.setExtremeMax(maxRow.getValue());
|
||||
agg.setExtremeMaxTime(SDF_DATETIME.format(maxRow.getTm()));
|
||||
}
|
||||
|
||||
result.put(rstcd, agg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String formatOrEmpty(BigDecimal value) {
|
||||
if (value == null) return "";
|
||||
return value.setScale(3, RoundingMode.HALF_UP).toPlainString();
|
||||
}
|
||||
|
||||
// ======================== 内部数据类 ========================
|
||||
|
||||
@lombok.Data
|
||||
private static class StationInfo {
|
||||
private final String stcd;
|
||||
private final String stationName;
|
||||
}
|
||||
|
||||
@lombok.Data
|
||||
private static class MonthRange {
|
||||
private final String label;
|
||||
private final Date start;
|
||||
private final Date end;
|
||||
}
|
||||
|
||||
@lombok.Data
|
||||
private static class Aggregation {
|
||||
private BigDecimal dailyMin;
|
||||
private BigDecimal dailyMax;
|
||||
private BigDecimal extremeMin;
|
||||
private String extremeMinTime;
|
||||
private BigDecimal extremeMax;
|
||||
private String extremeMaxTime;
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
||||
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||
import org.slf4j.Logger;
|
||||
@ -89,6 +90,51 @@ public class ExportZipUtil {
|
||||
private List<SheetData> sheets;
|
||||
}
|
||||
|
||||
// ======================== 分组表头数据模型 ========================
|
||||
|
||||
/**
|
||||
* 带分组表头的 Sheet 页数据定义(支持合并单元格)
|
||||
*
|
||||
* <p>生成的 Excel 结构:
|
||||
* <pre>
|
||||
* Row 0: [rowHeaderLabel(合并)] [group0Name(合并 subCols 列)] [group1Name(合并 subCols 列)] ...
|
||||
* Row 1: [ ] [sub0] [sub1] ... [subN] [sub0] [sub1] ... [subN] ...
|
||||
* Row 2+: rowLabel0 data...
|
||||
* Row 3 : rowLabel1 data...
|
||||
* </pre></p>
|
||||
*
|
||||
* <h3>使用示例</h3>
|
||||
* <pre>{@code
|
||||
* GroupedSheetData sheet = new GroupedSheetData("流速(m/s)", "日期",
|
||||
* Arrays.asList("XX水电站", "YY水电站"),
|
||||
* Arrays.asList("日均最低", "日均最高", "月内最低", "月内最低时间", "月内最高", "月内最高时间"));
|
||||
* // 每行:[rowLabel, g0_sub0, g0_sub1, ..., g0_sub5, g1_sub0, ...]
|
||||
* sheet.getRows().add(Arrays.asList("2026-08", 0.5, 1.2, 0.3, "2026-08-15 03:00:00", 2.1, "...", ...));
|
||||
* }</pre>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class GroupedSheetData {
|
||||
/** Sheet 页名称 */
|
||||
private String sheetName;
|
||||
/** 行标题列名(如"日期",该列在 Row0+Row1 纵向合并) */
|
||||
private String rowHeaderLabel;
|
||||
/** 分组名称列表(如["XX水电站","YY水电站"]) */
|
||||
private List<String> groupNames;
|
||||
/** 子表头列表(每个分组下都有相同的子表头) */
|
||||
private List<String> subHeaders;
|
||||
/** 数据行:每行为 [rowLabel, group0.sub0, group0.sub1, ..., group0.subN, group1.sub0, ...] */
|
||||
private List<List<Object>> rows = new ArrayList<>();
|
||||
|
||||
public GroupedSheetData(String sheetName, String rowHeaderLabel,
|
||||
List<String> groupNames, List<String> subHeaders) {
|
||||
this.sheetName = sheetName;
|
||||
this.rowHeaderLabel = rowHeaderLabel;
|
||||
this.groupNames = groupNames;
|
||||
this.subHeaders = subHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 核心导出方法 ========================
|
||||
|
||||
/**
|
||||
@ -167,6 +213,169 @@ public class ExportZipUtil {
|
||||
os.write(zipBaos.toByteArray());
|
||||
}
|
||||
|
||||
// ======================== 分组表头导出方法 ========================
|
||||
|
||||
/**
|
||||
* 导出分组表头数据为 ZIP(每个 GroupedSheetData 生成一个独立的 Excel 文件在 ZIP 中)
|
||||
*
|
||||
* @param response HttpServletResponse
|
||||
* @param zipFileName 下载文件名
|
||||
* @param sheets 分组表头数据列表(每个元素 = ZIP 中一个 .xlsx 文件)
|
||||
*/
|
||||
public static void exportGroupedToResponse(HttpServletResponse response, String zipFileName,
|
||||
List<GroupedSheetData> sheets) {
|
||||
response.setContentType("application/zip");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
try {
|
||||
response.setHeader("Content-Disposition",
|
||||
"attachment; filename=" + URLEncoder.encode(zipFileName, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
response.setHeader("Content-Disposition", "attachment; filename=export.zip");
|
||||
}
|
||||
try (ServletOutputStream os = response.getOutputStream()) {
|
||||
exportGroupedToStream(os, sheets);
|
||||
os.flush();
|
||||
} catch (IOException e) {
|
||||
log.error("导出分组 ZIP 到 Response 失败", e);
|
||||
throw new RuntimeException("导出失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出分组表头数据 ZIP 到任意 OutputStream
|
||||
* <p>
|
||||
* 每个 GroupedSheetData 作为一个 .xlsx 文件打入 ZIP。
|
||||
*/
|
||||
public static void exportGroupedToStream(OutputStream os, List<GroupedSheetData> sheets) throws IOException {
|
||||
if (sheets == null || sheets.isEmpty()) {
|
||||
log.warn("导出数据为空,跳过 ZIP 生成");
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream zipBaos = new ByteArrayOutputStream();
|
||||
try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(zipBaos)) {
|
||||
for (GroupedSheetData sheet : sheets) {
|
||||
SXSSFWorkbook workbook = new SXSSFWorkbook(100);
|
||||
try {
|
||||
fillGroupedSheet(workbook, sheet);
|
||||
byte[] bytes = workbookToBytes(workbook);
|
||||
String entryName = sanitizeFileName(sheet.getSheetName()) + ".xlsx";
|
||||
java.util.zip.ZipEntry entry = new java.util.zip.ZipEntry(entryName);
|
||||
zos.putNextEntry(entry);
|
||||
zos.write(bytes);
|
||||
zos.closeEntry();
|
||||
} finally {
|
||||
workbook.close();
|
||||
workbook.dispose();
|
||||
}
|
||||
}
|
||||
zos.finish();
|
||||
}
|
||||
os.write(zipBaos.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 向 Workbook 中填充一个带分组表头的 Sheet
|
||||
*
|
||||
* @param workbook 目标 Workbook
|
||||
* @param data 分组表头数据
|
||||
*/
|
||||
public static void fillGroupedSheet(Workbook workbook, GroupedSheetData data) {
|
||||
String name = safeName(data.getSheetName());
|
||||
Sheet sheet = workbook.createSheet(name);
|
||||
if (sheet instanceof SXSSFSheet) {
|
||||
((SXSSFSheet) sheet).trackAllColumnsForAutoSizing();
|
||||
}
|
||||
|
||||
CellStyle groupHeaderStyle = createHeaderStyle(workbook);
|
||||
CellStyle subHeaderStyle = createSubHeaderStyle(workbook);
|
||||
CellStyle dataStyle = createDataStyle(workbook);
|
||||
|
||||
int groupCount = data.getGroupNames() != null ? data.getGroupNames().size() : 0;
|
||||
int subCols = data.getSubHeaders() != null ? data.getSubHeaders().size() : 0;
|
||||
int totalCols = 1 + groupCount * subCols; // 1 = rowHeaderLabel
|
||||
|
||||
// Row 0: 分组表头
|
||||
Row groupRow = sheet.createRow(0);
|
||||
groupRow.setHeightInPoints(24);
|
||||
|
||||
Cell labelCell = groupRow.createCell(0);
|
||||
labelCell.setCellValue(data.getRowHeaderLabel());
|
||||
labelCell.setCellStyle(groupHeaderStyle);
|
||||
// 行标题列 Row0+Row1 合并
|
||||
if (data.getRowHeaderLabel() != null) {
|
||||
sheet.addMergedRegion(new CellRangeAddress(0, 1, 0, 0));
|
||||
}
|
||||
|
||||
int colIdx = 1;
|
||||
for (int g = 0; g < groupCount; g++) {
|
||||
String groupName = data.getGroupNames().get(g);
|
||||
Cell gc = groupRow.createCell(colIdx);
|
||||
gc.setCellValue(groupName);
|
||||
gc.setCellStyle(groupHeaderStyle);
|
||||
if (subCols > 1) {
|
||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, colIdx, colIdx + subCols - 1));
|
||||
}
|
||||
colIdx += subCols;
|
||||
}
|
||||
|
||||
// Row 1: 子表头
|
||||
Row subRow = sheet.createRow(1);
|
||||
subRow.setHeightInPoints(22);
|
||||
colIdx = 1;
|
||||
for (int g = 0; g < groupCount; g++) {
|
||||
for (int s = 0; s < subCols; s++) {
|
||||
Cell sc = subRow.createCell(colIdx++);
|
||||
sc.setCellValue(data.getSubHeaders().get(s));
|
||||
sc.setCellStyle(subHeaderStyle);
|
||||
}
|
||||
}
|
||||
|
||||
// Row 2+: 数据行
|
||||
if (data.getRows() != null) {
|
||||
int rowIdx = 2;
|
||||
for (List<Object> rowData : data.getRows()) {
|
||||
Row dataRow = sheet.createRow(rowIdx++);
|
||||
for (int c = 0; c < Math.min(rowData.size(), totalCols); c++) {
|
||||
Cell cell = dataRow.createCell(c);
|
||||
Object val = rowData.get(c);
|
||||
if (c == 0) {
|
||||
// 行标题列用居中对齐
|
||||
cell.setCellStyle(groupHeaderStyle);
|
||||
} else {
|
||||
cell.setCellStyle(dataStyle);
|
||||
}
|
||||
setCellValue(cell, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自动列宽
|
||||
autoSizeColumns(sheet, totalCols);
|
||||
|
||||
// 冻结前两行
|
||||
sheet.createFreezePane(0, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建子表头样式(浅蓝背景、加粗、居中、带边框)
|
||||
*/
|
||||
public static CellStyle createSubHeaderStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
|
||||
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||
|
||||
Font font = workbook.createFont();
|
||||
font.setBold(true);
|
||||
font.setFontHeightInPoints((short) 10);
|
||||
style.setFont(font);
|
||||
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
setThinBorder(style);
|
||||
return style;
|
||||
}
|
||||
|
||||
// ======================== Excel 生成方法 ========================
|
||||
|
||||
/**
|
||||
|
||||
@ -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.SdRiverRMapper">
|
||||
|
||||
<!--
|
||||
动态字段查询:根据 fieldName 参数选择 V/Q/Z 列
|
||||
利用 STCD + TM + IS_DELETED 复合索引
|
||||
-->
|
||||
<select id="selectRawData" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
STCD,
|
||||
TM,
|
||||
${fieldName} AS VALUE
|
||||
FROM SD_RIVER_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.SdRiverdaySMapper">
|
||||
|
||||
<!--
|
||||
动态字段查询:根据 fieldName 参数选择 V/Q/Z 列
|
||||
利用 STCD + DT + IS_DELETED 复合索引
|
||||
-->
|
||||
<select id="selectRawData" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
STCD,
|
||||
DT,
|
||||
${fieldName} AS VALUE
|
||||
FROM SD_RIVERDAY_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