fix:1、修改能耗管理-能耗报表,读取数据逻辑 2、修改总览大屏-能耗周趋势接口 3、修改产线大屏-能耗监测接口

hhk
HuangHuiKang 1 month ago
parent 02203d4527
commit d0569d9d0a

@ -195,14 +195,14 @@ public class RecipeDeviceRecordController {
} }
Map<Long, Map<String, Object>> deviceDataMap = deviceService.createDeviceDataMap(device.getId());//recipeRespVO.getDeviceId() // Map<Long, Map<String, Object>> deviceDataMap = deviceService.createDeviceDataMap(device.getId());//recipeRespVO.getDeviceId()
Map<String, Object> map = tDengineService.newSelectLatestRow(device.getId()); Map<String, Object> map = tDengineService.newSelectLatestRow(device.getId());
// OpcUtils.connect(device.getId(),device.getUrl(),device.getUsername(),device.getPassword(),10); // OpcUtils.connect(device.getId(),device.getUrl(),device.getUsername(),device.getPassword(),10);
for (RecipeDeviceAttributeDO attributeDO : attributeList) { for (RecipeDeviceAttributeDO attributeDO : attributeList) {
Map<String, Object> data = deviceDataMap.get(attributeDO.getAttributeId()); // Map<String, Object> data = deviceDataMap.get(attributeDO.getAttributeId());
DeviceContactModelDO deviceContactModelDO = deviceContactModelMap.get(attributeDO.getAttributeId()); DeviceContactModelDO deviceContactModelDO = deviceContactModelMap.get(attributeDO.getAttributeId());
if (deviceContactModelDO == null) { if (deviceContactModelDO == null) {

@ -809,12 +809,24 @@ public class DeviceServiceImpl implements DeviceService {
@Override @Override
public List<LineDeviceRespVO> lineDeviceList(LineDeviceRequestVO pageReqVO) { public List<LineDeviceRespVO> lineDeviceList(LineDeviceRequestVO pageReqVO) {
List<LineDeviceRespVO> records = deviceMapper.lineDeviceList(pageReqVO); List<LineDeviceRespVO> records = deviceMapper.lineDeviceList(pageReqVO);
if (records.isEmpty()) {
return records;
}
// 1. 收集所有设备ID
List<Long> deviceIds = records.stream()
.map(LineDeviceRespVO::getDeviceId)
.collect(Collectors.toList());
// 2. 批量查询每个设备的最新时间
Map<Long, LocalDateTime> latestTsMap = tdengineService.newSelectLatestTsBatch(deviceIds);
// 3. 填充 collectionTime
for (LineDeviceRespVO record : records) { for (LineDeviceRespVO record : records) {
Map<String, Object> latestDeviceData = tdengineService.getLatestDeviceData(record.getDeviceId()); LocalDateTime ts = latestTsMap.get(record.getDeviceId());
if(latestDeviceData != null) { if (ts != null) {
record.setCollectionTime((String) latestDeviceData.get("timestamp")); record.setCollectionTime(ts.toString()); // 或 formatDateTime(ts) 根据你的需求
} }
} }

@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.enums.JavaToTdengineT
import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO; import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DS; import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -1192,4 +1193,243 @@ public class TDengineService {
} }
@DS("tdengine")
public Map<Long, Map<String, Object>> queryDevicesLatestRow(
List<Long> deviceIds,
String startTime,
String endTime) {
if (CollectionUtils.isEmpty(deviceIds)) {
return Collections.emptyMap();
}
Map<Long, Map<String, Object>> result = new HashMap<>();
for (Long deviceId : deviceIds) {
String tableName = "besure_server.d_" + deviceId;
StringBuilder sql = new StringBuilder();
sql.append("SELECT * FROM ")
.append(tableName)
.append(" WHERE 1=1 ");
if (StringUtils.isNotBlank(startTime)) {
sql.append(" AND ts >= '").append(startTime).append("' ");
}
if (StringUtils.isNotBlank(endTime)) {
sql.append(" AND ts <= '").append(endTime).append("' ");
}
sql.append(" ORDER BY ts DESC LIMIT 1");
try {
List<Map<String, Object>> list =
jdbcTemplate.queryForList(sql.toString());
if (!list.isEmpty()) {
Map<String, Object> row = list.get(0);
convertTs(row);
result.put(deviceId, row);
}
} catch (Exception e) {
log.error("查询设备最新数据失败 deviceId={}", deviceId, e);
}
}
return result;
}
@DS("tdengine")
public Map<Long, Map<String, Object>> queryDevicesEarliestRow(
List<Long> deviceIds,
String startTime,
String endTime) {
if (CollectionUtils.isEmpty(deviceIds)) {
return Collections.emptyMap();
}
Map<Long, Map<String, Object>> result = new HashMap<>();
for (Long deviceId : deviceIds) {
String tableName = "besure_server.d_" + deviceId;
StringBuilder sql = new StringBuilder();
sql.append("SELECT * FROM ")
.append(tableName)
.append(" WHERE 1=1 ");
if (StringUtils.isNotBlank(startTime)) {
sql.append(" AND ts >= '").append(startTime).append("' ");
}
if (StringUtils.isNotBlank(endTime)) {
sql.append(" AND ts <= '").append(endTime).append("' ");
}
sql.append(" ORDER BY ts ASC LIMIT 1");
try {
List<Map<String, Object>> list =
jdbcTemplate.queryForList(sql.toString());
if (!list.isEmpty()) {
Map<String, Object> row = list.get(0);
convertTs(row);
result.put(deviceId, row);
}
} catch (Exception e) {
log.error("查询设备最早数据失败 deviceId={}", deviceId, e);
}
}
return result;
}
/**
*
* @param deviceIds
* @param startTime
* @param endTime
* @return
*/
@DS("tdengine")
public List<Map<String, Object>> newQueryLastDataByHourBatch(
Set<Long> deviceIds,
LocalDateTime startTime,
LocalDateTime endTime) {
if (deviceIds == null || deviceIds.isEmpty()) {
return Collections.emptyList();
}
List<Map<String, Object>> result = new ArrayList<>();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String startStr = startTime.format(fmt);
String endStr = endTime.format(fmt);
for (Long deviceId : deviceIds) {
String tableName = "besure_server.d_" + deviceId;
// 动态查询所有列(*),按时间倒序
String sql = "SELECT * FROM " + tableName +
" WHERE ts >= '" + startStr + "' AND ts <= '" + endStr + "'" +
" ORDER BY ts DESC";
try {
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
// 用 map 存储每小时最新一条数据
Map<String, Map<String, Object>> hourMap = new HashMap<>();
DateTimeFormatter hourFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH");
for (Map<String, Object> row : rows) {
LocalDateTime ts = ((Timestamp) row.get("ts")).toLocalDateTime();
String hourKey = ts.format(hourFmt);
// 每小时只保留最新一条
if (!hourMap.containsKey(hourKey)) {
Map<String, Object> map = new HashMap<>();
map.put("deviceId", deviceId);
map.put("timestamp", ts);
for (Map.Entry<String, Object> entry : row.entrySet()) {
String key = entry.getKey();
if (!"ts".equals(key)) {
map.put(key, entry.getValue());
}
}
hourMap.put(hourKey, map);
}
}
result.addAll(hourMap.values());
} catch (Exception e) {
log.error("查询设备 {} 数据异常", deviceId, e);
}
}
return result;
}
/**
* N
*
* @param deviceIds ID
* @param startDate
* @param days
* @return Map daydeviceIdtimestamp
*/
@DS("tdengine")
public List<Map<String, Object>> queryLastDataByDayBatch(Set<Long> deviceIds, LocalDate startDate, int days) {
if (deviceIds == null || deviceIds.isEmpty() || days <= 0) {
return Collections.emptyList();
}
List<Map<String, Object>> result = new ArrayList<>();
LocalDate endDate = startDate.plusDays(days);
for (Long deviceId : deviceIds) {
String tableName = "besure_server.d_" + deviceId;
String sql = String.format(
"SELECT *, ts as timestamp, DATE_FORMAT(ts, '%%Y-%%m-%%d') as day " +
"FROM %s " +
"WHERE ts >= '%s' AND ts < '%s' " +
"ORDER BY ts DESC",
tableName, startDate.toString(), endDate.toString()
);
try {
result.addAll(jdbcTemplate.queryForList(sql));
} catch (Exception e) {
log.error("查询设备 {} 最近 {} 天数据异常", deviceId, days, e);
}
}
return result;
}
private void convertTs(Map<String, Object> row) {
Object tsObj = row.get("ts");
if (tsObj instanceof Timestamp) {
row.put("ts",
((Timestamp) tsObj)
.toLocalDateTime());
}
}
} }

@ -8,7 +8,12 @@ import java.util.Map;
@Data @Data
public class TimePointCache { public class TimePointCache {
// private LocalDateTime timestamp;
// private String queryData;
// private Map<Long, JSONObject> pointIndex;
private LocalDateTime timestamp; private LocalDateTime timestamp;
private String queryData;
private Map<Long, JSONObject> pointIndex; private Map<Long, Double> pointValueMap;
} }

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.enums.JavaToTdengineTypeEnum;
import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.HourEnergyValueVO; import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.HourEnergyValueVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO; import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO;
import cn.iocoder.yudao.module.iot.dal.mysql.devicecontactmodel.DeviceContactModelMapper; import cn.iocoder.yudao.module.iot.dal.mysql.devicecontactmodel.DeviceContactModelMapper;
@ -28,11 +29,13 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -252,53 +255,54 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
return energyDeviceMapper.selectBatchIds(ids); return energyDeviceMapper.selectBatchIds(ids);
} }
@Override // @Override
public List<EnergyDeviceRespVO> queryDataRecords(EnergyDeviceConsumptionReqVO req) { // public List<EnergyDeviceRespVO> queryDataRecords(EnergyDeviceConsumptionReqVO req) {
List<EnergyDeviceRespVO> result = new ArrayList<>(); // List<EnergyDeviceRespVO> result = new ArrayList<>();
//
// 查询设备列表 // // 查询设备列表
List<EnergyDeviceDO> devices = queryDevices(req); // List<EnergyDeviceDO> devices = queryDevices(req);
if (devices.isEmpty()) return result; // if (devices.isEmpty()) return result;
//
// 解析规则 & 收集所有 ruleDeviceIds 和点位ID // // 解析规则 & 收集所有 ruleDeviceIds 和点位ID
Map<Long, List<OperationRulesVO>> deviceRulesMap = new HashMap<>(); // Map<Long, List<OperationRulesVO>> deviceRulesMap = new HashMap<>();
Set<Long> ruleDeviceIds = new HashSet<>(); // Set<Long> ruleDeviceIds = new HashSet<>();
Set<Long> pointIds = new HashSet<>(); // Set<Long> pointIds = new HashSet<>();
for (EnergyDeviceDO device : devices) { // for (EnergyDeviceDO device : devices) {
List<OperationRulesVO> rules = parseRules(device); // List<OperationRulesVO> rules = parseRules(device);
if (!rules.isEmpty()) { // if (!rules.isEmpty()) {
deviceRulesMap.put(device.getId(), rules); // deviceRulesMap.put(device.getId(), rules);
rules.forEach(r -> { // rules.forEach(r -> {
if (r.getDeviceId() != null) ruleDeviceIds.add(r.getDeviceId()); // if (r.getDeviceId() != null) ruleDeviceIds.add(r.getDeviceId());
if (r.getPointId() != null) pointIds.add(r.getPointId()); // if (r.getPointId() != null) pointIds.add(r.getPointId());
}); // });
} // }
} // }
//
if (ruleDeviceIds.isEmpty()) return result; // if (ruleDeviceIds.isEmpty()) return result;
//
// // TDengine 查询所有设备首末数据
// Map<Long, DeviceEdgeData> edgeDataMap =
// tDengineService.queryDeviceFirstAndLast(ruleDeviceIds, req.getStartTime(), req.getEndTime());
//
// // 构建缓存
// Map<Long, TimePointCache> firstCache = buildCache(edgeDataMap, true);
// Map<Long, TimePointCache> lastCache = buildCache(edgeDataMap, false);
//
// // 批量获取点位信息(名称/单位)避免 N+1 查询
// Map<Long, DeviceContactModelDO> pointInfoMap = batchGetPointInfoFromLocalDB(new ArrayList<>(pointIds));
//
// // 逐设备计算 EnergyDeviceRespVO
// for (EnergyDeviceDO device : devices) {
// List<OperationRulesVO> rules = deviceRulesMap.get(device.getId());
// if (rules == null || rules.isEmpty()) continue;
//
// EnergyDeviceRespVO vo = calculateDevice(device, rules, firstCache, lastCache, pointInfoMap, req);
// result.add(vo);
// }
//
// return result;
// }
// TDengine 查询所有设备首末数据
Map<Long, DeviceEdgeData> edgeDataMap =
tDengineService.queryDeviceFirstAndLast(ruleDeviceIds, req.getStartTime(), req.getEndTime());
// 构建缓存
Map<Long, TimePointCache> firstCache = buildCache(edgeDataMap, true);
Map<Long, TimePointCache> lastCache = buildCache(edgeDataMap, false);
// 批量获取点位信息(名称/单位)避免 N+1 查询
Map<Long, DeviceContactModelDO> pointInfoMap = batchGetPointInfoFromLocalDB(new ArrayList<>(pointIds));
// 逐设备计算 EnergyDeviceRespVO
for (EnergyDeviceDO device : devices) {
List<OperationRulesVO> rules = deviceRulesMap.get(device.getId());
if (rules == null || rules.isEmpty()) continue;
EnergyDeviceRespVO vo = calculateDevice(device, rules, firstCache, lastCache, pointInfoMap, req);
result.add(vo);
}
return result;
}
@Override @Override
public List<HourEnergyValueVO> lastEnergyStatistics(Long deviceTypeId, Long orgId) { public List<HourEnergyValueVO> lastEnergyStatistics(Long deviceTypeId, Long orgId) {
@ -316,7 +320,6 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
return buildEmptyLast7Hours(LocalDateTime.now()); return buildEmptyLast7Hours(LocalDateTime.now());
} }
// 2. 解析规则 // 2. 解析规则
List<OperationRulesVO> rules = JsonUtils.parseArray(energyDevice.getRules(), OperationRulesVO.class); List<OperationRulesVO> rules = JsonUtils.parseArray(energyDevice.getRules(), OperationRulesVO.class);
if (rules.isEmpty()) return Collections.emptyList(); if (rules.isEmpty()) return Collections.emptyList();
@ -328,43 +331,86 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (deviceIds.isEmpty()) return Collections.emptyList(); if (deviceIds.isEmpty()) return Collections.emptyList();
// 4. 时间范围:最近 7 小时 // 4. 查询设备点位信息 DeviceContactModelDO
List<Long> pointIds = rules.stream()
.map(OperationRulesVO::getPointId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
Map<Long, DeviceContactModelDO> pointInfoMap = deviceContactModelMapper.selectList(
Wrappers.<DeviceContactModelDO>lambdaQuery().in(DeviceContactModelDO::getId, pointIds)
).stream().collect(Collectors.toMap(DeviceContactModelDO::getId, Function.identity()));
// 5. 时间范围:最近 7 小时
LocalDateTime end = LocalDateTime.now(); LocalDateTime end = LocalDateTime.now();
LocalDateTime start = end.minusHours(7); LocalDateTime start = end.minusHours(7);
DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH"); DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH");
// 5. 批量查询所有设备数据 // 6. 批量查询 TDengine 数据
List<Map<String, Object>> allRows = tDengineService.queryLastDataByHourBatch(deviceIds, start, end); List<Map<String, Object>> allRows = tDengineService.newQueryLastDataByHourBatch(deviceIds, start, end);
// 6. 按小时 + 设备构建缓存(每小时最新覆盖) // 7. 按小时 + 设备构建缓存
Map<String, Map<Long, TimePointCache>> hourCacheMap = new LinkedHashMap<>(); Map<String, Map<Long, TimePointCache>> hourCacheMap = new LinkedHashMap<>();
for (Map<String, Object> row : allRows) { for (Map<String, Object> row : allRows) {
LocalDateTime ts = (LocalDateTime) row.get("timestamp"); LocalDateTime ts = (LocalDateTime) row.get("timestamp");
if (ts == null) continue; if (ts == null) continue;
String hourKey = ts.format(hourFormatter); String hourKey = ts.format(hourFormatter);
Long deviceId = (Long) row.get("deviceId"); Long deviceId = (Long) row.get("deviceId");
String queryData = (String) row.get("queryData");
// 过滤该设备的规则
List<OperationRulesVO> deviceRules = rules.stream()
.filter(r -> deviceId.equals(r.getDeviceId()))
.collect(Collectors.toList());
TimePointCache cache = new TimePointCache(); TimePointCache cache = new TimePointCache();
cache.setTimestamp(ts); cache.setTimestamp(ts);
cache.setQueryData(queryData);
Map<Long, Double> pointValueMap = new HashMap<>();
for (OperationRulesVO rule : deviceRules) {
Long pointId = rule.getPointId();
DeviceContactModelDO pointInfo = pointInfoMap.get(pointId);
if (pointInfo == null || !deviceId.equals(pointInfo.getDeviceId())) continue;
String attributeCode = pointInfo.getAttributeCode();
if (attributeCode == null) continue;
Object value = row.get(attributeCode.toLowerCase());
if (value != null) {
try {
// 保留2位小数
BigDecimal bd = new BigDecimal(value.toString());
Double finalValue = bd.setScale(2, RoundingMode.HALF_UP).doubleValue();
pointValueMap.put(pointId, finalValue);
} catch (Exception e) {
log.warn("点位值转换失败, pointId={}, value={}, dataType={}", pointId, value, pointInfo.getDataType(), e);
}
}
}
cache.setPointValueMap(pointValueMap);
hourCacheMap.computeIfAbsent(hourKey, k -> new HashMap<>()).put(deviceId, cache); hourCacheMap.computeIfAbsent(hourKey, k -> new HashMap<>()).put(deviceId, cache);
} }
// 7. 生成每小时结果,补齐缺失小时 // 8. 生成每小时结果,补齐缺失小时
List<HourEnergyValueVO> result = new ArrayList<>(); List<HourEnergyValueVO> result = new ArrayList<>();
for (int i = 0; i < 7; i++) { for (int i = 0; i < 7; i++) {
LocalDateTime hourTime = start.plusHours(i); LocalDateTime hourTime = start.plusHours(i);
String hourKey = hourTime.format(hourFormatter); String hourKey = hourTime.format(hourFormatter);
Map<Long, TimePointCache> deviceCache = hourCacheMap.getOrDefault(hourKey, new HashMap<>()); Map<Long, TimePointCache> deviceCache = hourCacheMap.getOrDefault(hourKey, new HashMap<>());
Double value = calculateByRules(rules, deviceCache);
double total = 0.0;
for (Long deviceId : deviceIds) {
total += calculateByRules(deviceId, rules, deviceCache);
}
HourEnergyValueVO vo = new HourEnergyValueVO(); HourEnergyValueVO vo = new HourEnergyValueVO();
vo.setHour(hourKey); vo.setHour(hourKey);
vo.setValue(formatDouble(value)); vo.setValue(String.valueOf(total));
result.add(vo); result.add(vo);
} }
@ -373,6 +419,7 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
} }
private List<HourEnergyValueVO> buildEmptyLast7Hours(LocalDateTime end) { private List<HourEnergyValueVO> buildEmptyLast7Hours(LocalDateTime end) {
List<HourEnergyValueVO> result = new ArrayList<>(); List<HourEnergyValueVO> result = new ArrayList<>();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH");
@ -404,7 +451,7 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
@Override @Override
public List<DayEnergyValueVO> latestSevenDaysStatistics(Long deviceTypeId, Long orgId) { public List<DayEnergyValueVO> latestSevenDaysStatistics(Long deviceTypeId, Long orgId) {
// 找最新能耗设备配置 // 1. 找最新能耗设备
EnergyDeviceDO energyDevice = energyDeviceMapper.selectOne( EnergyDeviceDO energyDevice = energyDeviceMapper.selectOne(
Wrappers.<EnergyDeviceDO>lambdaQuery() Wrappers.<EnergyDeviceDO>lambdaQuery()
.eq(EnergyDeviceDO::getDeviceTypeId, deviceTypeId) .eq(EnergyDeviceDO::getDeviceTypeId, deviceTypeId)
@ -413,85 +460,260 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
.last("LIMIT 1") .last("LIMIT 1")
); );
if (energyDevice == null || StringUtils.isBlank(energyDevice.getRules())) { if (energyDevice == null || StringUtils.isBlank(energyDevice.getRules())) {
return buildEmptyLast7Days(LocalDate.now()); return buildEmptyLast7Days(LocalDate.now());
} }
// 解析规则 // 2. 解析规则
List<OperationRulesVO> rules = List<OperationRulesVO> rules = JsonUtils.parseArray(energyDevice.getRules(), OperationRulesVO.class);
JsonUtils.parseArray(energyDevice.getRules(), OperationRulesVO.class); if (rules.isEmpty()) return Collections.emptyList();
if (rules.isEmpty()) {
return Collections.emptyList();
}
// 收集 deviceId // 3. 收集设备列表
Set<Long> deviceIds = rules.stream() Set<Long> deviceIds = rules.stream()
.map(OperationRulesVO::getDeviceId) .map(OperationRulesVO::getDeviceId)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (deviceIds.isEmpty()) return Collections.emptyList();
if (deviceIds.isEmpty()) { // 4. 查询设备点位信息 DeviceContactModelDO
return Collections.emptyList(); List<Long> pointIds = rules.stream()
} .map(OperationRulesVO::getPointId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
Map<Long, DeviceContactModelDO> pointInfoMap = deviceContactModelMapper.selectList(
Wrappers.<DeviceContactModelDO>lambdaQuery().in(DeviceContactModelDO::getId, pointIds)
).stream().collect(Collectors.toMap(DeviceContactModelDO::getId, Function.identity()));
// 时间范围:前 7 天 // 5. 时间范围:最近 7 天
LocalDate startDate = LocalDate.now().minusDays(7); LocalDate startDate = LocalDate.now().minusDays(7);
int days = 7; int days = 7;
// 查询 TDengine方案 B // 6. 批量查询 TDengine 数据(按天)
List<Map<String, Object>> rows = List<Map<String, Object>> allRows = tDengineService.queryLastDataByDayBatch(deviceIds, startDate, days);
tDengineService.queryLastDataByDaySafe(deviceIds, startDate, days);
// 7. 构建按天 + 设备缓存
Map<String, Map<Long, TimePointCache>> dayCacheMap = new LinkedHashMap<>(); Map<String, Map<Long, TimePointCache>> dayCacheMap = new LinkedHashMap<>();
for (Map<String, Object> row : rows) { for (Map<String, Object> row : allRows) {
String day = (String) row.get("day"); String dayKey = (String) row.get("day");
Long deviceId = (Long) row.get("deviceId"); Long deviceId = (Long) row.get("deviceId");
LocalDateTime ts = (LocalDateTime) row.get("timestamp"); LocalDateTime ts = (LocalDateTime) row.get("timestamp");
if (dayKey == null || deviceId == null || ts == null) continue;
if (day == null || deviceId == null || ts == null) {
continue;
}
TimePointCache cache = new TimePointCache(); TimePointCache cache = new TimePointCache();
cache.setTimestamp(ts); cache.setTimestamp(ts);
cache.setQueryData((String) row.get("queryData"));
dayCacheMap Map<Long, Double> pointValueMap = new HashMap<>();
.computeIfAbsent(day, k -> new HashMap<>())
.put(deviceId, cache); // 设备对应规则
List<OperationRulesVO> deviceRules = rules.stream()
.filter(r -> deviceId.equals(r.getDeviceId()))
.collect(Collectors.toList());
for (OperationRulesVO rule : deviceRules) {
Long pointId = rule.getPointId();
DeviceContactModelDO pointInfo = pointInfoMap.get(pointId);
if (pointInfo == null || !deviceId.equals(pointInfo.getDeviceId())) continue;
String attributeCode = pointInfo.getAttributeCode();
if (attributeCode == null) continue;
Object value = row.get(attributeCode.toLowerCase());
if (value != null) {
try {
BigDecimal bd = new BigDecimal(value.toString());
pointValueMap.put(pointId, bd.setScale(2, RoundingMode.HALF_UP).doubleValue());
} catch (Exception e) {
log.warn("点位值转换失败, pointId={}, value={}, dataType={}", pointId, value, pointInfo.getDataType(), e);
}
}
} }
// 生成结果(补齐缺失日期) cache.setPointValueMap(pointValueMap);
List<DayEnergyValueVO> result = new ArrayList<>(); dayCacheMap.computeIfAbsent(dayKey, k -> new HashMap<>()).put(deviceId, cache);
}
// 8. 生成结果,补齐缺失日期
List<DayEnergyValueVO> result = new ArrayList<>();
for (int i = 0; i < days; i++) { for (int i = 0; i < days; i++) {
LocalDate day = startDate.plusDays(i); LocalDate day = startDate.plusDays(i);
String dayKey = day.toString(); String dayKey = day.toString();
Map<Long, TimePointCache> deviceCache = Map<Long, TimePointCache> deviceCache = dayCacheMap.getOrDefault(dayKey, Collections.emptyMap());
dayCacheMap.getOrDefault(dayKey, Collections.emptyMap());
Double value = calculateByRules(rules, deviceCache); double total = 0.0;
for (Long deviceId : deviceIds) {
total += calculateByRules(deviceId, rules, deviceCache);
}
DayEnergyValueVO vo = new DayEnergyValueVO(); DayEnergyValueVO vo = new DayEnergyValueVO();
vo.setDay(dayKey); vo.setDay(dayKey);
vo.setValue(formatDouble(value)); vo.setValue(String.valueOf(total));
result.add(vo);
}
return result;
}
@Override
public List<EnergyDeviceRespVO> queryDataRecords(EnergyDeviceConsumptionReqVO req) {
List<EnergyDeviceRespVO> result = new ArrayList<>();
// 1. 查询能耗设备列表
List<EnergyDeviceDO> devices = queryDevices(req);
if (devices.isEmpty()) {
return result;
}
// 2. 解析规则
Map<Long, List<OperationRulesVO>> deviceRulesMap = new HashMap<>();
List<Long> ruleDeviceIds = new ArrayList<>();
List<Long> pointIds = new ArrayList<>();
for (EnergyDeviceDO device : devices) {
List<OperationRulesVO> rules = parseRules(device);
if (rules.isEmpty()) {
continue;
}
deviceRulesMap.put(device.getId(), rules);
for (OperationRulesVO r : rules) {
if (r.getDeviceId() != null) {
ruleDeviceIds.add(r.getDeviceId());
}
if (r.getPointId() != null) {
pointIds.add(r.getPointId());
}
}
}
if (ruleDeviceIds.isEmpty()) {
return result;
}
// 3. 查询每个 device 最早数据
Map<Long, Map<String, Object>> firstRowMap = tDengineService.queryDevicesEarliestRow(ruleDeviceIds, req.getStartTime(), req.getEndTime());
// 4. 查询每个 device 最新数据
Map<Long, Map<String, Object>> lastRowMap = tDengineService.queryDevicesLatestRow(ruleDeviceIds, req.getStartTime(), req.getEndTime());
// 5. 查询点位信息(名称/单位)
Map<Long, DeviceContactModelDO> pointInfoMap = batchGetPointInfoFromLocalDB(new ArrayList<>(pointIds));
// 6. 构建缓存
Map<Long, TimePointCache> firstCache = buildCacheFromRow(devices, firstRowMap, pointInfoMap);
Map<Long, TimePointCache> lastCache = buildCacheFromRow(devices, lastRowMap, pointInfoMap);
// 7. 逐个能耗设备计算
for (EnergyDeviceDO device : devices) {
List<OperationRulesVO> rules = deviceRulesMap.get(device.getId());
if (rules == null || rules.isEmpty()) {
continue;
}
EnergyDeviceRespVO vo = calculateDevice(device, rules, firstCache, lastCache, pointInfoMap, req);
result.add(vo); result.add(vo);
} }
return result; return result;
}
private Map<Long, TimePointCache> buildCacheFromRow(
List<EnergyDeviceDO> devices,
Map<Long, Map<String, Object>> rowMap,
Map<Long, DeviceContactModelDO> pointInfoMap) {
Map<Long, TimePointCache> cacheMap = new HashMap<>();
if (devices == null || devices.isEmpty()) return cacheMap;
for (EnergyDeviceDO device : devices) {
List<OperationRulesVO> rules = parseRules(device);
if (rules == null || rules.isEmpty()) continue;
TimePointCache cache = new TimePointCache();
Map<Long, Double> valueMap = new HashMap<>();
// 时间戳变量
LocalDateTime timestamp = null;
// 遍历规则
for (OperationRulesVO rule : rules) {
Long ruleDeviceId = rule.getDeviceId();
Long pointId = rule.getPointId();
if (ruleDeviceId == null || pointId == null)
continue;
DeviceContactModelDO pointInfo = pointInfoMap.get(pointId);
if (pointInfo == null)
continue;
// 规则设备和点位设备匹配
if (!ruleDeviceId.equals(pointInfo.getDeviceId()))
continue;
String attributeCode = pointInfo.getAttributeCode();
if (attributeCode == null)
continue;
// 从 TDengine 数据里取值
Map<String, Object> row = rowMap.get(ruleDeviceId);
if (row == null)
continue;
// 设置 timestamp只需要一次
if (timestamp == null) {
Object ts = row.get("ts");
if (ts instanceof LocalDateTime) {
timestamp = (LocalDateTime) ts;
}
}
Object value = row.get(attributeCode);
if (value != null) {
try {
//计算类型统一转换为通用double类型
Object converted = JavaToTdengineTypeEnum.convertValue(
pointInfo.getDataType(),
value.toString()
);
Double finalValue = convertToDouble(converted);
valueMap.put(pointId, finalValue);
} catch (Exception e) {
log.warn("点位值转换失败, pointId={}, value={}, dataType={}", pointId, value, pointInfo.getDataType(), e);
}
}
}
// 设置 timestamp
cache.setTimestamp(timestamp);
cache.setPointValueMap(valueMap);
cacheMap.put(device.getId(), cache);
} }
return cacheMap;
}
// =================== 核心方法 =================== // =================== 核心方法 ===================
@ -521,17 +743,7 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
} }
} }
/** 构建缓存 */
private Map<Long, TimePointCache> buildCache(Map<Long, DeviceEdgeData> dataMap, boolean first) {
Map<Long, TimePointCache> cache = new HashMap<>();
for (DeviceEdgeData d : dataMap.values()) {
TimePointCache c = new TimePointCache();
c.setTimestamp(first ? d.getFirstTs() : d.getLastTs());
c.setQueryData(first ? d.getFirstData() : d.getLastData());
cache.put(d.getDeviceId(), c);
}
return cache;
}
/** 单设备计算 */ /** 单设备计算 */
private EnergyDeviceRespVO calculateDevice(EnergyDeviceDO device, private EnergyDeviceRespVO calculateDevice(EnergyDeviceDO device,
@ -541,8 +753,9 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
Map<Long, DeviceContactModelDO> pointInfoMap, Map<Long, DeviceContactModelDO> pointInfoMap,
EnergyDeviceConsumptionReqVO req) { EnergyDeviceConsumptionReqVO req) {
Double firstTotal = calculateByRules(rules, firstCache); Double firstTotal = calculateByRules(device.getId(), rules, firstCache);
Double lastTotal = calculateByRules(rules, lastCache); Double lastTotal = calculateByRules(device.getId(), rules, lastCache);
EnergyDeviceRespVO vo = new EnergyDeviceRespVO(); EnergyDeviceRespVO vo = new EnergyDeviceRespVO();
vo.setId(device.getId()); vo.setId(device.getId());
@ -559,138 +772,144 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
vo.setLatestDataTime(req.getEndTime()); vo.setLatestDataTime(req.getEndTime());
// 构建点位详情 // 构建点位详情
vo.setPointDetails(buildPointDetails(rules, firstCache, lastCache, pointInfoMap)); vo.setPointDetails(buildPointDetails(device.getId(), rules, firstCache, lastCache, pointInfoMap));
return vo; return vo;
} }
/** 根据规则计算总值 */ /** 根据规则计算总值 */
private Double calculateByRules(List<OperationRulesVO> rules, Map<Long, TimePointCache> cache) { private Double calculateByRules(
Double total = null; Long energyDeviceId,
List<OperationRulesVO> rules,
Map<Long, TimePointCache> cache) {
TimePointCache c = cache.get(energyDeviceId);
if (c == null) {
return 0.0;
}
BigDecimal total = null;
String lastOp = "+"; String lastOp = "+";
for (OperationRulesVO r : rules) { for (OperationRulesVO r : rules) {
TimePointCache c = cache.get(r.getDeviceId());
if (c == null) continue;
Double value = getPointValue(c, r.getPointId()); Double value = getPointValue(c, r.getPointId());
if (value == null) continue; if (value == null) continue;
BigDecimal bdValue = BigDecimal.valueOf(value);
if (total == null) { if (total == null) {
total = value; total = bdValue.setScale(2, RoundingMode.HALF_UP);
} else { } else {
total = applyOperator(total, value, StringUtils.defaultIfBlank(r.getOperator(), lastOp)); total = applyOperator(total, bdValue, StringUtils.defaultIfBlank(r.getOperator(), lastOp));
} }
lastOp = r.getOperator(); lastOp = r.getOperator();
} }
return total != null ? total : 0.0; return total != null ? total.doubleValue() : 0.0;
} }
/** 延迟解析 JSON 获取点位值 */ /** 延迟解析 JSON 获取点位值 */
private Double getPointValue(TimePointCache cache, Long pointId) { private Double getPointValue(
if (cache == null || pointId == null) return null; TimePointCache cache,
if (cache.getPointIndex() == null) { Long pointId) {
cache.setPointIndex(buildPointIndex(cache.getQueryData()));
}
JSONObject obj = cache.getPointIndex().get(pointId);
if (obj == null) return null;
Object v = obj.get("addressValue"); if (cache == null) {
if (v instanceof Number) return ((Number) v).doubleValue();
try {
return Double.parseDouble(String.valueOf(v));
} catch (Exception e) {
return null; return null;
} }
}
/** 构建 JSON 点位索引 */ Map<Long, Double> map =
private Map<Long, JSONObject> buildPointIndex(String jsonData) { cache.getPointValueMap();
Map<Long, JSONObject> index = new HashMap<>();
if (StringUtils.isBlank(jsonData)) return index;
Object obj = JSON.parse(jsonData); if (map == null) {
JSONArray array; return null;
if (obj instanceof JSONArray) {
array = (JSONArray) obj;
} else if (obj instanceof String) {
// 外层字符串 -> 再解析一次
array = JSON.parseArray((String) obj);
} else {
array = new JSONArray();
} }
return map.get(pointId);
for (int i = 0; i < array.size(); i++) {
JSONObject jsonObject = array.getJSONObject(i);
Long id = jsonObject.getLong("id");
if (id != null) index.put(id, jsonObject);
}
return index;
} }
/** 应用运算符 */ /** 应用运算符 */
private Double applyOperator(Double current, Double value, String operator) { private BigDecimal applyOperator(BigDecimal current, BigDecimal value, String operator) {
if (current == null || value == null || StringUtils.isBlank(operator)) return current; if (current == null || value == null || StringUtils.isBlank(operator)) return current;
switch (operator.trim()) { switch (operator.trim()) {
case "+": return current + value; case "+":
case "-": return current - value; return current.add(value).setScale(2, RoundingMode.HALF_UP);
case "*": return current * value; case "-":
case "/": return Math.abs(value) > 1e-6 ? current / value : current; return current.subtract(value).setScale(2, RoundingMode.HALF_UP);
default: return current + value; case "*":
return current.multiply(value).setScale(2, RoundingMode.HALF_UP);
case "/":
return value.abs().compareTo(BigDecimal.valueOf(1e-6)) > 0
? current.divide(value, 2, RoundingMode.HALF_UP)
: current;
default:
return current.add(value).setScale(2, RoundingMode.HALF_UP);
} }
} }
/** 构建点位详情列表并使用本地数据库批量获取点位信息 */ /** 构建点位详情列表并使用本地数据库批量获取点位信息 */
private List<PointDetailVO> buildPointDetails(List<OperationRulesVO> rules, private List<PointDetailVO> buildPointDetails(
Long energyDeviceId,
List<OperationRulesVO> rules,
Map<Long, TimePointCache> firstCache, Map<Long, TimePointCache> firstCache,
Map<Long, TimePointCache> lastCache, Map<Long, TimePointCache> lastCache,
Map<Long, DeviceContactModelDO> pointInfoMap) { Map<Long, DeviceContactModelDO> pointInfoMap) {
List<PointDetailVO> result = new ArrayList<>(); List<PointDetailVO> result = new ArrayList<>();
if (rules == null || rules.isEmpty()) return result; if (rules == null || rules.isEmpty()) return result;
TimePointCache first = firstCache.get(energyDeviceId);
TimePointCache last = lastCache.get(energyDeviceId);
for (OperationRulesVO r : rules) { for (OperationRulesVO r : rules) {
PointDetailVO p = new PointDetailVO(); PointDetailVO p = new PointDetailVO();
p.setDeviceId(r.getDeviceId()); p.setDeviceId(r.getDeviceId());
p.setPointId(r.getPointId()); p.setPointId(r.getPointId());
p.setOperator(r.getOperator()); p.setOperator(r.getOperator());
// ====== 这里是改动点 ======
TimePointCache first = firstCache.get(r.getDeviceId());
TimePointCache last = lastCache.get(r.getDeviceId());
Double firstVal = getPointValue(first, r.getPointId()); Double firstVal = getPointValue(first, r.getPointId());
Double lastVal = getPointValue(last, r.getPointId()); Double lastVal = getPointValue(last, r.getPointId());
p.setEarliestValue(formatDouble(firstVal)); p.setEarliestValue(formatDouble(firstVal));
p.setLatestValue(formatDouble(lastVal)); p.setLatestValue(formatDouble(lastVal));
p.setDifference(formatDouble( p.setDifference(formatDouble(
(lastVal != null ? lastVal : 0.0) - (firstVal != null ? firstVal : 0.0) (lastVal != null ? lastVal : 0.0)
- (firstVal != null ? firstVal : 0.0)
)); ));
if (first != null) { if (first != null)
p.setEarliestTime(formatToSecond(first.getTimestamp())); p.setEarliestTime(formatToSecond(first.getTimestamp()));
}
if (last != null) { if (last != null)
p.setLatestTime(formatToSecond(last.getTimestamp())); p.setLatestTime(formatToSecond(last.getTimestamp()));
}
// 从本地数据库获取点位名称和单位
DeviceContactModelDO pointInfo = pointInfoMap.get(r.getPointId()); DeviceContactModelDO pointInfo = pointInfoMap.get(r.getPointId());
if (pointInfo != null) { if (pointInfo != null) {
p.setPointName(StringUtils.isNotBlank(pointInfo.getAttributeName())
p.setPointName(
StringUtils.isNotBlank(pointInfo.getAttributeName())
? pointInfo.getAttributeName() ? pointInfo.getAttributeName()
: pointInfo.getAttributeCode()); : pointInfo.getAttributeCode()
);
p.setUnit(pointInfo.getDataUnit()); p.setUnit(pointInfo.getDataUnit());
} }
result.add(p); result.add(p);
} }
return result; return result;
} }
private String formatToSecond(Object timestamp) { private String formatToSecond(Object timestamp) {
if (timestamp == null) { if (timestamp == null) {
return ""; return "";
@ -741,7 +960,13 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService {
} }
private Double convertToDouble(Object value) {
if (value == null) return null;
if (value instanceof Number) return ((Number) value).doubleValue();
if (value instanceof Boolean) return (Boolean) value ? 1.0 : 0.0;
if (value instanceof Character) return (double) ((Character) value);
return Double.valueOf(value.toString());
}
} }
Loading…
Cancel
Save