Browse Source

图表48小时、累积增加前年前日等

master
review512jwy@163.com 1 month ago
parent
commit
359c89d726
  1. 30
      dongjian-dashboard-back-model/src/main/java/com/dongjian/dashboard/back/vo/device/AccumulateLineData.java
  2. 2
      dongjian-dashboard-back-model/src/main/java/com/dongjian/dashboard/back/vo/device/LineData.java
  3. 375
      dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/common/CommonOpt.java
  4. 333
      dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/common/LineDataAggregator.java
  5. 224
      dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/common/LineDataHourAggregator.java
  6. 12
      dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/impl/DeviceDataAccumulateServiceImpl.java
  7. 12
      dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/impl/DeviceDataMeasureServiceImpl.java

30
dongjian-dashboard-back-model/src/main/java/com/dongjian/dashboard/back/vo/device/AccumulateLineData.java

@ -0,0 +1,30 @@
package com.dongjian.dashboard.back.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class AccumulateLineData {
@Schema(description = "属性编码, 默认:single,温湿度设备:temperature或者humidity")
private String attrCode;
@Schema(description = "Y-axis data", example = "[]")
private Map<String, childLineData> subData = new HashMap<>();
@Data
public static class childLineData {
@Schema(description = "X-axis data", example = "[]")
private List<String> xData = new ArrayList<>();
@Schema(description = "Y-axis data", example = "[]")
private List<Object> yData = new ArrayList<>();
}
}

2
dongjian-dashboard-back-model/src/main/java/com/dongjian/dashboard/back/vo/device/LineData.java

@ -15,7 +15,7 @@ public class LineData {
private String attrCode; private String attrCode;
@Schema(description = "X-axis data", example = "[]") @Schema(description = "X-axis data", example = "[]")
private List<Object> xData = new ArrayList<>(); private List<String> xData = new ArrayList<>();
@Schema(description = "Y-axis data", example = "[]") @Schema(description = "Y-axis data", example = "[]")
private Map<String, List<Object>> yData = new HashMap<>(); private Map<String, List<Object>> yData = new HashMap<>();

375
dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/common/CommonOpt.java

@ -8,22 +8,24 @@ import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.alibaba.fastjson2.JSON;
import com.dongjian.dashboard.back.common.Constants; import com.dongjian.dashboard.back.common.Constants;
import com.dongjian.dashboard.back.common.language.msg.MsgLanguageChange; import com.dongjian.dashboard.back.common.language.msg.MsgLanguageChange;
import com.dongjian.dashboard.back.common.response.ResponseCode;
import com.dongjian.dashboard.back.common.response.SimpleDataResponse;
import com.dongjian.dashboard.back.dao.ex.*; import com.dongjian.dashboard.back.dao.ex.*;
import com.dongjian.dashboard.back.dto.device.LineDataSearchParams; import com.dongjian.dashboard.back.dto.device.LineDataSearchParams;
import com.dongjian.dashboard.back.model.*; import com.dongjian.dashboard.back.model.*;
import com.dongjian.dashboard.back.util.DESUtil; import com.dongjian.dashboard.back.util.DESUtil;
import com.dongjian.dashboard.back.util.DateUtil;
import com.dongjian.dashboard.back.util.DurationData; import com.dongjian.dashboard.back.util.DurationData;
import com.dongjian.dashboard.back.vo.building.BindedBuildingVO; import com.dongjian.dashboard.back.vo.building.BindedBuildingVO;
import com.dongjian.dashboard.back.vo.company.AuroraInfo; import com.dongjian.dashboard.back.vo.company.AuroraInfo;
import com.dongjian.dashboard.back.vo.device.AccumulateLineData;
import com.dongjian.dashboard.back.vo.device.DeviceIncrement; import com.dongjian.dashboard.back.vo.device.DeviceIncrement;
import com.dongjian.dashboard.back.vo.device.LineData; import com.dongjian.dashboard.back.vo.device.LineData;
import com.dongjian.dashboard.back.vo.record.RecordAccumulateDto; import com.dongjian.dashboard.back.vo.record.RecordAccumulateDto;
@ -257,8 +259,10 @@ public class CommonOpt {
} }
public List<String> getPreDay(int days) { public List<String> getPreDay(int days) {
// 日本时区
ZoneId JST = ZoneId.of("Asia/Tokyo");
// 获取当前日期 // 获取当前日期
LocalDate currentDate = LocalDate.now(); LocalDate currentDate = LocalDate.now(JST);
// 设置日期格式化 // 设置日期格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy_MM_dd"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy_MM_dd");
// 用于存储日期的列表 // 用于存储日期的列表
@ -272,94 +276,182 @@ public class CommonOpt {
return dateList; return dateList;
} }
public List<LineData> getLineData(Long companyId, LineDataSearchParams lineDataSearchParams, int deviceType) { public List<LineData> getAccumulateLineData(Long companyId, Integer searchType, LineDataSearchParams lineDataSearchParams) {
List<LineData> lineDataList = new ArrayList<>(); List<LineData> result = new ArrayList<>();
for (String attrCode : lineDataSearchParams.getAttrCodeList()){ executeLineDataQuery(
DeviceInfo deviceInfo = getDeviceInfoByDeviceId(lineDataSearchParams.getDeviceId()); result,
if (null == deviceInfo){ companyId,
continue; searchType,
} lineDataSearchParams,
(conn, lineData) -> {
DeviceInfo deviceInfo = getDeviceInfoByDeviceId(lineDataSearchParams.getDeviceId());
List<String> dateList = getPreDay(1);
for (String date : dateList) {
LocalDate current = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy_MM_dd"));
LocalDate yesterday = current.minusDays(1);
LocalDate beforeYesterday = current.minusDays(2);
LocalDate lastYear = DateUtil.getLastYearSameIsoWeekDay(current);
LocalDate lastYearPrev = lastYear.minusDays(1);
Double todayBase = getDayAccumulate(conn, yesterday, lineDataSearchParams.getDeviceId());
Double yesterdayBase = getDayAccumulate(conn, beforeYesterday, lineDataSearchParams.getDeviceId());
Double lastYearBase = getDayAccumulate(conn, lastYearPrev, lineDataSearchParams.getDeviceId());
AccumulateLineData acc = new AccumulateLineData();
acc.setAttrCode(lineData.getAttrCode());
processAccumulateDay(conn, current, todayBase, "today", acc, deviceInfo);
if (searchType == 1) {
processAccumulateDay(conn, yesterday, yesterdayBase, "yesterday", acc, deviceInfo);
processAccumulateDay(conn, lastYear, lastYearBase, "lastYear", acc, deviceInfo);
LineDataAggregator.convertToLineDataHour(acc, lineData);
} else {
LineDataAggregator.convertToLineDataAll(acc, lineData);
}
}
});
return result;
}
private void executeLineDataQuery(
List<LineData> result,
Long companyId,
Integer searchType,
LineDataSearchParams params,
BiConsumer<Connection, LineData> dataProcessor) {
DeviceInfo deviceInfo = getDeviceInfoByDeviceId(params.getDeviceId());
if (deviceInfo == null) {
return;
}
AuroraInfo auroraInfo = getAuroraInfoByApikey(Collections.singletonMap("companyId", companyId));
if (auroraInfo == null || StringUtils.isBlank(auroraInfo.getAuroraUrl())) {
logger.error("AuroraInfo is not set for companyId: {}", companyId);
return;
}
String jdbcUrl = null;
try {
jdbcUrl = buildAuroraJdbcUrl(auroraInfo);
} catch (ClassNotFoundException e) {
logger.error("Failed to build Aurora JDBC URL for companyId: {}", companyId, e);
return;
}
for (String attrCode : params.getAttrCodeList()) {
LineData lineData = new LineData(); LineData lineData = new LineData();
lineData.setAttrCode(attrCode); lineData.setAttrCode(attrCode);
try {
Map<String, Object> apikeyParamMap = new HashMap<>();
apikeyParamMap.put("companyId", companyId);
AuroraInfo apikeyInfo = getAuroraInfoByApikey(apikeyParamMap);
if (null == apikeyInfo) {
logger.error("Failed to get AuroraInfo for companyId: {}", companyId);
lineDataList.add(lineData);
}
if (StringUtils.isNotBlank(apikeyInfo.getAuroraUrl())) { try (Connection conn = DriverManager.getConnection(
Class.forName("com.mysql.cj.jdbc.Driver"); jdbcUrl,
DESUtil.decrypt(auroraInfo.getAuroraUsername(), Constants.DES_SALT),
DESUtil.decrypt(auroraInfo.getAuroraPassword(), Constants.DES_SALT))) {
String regex = "(jdbc:mysql://)([^/]+)(/data_center_aeon_admin.*)"; dataProcessor.accept(conn, lineData);
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(dbUrl);
String newJdbcUrl = "";
if (matcher.find()) {
newJdbcUrl = matcher.replaceAll("$1" + apikeyInfo.getAuroraUrl() + "$3");
}
try (Connection conn = DriverManager.getConnection( } catch (Exception e) {
newJdbcUrl.replace("data_center_aeon_admin", "aeon") + "&allowPublicKeyRetrieval=true", logger.error("executeLineDataQuery error", e);
DESUtil.decrypt(apikeyInfo.getAuroraUsername(), Constants.DES_SALT), }
DESUtil.decrypt(apikeyInfo.getAuroraPassword(), Constants.DES_SALT))) {
List<String> dateList = getPreDay(1); result.add(lineData);
}
}
for (String date : dateList) { private String buildAuroraJdbcUrl(AuroraInfo auroraInfo) throws ClassNotFoundException {
//获取昨天的值,这里只针对累积设备 Class.forName("com.mysql.cj.jdbc.Driver");
Double lastDayValue = null;
if (deviceType == 2) {
lastDayValue = getLastDayValue(conn, date, lineDataSearchParams.getDeviceId());
}
// 提取年、月、日 String regex = "(jdbc:mysql://)([^/]+)(/data_center_aeon_admin.*)";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy_MM_dd"); Matcher matcher = Pattern.compile(regex).matcher(dbUrl);
LocalDate currentDate = LocalDate.parse(date, formatter);
int year = currentDate.getYear(); if (!matcher.find()) {
int month = currentDate.getMonthValue(); throw new RuntimeException("Invalid dbUrl format");
int day = currentDate.getDayOfMonth(); }
String sql = ""; return matcher.replaceAll("$1" + auroraInfo.getAuroraUrl() + "$3")
if (deviceType == 2){ .replace("data_center_aeon_admin", "aeon")
sql = "SELECT upload_value, upload_at FROM dashboard_record_accumulate " + + "&allowPublicKeyRetrieval=true";
"WHERE device_id = ? AND attr_code = ? AND date_year = ? AND date_month = ? AND date_day = ? order by id "; }
} else if (deviceType == 3){
sql = "SELECT upload_value, upload_at FROM dashboard_record_measure " + private void processAccumulateDay(
"WHERE device_id = ? AND attr_code = ? AND date_year = ? AND date_month = ? AND date_day = ? order by id "; Connection conn,
} LocalDate date,
logger.info("getLineData sql: {}", sql); Double baseValue,
try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { String yKey,
preparedStatement.setString(1, lineDataSearchParams.getDeviceId()); AccumulateLineData accumulateLineData,
preparedStatement.setString(2, attrCode); DeviceInfo deviceInfo) {
preparedStatement.setInt(3, year); // 设置年份
preparedStatement.setInt(4, month); // 设置月份 AccumulateLineData.childLineData childLineData = new AccumulateLineData.childLineData();
preparedStatement.setInt(5, day); // 设置日期
try (ResultSet result = preparedStatement.executeQuery()) {
if (result.next()) { String sql = "SELECT upload_value, upload_at FROM dashboard_record_accumulate " +
if (deviceType == 2) { "WHERE device_id = ? AND attr_code = ? AND date_year = ? AND date_month = ? AND date_day = ? ORDER BY id";
processResult(result, lineData, deviceInfo, lastDayValue);
} else if (deviceType == 3) { try (PreparedStatement ps = conn.prepareStatement(sql)) {
processResult(result, lineData, deviceInfo); ps.setString(1, deviceInfo.getDeviceId());
} ps.setString(2, accumulateLineData.getAttrCode());
} ps.setInt(3, date.getYear());
} ps.setInt(4, date.getMonthValue());
} ps.setInt(5, date.getDayOfMonth());
}
} catch (Exception e) { try (ResultSet rs = ps.executeQuery()) {
logger.error("getLineData processing aurora error", e);
} DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ZoneId JST = ZoneId.of("Asia/Tokyo");
while (rs.next()) {
long ts = rs.getLong("upload_at");
String value = rs.getString("upload_value");
if (ts == 0) continue;
String time = Instant.ofEpochMilli(ts)
.atZone(JST)
.toLocalDateTime()
.format(formatter);
double current = StringUtils.isBlank(value)
? 0
: new BigDecimal(value).doubleValue();
double y = baseValue == null || current < baseValue
? current
: current - baseValue;
childLineData.getXData().add(time);
childLineData.getYData().add(CommonUtil.formatDecimal(y, deviceInfo.getDashboardDecimalPlaces()));
} }
}
} catch (Exception e) {
logger.error("processAccumulateDay error", e);
}
} catch (Exception e) { accumulateLineData.getSubData().put(yKey, childLineData);
logger.error("getLineData error", e); }
private Double getDayAccumulate(Connection conn, LocalDate date, String deviceId) {
String sql = "SELECT upload_value FROM dashboard_realtime_accumulate_day " +
"WHERE device_id = ? AND date_year = ? AND date_month = ? AND date_day = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, deviceId);
ps.setInt(2, date.getYear());
ps.setInt(3, date.getMonthValue());
ps.setInt(4, date.getDayOfMonth());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return new BigDecimal(rs.getString("upload_value")).doubleValue();
}
} }
lineDataList.add(lineData); } catch (Exception e) {
logger.error("getDayAccumulate error", e);
} }
return lineDataList; return null;
} }
private DeviceInfo getDeviceInfoByDeviceId(String deviceId) { private DeviceInfo getDeviceInfoByDeviceId(String deviceId) {
@ -371,47 +463,56 @@ public class CommonOpt {
return list.isEmpty() ? null : list.get(0); return list.isEmpty() ? null : list.get(0);
} }
private Double getLastDayValue(Connection conn, String date, String deviceId) { public List<LineData> getMeasureLineData(Long companyId, Integer searchType, LineDataSearchParams lineDataSearchParams) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy_MM_dd"); List<LineData> list = new ArrayList<>();
// 解析日期字符串为 LocalDate 对象 executeLineDataQuery(
LocalDate previousDate = LocalDate.parse(date, formatter); list,
// 获取前一天的日期 companyId,
LocalDate previousDay = previousDate.minusDays(1); searchType,
// 获取年、月、日 lineDataSearchParams,
int year = previousDay.getYear(); (conn, lineData) -> {
int month = previousDay.getMonthValue();
int day = previousDay.getDayOfMonth(); DeviceInfo deviceInfo = getDeviceInfoByDeviceId(lineDataSearchParams.getDeviceId());
List<String> dateList = getPreDay(1);
String sql = "SELECT upload_value FROM dashboard_realtime_accumulate_day " +
"WHERE device_id = ? AND date_year = ? AND date_month = ? AND date_day = ?"; for (String date : dateList) {
LocalDate localDate = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy_MM_dd"));
logger.info("getUploadValue sql: {}", sql);
String sql = "SELECT upload_value, upload_at FROM dashboard_record_measure " +
try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { "WHERE device_id = ? AND attr_code = ? AND date_year = ? AND date_month = ? AND date_day = ? ORDER BY id";
preparedStatement.setString(1, deviceId); // 设置设备ID
preparedStatement.setInt(2, year); // 设置年份 try (PreparedStatement ps = conn.prepareStatement(sql)) {
preparedStatement.setInt(3, month); // 设置月份 ps.setString(1, lineDataSearchParams.getDeviceId());
preparedStatement.setInt(4, day); // 设置日期 ps.setString(2, lineData.getAttrCode());
ps.setInt(3, localDate.getYear());
ps.setInt(4, localDate.getMonthValue());
ps.setInt(5, localDate.getDayOfMonth());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
processResult(rs, lineData, deviceInfo);
}
}
} catch (Exception e) {
logger.error("process measure data error", e);
}
}
});
try (ResultSet result = preparedStatement.executeQuery()) { if (searchType == 1) {
if (result.next()) { list.forEach(ld -> {
String uploadValue = result.getString("upload_value"); LineDataAggregator.aggregateAverageByHalfHour(ld);
logger.info("Found upload_value: {}", uploadValue); LineDataAggregator.fillTo48HalfHour(ld);
return new BigDecimal(uploadValue).doubleValue(); });
} else {
logger.warn("No data found for deviceId: {} on {}/{}/{}", deviceId, year, month, day);
}
}
} catch (SQLException e) {
logger.error("Error getLastDayValue query: ", e);
} }
return null;
return list;
} }
private void processResult(ResultSet result, LineData lineData, DeviceInfo deviceInfo) { private void processResult(ResultSet result, LineData lineData, DeviceInfo deviceInfo) {
try { try {
// 用于存储 xData 和 yData // 用于存储 xData 和 yData
List<Object> xDataList = new ArrayList<>(); List<String> xDataList = new ArrayList<>();
List<Object> yDataList = new ArrayList<>(); List<Object> yDataList = new ArrayList<>();
// 使用 DateTimeFormatter 来格式化时间 // 使用 DateTimeFormatter 来格式化时间
@ -451,54 +552,6 @@ public class CommonOpt {
} }
} }
private void processResult(ResultSet result, LineData lineData, DeviceInfo deviceInfo, Double lastDayValue) {
try {
List<Object> xDataList = new ArrayList<>();
List<Object> yDataList = new ArrayList<>();
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ZoneId JST = ZoneId.of("Asia/Tokyo");
do {
long receiveTs = result.getLong("upload_at");
String value = result.getString("upload_value");
if (receiveTs == 0) {
continue;
}
// 日本时区时间
Instant instant = Instant.ofEpochMilli(receiveTs);
String formattedDate = instant
.atZone(JST)
.toLocalDateTime()
.format(formatter);
double todayValue = StringUtils.isBlank(value) ? 0.0 : new BigDecimal(value).doubleValue();
// 默认今天值
double yValue = todayValue;
// 计算差值
if (lastDayValue != null && todayValue >= lastDayValue) {
yValue = todayValue - lastDayValue;
}
xDataList.add(formattedDate);
yDataList.add(CommonUtil.formatDecimal(yValue, deviceInfo.getDashboardDecimalPlaces()));
} while (result.next());
lineData.getXData().addAll(xDataList);
lineData.getYData().put("common", yDataList);
} catch (Exception e) {
logger.error("Error processing result set", e);
}
}
public Map<String, String> buildDeviceId85To111Map(List<String> deviceIds) { public Map<String, String> buildDeviceId85To111Map(List<String> deviceIds) {
return deviceIds.stream() return deviceIds.stream()
.filter(id -> id.endsWith(SUFFIX_85) || id.endsWith(SUFFIX_85_9003)) .filter(id -> id.endsWith(SUFFIX_85) || id.endsWith(SUFFIX_85_9003))

333
dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/common/LineDataAggregator.java

@ -0,0 +1,333 @@
package com.dongjian.dashboard.back.service.common;
import com.dongjian.dashboard.back.vo.device.AccumulateLineData;
import com.dongjian.dashboard.back.vo.device.LineData;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* 完整整合版LineData 聚合与转换工具类统一使用 LineDataAligner 对齐
*/
public class LineDataAggregator {
/* ===============================
* 时间工具
* =============================== */
public static final class TimeAxisUtil {
public static final DateTimeFormatter DATETIME_FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static String hourKey(LocalDateTime time) {
return time.withMinute(0).withSecond(0).withNano(0).format(DATETIME_FMT);
}
public static String halfHourKey(LocalDateTime time) {
int minute = time.getMinute() < 30 ? 0 : 30;
return time.withMinute(minute).withSecond(0).withNano(0).format(DATETIME_FMT);
}
public static String alignToHalfHour(LocalTime time) {
int minute = time.getMinute() < 30 ? 0 : 30;
return LocalTime.of(time.getHour(), minute).format(DateTimeFormatter.ofPattern("HH:mm"));
}
public static String extractDate(String datetime) {
return LocalDateTime.parse(datetime, DATETIME_FMT).toLocalDate().toString();
}
public static List<String> generate48HalfHourPoints(String date) {
List<String> list = new ArrayList<>(48);
LocalDateTime t = LocalDate.parse(date).atStartOfDay();
for (int i = 0; i < 48; i++) {
list.add(t.plusMinutes(i * 30).format(DATETIME_FMT));
}
return list;
}
}
/* ===============================
* Y 对齐工具
* =============================== */
public static final class LineDataAligner {
/**
* 对齐 X null
*/
public static Map<String, List<Object>> alignY(
List<String> xAxis,
Map<String, Map<String, Object>> seriesValueMap
) {
Map<String, List<Object>> result = new LinkedHashMap<>();
for (Map.Entry<String, Map<String, Object>> entry : seriesValueMap.entrySet()) {
List<Object> aligned = new ArrayList<>(xAxis.size());
Map<String, Object> valueMap = entry.getValue();
for (String x : xAxis) {
aligned.add(valueMap.get(x));
}
result.put(entry.getKey(), aligned);
}
return result;
}
}
/* ===============================
* 聚合策略接口可扩展
* =============================== */
@FunctionalInterface
public interface HalfHourCollector<T> {
void collect(Map<String, T> bucket, String key, LocalDateTime time, Object value);
}
/* 平均值聚合实现 */
public static class AvgCollector implements HalfHourCollector<List<Double>> {
@Override
public void collect(Map<String, List<Double>> bucket, String key, LocalDateTime time, Object value) {
bucket.computeIfAbsent(key, k -> new ArrayList<>()).add(Double.parseDouble(value.toString()));
}
}
/* 取最后一条实现 */
public static class LastValueCollector implements HalfHourCollector<TimeValue> {
@Override
public void collect(Map<String, TimeValue> bucket, String key, LocalDateTime time, Object value) {
TimeValue old = bucket.get(key);
if (old == null || time.isAfter(old.time)) {
bucket.put(key, new TimeValue(time, value));
}
}
}
/* ===============================
* 核心数据模型
* =============================== */
public static class TimeValue {
LocalDateTime time;
Object value;
TimeValue(LocalDateTime time, Object value) {
this.time = time;
this.value = value;
}
}
public static class HalfHourSeries {
String date;
List<String> xList;
List<Object> yList;
HalfHourSeries(String date) {
this(date, new ArrayList<>(), new ArrayList<>());
}
HalfHourSeries(String date, List<String> xList, List<Object> yList) {
this.date = date;
this.xList = xList;
this.yList = yList;
}
Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < xList.size(); i++) {
map.put(xList.get(i), yList.get(i));
}
return map;
}
}
/* ===============================
* 工具方法
* =============================== */
private static Object safeGet(List<Object> list, int index) {
return (list != null && index < list.size()) ? list.get(index) : null;
}
private static LocalDateTime parseTime(Object x) {
return LocalDateTime.parse(String.valueOf(x), TimeAxisUtil.DATETIME_FMT);
}
/* ===============================
* 按小时聚合平均值
* =============================== */
public static void aggregateByHour(LineData lineData) {
aggregateByHourOrHalfHour(lineData, new AvgCollector(), true);
}
/* ===============================
* 按半小时聚合平均值
* =============================== */
public static void aggregateAverageByHalfHour(LineData lineData) {
aggregateByHourOrHalfHour(lineData, new AvgCollector(), false);
}
/* 公共聚合方法 */
private static <T> void aggregateByHourOrHalfHour(
LineData lineData,
HalfHourCollector<T> collector,
boolean byHour
) {
if (lineData == null) return;
List<String> xData = lineData.getXData();
Map<String, List<Object>> yDataMap = lineData.getYData();
if (xData == null || xData.isEmpty() || yDataMap == null || yDataMap.isEmpty()) return;
Set<String> allSeriesKeys = yDataMap.keySet();
Map<String, Map<String, T>> bucket = new TreeMap<>();
for (int i = 0; i < xData.size(); i++) {
LocalDateTime time = parseTime(xData.get(i));
String key = byHour ? TimeAxisUtil.hourKey(time) : TimeAxisUtil.halfHourKey(time);
for (String series : allSeriesKeys) {
Object yVal = safeGet(yDataMap.get(series), i);
if (yVal == null) continue;
bucket.computeIfAbsent(key, k -> new HashMap<>());
collector.collect(bucket.get(key), series, time, yVal);
}
}
// 构建临时 Map 用于统一对齐
Map<String, Map<String, Object>> tmpMap = new HashMap<>();
boolean isAvg = collector instanceof AvgCollector;
for (Map.Entry<String, Map<String, T>> entry : bucket.entrySet()) {
String xKey = entry.getKey();
Map<String, T> seriesMap = entry.getValue();
for (String series : allSeriesKeys) {
Object val;
T tVal = seriesMap.get(series);
if (isAvg && tVal instanceof List) {
List<Double> list = (List<Double>) tVal;
val = list.stream().mapToDouble(Double::doubleValue).average().orElse(0);
} else if (!isAvg && tVal instanceof TimeValue) {
val = ((TimeValue) tVal).value;
} else {
val = tVal;
}
tmpMap.computeIfAbsent(series, k -> new HashMap<>()).put(xKey, val);
}
}
List<String> newX = new ArrayList<>(bucket.keySet());
lineData.setXData(newX);
lineData.setYData(LineDataAligner.alignY(newX, tmpMap));
}
/* ===============================
* AccumulateLineData -> LineData
* 半小时取最后一条
* =============================== */
public static void convertToLineDataHour(AccumulateLineData accumulate, LineData lineData) {
if (accumulate == null || accumulate.getSubData() == null) return;
Map<String, HalfHourSeries> seriesMap = new LinkedHashMap<>();
for (Map.Entry<String, AccumulateLineData.childLineData> entry : accumulate.getSubData().entrySet()) {
seriesMap.put(entry.getKey(), aggregateLastByHalfHour(entry.getValue()));
}
HalfHourSeries today = seriesMap.get("today");
if (today == null) throw new IllegalStateException("subData 必须包含 today");
// 构建最终 X
List<String> finalX = new ArrayList<>();
for (String hm : today.xList) finalX.add(today.date + " " + hm + ":00");
// 构建临时 Map
Map<String, Map<String, Object>> tmpMap = new HashMap<>();
for (Map.Entry<String, HalfHourSeries> entry : seriesMap.entrySet()) {
tmpMap.put(entry.getKey(), entry.getValue().toMap());
}
lineData.setXData(finalX);
lineData.setYData(LineDataAligner.alignY(today.xList, tmpMap));
lineData.setAttrCode(accumulate.getAttrCode());
fillTo48HalfHour(lineData);
}
private static HalfHourSeries aggregateLastByHalfHour(AccumulateLineData.childLineData child) {
if (child == null || child.getXData() == null || child.getYData() == null)
return new HalfHourSeries(null);
Map<String, TimeValue> bucket = new HashMap<>();
String date = null;
int size = Math.min(child.getXData().size(), child.getYData().size());
for (int i = 0; i < size; i++) {
LocalDateTime time = LocalDateTime.parse(child.getXData().get(i).toString(), TimeAxisUtil.DATETIME_FMT);
Object value = child.getYData().get(i);
if (value == null) continue;
if (date == null) date = time.toLocalDate().toString();
String hm = TimeAxisUtil.alignToHalfHour(time.toLocalTime());
TimeValue old = bucket.get(hm);
if (old == null || time.isAfter(old.time)) bucket.put(hm, new TimeValue(time, value));
}
List<String> sortedHM = new ArrayList<>(bucket.keySet());
Collections.sort(sortedHM);
List<Object> values = new ArrayList<>();
for (String hm : sortedHM) values.add(bucket.get(hm).value);
return new HalfHourSeries(date, sortedHM, values);
}
/* ===============================
* 补齐 48 半小时点
* =============================== */
public static void fillTo48HalfHour(LineData lineData) {
List<String> xData = lineData.getXData();
Map<String, List<Object>> yData = lineData.getYData();
if (xData == null || xData.isEmpty() || yData == null) return;
String date = TimeAxisUtil.extractDate(xData.get(0));
List<String> fixedX = TimeAxisUtil.generate48HalfHourPoints(date);
// 构建临时 Map
Map<String, Map<String, Object>> tmpMap = new HashMap<>();
for (Map.Entry<String, List<Object>> entry : yData.entrySet()) {
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < xData.size(); i++) {
map.put(xData.get(i), entry.getValue().get(i));
}
tmpMap.put(entry.getKey(), map);
}
lineData.setXData(new ArrayList<>(fixedX));
lineData.setYData(LineDataAligner.alignY(fixedX, tmpMap));
}
/* ===============================
* 全量对齐
* =============================== */
public static void convertToLineDataAll(AccumulateLineData accumulate, LineData lineData) {
if (accumulate == null || accumulate.getSubData() == null) return;
LinkedHashSet<String> xSet = new LinkedHashSet<>();
for (AccumulateLineData.childLineData child : accumulate.getSubData().values()) {
if (child.getXData() != null) xSet.addAll(child.getXData());
}
List<String> mergedX = new ArrayList<>(xSet);
Map<String, Map<String, Object>> tmpMap = new LinkedHashMap<>();
for (Map.Entry<String, AccumulateLineData.childLineData> entry : accumulate.getSubData().entrySet()) {
Map<String, Object> map = new LinkedHashMap<>();
AccumulateLineData.childLineData child = entry.getValue();
if (child.getXData() != null && child.getYData() != null) {
int size = Math.min(child.getXData().size(), child.getYData().size());
for (int i = 0; i < size; i++) map.put(child.getXData().get(i), child.getYData().get(i));
}
tmpMap.put(entry.getKey(), map);
}
lineData.setXData(mergedX);
lineData.setYData(LineDataAligner.alignY(mergedX, tmpMap));
}
}

224
dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/common/LineDataHourAggregator.java

@ -1,224 +0,0 @@
package com.dongjian.dashboard.back.service.common;
import com.dongjian.dashboard.back.vo.device.LineData;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* 给计测设备按小时聚合数据求平均值
*/
public class LineDataHourAggregator {
private static final DateTimeFormatter INPUT_FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter HOUR_FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00:00");
public static void aggregateByHour(LineData lineData) {
List<Object> xData = lineData.getXData();
Map<String, List<Object>> yDataMap = lineData.getYData();
if (xData == null || yDataMap == null || yDataMap.isEmpty()) {
return;
}
// hour -> index
Map<String, Integer> hourIndexMap = new TreeMap<>();
Map<String, Map<String, List<Double>>> hourValueMap = new TreeMap<>();
for (int i = 0; i < xData.size(); i++) {
String timeStr = String.valueOf(xData.get(i));
LocalDateTime time = LocalDateTime.parse(timeStr, INPUT_FMT);
String hourKey = time.format(HOUR_FMT);
hourIndexMap.putIfAbsent(hourKey, hourIndexMap.size());
for (Map.Entry<String, List<Object>> entry : yDataMap.entrySet()) {
Object yVal = entry.getValue().get(i);
if (yVal == null) {
continue;
}
hourValueMap
.computeIfAbsent(hourKey, k -> new HashMap<>())
.computeIfAbsent(entry.getKey(), k -> new ArrayList<>())
.add(Double.parseDouble(String.valueOf(yVal)));
}
}
List<Object> newX = new ArrayList<>();
Map<String, List<Object>> newY = new HashMap<>();
for (String hourKey : hourIndexMap.keySet()) {
newX.add(hourKey);
Map<String, List<Double>> seriesMap = hourValueMap.get(hourKey);
if (seriesMap == null) {
continue;
}
for (Map.Entry<String, List<Double>> e : seriesMap.entrySet()) {
double avg = e.getValue().stream().mapToDouble(Double::doubleValue).average().orElse(0);
newY.computeIfAbsent(e.getKey(), k -> new ArrayList<>()).add(avg);
}
}
lineData.setXData(newX);
lineData.setYData(newY);
}
/**
* LineData 补齐为日本时区当天 0:00 - 23:30 48 个半小时点
* 没有数据的时间点Y 值补 0
*/
public static void fillTodayHalfHourPointsJST(LineData lineData) {
List<Object> xData = lineData.getXData();
Map<String, List<Object>> yDataMap = lineData.getYData();
if (xData == null || yDataMap == null) {
return;
}
ZoneId JST = ZoneId.of("Asia/Tokyo");
LocalDate todayJst = LocalDate.now(JST);
LocalDateTime start = todayJst.atStartOfDay();
// 原数据转 Map:time -> series -> value
Map<String, Map<String, Object>> valueMap = new HashMap<>();
for (int i = 0; i < xData.size(); i++) {
String time = String.valueOf(xData.get(i));
for (Map.Entry<String, List<Object>> e : yDataMap.entrySet()) {
valueMap
.computeIfAbsent(time, k -> new HashMap<>())
.put(e.getKey(), e.getValue().get(i));
}
}
List<Object> newX = new ArrayList<>(48);
Map<String, List<Object>> newY = new HashMap<>();
for (int i = 0; i < 48; i++) {
LocalDateTime t = start.plusMinutes(i * 30);
String key = t.format(INPUT_FMT);
newX.add(key);
Map<String, Object> seriesValues = valueMap.get(key);
for (String series : yDataMap.keySet()) {
newY
.computeIfAbsent(series, k -> new ArrayList<>())
.add(seriesValues == null ? null : seriesValues.get(series));
}
}
lineData.setXData(newX);
lineData.setYData(newY);
}
/**
* 给计测设备按半小时聚合数据求平均值
* 00~29 HH:00:00
* 30~59 HH:30:00
*/
public static void aggregateAverageByHalfHour(LineData lineData) {
List<Object> xData = lineData.getXData();
Map<String, List<Object>> yDataMap = lineData.getYData();
if (xData == null || yDataMap == null) {
return;
}
Map<String, Map<String, List<Double>>> halfHourMap = new TreeMap<>();
for (int i = 0; i < xData.size(); i++) {
LocalDateTime time = LocalDateTime.parse(String.valueOf(xData.get(i)), INPUT_FMT);
int minute = time.getMinute() < 30 ? 0 : 30;
LocalDateTime halfHourTime = time.withMinute(minute).withSecond(0).withNano(0);
String key = halfHourTime.format(INPUT_FMT);
for (Map.Entry<String, List<Object>> e : yDataMap.entrySet()) {
Object yVal = e.getValue().get(i);
if (yVal == null) continue;
halfHourMap
.computeIfAbsent(key, k -> new HashMap<>())
.computeIfAbsent(e.getKey(), k -> new ArrayList<>())
.add(Double.parseDouble(String.valueOf(yVal)));
}
}
List<Object> newX = new ArrayList<>();
Map<String, List<Object>> newY = new HashMap<>();
for (Map.Entry<String, Map<String, List<Double>>> entry : halfHourMap.entrySet()) {
newX.add(entry.getKey());
for (Map.Entry<String, List<Double>> e : entry.getValue().entrySet()) {
double avg = e.getValue().stream().mapToDouble(Double::doubleValue).average().orElse(0);
newY.computeIfAbsent(e.getKey(), k -> new ArrayList<>()).add(avg);
}
}
lineData.setXData(newX);
lineData.setYData(newY);
}
/**
* 按半小时聚合只保留每个半小时内的最后一条数据
*/
public static void aggregateLastByHalfHour(LineData lineData) {
List<Object> xData = lineData.getXData();
Map<String, List<Object>> yDataMap = lineData.getYData();
if (xData == null || yDataMap == null) {
return;
}
Map<String, Map<String, Object>> halfHourLastMap = new TreeMap<>();
for (int i = 0; i < xData.size(); i++) {
LocalDateTime time = LocalDateTime.parse(String.valueOf(xData.get(i)), INPUT_FMT);
int minute = time.getMinute() < 30 ? 0 : 30;
LocalDateTime halfHourTime = time.withMinute(minute).withSecond(0).withNano(0);
String key = halfHourTime.format(INPUT_FMT);
for (Map.Entry<String, List<Object>> e : yDataMap.entrySet()) {
Object yVal = e.getValue().get(i);
if (yVal == null) continue;
halfHourLastMap
.computeIfAbsent(key, k -> new HashMap<>())
.put(e.getKey(), yVal);
}
}
List<Object> newX = new ArrayList<>();
Map<String, List<Object>> newY = new HashMap<>();
for (Map.Entry<String, Map<String, Object>> entry : halfHourLastMap.entrySet()) {
newX.add(entry.getKey());
for (Map.Entry<String, Object> e : entry.getValue().entrySet()) {
newY.computeIfAbsent(e.getKey(), k -> new ArrayList<>()).add(e.getValue());
}
}
lineData.setXData(newX);
lineData.setYData(newY);
}
}

12
dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/impl/DeviceDataAccumulateServiceImpl.java

@ -10,6 +10,7 @@ import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.alibaba.fastjson2.JSON;
import com.dongjian.dashboard.back.common.Constants; import com.dongjian.dashboard.back.common.Constants;
import com.dongjian.dashboard.back.common.language.msg.MsgLanguageChange; import com.dongjian.dashboard.back.common.language.msg.MsgLanguageChange;
import com.dongjian.dashboard.back.common.response.PageInfo; import com.dongjian.dashboard.back.common.response.PageInfo;
@ -23,7 +24,6 @@ import com.dongjian.dashboard.back.dto.device.LineDataSearchParams;
import com.dongjian.dashboard.back.model.DeviceRawdataRealtime; import com.dongjian.dashboard.back.model.DeviceRawdataRealtime;
import com.dongjian.dashboard.back.service.DeviceDataAccumulateService; import com.dongjian.dashboard.back.service.DeviceDataAccumulateService;
import com.dongjian.dashboard.back.service.common.CommonOpt; import com.dongjian.dashboard.back.service.common.CommonOpt;
import com.dongjian.dashboard.back.service.common.LineDataHourAggregator;
import com.dongjian.dashboard.back.util.CommonUtil; import com.dongjian.dashboard.back.util.CommonUtil;
import com.dongjian.dashboard.back.util.DateUtil; import com.dongjian.dashboard.back.util.DateUtil;
import com.dongjian.dashboard.back.vo.data.DeviceAccumulateData; import com.dongjian.dashboard.back.vo.data.DeviceAccumulateData;
@ -198,15 +198,7 @@ public class DeviceDataAccumulateServiceImpl implements DeviceDataAccumulateServ
if (null == searchType) { if (null == searchType) {
searchType = 2;//默认全部数据 searchType = 2;//默认全部数据
} }
List<LineData> lineDataList = commonOpt.getLineData(companyId, lineDataSearchParams, 2); List<LineData> lineDataList = commonOpt.getAccumulateLineData(companyId, searchType, lineDataSearchParams);
if (1 == searchType && CollectionUtils.isNotEmpty(lineDataList)) {
lineDataList.forEach(lineData -> {
//按半小时聚合
LineDataHourAggregator.aggregateLastByHalfHour(lineData);
//补齐48个横坐标
LineDataHourAggregator.fillTodayHalfHourPointsJST(lineData);
});
}
return SimpleDataResponse.success(lineDataList); return SimpleDataResponse.success(lineDataList);
} }

12
dongjian-dashboard-back-service/src/main/java/com/dongjian/dashboard/back/service/impl/DeviceDataMeasureServiceImpl.java

@ -17,7 +17,7 @@ import com.dongjian.dashboard.back.model.DeviceRawdataRealtime;
import com.dongjian.dashboard.back.model.DeviceRawdataRealtimeExample; import com.dongjian.dashboard.back.model.DeviceRawdataRealtimeExample;
import com.dongjian.dashboard.back.service.DeviceDataMeasureService; import com.dongjian.dashboard.back.service.DeviceDataMeasureService;
import com.dongjian.dashboard.back.service.common.CommonOpt; import com.dongjian.dashboard.back.service.common.CommonOpt;
import com.dongjian.dashboard.back.service.common.LineDataHourAggregator; //import com.dongjian.dashboard.back.service.common.LineDataHourAggregator;
import com.dongjian.dashboard.back.util.CommonUtil; import com.dongjian.dashboard.back.util.CommonUtil;
import com.dongjian.dashboard.back.vo.data.DeviceAccumulateData; import com.dongjian.dashboard.back.vo.data.DeviceAccumulateData;
import com.dongjian.dashboard.back.vo.data.DeviceMeasureData; import com.dongjian.dashboard.back.vo.data.DeviceMeasureData;
@ -236,15 +236,7 @@ public class DeviceDataMeasureServiceImpl implements DeviceDataMeasureService {
if (null == searchType) { if (null == searchType) {
searchType = 2;//默认全部数据 searchType = 2;//默认全部数据
} }
List<LineData> lineDataList = commonOpt.getLineData(companyId, lineDataSearchParams, 3); List<LineData> lineDataList = commonOpt.getMeasureLineData(companyId, searchType, lineDataSearchParams);
if (1 == searchType && CollectionUtils.isNotEmpty(lineDataList)) {
lineDataList.forEach(lineData -> {
//按半小时聚合
LineDataHourAggregator.aggregateAverageByHalfHour(lineData);
//补齐48个横坐标
LineDataHourAggregator.fillTodayHalfHourPointsJST(lineData);
});
}
return SimpleDataResponse.success(lineDataList); return SimpleDataResponse.success(lineDataList);
} }

Loading…
Cancel
Save