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