Prechádzať zdrojové kódy

添加和平台交互的接口

wzl 2 mesiacov pred
rodič
commit
b6f0de684d

+ 0 - 1
.gitignore

@@ -33,4 +33,3 @@ build/
 .vscode/
 
 *.sh
-

+ 8 - 0
src/main/java/com/znzn/project/dc/dtdata/controller/WindFarmController.java

@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -42,4 +43,11 @@ public class WindFarmController {
     public R<List<String>> getTypes(@NotBlank(message = "风场不能为空") @Valid String windFarmId) {
         return R.ok(windFarmService.getTypes(windFarmId));
     }
+
+    @PostMapping("/queryWindFarmExists")
+    @Operation(summary = "获取风场是否存在")
+    public R<List<String>> queryWindFarmExists(String windFarmStr) {
+        List<String> windFarms = Arrays.asList(windFarmStr.split(","));
+        return R.ok(windFarmService.queryWindFarmExists(windFarms));
+    }
 }

+ 8 - 0
src/main/java/com/znzn/project/dc/dtdata/controller/WindTurbineController.java

@@ -2,6 +2,7 @@ package com.znzn.project.dc.dtdata.controller;
 
 import com.znzn.project.dc.dtdata.common.R;
 import com.znzn.project.dc.dtdata.entity.WindTurbine;
+import com.znzn.project.dc.dtdata.inOutEntity.QueryWindTurbineExistsInData;
 import com.znzn.project.dc.dtdata.inOutEntity.WindTurbineInData;
 import com.znzn.project.dc.dtdata.service.IWindTurbineService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -36,4 +37,11 @@ public class WindTurbineController {
     public R<List<WindTurbine>> getWindTurbineByParams(@RequestBody @Valid WindTurbineInData inData) {
         return R.ok(windTurbineService.getByParams(inData));
     }
+
+
+    @PostMapping("/queryWindTurbineExists")
+    @Operation(summary = "查询风机是否存在", description = "查询风机是否存在")
+    public R<List<String>> queryWindTurbineExists(@RequestBody @Valid QueryWindTurbineExistsInData inData) {
+        return R.ok(windTurbineService.queryWindTurbineExists(inData));
+    }
 }

+ 0 - 1
src/main/java/com/znzn/project/dc/dtdata/controller/WindTurbineDataInfoController.java

@@ -73,6 +73,5 @@ public class WindTurbineDataInfoController {
                 .contentLength(csvBytes.length)
                 .contentType(MediaType.parseMediaType("text/csv; charset=UTF-8"))
                 .body(resource);
-
     }
 }

+ 24 - 15
src/main/java/com/znzn/project/dc/dtdata/crontab/CollectionDataService.java

@@ -1,8 +1,12 @@
 package com.znzn.project.dc.dtdata.crontab;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.znzn.project.dc.dtdata.common.CommonData;
+import com.znzn.project.dc.dtdata.entity.ExecDataDate;
 import com.znzn.project.dc.dtdata.entity.WindTurbineDataInfo;
 import com.znzn.project.dc.dtdata.inOutEntity.CollectDataInData;
+import com.znzn.project.dc.dtdata.service.IDataCollectionService;
+import com.znzn.project.dc.dtdata.service.IExecDataDateService;
 import com.znzn.project.dc.dtdata.service.IInitService;
 import com.znzn.project.dc.dtdata.service.IWindTurbineDataInfoService;
 import jakarta.annotation.Resource;
@@ -20,32 +24,37 @@ public class CollectionDataService {
 
     @Resource
     private IWindTurbineDataInfoService windTurbineDataInfoService;
-
     @Resource
     private IInitService iInitService;
+    @Resource
+    private IDataCollectionService dataCollectionService;
+    @Resource
+    private IExecDataDateService execDataDateService;
 
     /**
      * 每小时0分5秒执行
      * cron表达式:10 0 * * * ? (秒 分 时 日 月 周)
      */
-//    @Scheduled(cron = "5 0 0 * * ?")
-//    public void collectionDataEveryHour() {
-//        LocalDateTime now = LocalDateTime.now();
-//        // 重新动态获取风机
+    @Scheduled(cron = "5 0 0 * * ?")
+    public void collectionDataEveryHour() {
+        // 重新动态获取风机
 //        if (now.getHour() == 0) {
 //            iInitService.init();
 //            log.info("每日重新获取风机成功");
 //        }
-//
-//        CollectDataInData inData = new CollectDataInData();
-//        LocalDateTime today = LocalDateTime.now();
-//        LocalDateTime lastDay = today.minusDays(1);
-//        inData.setBeginTime(lastDay.format(DATE_FORMATTER_WITH_ZERO));
-//        inData.setEndTime(today.format(DATE_FORMATTER_WITH_ZERO));
-//        inData.setMinute(1440);
-//
-//        log.info("每日执行{}的数据", lastDay.format(DATE_FORMATTER_WITH_ZERO));
-//    }
+        ExecDataDate execDataDate = execDataDateService.getById(CommonData.FILE_STORE.getId());
+        LocalDateTime today = LocalDateTime.now().toLocalDate().atStartOfDay();
+        LocalDateTime lastDay = today.minusDays(1);
+
+        if (execDataDate.getExecDateTime().equals(today)) {
+            CollectDataInData inData = new CollectDataInData();
+            inData.setBeginTime(lastDay.format(DATE_FORMATTER_WITH_ZERO));
+            inData.setEndTime(today.format(DATE_FORMATTER_WITH_ZERO));
+            inData.setMinute(1440);
+            dataCollectionService.collectData(inData);
+            log.info("每日执行{}的数据", lastDay.format(DATE_FORMATTER_WITH_ZERO));
+        }
+    }
 
     /**
      * 每小时0分0秒执行

+ 1 - 1
src/main/java/com/znzn/project/dc/dtdata/crontab/FillDataService.java

@@ -47,7 +47,7 @@ public class FillDataService {
         List<ExecError> list = execErrorService.list(queryWrapper);
 
         for (ExecError execError : list) {
-            log.info("开始执行重新获取失败数据:{}", execError);
+            log.info("开始执行重新获取失败数据[{}]", execError);
             CollectDataInData inData = new CollectDataInData();
             inData.setBeginTime(execError.getQueryBegin().format(DATE_FORMATTER));
             inData.setEndTime(execError.getQueryEnd().format(DATE_FORMATTER));

+ 22 - 0
src/main/java/com/znzn/project/dc/dtdata/inOutEntity/QueryWindTurbineExistsInData.java

@@ -0,0 +1,22 @@
+package com.znzn.project.dc.dtdata.inOutEntity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.util.List;
+
+
+@Schema
+@Data
+public class QueryWindTurbineExistsInData {
+
+    @Schema(description = "风场ID")
+    @NotBlank
+    private String windFarmId;
+
+    @Size(min = 1)
+    private List<String> turbines;
+
+}

+ 1 - 0
src/main/java/com/znzn/project/dc/dtdata/mapper/WindTurbineMapper.java

@@ -27,4 +27,5 @@ public interface WindTurbineMapper extends BaseMapper<WindTurbine> {
 
     @Delete("DELETE a FROM wind_turbine  a  LEFT JOIN wind_farm b ON a.wind_farm_id = b.id AND a.type = b.type  WHERE b.store_id =  #{fileStoreId}")
     void deleteByFileStoreId(Integer fileStoreId);
+
 }

+ 3 - 0
src/main/java/com/znzn/project/dc/dtdata/service/IWindFarmService.java

@@ -18,4 +18,7 @@ public interface IWindFarmService extends IService<WindFarm> {
     List<String> getTypes(String windFarmId);
 
     List<WindFarm> getByFileStoreId(Integer fileStoreId);
+
+    List<String> queryWindFarmExists(List<String> windFarms);
+
 }

+ 3 - 0
src/main/java/com/znzn/project/dc/dtdata/service/IWindTurbineService.java

@@ -2,6 +2,7 @@ package com.znzn.project.dc.dtdata.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.znzn.project.dc.dtdata.entity.WindTurbine;
+import com.znzn.project.dc.dtdata.inOutEntity.QueryWindTurbineExistsInData;
 import com.znzn.project.dc.dtdata.inOutEntity.WindTurbineInData;
 
 import java.util.List;
@@ -33,4 +34,6 @@ public interface IWindTurbineService extends IService<WindTurbine> {
      * @param fileStoreId 文件存储配置ID
      */
     void deleteByFileStoreId(Integer fileStoreId);
+
+    List<String> queryWindTurbineExists(QueryWindTurbineExistsInData inData);
 }

+ 34 - 19
src/main/java/com/znzn/project/dc/dtdata/service/impl/DataCollectionService.java

@@ -2,6 +2,7 @@ package com.znzn.project.dc.dtdata.service.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.enosiot.apim.poseidon.exception.PoseidonException;
 import com.znzn.project.dc.dtdata.common.CommonData;
 import com.znzn.project.dc.dtdata.entity.ExecError;
 import com.znzn.project.dc.dtdata.entity.FileStore;
@@ -59,14 +60,15 @@ public class DataCollectionService implements IDataCollectionService {
         int coreCount = Runtime.getRuntime().availableProcessors();
         int threadPoolCount = Math.min(coreCount * 2, 6);
         this.executorService = Executors.newFixedThreadPool(threadPoolCount);
-        log.info("初始化数据收集服务,线程池大小: {}", threadPoolCount);
+        log.info("初始化数据收集服务,线程池大小[{}]", threadPoolCount);
     }
 
     @Override
     @Async
     public void collectData(CollectDataInData inData) {
 
-        LocalDateTime end = inData.getEndTime();
+        LocalDateTime execBegin = LocalDateTime.now();
+        LocalDateTime end = LocalDateTime.now().toLocalDate().atStartOfDay();
         FileStore fileStore = CommonData.FILE_STORE;
         Integer id = fileStore.getId();
 
@@ -75,13 +77,17 @@ public class DataCollectionService implements IDataCollectionService {
         while (!date.isAfter(end) && !date.isEqual(end)) {
             LocalDateTime queryEndDate = date.plusMinutes(inData.getMinute());
             long begin = System.currentTimeMillis();
-            log.info("{},开始时间{},结束时间{},时间跨度为{}分钟", fileStore.getName(), date, end, inData.getMinute());
+            log.info("[{}],开始时间[{}],结束时间[{}],时间跨度为[{}]分钟", fileStore.getName(), date, end, inData.getMinute());
             processSingleData(date, queryEndDate, inData);
-            execDataDateService.finishAndAddNextTime(id, queryEndDate);
+            if (null == inData.getExecErrorId()) {
+                execDataDateService.finishAndAddNextTime(id, queryEndDate);
+            }
             date = queryEndDate;
-            log.info("{},开始时间{},结束时间{},时间跨度为{}分钟,耗时{}", fileStore.getName(), date, end, inData.getMinute(),
+            end = LocalDateTime.now().toLocalDate().atStartOfDay();
+            log.info("[{}],开始时间[{}],结束时间[{}],时间跨度为[{}]分钟,耗时[{}]", fileStore.getName(), date, end, inData.getMinute(),
                     (System.currentTimeMillis() - begin) / 1000);
         }
+        log.info("历史数据补全结束,截止时间[{}],总耗时[{}]", date, DateUtils.formatDuration(LocalDateTime.now(), execBegin));
     }
 
     @SneakyThrows
@@ -108,7 +114,7 @@ public class DataCollectionService implements IDataCollectionService {
      */
     private void processSingleData(LocalDateTime queryBeginDate, LocalDateTime queryEndDate, CollectDataInData inData) {
         long logBegin = System.currentTimeMillis();
-        log.info("开始处理日期: {} 的数据", queryBeginDate);
+        log.info("开始处理日期[{}]的数据", queryBeginDate);
         List<String> windTurbineIdList = inData.getWindTurbineIdList();
         FileStore fileStore = CommonData.FILE_STORE;
 
@@ -146,7 +152,8 @@ public class DataCollectionService implements IDataCollectionService {
                                 log.error("[{}]没有配置测点", turbine);
                             }
                         } catch (Exception e) {
-                            log.error("处理风机 {} 数据失败: {}", turbine.getId(), e.getMessage(), e);
+                            log.error("处理风机[{}] 数据失败[{}]", turbine.getId(), e.getMessage());
+                            e.fillInStackTrace();
                         }
                     }, executorService);
                     futures.add(future);
@@ -156,7 +163,7 @@ public class DataCollectionService implements IDataCollectionService {
 
         // 等待所有任务完成
         CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-        log.info("日期: {} 的数据处理完成, 耗时:{} 秒", queryBeginDate, (System.currentTimeMillis() - logBegin) / 1000);
+        log.info("日期[{}] 的数据处理完成, 耗时[{}]秒", queryBeginDate, (System.currentTimeMillis() - logBegin) / 1000);
     }
 
     private void saveExecError(WindTurbine turbine, LocalDateTime queryStartDate, LocalDateTime queryEndDate, Integer errorCode, String message, Integer execErrorId) {
@@ -183,14 +190,26 @@ public class DataCollectionService implements IDataCollectionService {
     /**
      * 处理单个风机数据
      */
+
     private void processTurbineData(WindTurbine turbine, LocalDateTime queryStartDate, LocalDateTime queryEndDate, HashSet<String>
             pointIdsWithLogic, Integer execErrorId) throws IOException {
+        processTurbineData(turbine, queryStartDate, queryEndDate, pointIdsWithLogic, execErrorId, 1);
+    }
+
+
+    private void processTurbineData(WindTurbine turbine, LocalDateTime queryStartDate, LocalDateTime queryEndDate, HashSet<String>
+            pointIdsWithLogic, Integer execErrorId, Integer retryTimes) throws IOException {
 
         long begin = System.currentTimeMillis();
         // 查询历史数据
         List<JSONObject> historicalData = null;
         try {
             historicalData = EnosapiUtils.queryHistoricalMeasurementPoint(turbine.getId(), queryStartDate.format(DATE_FORMATTER), queryEndDate.format(DATE_FORMATTER), new ArrayList<>(pointIdsWithLogic), "1m", true);
+        } catch (PoseidonException e) {
+            if (EnosapiUtils.containsException(e, java.net.ConnectException.class)) {
+                log.error("风机[{}]请求超时,第[{}]次", turbine.getId(), retryTimes);
+                processTurbineData(turbine, queryStartDate, queryEndDate, pointIdsWithLogic, execErrorId, retryTimes + 1);
+            }
         } catch (Exception e) {
             String message = e.getMessage();
             saveExecError(turbine, queryStartDate, queryEndDate, 2, message, execErrorId);
@@ -199,7 +218,7 @@ public class DataCollectionService implements IDataCollectionService {
         if (null != historicalData && historicalData.isEmpty()) {
             String message = "没有获取到数据";
             saveExecError(turbine, queryStartDate, queryEndDate, 1, message, execErrorId);
-            log.warn("风机 {} 在日期 {} 没有数据", turbine.getId(), queryStartDate);
+            log.warn("风机[{}]在日期[{}]没有数据", turbine.getId(), queryStartDate);
         }
 
         if (historicalData != null && !historicalData.isEmpty()) {
@@ -223,11 +242,9 @@ public class DataCollectionService implements IDataCollectionService {
             }
 
             if (null != execErrorId) {
-//                saveExecError(turbine, queryStartDate, queryEndDate, 0, "", execErrorId);
-                execErrorService.removeById(execErrorId);
+                saveExecError(turbine, queryStartDate, queryEndDate, 0, "", execErrorId);
             }
 
-
             // 构建文件路径 保存为文件
             String filePath = buildFilePath(turbine, queryStartDate);
             createDirectories(filePath);
@@ -238,11 +255,9 @@ public class DataCollectionService implements IDataCollectionService {
             }
             ParquetUtils.saveAsParquet(jsonDatas, allStanderList, filePath);
 
-
             updateWindTurbineDataInfoTable(turbine, queryStartDate, jsonDatas.size(), lastObject, filePath);
-
         }
-        log.info("风场:{},风机:{},风机ID:{},开始时间:{},结束时间:{},耗时:{}", turbine.getName(), turbine.getCode(), turbine.getId(),
+        log.info("风场[{}],风机[{}],风机ID[{}],开始时间[{}],结束时间[{}],耗时[{}]", turbine.getName(), turbine.getCode(), turbine.getId(),
                 queryStartDate.format(DATE_FORMATTER), queryEndDate.format(DATE_FORMATTER), (System.currentTimeMillis() - begin) / 1000);
     }
 
@@ -329,9 +344,9 @@ public class DataCollectionService implements IDataCollectionService {
      */
 
     private String buildFilePath(WindTurbine turbine, LocalDateTime date) {
-        String basePath = CommonData.FILE_STORE != null ? CommonData.FILE_STORE.getSavaDir() : "/data/wind-turbine";
-        return String.format("%s/%s/%s/%s/%s.%s", basePath, turbine.getType(), turbine.getWindFarmId(),
-                date.format(DATE_FORMATTER_WITH_NUMBER), turbine.getId(), "parquet");
+        String basePath = CommonData.FILE_STORE != null ? CommonData.FILE_STORE.getSavaDir() : "/data/wind-turbine/tmp";
+        return String.format("%s/%s/%s/%s/%s.%s", basePath, date.format(DATE_FORMATTER_WITH_NUMBER), turbine.getType(),
+                turbine.getWindFarmId(), turbine.getId(), "parquet");
     }
 
     /**
@@ -352,7 +367,7 @@ public class DataCollectionService implements IDataCollectionService {
                 Files.createDirectories(parentDir.toPath());
             }
         } catch (IOException e) {
-            log.error("创建目录失败: {}, 次数: {}", filePath, retryTimes, e);
+            log.error("创建目录失败[{}], 次数[{}]", filePath, retryTimes, e);
             if (retryTimes < 6) {
                 createDirectories(filePath, retryTimes + 1);
                 return;

+ 8 - 9
src/main/java/com/znzn/project/dc/dtdata/service/impl/InitServiceImpl.java

@@ -52,11 +52,11 @@ public class InitServiceImpl implements IInitService {
         log.info("写入风机数据完成");
         // 应用启动时预加载所有数据
         processWindPointsData();
-        log.info("风场测点数据初始化完成,需要执行的风机数为{}", CommonData.NEED_RUN_TURBINE.size());
+        log.info("风场测点数据初始化完成,需要执行的风机数为[{}]", CommonData.NEED_RUN_TURBINE.size());
 
         log.info("开始查询风场机型表名映射");
         processWindFarmTables();
-        log.info("查询风场机型表名映射完成,风场+机型数量为{}", CommonData.TABLE_MAP.size());
+        log.info("查询风场机型表名映射完成,风场+机型数量为[{}]", CommonData.TABLE_MAP.size());
     }
 
     private void processWindFarmTables() {
@@ -69,7 +69,7 @@ public class InitServiceImpl implements IInitService {
     @SneakyThrows
     private void rereadWindTurbineTable() {
         List<String> farmTypeList = windFarmService.getByFileStoreId(CommonData.FILE_STORE.getId()).stream().map(data -> String.join("--", data.getId(), data.getType())).toList();
-        log.info("本机风场+机型数量:{}", farmTypeList.size());
+        log.info("本机风场+机型数量[{}]", farmTypeList.size());
         List<AccessibleAsset> assetList;
         try {
             assetList = EnosapiUtils.queryAccessibleAsset("EnOS_Wind_Turbine");
@@ -108,13 +108,13 @@ public class InitServiceImpl implements IInitService {
 
     private void processFileStoreData() throws Exception {
         FileStore fileStore = fileStoreService.getById(getFileStoreIdFromEnv());
-        log.info("获取到本机的存储配置{}", fileStore.toString());
+        log.info("获取到本机的存储配置[{}]", fileStore.toString());
         CommonData.FILE_STORE = fileStore;
     }
 
     private void processWindPointsData() {
         windTurbineService.getByFileStoreId(CommonData.FILE_STORE.getId()).forEach(windTurbine -> CommonData.NEED_RUN_TURBINE.put(windTurbine.getId(), windTurbine));
-        log.info("本机需要运行的风机数量{}", CommonData.NEED_RUN_TURBINE.size());
+        log.info("本机需要运行的风机数量[{}]", CommonData.NEED_RUN_TURBINE.size());
         Map<String, HashSet<WindPoints>> groupedByKey = new HashMap<>();
         Map<String, HashSet<String>> pointIdsMap = new HashMap<>();
         Map<String, Map<String, Set<String>>> standerMaps = new HashMap<>();
@@ -165,7 +165,7 @@ public class InitServiceImpl implements IInitService {
 
     private String getFileStoreIdFromEnv() throws Exception {
 
-        log.info("从系统环境变量获取 FILE_STORE_ID: {}, 从Spring Environment获取 FILE_STORE_ID: {}", System.getenv(FILE_STORE_ID_ENV_VAR), environment.getProperty(FILE_STORE_ID_ENV_VAR));
+        log.info("从系统环境变量获取 FILE_STORE_ID[{}], 从Spring Environment获取 FILE_STORE_ID[{}]", System.getenv(FILE_STORE_ID_ENV_VAR), environment.getProperty(FILE_STORE_ID_ENV_VAR));
 //
 //        if (1 == 1) {
 //            return "1";
@@ -173,18 +173,17 @@ public class InitServiceImpl implements IInitService {
 
         String fileStoreId = System.getenv(FILE_STORE_ID_ENV_VAR);
         if (StringUtils.isNotBlank(fileStoreId)) {
-            log.info("从系统环境变量获取 FILE_STORE_ID: {}", fileStoreId);
+            log.info("从系统环境变量获取 FILE_STORE_ID[{}]", fileStoreId);
             return fileStoreId.trim();
         }
 
         fileStoreId = environment.getProperty(FILE_STORE_ID_ENV_VAR);
         if (StringUtils.isNotBlank(fileStoreId)) {
-            log.info("从Spring Environment获取 FILE_STORE_ID: {}", fileStoreId);
+            log.info("从Spring Environment获取 FILE_STORE_ID[{}]", fileStoreId);
             return fileStoreId.trim();
         }
 
         log.warn("所有方式都未获取到有效的 FILE_STORE_ID");
         throw new Exception("所有方式都未获取到有效的 FILE_STORE_ID");
     }
-
 }

+ 9 - 0
src/main/java/com/znzn/project/dc/dtdata/service/impl/WindFarmServiceImpl.java

@@ -9,6 +9,7 @@ import com.znzn.project.dc.dtdata.service.IWindFarmService;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -34,4 +35,12 @@ public class WindFarmServiceImpl extends ServiceImpl<WindFarmMapper, WindFarm> i
         wrapper.eq(WindFarm::getStoreId, fileStoreId);
         return list(wrapper);
     }
+
+    @Override
+    public List<String> queryWindFarmExists(List<String> windFarms) {
+        LambdaQueryWrapper<WindFarm> wrapper = Wrappers.lambdaQuery(WindFarm.class);
+        wrapper.in(WindFarm::getId, windFarms);
+        List<String> exists = list(wrapper).stream().map(WindFarm::getId).toList();
+        return windFarms.stream().filter(e -> !exists.contains(e)).collect(Collectors.toList());
+    }
 }

+ 13 - 0
src/main/java/com/znzn/project/dc/dtdata/service/impl/WindTurbineServiceImpl.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.znzn.project.dc.dtdata.entity.WindTurbine;
 import com.znzn.project.dc.dtdata.exception.BusinessException;
+import com.znzn.project.dc.dtdata.inOutEntity.QueryWindTurbineExistsInData;
 import com.znzn.project.dc.dtdata.inOutEntity.WindTurbineInData;
 import com.znzn.project.dc.dtdata.mapper.WindTurbineMapper;
 import com.znzn.project.dc.dtdata.service.IWindTurbineService;
@@ -13,6 +14,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -61,4 +63,15 @@ public class WindTurbineServiceImpl extends ServiceImpl<WindTurbineMapper, WindT
     public void deleteByFileStoreId(Integer fileStoreId) {
         windTurbineMapper.deleteByFileStoreId(fileStoreId);
     }
+
+    @Override
+    public List<String> queryWindTurbineExists(QueryWindTurbineExistsInData inData) {
+        String windFarmId = inData.getWindFarmId();
+        List<String> turbines = inData.getTurbines();
+        LambdaQueryWrapper<WindTurbine> wrapper = Wrappers.lambdaQuery(WindTurbine.class);
+        wrapper.eq(WindTurbine::getWindFarmId, windFarmId)
+                .in(WindTurbine::getId, turbines);
+        List<String> exists = list(wrapper).stream().map(WindTurbine::getId).toList();
+        return turbines.stream().filter(e -> !exists.contains(e)).collect(Collectors.toList());
+    }
 }

+ 23 - 0
src/main/java/com/znzn/project/dc/dtdata/utils/enos/DateUtils.java

@@ -1,5 +1,6 @@
 package com.znzn.project.dc.dtdata.utils.enos;
 
+import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
@@ -23,4 +24,26 @@ public class DateUtils {
     public static LocalDateTime toLocalDateTime(Date date) {
         return LocalDateTime.ofInstant(date.toInstant(), ZoneId.of("Asia/Shanghai"));
     }
+
+    /**
+     * 计算两个时间差,返回 天 时:分:秒
+     */
+    public static String formatDuration(LocalDateTime start, LocalDateTime end) {
+        Duration duration = Duration.between(start, end);
+
+        long days = duration.toDays();
+        duration = duration.minusDays(days);
+
+        long hours = duration.toHours();
+        duration = duration.minusHours(hours);
+
+        long minutes = duration.toMinutes();
+        duration = duration.minusMinutes(minutes);
+
+        long seconds = duration.getSeconds();
+
+        // 输出格式:1天 03:02:01
+        return String.format("%d天 %02d:%02d:%02d",
+                days, hours, minutes, seconds);
+    }
 }

+ 9 - 23
src/main/java/com/znzn/project/dc/dtdata/utils/enos/EnosapiUtils.java

@@ -4,9 +4,13 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.enosiot.apim.poseidon.config.PConfig;
 import com.enosiot.apim.poseidon.core.Poseidon;
+import com.enosiot.apim.poseidon.exception.PoseidonException;
 import com.znzn.project.dc.dtdata.utils.enos.entity.*;
 import lombok.extern.slf4j.Slf4j;
 
+import java.net.ConnectException;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
 
 @Slf4j
@@ -275,29 +279,11 @@ public class EnosapiUtils {
         return message;
     }
 
-
-    public static void main(String[] args) throws Exception {
-//        Map<String, AssetAttribute> data = queryAssetTopology(Arrays.asList("0iffMVPY"));
-//        System.out.println(JSON.toJSONString(data));
-
-
-//        String logicPOints = "last(WCNV.SLCNVAI004),last(WCNV.SLCNVAI005)";
-//        String mdmId = "Vg24xugq";
-//        List<JSONObject> datas = queryHistoricalMeasurementPoint(mdmId, "2025-11-19 17:30:00", "2025-11-19 17:50:00", logicPOints, "RAW", true);
-//        System.out.println(datas.size());
-//        FileWriter fileWriter = new FileWriter(mdmId + "1119-SECOND.json");
-//        fileWriter.write(JSON.toJSONString(datas));
-//        fileWriter.flush();
-//        fileWriter.close();
-
-        String mdmIdStr = "Vg24xugq";
-        List<String> mdmIds = Arrays.asList(mdmIdStr.split(","));
-        Map<String, AssetAttribute> data = queryAssetAttribute(mdmIds);
-        System.out.println(data);
-//        FileWriter fileWriter = new FileWriter("test.json");
-//        fileWriter.write(JSON.toJSONString(data));
-//        fileWriter.flush();
-//        fileWriter.close();
+    // 工具方法:递归检查嵌套异常
+    public static boolean containsException(Throwable e, Class<? extends Throwable> target) {
+        if (e == null) return false;
+        if (target.isInstance(e)) return true;
+        return containsException(e.getCause(), target);
     }
 
 }

+ 154 - 63
src/main/java/com/znzn/project/dc/dtdata/utils/enos/ParquetUtils.java

@@ -13,6 +13,8 @@ import org.apache.parquet.hadoop.ParquetReader;
 import org.apache.parquet.hadoop.ParquetWriter;
 import org.apache.parquet.hadoop.metadata.CompressionCodecName;
 import org.apache.parquet.hadoop.util.HadoopInputFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
@@ -20,63 +22,122 @@ import java.nio.file.Files;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
-
+/**
+ * Parquet文件操作工具类
+ * 支持将JSON数据写入Parquet文件、读取Parquet文件最后一行数据
+ * 适配无 ParquetWriter.Mode 的低版本Parquet
+ *
+ * @author 开发者
+ * @date 2025
+ */
 public class ParquetUtils {
 
+    // 日志组件(替换System.out,便于线上排查问题)
+    private static final Logger LOGGER = LoggerFactory.getLogger(ParquetUtils.class);
+
+    // 全局Jackson对象映射器(可复用,提升性能)
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    // Hadoop配置单例(避免重复创建)
+    private static final Configuration HADOOP_CONF = initHadoopConf();
+
     /**
-     * 将映射后的JSON数据保存为Parquet文件
+     * 初始化Hadoop配置(适配本地文件系统)
+     * @return 配置好的Hadoop Configuration
      */
-    public static void saveAsParquet(
-            List<JSONObject> mappedData,
-            List<String> standardKeys,
-            String outputPath) throws IOException {
+    private static Configuration initHadoopConf() {
+        Configuration conf = new Configuration();
+        conf.set("fs.defaultFS", "file:///");
+        conf.setBoolean("fs.write.checksum", false); // 禁用crc校验文件
+        conf.setInt("io.file.buffer.size", 4096); // 优化文件读写缓冲区
+        return conf;
+    }
 
-        if (mappedData == null || mappedData.isEmpty()) {
-            System.out.println("警告: 没有数据需要保存");
+    /**
+     * 将映射后的JSON数据保存为Parquet文件
+     *
+     * @param mappedData   待写入的JSON数据列表
+     * @param standardKeys 标准字段名列表(用于构建Schema)
+     * @param outputPath   Parquet文件输出路径
+     * @throws IOException 文件写入失败时抛出异常
+     */
+    public static void saveAsParquet(List<JSONObject> mappedData, List<String> standardKeys, String outputPath) throws IOException {
+        // 参数校验
+        if (Objects.isNull(mappedData) || mappedData.isEmpty()) {
+            LOGGER.warn("待写入的JSON数据为空,无需保存Parquet文件");
             return;
         }
+        if (Objects.isNull(standardKeys) || standardKeys.isEmpty()) {
+            throw new IllegalArgumentException("标准字段名列表不能为空");
+        }
+        if (Objects.isNull(outputPath) || outputPath.trim().isEmpty()) {
+            throw new IllegalArgumentException("Parquet输出路径不能为空");
+        }
 
-        // 如果文件存在则删除
-        File file = new File(outputPath);
-        Files.deleteIfExists(file.toPath());
+        // 清理已存在的文件(低版本Parquet无自动覆盖,手动删除)
+        File outputFile = new File(outputPath);
+        if (outputFile.exists()) {
+            Files.deleteIfExists(outputFile.toPath());
+            LOGGER.info("已删除已存在的Parquet文件:{}", outputPath);
+        }
 
         // 创建Avro Schema
         Schema schema = createAvroSchema(standardKeys);
 
-        // 创建Parquet写入器
-        try (ParquetWriter<GenericRecord> writer = AvroParquetWriter.<GenericRecord>builder(new org.apache.hadoop.fs.Path(outputPath))
+        // 写入Parquet文件(移除Mode,适配低版本)
+        try (ParquetWriter<GenericRecord> writer = AvroParquetWriter.<GenericRecord>builder(new Path(outputPath))
                 .withSchema(schema)
                 .withCompressionCodec(CompressionCodecName.SNAPPY)
-                .build()) {
+                .withConf(HADOOP_CONF)
+                .build()) { // 移除 withWriteMode,低版本无需指定
 
-            // 将JSON数据转换为Avro记录并写入
+            // 批量写入数据
+            int writeCount = 0;
             for (JSONObject jsonObj : mappedData) {
+                if (Objects.isNull(jsonObj)) {
+                    LOGGER.warn("跳过空的JSON对象,当前索引:{}", writeCount);
+                    continue;
+                }
                 GenericRecord record = convertToAvroRecord(jsonObj, schema, standardKeys);
                 writer.write(record);
+                writeCount++;
             }
 
-            System.out.println("成功保存Parquet文件: " + outputPath);
-            System.out.println("总记录数: " + mappedData.size());
+            LOGGER.info("成功保存Parquet文件:{},总写入记录数:{}(跳过空记录数:{})",
+                    outputPath, writeCount, mappedData.size() - writeCount);
+
+        } catch (IOException e) {
+            LOGGER.error("写入Parquet文件失败,路径:{}", outputPath, e);
+            throw e; // 抛出异常让上层处理
         }
     }
 
     /**
-     * 根据标准key创建Avro Schema
+     * 根据标准字段名创建Avro Schema
+     *
+     * @param standardKeys 标准字段名列表
+     * @return Avro Schema对象
      */
     private static Schema createAvroSchema(List<String> standardKeys) {
         StringBuilder schemaBuilder = new StringBuilder();
-        schemaBuilder.append("{\n");
-        schemaBuilder.append("  \"type\": \"record\",\n");
-        schemaBuilder.append("  \"name\": \"StandardRecord\",\n");
-        schemaBuilder.append("  \"namespace\": \"com.example.parquet\",\n");
-        schemaBuilder.append("  \"fields\": [\n");
+        schemaBuilder.append("{\n")
+                .append("  \"type\": \"record\",\n")
+                .append("  \"name\": \"StandardRecord\",\n")
+                .append("  \"namespace\": \"com.example.parquet\",\n")
+                .append("  \"fields\": [\n");
 
         for (int i = 0; i < standardKeys.size(); i++) {
             String fieldName = standardKeys.get(i);
-            schemaBuilder.append("    {\n");
-            schemaBuilder.append("      \"name\": \"").append(fieldName).append("\",\n");
-            schemaBuilder.append("      \"type\": [\"string\", \"null\"]\n"); // 所有字段都设为string类型,可为null
+            // 字段名合法性校验
+            if (Objects.isNull(fieldName) || fieldName.trim().isEmpty()) {
+                throw new IllegalArgumentException("字段名不能为空,索引:" + i);
+            }
+            schemaBuilder.append("    {\n")
+                    .append("      \"name\": \"").append(fieldName.trim()).append("\",\n")
+                    .append("      \"type\": [\"string\", \"null\"]\n"); // 所有字段设为string类型,可为null
+
             if (i < standardKeys.size() - 1) {
                 schemaBuilder.append("    },\n");
             } else {
@@ -84,71 +145,94 @@ public class ParquetUtils {
             }
         }
 
-        schemaBuilder.append("  ]\n");
-        schemaBuilder.append("}");
+        schemaBuilder.append("  ]\n")
+                .append("}");
 
-        return new Schema.Parser().parse(schemaBuilder.toString());
+        try {
+            return new Schema.Parser().parse(schemaBuilder.toString());
+        } catch (Exception e) {
+            LOGGER.error("创建Avro Schema失败,Schema字符串:{}", schemaBuilder, e);
+            throw new RuntimeException("Avro Schema解析失败", e);
+        }
     }
 
     /**
      * 将JSONObject转换为Avro GenericRecord
+     *
+     * @param jsonObj      JSON对象
+     * @param schema       Avro Schema
+     * @param standardKeys 标准字段名列表
+     * @return Avro GenericRecord对象
      */
     private static GenericRecord convertToAvroRecord(JSONObject jsonObj, Schema schema, List<String> standardKeys) {
         GenericRecord record = new GenericData.Record(schema);
 
         for (String key : standardKeys) {
-            Object value = jsonObj.get(key);
+            String fieldName = key.trim();
+            Object value = jsonObj.get(fieldName);
             if (value != null) {
-                // 将所有值转换为字符串
-                record.put(key, value.toString());
+                // 将所有值转换为字符串(兼容各种数据类型)
+                record.put(fieldName, value.toString());
             } else {
-                record.put(key, null);
+                record.put(fieldName, null);
             }
         }
 
         return record;
     }
 
-
-    // 全局Jackson对象映射器(可复用,提升性能)
-    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
-
     /**
-     * 读取Parquet文件最后一行并转换为JSON字符串
+     * 读取Parquet文件最后一行并转换为JSON对象
      *
      * @param parquetFilePath Parquet文件本地路径(如:D:/data/test.parquet)
-     * @return 最后一行数据的JSON字符串
-     * @throws IOException 读取文件或转换JSON时的异常
+     * @return 最后一行数据的JSON对象(无数据时返回空JSON)
+     * @throws IOException 读取文件失败时抛出异常
      */
     public static JSONObject getParquetLastLineAsJson(String parquetFilePath) throws IOException {
-        // 1. 初始化Hadoop配置 - 适配本地文件系统(关键:支持file://协议)
-        Configuration conf = new Configuration();
-        conf.set("fs.defaultFS", "file:///");
-        Path parquetPath = new Path(parquetFilePath);
-        HadoopInputFile inputFile = HadoopInputFile.fromPath(parquetPath, conf);
+        // 参数校验
+        if (Objects.isNull(parquetFilePath) || parquetFilePath.trim().isEmpty()) {
+            throw new IllegalArgumentException("Parquet文件路径不能为空");
+        }
+        File parquetFile = new File(parquetFilePath);
+        if (!parquetFile.exists()) {
+            LOGGER.error("Parquet文件不存在,路径:{}", parquetFilePath);
+            return new JSONObject();
+        }
+        if (parquetFile.length() == 0) {
+            LOGGER.warn("Parquet文件为空,路径:{}", parquetFilePath);
+            return new JSONObject();
+        }
 
-        // 2. 构建Avro Parquet读取器(Parquet文件多基于Avro序列化)
+        // 构建Hadoop输入文件
+        HadoopInputFile inputFile = HadoopInputFile.fromPath(new Path(parquetFilePath), HADOOP_CONF);
+
+        // 读取Parquet文件,获取最后一行
         try (ParquetReader<GenericRecord> reader = AvroParquetReader.<GenericRecord>builder(inputFile)
-                .withConf(conf)
+                .withConf(HADOOP_CONF)
                 .build()) {
 
-            // 3. 遍历Parquet文件,记录最后一行数据(无需缓存所有行,节省内存)
-            GenericRecord currentRecord;
             GenericRecord lastRecord = null;
+            GenericRecord currentRecord;
+            long lineCount = 0;
+
+            // 遍历文件(仅保留最后一行,节省内存)
             while ((currentRecord = reader.read()) != null) {
-                lastRecord = currentRecord; // 不断覆盖,最终保留最后一行
+                lastRecord = currentRecord;
+                lineCount++;
             }
 
-            // 4. 校验是否存在有效数据
+            LOGGER.info("读取Parquet文件完成,路径:{},总行数:{}", parquetFilePath, lineCount);
+
+            // 转换为JSON对象
             if (lastRecord == null) {
-                return new JSONObject(); // 无数据时返回空JSON对象
+                return new JSONObject();
             }
-
-            // 5. 将GenericRecord转换为Map(方便Jackson转换为JSON)
             Map<String, Object> dataMap = convertGenericRecordToMap(lastRecord);
-
-            // 6. Jackson将Map转换为格式化的JSON字符串
             return new JSONObject(dataMap);
+
+        } catch (IOException e) {
+            LOGGER.error("读取Parquet文件失败,路径:{}", parquetFilePath, e);
+            throw e;
         }
     }
 
@@ -160,6 +244,10 @@ public class ParquetUtils {
      */
     private static Map<String, Object> convertGenericRecordToMap(GenericRecord genericRecord) {
         Map<String, Object> resultMap = new HashMap<>();
+        if (Objects.isNull(genericRecord)) {
+            return resultMap;
+        }
+
         genericRecord.getSchema().getFields().forEach(field -> {
             String fieldName = field.name();
             Object fieldValue = genericRecord.get(fieldName);
@@ -168,19 +256,22 @@ public class ParquetUtils {
         return resultMap;
     }
 
-    // 测试主方法
+    /**
+     * 测试方法
+     * @param args 命令行参数
+     */
     public static void main(String[] args) {
         try {
-            // 替换为你的Parquet文件本地路径
             String parquetPath = "C:\\jupyter-lab\\大唐25年科研\\files\\大唐验证数据\\CCWE1500-82.DF\\内蒙塔林花风电场-20251015.parquet";
             long begin = System.currentTimeMillis();
             JSONObject lastLineJson = getParquetLastLineAsJson(parquetPath);
-            System.out.println(System.currentTimeMillis() - begin);
-            System.out.println("Parquet文件最后一行数据(JSON格式):");
-            System.out.println(lastLineJson);
+            long costTime = System.currentTimeMillis() - begin;
+
+            LOGGER.info("读取Parquet最后一行耗时:{}ms", costTime);
+            LOGGER.info("Parquet文件最后一行数据(JSON格式):\n{}", lastLineJson.toJSONString());
+
         } catch (Exception e) {
-            e.printStackTrace();
+            LOGGER.error("测试读取Parquet文件失败", e);
         }
     }
-
 }