浏览代码

[101903] 上报批次定时任务

taosj 2 周之前
父节点
当前提交
dc4d8fa500

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

@@ -0,0 +1,273 @@
+package com.ufo.project.forecast.fileupload.task;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import com.alibaba.fastjson2.JSON;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import com.ufo.base.common.utils.StringUtils;
+import com.ufo.module.system.domain.SysDictData;
+import com.ufo.module.system.service.ISysDictTypeService;
+import com.ufo.project.config.domain.ConfigStation;
+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;
+
+/**
+ * 功率预测上报 - 批次生成定时任务
+ *
+ * 读取字典 power_forecast_report_batch_config 中的场站配置(JSON数组),
+ * 在每个预计上报时间前 initInterval 分钟窗口内生成上报批次行, 并触发对应处理器初始化上报数据.
+ *
+ * 配置示例:
+ * <pre>
+ * [
+ *   { "stationCode": "3301", "type": "short", "period": "daily", "times": ["08:00:00"], "initInterval": 60 }
+ * ]
+ * </pre>
+ * initInterval 单位为分钟, 缺省 0.
+ *
+ * @author ufo
+ * @date 2026-10-19
+ */
+@Component
+public class DataPowerForecastReportBatchTask
+{
+    private static final Logger log = LoggerFactory.getLogger(DataPowerForecastReportBatchTask.class);
+
+    /** 上报批次配置字典类型 */
+    public static final String DICT_TYPE = "power_forecast_report_batch_config";
+
+    /** 扫描间隔(毫秒), 每轮结束后间隔5分钟 */
+    private static final long SCAN_INTERVAL_MILLIS = 300_000L;
+
+    /** 北京时间时区 */
+    private static final ZoneId ZONE_SH = ZoneId.of("Asia/Shanghai");
+
+    private static final DateTimeFormatter FMT_REPORT_DAY = DateTimeFormatter.ofPattern("yyyyMMdd");
+
+    private static final DateTimeFormatter FMT_BATCH_NO = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+
+    @Autowired
+    private ISysDictTypeService dictTypeService;
+
+    @Autowired
+    private IConfigStationService configStationService;
+
+    @Autowired
+    private IDataPowerForecastReportBatchService reportBatchService;
+
+    @Autowired
+    private ForecastProcessorFactory factory;
+
+    /**
+     * 扫描字典配置, 为窗口内的预计上报时点生成批次行并触发数据初始化
+     */
+    @Scheduled(fixedDelay = SCAN_INTERVAL_MILLIS)
+    public void autoGenerateBatchNo()
+    {
+        List<Config> configs = loadConfigs();
+        if (configs.isEmpty())
+        {
+            return;
+        }
+        for (Config config : configs)
+        {
+            try
+            {
+                process(config);
+            }
+            catch (Exception e)
+            {
+                log.error("[PowerForecastReportBatch] 处理配置项失败, stationCode={}, type={}",
+                        config == null ? null : config.getStationCode(), config == null ? null : config.getType(), e);
+            }
+        }
+    }
+
+    /**
+     * 读取字典配置, 解析为场站配置列表
+     */
+    private List<Config> loadConfigs()
+    {
+        try
+        {
+            List<SysDictData> dictList = dictTypeService.selectDictDataByType(DICT_TYPE);
+            if (dictList == null || dictList.isEmpty())
+            {
+                return Collections.emptyList();
+            }
+            String dictValue = dictList.get(0).getDictValue();
+            if (StringUtils.isBlank(dictValue))
+            {
+                return Collections.emptyList();
+            }
+            List<Config> configs = JSON.parseArray(dictValue, Config.class);
+            return configs == null ? Collections.<Config>emptyList() : configs;
+        }
+        catch (Exception e)
+        {
+            log.error("[PowerForecastReportBatch] 解析字典 {} 配置失败", DICT_TYPE, e);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * 处理单个场站配置: 遍历预计上报时点(升序), 在提前窗口内生成批次行并触发初始化
+     */
+    private void process(Config config)
+    {
+        if (config == null || StringUtils.isBlank(config.getStationCode()))
+        {
+            return;
+        }
+        if (!"daily".equals(config.getPeriod()))
+        {
+            log.warn("[PowerForecastReportBatch] period 非 daily, 跳过, stationCode={}", config.getStationCode());
+            return;
+        }
+        ConfigStation q = new ConfigStation();
+        q.setStationCode(config.getStationCode());
+        List<ConfigStation> stations = configStationService.selectConfigStationList(q);
+        if (stations == null || stations.isEmpty())
+        {
+            log.warn("[PowerForecastReportBatch] 未找到场站, 跳过, stationCode={}", config.getStationCode());
+            return;
+        }
+        String stationName = stations.get(0).getStationName();
+        List<String> times = config.getTimes();
+        if (times == null || times.isEmpty())
+        {
+            log.warn("[PowerForecastReportBatch] times 为空, 跳过, stationCode={}", config.getStationCode());
+            return;
+        }
+        times.sort(Comparator.naturalOrder());
+        for (String t : times)
+        {
+            LocalDateTime now = LocalDateTime.now(ZONE_SH);
+            LocalDate today = LocalDate.now(ZONE_SH);
+            LocalDateTime slot = today.atTime(LocalTime.parse(t));
+            // 已过时点, 跳过后续更远的时点
+            if (now.isAfter(slot))
+            {
+                continue;
+            }
+            // 未到提前窗口, 后续时点更远, 直接结束
+            if (now.isBefore(slot.minusMinutes(config.getInitInterval() == null ? 0 : config.getInitInterval())))
+            {
+                break;
+            }
+            int reportDay = Integer.parseInt(today.format(FMT_REPORT_DAY));
+            boolean exists = reportBatchService.selectByTypeDayTime(config.getType(), reportDay, t)
+                    .stream().anyMatch(b -> config.getStationCode().equals(b.getStationCode()));
+            if (exists)
+            {
+                continue;
+            }
+            DataPowerForecastReportBatch b = new DataPowerForecastReportBatch();
+            b.setStationCode(config.getStationCode());
+            b.setStationName(stationName);
+            b.setType(config.getType());
+            b.setReportDay(reportDay);
+            b.setEstReportTime(t);
+            b.setStatus(DataPowerForecastReportBatch.STATUS_PENDING);
+            b.setGenerateSource("auto");
+            b.setBatchNo(prefix(config.getType()) + slot.format(FMT_BATCH_NO) + "_" + config.getStationCode());
+            reportBatchService.insertDataPowerForecastReportBatch(b);
+            log.info("[PowerForecastReportBatch] 新生成上报批次, batchNo={}, stationCode={}, time={}",
+                    b.getBatchNo(), config.getStationCode(), t);
+            factory.getInstant(config.getType()).initData(reportDay, t);
+        }
+    }
+
+    /**
+     * 批次号前缀: 超短期 ULT, 其余 ST
+     */
+    private static String prefix(String type)
+    {
+        return "ultra".equals(type) ? "ULT" : "ST";
+    }
+
+    /**
+     * 字典 power_forecast_report_batch_config 的单个场站配置
+     */
+    public static class Config
+    {
+        /** 场站编码 */
+        private String stationCode;
+
+        /** 类型(short短期,ultra超短期) */
+        private String type;
+
+        /** 周期(daily) */
+        private String period;
+
+        /** 预计上报时点列表(HH:mm:ss) */
+        private List<String> times;
+
+        /** 提前初始化窗口(分钟), 缺省 0 */
+        private Integer initInterval;
+
+        public String getStationCode()
+        {
+            return stationCode;
+        }
+
+        public void setStationCode(String stationCode)
+        {
+            this.stationCode = stationCode;
+        }
+
+        public String getType()
+        {
+            return type;
+        }
+
+        public void setType(String type)
+        {
+            this.type = type;
+        }
+
+        public String getPeriod()
+        {
+            return period;
+        }
+
+        public void setPeriod(String period)
+        {
+            this.period = period;
+        }
+
+        public List<String> getTimes()
+        {
+            return times;
+        }
+
+        public void setTimes(List<String> times)
+        {
+            this.times = times;
+        }
+
+        public Integer getInitInterval()
+        {
+            return initInterval;
+        }
+
+        public void setInitInterval(Integer initInterval)
+        {
+            this.initInterval = initInterval;
+        }
+    }
+}