|
@@ -0,0 +1,398 @@
|
|
|
+package com.jiayue.insu.inclientqn.service.client;
|
|
|
+
|
|
|
+
|
|
|
+import cn.hutool.core.date.DateTime;
|
|
|
+import cn.hutool.core.date.DateUtil;
|
|
|
+import cn.hutool.core.date.LocalDateTimeUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import cn.hutool.http.HttpRequest;
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.jiayue.insu.inclientqn.constant.CommonStant;
|
|
|
+import com.jiayue.insu.inclientqn.entity.*;
|
|
|
+import com.jiayue.insu.inclientqn.inenum.BJZYYJYSDEnum;
|
|
|
+import com.jiayue.insu.inclientqn.inenum.StatusEnum;
|
|
|
+import com.jiayue.insu.inclientqn.mapper.BJZYYJYSDCorrforeMapper;
|
|
|
+import com.jiayue.insu.inclientqn.model.BJZYYJYSDResponseVo;
|
|
|
+import com.jiayue.insu.inclientqn.permisson.com.BJZYYJYSDComPermisson;
|
|
|
+import com.jiayue.insu.inclientqn.pulldata.IPullInitForecastData;
|
|
|
+import com.jiayue.insu.inclientqn.service.*;
|
|
|
+import com.jiayue.insu.inclientqn.util.SystermUtils;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.velocity.Template;
|
|
|
+import org.apache.velocity.VelocityContext;
|
|
|
+import org.apache.velocity.app.VelocityEngine;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.io.FileOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.StringWriter;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.*;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @description:
|
|
|
+ * @author: yuanhao
|
|
|
+ * @createDate: 2021/9/27
|
|
|
+ * @version: 1.0
|
|
|
+ */
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class BJZYYJYCorrforeService extends ServiceImpl<BJZYYJYSDCorrforeMapper, BJZYYJYSDCorrfore> {
|
|
|
+
|
|
|
+ private final RecordService recordService;
|
|
|
+ private final StationService stationService;
|
|
|
+ private final BJZYYJYSDCorrforeService bjzyyjysdCorrforeService;
|
|
|
+ private final VelocityEngine velocityEngine;
|
|
|
+ private final BJZYYJYSDComPermisson bjzyyjysdComPermisson;
|
|
|
+ private final IPullInitForecastData iPullInitForecastData;
|
|
|
+
|
|
|
+
|
|
|
+ @Value("${minio.bucketname}")
|
|
|
+ private String bucketName;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 山东国家研究院下载修正短期预测数据,从一体化上
|
|
|
+ *
|
|
|
+ * @param station
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public boolean downLoadCorrforeData(Station station) {
|
|
|
+ // 初始化操作记录
|
|
|
+ Record record = new Record();
|
|
|
+ record.setType(CommonStant.RECORD_TYPE_PULL_CORRECT);
|
|
|
+ record.setStationCode(station.getStationCode());
|
|
|
+ LocalDateTime localDateTime = LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
|
|
|
+ record.setTime(localDateTime);
|
|
|
+ record.setCreateTime(LocalDateTime.now());
|
|
|
+ // 获取token
|
|
|
+ String token = station.getComKey();
|
|
|
+ // 获取下载路径
|
|
|
+ String dataPullUrl = station.getDownloadurl();
|
|
|
+ // 获取场站标识
|
|
|
+ String signCode = station.getSignCode();
|
|
|
+
|
|
|
+ boolean result = false;
|
|
|
+ boolean isUpload = false;
|
|
|
+ String response;
|
|
|
+ List<BJZYYJYSDCorrfore> list = new ArrayList<>();
|
|
|
+ List<BJZYYJYSDCorrfore> listMinio = new ArrayList<>();
|
|
|
+
|
|
|
+ // 如果token为空或者token失效,从新获取token
|
|
|
+ LocalDateTime tokenTime = station.getKeyTime();
|
|
|
+ if (StrUtil.isEmpty(token) || LocalDateTime.now().isAfter(tokenTime)) {
|
|
|
+ if (bjzyyjysdComPermisson.generateKey(station)) {
|
|
|
+ station = stationService.findThis();
|
|
|
+ token = station.getComKey();
|
|
|
+ tokenTime = station.getKeyTime();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果token为不为空并且者token未失效
|
|
|
+ if (StrUtil.isNotEmpty(token) && LocalDateTime.now().isBefore(tokenTime)) {
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 实装请求参数
|
|
|
+ DateTime dateTime = DateUtil.parse(DateUtil.today());
|
|
|
+ DateTime tomorrow = DateUtil.offsetDay(dateTime, 1);
|
|
|
+ // 拼接请求地址
|
|
|
+ // dataPullUrl = dataPullUrl + "?pdate=" + dateTime.toString("yyyy-MM-dd") + "&farm_id=SD0015";
|
|
|
+ dataPullUrl = dataPullUrl + "?pdate="+tomorrow.toString("yyyy-MM-dd")+"&farm_id="+ signCode;
|
|
|
+
|
|
|
+ //https://www.dljyfzjc.spic.com.cn:20280/sdapi/powerforecast/txnypowerforecast/getPowerForecastByFarmAndPdate?pdate=2023-12-15&farm_id=SD0015
|
|
|
+ // 发送请求
|
|
|
+ HttpRequest httpRequest = HttpRequest.get(dataPullUrl)
|
|
|
+ .header("TENANT-ID", "4")
|
|
|
+ .header("Authorization", "Bearer " + token);
|
|
|
+ httpRequest.setGlobalTimeout(20000);
|
|
|
+
|
|
|
+ response = httpRequest.execute().body();
|
|
|
+
|
|
|
+ // 如果响应不为空
|
|
|
+ if (StrUtil.isNotEmpty(response)) {
|
|
|
+ boolean isJson = JSONUtil.isJsonObj(response);
|
|
|
+ // 如果返回结果是json类型
|
|
|
+ if (isJson) {
|
|
|
+ BJZYYJYSDResponseVo bjzyyjysdResponseVo = JSONUtil.toBean(response, BJZYYJYSDResponseVo.class);
|
|
|
+ String code = bjzyyjysdResponseVo.getCode();
|
|
|
+ // 如果调用结果成功
|
|
|
+ if (code.equals(BJZYYJYSDEnum.REQUEST_SUCCESS.getCode())) {
|
|
|
+ log.info(station.getStationCode() + " 请求短期修正数据返回内容:{}", response);
|
|
|
+ try {
|
|
|
+ // 如果data = null 则用原始数据进行上报
|
|
|
+ if (!"[]".equals(bjzyyjysdResponseVo.getData())) {
|
|
|
+ list = correctData(bjzyyjysdResponseVo.getData());
|
|
|
+ /**************检测解析数据完整性*******************/
|
|
|
+ BigDecimal one = new BigDecimal("1");
|
|
|
+ BigDecimal checkCount = new BigDecimal(station.getDays().toString()).add(one).multiply(new BigDecimal("96")).add(one);
|
|
|
+ // 如果修正后短期数据不为0
|
|
|
+ if (list.size() > 0) {
|
|
|
+ if (list.size() < checkCount.intValue()) {
|
|
|
+ log.warn("========== 请求短期修正数据缺数: {}", list.size());
|
|
|
+ }
|
|
|
+ // 将修正数据存库
|
|
|
+ bjzyyjysdCorrforeService.updateBetweenForecastTime(list.get(0).getTimeFormat(), list.get(list.size() - 1).getTimeFormat(), list);
|
|
|
+
|
|
|
+ // 山东六六巨光需要从minio上请求最新短期文件进行拼接
|
|
|
+ listMinio = iPullInitForecastData.pullDQData(station);
|
|
|
+
|
|
|
+ // 将一体化修正数据存入map
|
|
|
+ Map<Long, BJZYYJYSDCorrfore> map = new HashMap<>();
|
|
|
+ for (BJZYYJYSDCorrfore bjzyyjysdCorrfore : list) {
|
|
|
+ map.put(bjzyyjysdCorrfore.getForecastTime(), bjzyyjysdCorrfore);
|
|
|
+ }
|
|
|
+ // 根据关联字段进行替换
|
|
|
+ for (int i = 0; i < listMinio.size(); i++) {
|
|
|
+ BJZYYJYSDCorrfore bjzyyjysdCorrfore = listMinio.get(i);
|
|
|
+ BJZYYJYSDCorrfore bjzyyjysdCorrforeReplace = map.get(bjzyyjysdCorrfore.getForecastTime());
|
|
|
+ if (bjzyyjysdCorrforeReplace != null) {
|
|
|
+ // 根据关联字段进行替换
|
|
|
+ listMinio.set(i, bjzyyjysdCorrforeReplace);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 生成短期修正后文件
|
|
|
+ if (genFile(station, listMinio)) {
|
|
|
+ result = true;
|
|
|
+ // 存入记录文件已经生成过
|
|
|
+ record.setState(CommonStant.RECORD_TYPE_PULL_CORRECT);
|
|
|
+ record.setStateContent(StatusEnum.SUCCESS.getCode());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 如果短期数据为空
|
|
|
+ } else {
|
|
|
+ // 存入记录
|
|
|
+ record.setState(StatusEnum.CONNECT_RESPONSE_CONTENT_NULL.getCode());
|
|
|
+ record.setStateContent(StatusEnum.CONNECT_RESPONSE_CONTENT_NULL.getMsg());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ record.setState(StatusEnum.CONNECT_RESPONSE_FORMAT_ERROR.getSign());
|
|
|
+ record.setStateContent("解析短期或生成文件失败");
|
|
|
+ log.error("========== 解析短期或生成文件失败 ");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (result) {
|
|
|
+ log.info("========== 拉取短期修正数据成功! ==========");
|
|
|
+ record.setState(StatusEnum.SUCCESS.getSign());
|
|
|
+ record.setStateContent(String.valueOf(list.size()));
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ record.setState(StatusEnum.RESPONSE_FAIL.getSign());
|
|
|
+ record.setStateContent(StatusEnum.RESPONSE_FAIL.getMsg());
|
|
|
+ log.error("========== 拉取短期修正数据失败 响应码异常 " + station.getStationCode() + " :请求短期修正数据返回内容:" + response);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ record.setState(StatusEnum.CONNECT_RESPONSE_FORMAT_ERROR.getSign());
|
|
|
+ record.setStateContent(StatusEnum.CONNECT_RESPONSE_FORMAT_ERROR.getMsg());
|
|
|
+ log.error("========== 拉取短期修正数据失败 接收响应字符串非json格式 ");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ record.setState(StatusEnum.CONNECT_RESPONSE_CONTENT_NULL.getSign());
|
|
|
+ record.setStateContent(StatusEnum.CONNECT_RESPONSE_CONTENT_NULL.getMsg());
|
|
|
+ log.error("========== 拉取短期修正数据失败 返回响应内容为空 ==========");
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ record.setState(StatusEnum.CONNECT_ERROR.getSign());
|
|
|
+ record.setStateContent(StatusEnum.CONNECT_ERROR.getMsg());
|
|
|
+ log.error("========== 请求短期修正数据失败 连接断开或请求超时 ==========");
|
|
|
+ e.printStackTrace();
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ recordService.save(record);
|
|
|
+ return isUpload;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 数据解析
|
|
|
+ *
|
|
|
+ * @param data 待解析数据
|
|
|
+ * @return
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ public List<BJZYYJYSDCorrfore> correctData(String data) throws Exception {
|
|
|
+
|
|
|
+ List<BJZYYJYSDCorrfore> list = new ArrayList<>();
|
|
|
+ data = data.substring(1, data.length() - 1);
|
|
|
+ String[] content = data.split("(?<=})\\s*,\\s*");
|
|
|
+
|
|
|
+ for (int i = 0; i < content.length; i++) {
|
|
|
+ BJZYYJYSDCorrfore bjzyyjysdCorrfore = new BJZYYJYSDCorrfore();
|
|
|
+ String column = content[i];
|
|
|
+ //JSON 字符串转换为一个树状结构
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ JsonNode rootNode = objectMapper.readTree(column);
|
|
|
+ // 通过键名获取相应的值
|
|
|
+ String updateTime = rootNode.get("updateTime").asText();
|
|
|
+ String forecastTime = rootNode.get("pdate").asText();
|
|
|
+ double ageValue = rootNode.get("forecast").asDouble();
|
|
|
+ // 将预测时间转换为时间戳格式
|
|
|
+ Long forecast = DateUtil.parse(forecastTime, "yyyy-MM-dd HH:mm:ss").getTime() + 15*60*1000L +15*60*1000L*i;
|
|
|
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
+ String forecastDateFormat = dateFormat.format(new Date(forecast));
|
|
|
+ // 存入实体
|
|
|
+ bjzyyjysdCorrfore.setForecastTime(forecast);
|
|
|
+ bjzyyjysdCorrfore.setTimeFormat(DateUtil.parse(forecastDateFormat, "yyyy-MM-dd HH:mm:ss").toTimestamp().toLocalDateTime());
|
|
|
+ bjzyyjysdCorrfore.setUpdateTime(DateUtil.parse(updateTime, "yyyy-MM-dd HH:mm:ss").toTimestamp().toLocalDateTime());
|
|
|
+ bjzyyjysdCorrfore.setFpValue(isNumberOrNull(String.valueOf(ageValue)));
|
|
|
+ // 排序
|
|
|
+ Collections.sort(list, Comparator.comparing(BJZYYJYSDCorrfore::getForecastTime));
|
|
|
+ list.add(bjzyyjysdCorrfore);
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断是否是NULL 和是否是数值
|
|
|
+ *
|
|
|
+ * @param data
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public BigDecimal isNumberOrNull(String data) {
|
|
|
+ BigDecimal bigDecimal = new BigDecimal("-99");
|
|
|
+ if (!data.contains("null") && !data.contains("NULL")) {
|
|
|
+ Pattern pattern = Pattern.compile("-[0-9]+(.[0-9]+)?|[0-9]+(.[0-9]+)?");
|
|
|
+ Matcher isNum = pattern.matcher(data);
|
|
|
+ try {
|
|
|
+ if (isNum.matches()) {
|
|
|
+ bigDecimal = new BigDecimal(data).setScale(2, BigDecimal.ROUND_DOWN);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return bigDecimal;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成修正后的短期文件(山东中央研究院)
|
|
|
+ *
|
|
|
+ * @param station
|
|
|
+ * @param list
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public boolean genFile(Station station, List<BJZYYJYSDCorrfore> list) {
|
|
|
+ boolean result = true;
|
|
|
+ // 初始化模板
|
|
|
+ String vmsPath = SystermUtils.getResourceBasePath() + "/vms/DQ.vm";
|
|
|
+ File file;
|
|
|
+ Template template = this.velocityEngine.getTemplate(vmsPath);
|
|
|
+ // 如果模板不为空
|
|
|
+ if (template != null) {
|
|
|
+ VelocityContext velocityContext;
|
|
|
+ StringWriter writer;
|
|
|
+ // 将修正后的短期数据按照预测时间排序
|
|
|
+ list.sort(Comparator.comparing(BJZYYJYSDCorrfore::getForecastTime));
|
|
|
+ // 根据修正后的数据,拼接需要的数据
|
|
|
+ List<Map<String, Object>> vList = new ArrayList<>();
|
|
|
+ for (BJZYYJYSDCorrfore a : list) {
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
+ map.put("id", a.getId());
|
|
|
+ map.put("stationName", station.getName());
|
|
|
+ map.put("forecastTime", DateUtil.format(new Date(a.getForecastTime()), "yyyy-MM-dd HH:mm:ss"));
|
|
|
+ map.put("fpValue", a.getFpValue() == null ? "-99" : a.getFpValue());
|
|
|
+ vList.add(map);
|
|
|
+ }
|
|
|
+ // 格式化模板数据
|
|
|
+ writer = new StringWriter();
|
|
|
+ velocityContext = new VelocityContext();
|
|
|
+ velocityContext.put("stationName", station.getName());
|
|
|
+ velocityContext.put("date", DateUtil.format(new Date(), "yyyy-MM-dd"));
|
|
|
+ velocityContext.put("vList", vList);
|
|
|
+ template.merge(velocityContext, writer);
|
|
|
+ // 山东场站分为AB机,需拆分文件生成路径
|
|
|
+ String[] filePath = station.getLocalFilePath().split(";");
|
|
|
+ for (int i = 0; i < filePath.length; i++) {
|
|
|
+ // 获取文件路径
|
|
|
+ File fileUrl = new File(filePath[i]);
|
|
|
+ // 判断目录是否存在
|
|
|
+ if (!fileUrl.exists()) {
|
|
|
+ fileUrl.mkdirs();
|
|
|
+ }
|
|
|
+ // 获取文件名
|
|
|
+ String fileName = "DQ_" + DateUtil.format(DateUtil.beginOfDay(new Date()), "yyyyMMddHHmmss") + "0.RB";
|
|
|
+ file = new File(filePath[i] + File.separatorChar + fileName);
|
|
|
+ // 写入文件
|
|
|
+ result = writeFile(file, station, writer, fileName, filePath[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 写入文件
|
|
|
+ * @param file
|
|
|
+ * @param station
|
|
|
+ * @param writer
|
|
|
+ * @param fileName
|
|
|
+ * @param filePath
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean writeFile(File file, Station station, StringWriter writer, String fileName,String filePath) {
|
|
|
+ boolean result = true;
|
|
|
+ // 获取文件路径
|
|
|
+ File fileUrl = new File(filePath);
|
|
|
+ // 判断目录是否存在
|
|
|
+ if (!fileUrl.exists()) {
|
|
|
+ fileUrl.mkdirs();
|
|
|
+ }
|
|
|
+ // 检查文件是否存在
|
|
|
+ File existingFile = new File(filePath + File.separator + fileName);
|
|
|
+ if (existingFile.exists()) {
|
|
|
+ // 如果存在同名文件,尝试删除
|
|
|
+ boolean deleted = existingFile.delete();
|
|
|
+ if (!deleted) {
|
|
|
+ // 如果无法删除,记录警告并返回false
|
|
|
+ log.warn("无法删除文件:" + fileName);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建流
|
|
|
+ FileOutputStream os = null;
|
|
|
+ try {
|
|
|
+ boolean res = file.createNewFile();
|
|
|
+ // 如果创建成功
|
|
|
+ if (res) {
|
|
|
+ os = new FileOutputStream(file);
|
|
|
+ // 采用UTF-8字符集
|
|
|
+ os.write(writer.toString().getBytes("UTF-8"));
|
|
|
+ os.flush();
|
|
|
+ // 创建文件失败
|
|
|
+ } else {
|
|
|
+ result = false;
|
|
|
+ log.warn(station.getStationCode() + "文件名:" + fileName + " 生成失败");
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ result = false;
|
|
|
+ log.warn(station.getStationCode() + " 创建" + "文件名:" + fileName + "失败");
|
|
|
+ } finally {
|
|
|
+ // 如果流不为空,关闭流
|
|
|
+ if (os != null) {
|
|
|
+ try {
|
|
|
+ os.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("文件生成关闭流失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("执行生成文件完毕:" + filePath + File.separatorChar + fileName);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|