Kaynağa Gözat

数采数据查询导出

Zimo 13 saat önce
ebeveyn
işleme
8461911b36

+ 42 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/ly/controller/LyController.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.pms.controller.admin.ly.controller;
+
+import cn.iocoder.yudao.module.pms.controller.admin.ly.util.ExcelExportUtil;
+import cn.iocoder.yudao.module.pms.dal.mysql.TDDeviceMapper;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.sql.Timestamp;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.generateTimestamp;
+
+@RestController
+@RequestMapping("/rq/ly")
+public class LyController {
+
+    @Resource
+    private ExcelExportUtil excelExportUtil;
+
+    @Resource
+    private TDDeviceMapper tdDeviceMapper;
+
+    /**
+     * 导出设备数据
+     */
+    @GetMapping("/export")
+    public void export(
+            @RequestParam String deviceCode,
+            @RequestParam String beginTime,
+            @RequestParam String endTime,
+            @RequestParam(required = false) String identity,
+            HttpServletResponse response
+    ) {
+        Timestamp start1 = generateTimestamp(beginTime);
+        Timestamp end1 = generateTimestamp(endTime);
+        // 异步导出,避免阻塞HTTP请求
+        excelExportUtil.exportDeviceDataSync(response, deviceCode, start1, end1, identity);
+    }
+}

+ 150 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/ly/util/ExcelExportUtil.java

@@ -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);
+            }
+        }
+    }
+}

+ 21 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/ly/vo/DeviceDataExcelDTO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.pms.controller.admin.ly.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+/**
+ * 设备数据Excel导出实体
+ */
+@Data
+public class DeviceDataExcelDTO {
+    @ExcelProperty("设备")
+    private String deviceCode;
+    @ExcelProperty("时间")
+    private String ts; // 格式:YYYY-MM-DD HH:mm:ss
+
+    @ExcelProperty("属性")
+    private String identity; // 压力/温度/悬重等
+
+    @ExcelProperty("数值")
+    private String value;
+}

+ 0 - 3
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/stat/IotStaticController.java

@@ -486,9 +486,6 @@ public class IotStaticController {
     @GetMapping("/td/ly/chart")
     public CommonResult<ImmutableMap> getTdChartLy( @RequestParam("pageSize") Integer pageSize, @RequestParam("deviceCode") String deviceCode, @RequestParam(value = "identifier", required = false) String identifier,
                                                    @RequestParam("beginTime") String beginTime, @RequestParam("endTime") String endTime) {
-//        if (StringUtils.isBlank(identifier)) {
-//            return null;
-//        }
         List<IotDeviceDO> iotDeviceDOS = iotDeviceMapper.selectByCodeIn(Collections.singleton(deviceCode));
         if (CollUtil.isEmpty(iotDeviceDOS)) {
             throw new ServiceException(new ErrorCode(111,"不存在设备"));