|
|
@@ -0,0 +1,150 @@
|
|
|
+package cn.iocoder.yudao.module.pms.controller.admin.ly.util;
|
|
|
+
|
|
|
+import cn.iocoder.yudao.module.pms.controller.admin.ly.vo.DeviceDataExcelDTO;
|
|
|
+import cn.iocoder.yudao.module.pms.controller.admin.vo.DeviceVO;
|
|
|
+import cn.iocoder.yudao.module.pms.dal.mysql.TDDeviceMapper;
|
|
|
+import com.alibaba.excel.EasyExcel;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.sql.Timestamp;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.function.Supplier;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * EasyExcel同步导出工具类(终极修复:适配所有EasyExcel版本,解决泛型不匹配问题)
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class ExcelExportUtil {
|
|
|
+
|
|
|
+ private static final int BATCH_SIZE = 1000000;
|
|
|
+ private final TDDeviceMapper tdDeviceMapper;
|
|
|
+
|
|
|
+ public void exportDeviceDataSync(
|
|
|
+ HttpServletResponse response,
|
|
|
+ String deviceCode,
|
|
|
+ Timestamp startTime,
|
|
|
+ Timestamp endTime,
|
|
|
+ String identity
|
|
|
+ ) {
|
|
|
+ try {
|
|
|
+ // 1. 入参校验
|
|
|
+ if (Objects.isNull(startTime) || Objects.isNull(endTime) || StringUtils.isBlank(deviceCode)) {
|
|
|
+ throw new RuntimeException("设备编码和时间范围不能为空");
|
|
|
+ }
|
|
|
+ if (startTime.after(endTime)) {
|
|
|
+ throw new RuntimeException("开始时间不能晚于结束时间");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 设置响应头
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
|
+ response.setCharacterEncoding("utf-8");
|
|
|
+ DateTimeFormatter fileNameFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
|
|
+ String startStr = startTime.toLocalDateTime().format(fileNameFormatter);
|
|
|
+ String endStr = endTime.toLocalDateTime().format(fileNameFormatter);
|
|
|
+ String fileName = URLEncoder.encode("设备数据_" + deviceCode + "_" + startStr + "_" + endStr, StandardCharsets.UTF_8.toString());
|
|
|
+ response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + fileName + ".xlsx");
|
|
|
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
|
+ response.setHeader("Pragma", "no-cache");
|
|
|
+ response.setHeader("Expires", "0");
|
|
|
+
|
|
|
+ // 3. 核心修复:强转泛型为Supplier<Collection<?>>,适配EasyExcel API
|
|
|
+ Supplier<Collection<?>> dataSupplier = new DeviceDataSupplier(deviceCode, startTime, endTime, identity);
|
|
|
+
|
|
|
+ EasyExcel.write(response.getOutputStream(), DeviceDataExcelDTO.class)
|
|
|
+ .sheet("设备原始数据")
|
|
|
+ .doWrite(dataSupplier); // 此时参数完全匹配
|
|
|
+
|
|
|
+ log.info("设备{}数据导出成功,时间范围:{} - {}", deviceCode, startTime, endTime);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("设备{}数据导出失败", deviceCode, e);
|
|
|
+ throw new RuntimeException("数据导出失败:" + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 供给型函数:明确实现Supplier<Collection<?>>,解决泛型不匹配
|
|
|
+ */
|
|
|
+ private class DeviceDataSupplier implements Supplier<Collection<?>> {
|
|
|
+ private String deviceCode;
|
|
|
+ private Timestamp currentCursor;
|
|
|
+ private final Timestamp endTime;
|
|
|
+ private final String identity;
|
|
|
+ private boolean hasMoreData = true;
|
|
|
+ private boolean isFirstQuery = true;
|
|
|
+
|
|
|
+ public DeviceDataSupplier(String deviceCode, Timestamp startTime, Timestamp endTime, String identity) {
|
|
|
+ this.deviceCode = deviceCode;
|
|
|
+ this.currentCursor = startTime;
|
|
|
+ this.endTime = endTime;
|
|
|
+ this.identity = identity;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Collection<?> get() {
|
|
|
+ if (!hasMoreData) {
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 分批查询数据
|
|
|
+ List<DeviceVO> deviceVOs;
|
|
|
+ Timestamp queryStart = currentCursor;
|
|
|
+ if (!isFirstQuery) {
|
|
|
+ queryStart = new Timestamp(currentCursor.getTime() + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Objects.nonNull(identity) && StringUtils.isNotBlank(identity)) {
|
|
|
+ deviceVOs = tdDeviceMapper.selectAllBtTimeAndidentifierAll(
|
|
|
+ deviceCode, identity, queryStart, endTime, BATCH_SIZE);
|
|
|
+ } else {
|
|
|
+ deviceVOs = tdDeviceMapper.selectAllBtTime(
|
|
|
+ deviceCode, queryStart, endTime, BATCH_SIZE);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.debug("设备{}分批查询,游标:{},查询到{}条", deviceCode, queryStart, deviceVOs.size());
|
|
|
+
|
|
|
+ // 判断是否有更多数据
|
|
|
+ if (deviceVOs == null || deviceVOs.isEmpty()) {
|
|
|
+ hasMoreData = false;
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+ if (deviceVOs.size() < BATCH_SIZE) {
|
|
|
+ hasMoreData = false;
|
|
|
+ } else {
|
|
|
+ currentCursor = deviceVOs.get(deviceVOs.size() - 1).getTs();
|
|
|
+ }
|
|
|
+ isFirstQuery = false;
|
|
|
+
|
|
|
+ // 转换为DTO
|
|
|
+ DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+ return deviceVOs.stream().map(vo -> {
|
|
|
+ DeviceDataExcelDTO dto = new DeviceDataExcelDTO();
|
|
|
+ dto.setDeviceCode(deviceCode);
|
|
|
+ dto.setTs(vo.getTs().toLocalDateTime().format(timeFormatter));
|
|
|
+ dto.setIdentity(vo.getIdentity());
|
|
|
+ dto.setValue(String.valueOf(vo.getLogValue()));
|
|
|
+ return dto;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("分批查询失败", e);
|
|
|
+ hasMoreData = false;
|
|
|
+ throw new RuntimeException("分批查询数据失败:" + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|