Pārlūkot izejas kodu

[101905] 文件生成定时任务 2→4→5/9 及 .dat 生成保存

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
taosj 2 nedēļas atpakaļ
vecāks
revīzija
b91ff01934

+ 208 - 0
forecast-backend-server/src/main/java/com/ufo/project/forecast/fileupload/task/DataPowerForecastReportBatchTask.java

@@ -1,18 +1,26 @@
 package com.ufo.project.forecast.fileupload.task;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Locale;
 
 import com.alibaba.fastjson2.JSON;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
@@ -24,6 +32,10 @@ import com.ufo.project.config.service.IConfigStationService;
 import com.ufo.project.forecast.fileupload.domain.DataPowerForecastReportBatch;
 import com.ufo.project.forecast.fileupload.service.IDataPowerForecastReportBatchService;
 import com.ufo.project.forecast.processor.ForecastProcessorFactory;
+import com.ufo.project.forecast.shortterm.power.domain.DataShortTermPowerForecastReport;
+import com.ufo.project.forecast.shortterm.power.mapper.DataShortTermPowerForecastReportMapper;
+import com.ufo.project.forecast.supershort.domain.DataSuperShortTermForecastReport;
+import com.ufo.project.forecast.supershort.mapper.DataSuperShortTermForecastReportMapper;
 
 /**
  * 功率预测上报 - 批次生成定时任务
@@ -60,6 +72,12 @@ public class DataPowerForecastReportBatchTask
 
     private static final DateTimeFormatter FMT_BATCH_NO = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
 
+    /** 数据行时间格式(北京时间) */
+    private static final DateTimeFormatter FMT_POINT_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZONE_SH);
+
+    /** 日期格式(yyyy-MM-dd) */
+    private static final DateTimeFormatter FMT_DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
     @Autowired
     private ISysDictTypeService dictTypeService;
 
@@ -72,6 +90,15 @@ public class DataPowerForecastReportBatchTask
     @Autowired
     private ForecastProcessorFactory factory;
 
+    @Autowired
+    private DataShortTermPowerForecastReportMapper shortTermReportMapper;
+
+    @Autowired
+    private DataSuperShortTermForecastReportMapper superShortTermReportMapper;
+
+    @Value("${project.forecast.reportfile.rootfolder}")
+    private String reportFileRootFolder;
+
     /**
      * 扫描字典配置, 为窗口内的预计上报时点生成批次行并触发数据初始化
      */
@@ -97,6 +124,187 @@ public class DataPowerForecastReportBatchTask
         }
     }
 
+    /**
+     * 状态推进: 2已确认 -> 4文件上传中 (每分钟第23秒)
+     */
+    @Scheduled(cron = "23 * * * * *")
+    public void startingFileGenerating()
+    {
+        DataPowerForecastReportBatch q = new DataPowerForecastReportBatch();
+        q.setStatus(DataPowerForecastReportBatch.STATUS_CONFIRMED);
+        List<DataPowerForecastReportBatch> batches = reportBatchService.selectDataPowerForecastReportBatchList(q);
+        if (batches == null || batches.isEmpty())
+        {
+            return;
+        }
+        for (DataPowerForecastReportBatch b : batches)
+        {
+            try
+            {
+                DataPowerForecastReportBatch u = new DataPowerForecastReportBatch();
+                u.setId(b.getId());
+                u.setStatus(DataPowerForecastReportBatch.STATUS_FILE_UPLOADING);
+                reportBatchService.updateDataPowerForecastReportBatch(u);
+                log.info("[PowerForecastReportBatch] 状态 2->4, batchNo={}", b.getBatchNo());
+            }
+            catch (Exception e)
+            {
+                log.error("[PowerForecastReportBatch] 状态 2->4 失败, batchNo={}", b.getBatchNo(), e);
+            }
+        }
+    }
+
+    /**
+     * 生成上报文件: 4文件上传中 -> 5文件上传成功 / 9文件上传失败 (每分钟第45秒)
+     */
+    @Scheduled(cron = "45 * * * * *")
+    public void generateReportFile()
+    {
+        DataPowerForecastReportBatch q = new DataPowerForecastReportBatch();
+        q.setStatus(DataPowerForecastReportBatch.STATUS_FILE_UPLOADING);
+        List<DataPowerForecastReportBatch> batches = reportBatchService.selectDataPowerForecastReportBatchList(q);
+        if (batches == null || batches.isEmpty())
+        {
+            return;
+        }
+        for (DataPowerForecastReportBatch b : batches)
+        {
+            try
+            {
+                File file = buildReportFile(b);
+                DataPowerForecastReportBatch u = new DataPowerForecastReportBatch();
+                u.setId(b.getId());
+                u.setFile(file.getAbsolutePath());
+                u.setStatus(DataPowerForecastReportBatch.STATUS_FILE_UPLOAD_SUCCESS);
+                reportBatchService.updateDataPowerForecastReportBatch(u);
+                log.info("[PowerForecastReportBatch] 上报文件生成成功, batchNo={}, file={}",
+                        b.getBatchNo(), file.getAbsolutePath());
+            }
+            catch (Exception e)
+            {
+                log.error("[PowerForecastReportBatch] 上报文件生成失败, batchNo={}", b.getBatchNo(), e);
+                DataPowerForecastReportBatch u = new DataPowerForecastReportBatch();
+                u.setId(b.getId());
+                u.setStatus(DataPowerForecastReportBatch.STATUS_FILE_UPLOAD_FAILED);
+                try
+                {
+                    reportBatchService.updateDataPowerForecastReportBatch(u);
+                }
+                catch (Exception ex)
+                {
+                    log.error("[PowerForecastReportBatch] 状态置9失败, batchNo={}", b.getBatchNo(), ex);
+                }
+            }
+        }
+    }
+
+    /**
+     * 生成 .dat 上报文件并返回文件对象(异常向上抛, 由调用方置状态9)
+     */
+    private File buildReportFile(DataPowerForecastReportBatch batch) throws IOException
+    {
+        List<ReportPoint> points = loadReportPoints(batch);
+        if (points.isEmpty())
+        {
+            throw new IllegalStateException("批次无上报数据, batchNo=" + batch.getBatchNo());
+        }
+        ConfigStation sq = new ConfigStation();
+        sq.setStationCode(batch.getStationCode());
+        List<ConfigStation> stations = configStationService.selectConfigStationList(sq);
+        if (stations == null || stations.isEmpty())
+        {
+            throw new IllegalStateException("未找到场站, stationCode=" + batch.getStationCode());
+        }
+        Long capacity = stations.get(0).getInstalledCapacity();
+        if (capacity == null)
+        {
+            throw new IllegalStateException("场站装机容量为空, stationCode=" + batch.getStationCode());
+        }
+        String marker = DataPowerForecastReportBatch.TYPE_ULTRA.equals(batch.getType()) ? "CDQYC" : "DQYC";
+        String fileName = "GF_" + batch.getStationName() + "_" + marker + "_" + batch.getReportDay()
+                + "_" + batch.getEstReportTime().replace(":", "") + ".dat";
+        int reportDay = batch.getReportDay();
+        String year = String.valueOf(reportDay / 10000);
+        String month = String.format(Locale.ROOT, "%02d", (reportDay / 100) % 100);
+        File dir = new File(reportFileRootFolder,
+                batch.getType() + File.separator + batch.getStationCode()
+                        + File.separator + year + File.separator + month);
+        if (!dir.exists() && !dir.mkdirs())
+        {
+            throw new IOException("创建目录失败: " + dir.getAbsolutePath());
+        }
+        File target = new File(dir, fileName);
+        StringBuilder sb = new StringBuilder();
+        sb.append("<! System=PowerForecast, Version=1.0, Code=UTF-8, Data=").append(batch.getReportDay()).append(" !>\n");
+        sb.append('<').append(marker).append("::").append(batch.getStationName()).append(">\n");
+        sb.append("Date=").append(LocalDate.parse(String.valueOf(batch.getReportDay()), FMT_REPORT_DAY).format(FMT_DATE))
+                .append(", Time=").append(batch.getEstReportTime().replace(":", "-")).append('\n');
+        sb.append("# 点号\t时间\t预测功率(MW)\t预计开机容量(MW)\t备注\n");
+        String capacityMw = String.format(Locale.ROOT, "%.2f", capacity / 1000.0);
+        for (int i = 0; i < points.size(); i++)
+        {
+            ReportPoint p = points.get(i);
+            sb.append(i + 1).append('\t')
+                    .append(FMT_POINT_TIME.format(Instant.ofEpochSecond(p.forecastTime))).append('\t')
+                    .append(String.format(Locale.ROOT, "%.2f", p.power / 1000.0)).append('\t')
+                    .append(capacityMw).append('\t')
+                    .append("正常").append('\n');
+        }
+        sb.append("</").append(marker).append("::").append(batch.getStationName()).append(">\n");
+        Files.write(target.toPath(), sb.toString().getBytes(StandardCharsets.UTF_8));
+        return target;
+    }
+
+    /**
+     * 按批次号加载上报数据点, 按预测时刻升序(power 为 kW 原始值)
+     */
+    private List<ReportPoint> loadReportPoints(DataPowerForecastReportBatch batch)
+    {
+        List<ReportPoint> points = new ArrayList<>();
+        if (DataPowerForecastReportBatch.TYPE_ULTRA.equals(batch.getType()))
+        {
+            DataSuperShortTermForecastReport q = new DataSuperShortTermForecastReport();
+            q.setBatchNo(batch.getBatchNo());
+            for (DataSuperShortTermForecastReport r : superShortTermReportMapper.selectDataSuperShortTermForecastReportList(q))
+            {
+                if (r.getForecastTime() != null && StringUtils.isNotBlank(r.getPower()))
+                {
+                    points.add(new ReportPoint(r.getForecastTime(), Double.parseDouble(r.getPower())));
+                }
+            }
+        }
+        else
+        {
+            DataShortTermPowerForecastReport q = new DataShortTermPowerForecastReport();
+            q.setBatchNo(batch.getBatchNo());
+            for (DataShortTermPowerForecastReport r : shortTermReportMapper.selectDataShortTermPowerForecastReportList(q))
+            {
+                if (r.getForecastTime() != null && StringUtils.isNotBlank(r.getPower()))
+                {
+                    points.add(new ReportPoint(r.getForecastTime(), Double.parseDouble(r.getPower())));
+                }
+            }
+        }
+        points.sort(Comparator.comparingLong(p -> p.forecastTime));
+        return points;
+    }
+
+    /**
+     * 上报数据点: 预测时刻(UTC秒) + 功率(kW)
+     */
+    private static class ReportPoint
+    {
+        private final long forecastTime;
+
+        private final double power;
+
+        ReportPoint(long forecastTime, double power)
+        {
+            this.forecastTime = forecastTime;
+            this.power = power;
+        }
+    }
+
     /**
      * 读取字典配置, 解析为场站配置列表
      */

+ 4 - 0
forecast-backend-server/src/main/resources/application.yml

@@ -9,6 +9,10 @@ project:
   # 文件上传路径 (默认为项目根目录下的upload文件夹)
   # 生产环境可通过环境变量 UPLOAD_PATH 自定义路径
   profile: ${UPLOAD_PATH:./upload}
+  # 功率预测上报文件根目录
+  forecast:
+    reportfile:
+      rootfolder: ${REPORT_FILE_ROOT:./upload/forecast/report}
   # 获取ip地址开关
   addressEnabled: false
   # 验证码类型 math 数字计算 char 字符验证