|
|
@@ -0,0 +1,252 @@
|
|
|
+package com.energy.manage.service.service.healthscores.impl;
|
|
|
+
|
|
|
+import com.energy.manage.common.po.windfield.WindFieldPO;
|
|
|
+import com.energy.manage.service.domain.vo.healthscores.HealthOverviewListVO;
|
|
|
+import com.energy.manage.service.domain.vo.healthscores.HealthOverviewVO;
|
|
|
+import com.energy.manage.service.domain.vo.healthscores.HealthscoresTendencyVO;
|
|
|
+import com.energy.manage.service.domain.vo.healthscores.HealthscoresWindVO;
|
|
|
+import com.energy.manage.service.mappers.healthscores.HealthscoresMapper;
|
|
|
+import com.energy.manage.service.mappers.windfield.WindFieldMapper;
|
|
|
+import com.energy.manage.service.service.healthscores.HealthscoresService;
|
|
|
+import groovy.util.logging.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import java.util.stream.Stream;
|
|
|
+
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class HealthscoresServiceImpl implements HealthscoresService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private HealthscoresMapper healthscoresMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private WindFieldMapper windFieldMapper;
|
|
|
+
|
|
|
+ // 通用日期格式化器(线程安全,可全局复用)
|
|
|
+ private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
+ private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<HealthscoresWindVO> getHealthscoresWind(String fieldCode, String datatime) {
|
|
|
+
|
|
|
+ String fieldId = null;
|
|
|
+ if (!StringUtils.isEmpty(fieldCode)) {
|
|
|
+ WindFieldPO po = new WindFieldPO();
|
|
|
+ po.setFieldCode(fieldCode);
|
|
|
+ WindFieldPO windFieldPO = windFieldMapper.selectOne(po);
|
|
|
+ fieldId = windFieldPO.getFieldId();
|
|
|
+ }
|
|
|
+
|
|
|
+ String beginTime = null;
|
|
|
+ String endTime = null;
|
|
|
+ if (!StringUtils.isEmpty(datatime)) {
|
|
|
+ beginTime = appendTimeToString(datatime, 00, 00, 00);
|
|
|
+ endTime = appendTimeToString(datatime, 23, 59, 59);
|
|
|
+ } else {
|
|
|
+ beginTime = getYesterdayStartStr();
|
|
|
+ endTime = getYesterdayEnd().format(DATETIME_FORMATTER);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<HealthscoresWindVO> list = healthscoresMapper.selectWindScoreList(fieldId, beginTime, endTime);
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public HealthOverviewVO getHealthOverview(String fieldCode, String datatime) {
|
|
|
+
|
|
|
+ String fieldId = null;
|
|
|
+ if (!StringUtils.isEmpty(fieldCode)) {
|
|
|
+ WindFieldPO po = new WindFieldPO();
|
|
|
+ po.setFieldCode(fieldCode);
|
|
|
+ WindFieldPO windFieldPO = windFieldMapper.selectOne(po);
|
|
|
+ fieldId = windFieldPO.getFieldId();
|
|
|
+ }
|
|
|
+
|
|
|
+ String beginTime = null;
|
|
|
+ String endTime = null;
|
|
|
+ if (!StringUtils.isEmpty(datatime)) {
|
|
|
+ beginTime = appendTimeToString(datatime, 00, 00, 00);
|
|
|
+ endTime = appendTimeToString(datatime, 23, 59, 59);
|
|
|
+ } else {
|
|
|
+ beginTime = getYesterdayStartStr();
|
|
|
+ endTime = getYesterdayEnd().format(DATETIME_FORMATTER);
|
|
|
+ }
|
|
|
+
|
|
|
+ HealthOverviewVO healthOverviewVO = new HealthOverviewVO();
|
|
|
+ HealthscoresWindVO healthscoresWind = healthscoresMapper.selectWindHealthscoresScore(fieldId, beginTime, endTime);
|
|
|
+
|
|
|
+ if (healthscoresWind != null) {
|
|
|
+ healthOverviewVO.setHealthscoresWindVO(healthscoresWind);
|
|
|
+ }
|
|
|
+ String year = String.valueOf(LocalDate.now().getYear());
|
|
|
+ List<HealthOverviewListVO> overviewListVOS = healthscoresMapper.selectHealthOverviewList(year, fieldId, beginTime, endTime);
|
|
|
+ healthOverviewVO.setHealthOverviewListVOList(overviewListVOS);
|
|
|
+
|
|
|
+ return healthOverviewVO;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<HealthscoresTendencyVO> getLastDaysTrend(int day, String fieldId, String engineId) {
|
|
|
+ // 1. 校验入参(非空)
|
|
|
+ if (Objects.isNull(fieldId) || fieldId.isEmpty() || Objects.isNull(engineId) || engineId.isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("风场ID和风机ID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 计算起止日期
|
|
|
+ LocalDate[] range = getLastNDaysRange(day);
|
|
|
+ LocalDate startDate = range[0];
|
|
|
+ LocalDate endDate = range[1];
|
|
|
+
|
|
|
+ // 3. 生成连续的365天日期序列(基础维度,确保每个日期都有记录)
|
|
|
+ List<LocalDate> allDates = generateContinuousDates(startDate, endDate);
|
|
|
+
|
|
|
+ // 4. 确定要查询的表名(按年份拆分,如healthscores_2025、healthscores_2026)
|
|
|
+ List<String> tableNames = getValidTableNames(startDate, endDate);
|
|
|
+
|
|
|
+ // 3. 跨表查询数据
|
|
|
+ List<HealthscoresTendencyVO> hscoresLastays = healthscoresMapper.listHscoresLastays(fieldId, engineId, startDate, endDate, tableNames);
|
|
|
+
|
|
|
+ // 6. 封装已有数据到Map(key=日期,value=评分数据,方便快速匹配)
|
|
|
+ Map<LocalDate, HealthscoresTendencyVO> existDataMap = hscoresLastays.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ HealthscoresTendencyVO::getSourceDatetime,
|
|
|
+ data -> data,
|
|
|
+ (oldVal, newVal) -> oldVal
|
|
|
+ ));
|
|
|
+
|
|
|
+ // 7. 补0处理:遍历所有日期,缺失则新建(所有评分字段默认0)
|
|
|
+ List<HealthscoresTendencyVO> finalResult = new ArrayList<>();
|
|
|
+ for (LocalDate date : allDates) {
|
|
|
+ HealthscoresTendencyVO score = existDataMap.getOrDefault(date, new HealthscoresTendencyVO(date));
|
|
|
+ // 补全风场/风机ID(前端展示用)
|
|
|
+ score.setFieldId(fieldId);
|
|
|
+ score.setEngineId(engineId);
|
|
|
+ finalResult.add(score);
|
|
|
+ }
|
|
|
+
|
|
|
+ return finalResult;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * ====================== 当前接口使用日期工具 ======================
|
|
|
+ */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 给String类型的年月日拼接时分秒
|
|
|
+ *
|
|
|
+ * @param dateStr 年月日字符串(如 "2026-03-02")
|
|
|
+ * @param hour 小时
|
|
|
+ * @param minute 分钟
|
|
|
+ * @param second 秒
|
|
|
+ * @return 带时分秒的字符串(如 "2026-03-02 15:30:00")
|
|
|
+ */
|
|
|
+ private String appendTimeToString(String dateStr, int hour, int minute, int second) {
|
|
|
+ // 1. 解析为LocalDate(校验日期合法性)
|
|
|
+ LocalDate localDate = LocalDate.parse(dateStr, DATE_FORMATTER);
|
|
|
+ // 2. 拼接时分秒为LocalDateTime
|
|
|
+ LocalDateTime localDateTime = localDate.atTime(hour, minute, second);
|
|
|
+ // 3. 格式化为字符串
|
|
|
+ return localDateTime.format(DATETIME_FORMATTER);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重载:获取昨天开始/结束时间的字符串格式
|
|
|
+ */
|
|
|
+ private String getYesterdayStartStr() {
|
|
|
+ return getYesterdayStart().format(DATETIME_FORMATTER);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getYesterdayEndStr() {
|
|
|
+ // 严格格式化为 23:59:59(去掉纳秒)
|
|
|
+ return getYesterdayEnd().format(DATETIME_FORMATTER).substring(0, 19);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取昨天的开始时间(yyyy-MM-dd 00:00:00)
|
|
|
+ *
|
|
|
+ * @return LocalDateTime
|
|
|
+ */
|
|
|
+ private LocalDateTime getYesterdayStart() {
|
|
|
+ // 1. 获取昨天的日期
|
|
|
+ LocalDate yesterday = LocalDate.now().minusDays(1);
|
|
|
+ // 2. 拼接 00:00:00 等价于 yesterday.atTime(0, 0, 0)
|
|
|
+ return yesterday.atStartOfDay();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取昨天的结束时间(yyyy-MM-dd 23:59:59)
|
|
|
+ *
|
|
|
+ * @return LocalDateTime
|
|
|
+ */
|
|
|
+ private LocalDateTime getYesterdayEnd() {
|
|
|
+ LocalDate yesterday = LocalDate.now().minusDays(1);
|
|
|
+ // 拼接 23:59:59
|
|
|
+ return yesterday.atTime(23, 59, 59);
|
|
|
+ // 若需严格的23:59:59,用:yesterday.atTime(23, 59, 59)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取近 N 天的开始、结束日期
|
|
|
+ * 参数days 为需要查几天的数据
|
|
|
+ */
|
|
|
+ private LocalDate[] getLastNDaysRange(int days) {
|
|
|
+ LocalDate endDate = LocalDate.now().minusDays(1);
|
|
|
+ LocalDate startDate = endDate.minusDays(days-1);
|
|
|
+ return new LocalDate[]{startDate, endDate};
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取日期范围内涉及的所有年份(最多2个)
|
|
|
+ */
|
|
|
+ private Set<Integer> getYearSet(LocalDate start, LocalDate end) {
|
|
|
+ return Stream.of(start.getYear(), end.getYear())
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成连续的日期序列(Java8 Stream实现)
|
|
|
+ */
|
|
|
+ private List<LocalDate> generateContinuousDates(LocalDate start, LocalDate end) {
|
|
|
+ long days = java.time.temporal.ChronoUnit.DAYS.between(start, end);
|
|
|
+ return Stream.iterate(start, date -> date.plusDays(1))
|
|
|
+ .limit(days + 1) // +1 包含结束日期
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取合法的表名列表(防SQL注入,仅允许healthscores_+4位数字)
|
|
|
+ */
|
|
|
+ private List<String> getValidTableNames(LocalDate start, LocalDate end) {
|
|
|
+ Set<String> tableNames = new HashSet<>();
|
|
|
+ String tablePrefix = "healthscores_";
|
|
|
+
|
|
|
+ // 遍历日期范围的所有年份
|
|
|
+ int startYear = start.getYear();
|
|
|
+ int endYear = end.getYear();
|
|
|
+ for (int year = startYear; year <= endYear; year++) {
|
|
|
+ tableNames.add(tablePrefix + year);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 严格校验表名格式(防SQL注入)
|
|
|
+ tableNames.forEach(name -> {
|
|
|
+ if (!name.matches("^healthscores_\\d{4}$")) {
|
|
|
+ throw new IllegalArgumentException("非法表名:" + name + "(仅允许healthscores_四位数字格式)");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return new ArrayList<>(tableNames);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|