diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/DeviceEdgeData.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/DeviceEdgeData.java new file mode 100644 index 0000000000..fb7f8e0586 --- /dev/null +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/pojo/DeviceEdgeData.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.framework.common.pojo; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class DeviceEdgeData { + private Long deviceId; + private LocalDateTime firstTs; + private String firstData; + private LocalDateTime lastTs; + private String lastData; +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java index 2bebb0a1d2..dca260bc7f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.infra.service.job.JobService; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*; @@ -37,7 +36,6 @@ import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; @Tag(name = "管理后台 - 物联设备") @RestController diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/coretask/DeviceTask.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/coretask/DeviceTask.java index c0791e532e..9caa15cab1 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/coretask/DeviceTask.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/coretask/DeviceTask.java @@ -22,7 +22,6 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.eclipse.milo.opcua.sdk.client.OpcUaClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -32,11 +31,6 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; @Component @Slf4j diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedeviceattribute/RecipeDeviceAttributeController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedeviceattribute/RecipeDeviceAttributeController.java index 290ed9c111..4bc2b0b375 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedeviceattribute/RecipeDeviceAttributeController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedeviceattribute/RecipeDeviceAttributeController.java @@ -9,7 +9,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Operation; -import javax.validation.constraints.*; import javax.validation.*; import javax.servlet.http.*; import java.util.*; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java index 31809123a0..d1316e36b3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java @@ -1,10 +1,10 @@ package cn.iocoder.yudao.module.iot.service.device; +import cn.iocoder.yudao.framework.common.pojo.DeviceEdgeData; +import com.alibaba.fastjson.JSON; import com.baomidou.dynamic.datasource.annotation.DS; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.taosdata.jdbc.utils.BlobUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; @@ -15,11 +15,11 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.nio.charset.StandardCharsets; -import java.sql.Blob; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.text.SimpleDateFormat; +import java.time.LocalDateTime; import java.util.*; @Service @@ -387,4 +387,145 @@ public class TDengineService { return Collections.singletonList(createEmptyResult(id)); } } + + /** + * 从 TDengine 中查询设备边缘数据 + * + * 说明: + * 1. TDengine 中每个设备对应一张子表,表名规则:d_{deviceId} + * 2. 根据时间范围查询 + * 3. 根据 latest 参数决定取最新一条还是最早一条 + * + * @param deviceId 设备ID,同时也是子表后缀 + * @param startTime 起始时间(可以为 null) + * @param endTime 结束时间(可以为 null) + * @param latest true 表示取最新一条,false 表示取最早一条 + * @return 查询结果,包含 timestamp / deviceId / queryData + */ + @DS("tdengine") + public Map getDeviceEdgeData( + Long deviceId, + String startTime, + String endTime, + boolean latest) { + + // TDengine 子表名:d_设备ID + String tableName = "d_" + deviceId; + + // 构建 SQL + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ts, query_data ") + .append("FROM besure.").append(tableName) + .append(" WHERE 1=1 "); + + // 起始时间条件 + if (startTime != null) { + sql.append(" AND ts >= '").append(startTime).append("' "); + } + + // 结束时间条件 + if (endTime != null) { + sql.append(" AND ts <= '").append(endTime).append("' "); + } + + // 根据 latest 决定排序方式 + // latest = true -> 按时间倒序,取最新一条 + // latest = false -> 按时间正序,取最早一条 + sql.append(" ORDER BY ts ") + .append(latest ? "DESC" : "ASC") + .append(" LIMIT 1"); + + try { + return jdbcTemplate.query(sql.toString(), rs -> { + + // 没有数据直接返回 null + if (!rs.next()) { + return null; + } + + Map result = new HashMap<>(); + result.put("timestamp", rs.getTimestamp("ts")); + result.put("deviceId", deviceId); + + // 读取 query_data(二进制字段) + byte[] blob = rs.getBytes("query_data"); + if (blob != null) { + // 转为字符串 + String json = new String(blob, StandardCharsets.UTF_8).trim(); + + // 去除 TDengine 中可能存在的外层双引号 + if (json.startsWith("\"") && json.endsWith("\"")) { + json = json.substring(1, json.length() - 1); + } + + // 如果是十六进制字符串,先解码 + if (isHexString(json)) { + json = hexToString(json); + } + + // 统一约定:queryData 始终返回 String,由上层决定是否解析 JSON + result.put("queryData", json); + } else { + // 没有数据时返回空数组字符串 + result.put("queryData", "[]"); + } + + return result; + }); + } catch (Exception e) { + log.error("TDengine 查询失败,deviceId={} ,td表不存在", deviceId); + return null; + } + } + + + /** + * 批量获取设备在时间区间的首末数据 + * @param deviceIds 设备ID列表 + * @param startTime 开始时间, 格式 yyyy-MM-dd HH:mm:ss + * @param endTime 结束时间, 格式 yyyy-MM-dd HH:mm:ss + * @return Map + */ + @DS("tdengine") + public Map queryDeviceFirstAndLast(Set deviceIds, String startTime, String endTime) { + if (deviceIds == null || deviceIds.isEmpty()) { + return Collections.emptyMap(); + } + + Map result = new HashMap<>(); + + for (Long deviceId : deviceIds) { + try { + // 查询首条数据 + List> firstList = getstDeviceDataOrderByTimeDesc(deviceId, startTime, endTime, 1); + Map firstData = firstList.isEmpty() ? null : firstList.get(0); + + // 查询末条数据 + List> lastList = getstDeviceDataOrderByTimeDesc(deviceId, startTime, endTime, 1); + Map lastData = lastList.isEmpty() ? null : lastList.get(0); + + DeviceEdgeData edgeData = new DeviceEdgeData(); + edgeData.setDeviceId(deviceId); + + if (firstData != null) { + edgeData.setFirstTs(((Timestamp) firstData.get("timestamp")).toLocalDateTime()); + edgeData.setFirstData(JSON.toJSONString(firstData.get("queryData"))); + } + + if (lastData != null) { + edgeData.setLastTs(((Timestamp) lastData.get("timestamp")).toLocalDateTime()); + edgeData.setLastData(JSON.toJSONString(lastData.get("queryData"))); + } + + result.put(deviceId, edgeData); + } catch (Exception e) { + log.error("查询设备首末数据失败, deviceId={}", deviceId, e); + } + } + + return result; + } + + + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicemodelattribute/DeviceModelAttributeServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicemodelattribute/DeviceModelAttributeServiceImpl.java index 2b0216e649..ec751d1b4c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicemodelattribute/DeviceModelAttributeServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicemodelattribute/DeviceModelAttributeServiceImpl.java @@ -1,8 +1,6 @@ package cn.iocoder.yudao.module.iot.service.devicemodelattribute; -import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattributetype.DeviceAttributeTypeDO; -import cn.iocoder.yudao.module.iot.dal.dataobject.devicemodel.DeviceModelDO; import cn.iocoder.yudao.module.iot.dal.mysql.deviceattributetype.DeviceAttributeTypeMapper; import cn.iocoder.yudao.module.iot.service.device.TDengineService; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -16,7 +14,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; -import java.sql.Wrapper; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; @@ -24,7 +21,6 @@ import java.util.stream.Collectors; import cn.iocoder.yudao.module.iot.controller.admin.devicemodelattribute.vo.*; import cn.iocoder.yudao.module.iot.dal.dataobject.devicemodelattribute.DeviceModelAttributeDO; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.iot.dal.mysql.devicemodelattribute.DeviceModelAttributeMapper; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedeviceattribute/RecipeDeviceAttributeServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedeviceattribute/RecipeDeviceAttributeServiceImpl.java index 44fb64b178..227719a121 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedeviceattribute/RecipeDeviceAttributeServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedeviceattribute/RecipeDeviceAttributeServiceImpl.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.iot.service.recipedeviceattribute; -import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattributetype.DeviceAttributeTypeDO; import cn.iocoder.yudao.module.iot.service.device.TDengineService; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -13,14 +12,11 @@ import javax.annotation.Resource; import org.springframework.validation.annotation.Validated; import org.springframework.transaction.annotation.Transactional; -import java.sql.Timestamp; -import java.text.SimpleDateFormat; import java.util.*; import cn.iocoder.yudao.module.iot.controller.admin.recipedeviceattribute.vo.*; import cn.iocoder.yudao.module.iot.dal.dataobject.recipedeviceattribute.RecipeDeviceAttributeDO; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.iot.dal.mysql.recipedeviceattribute.RecipeDeviceAttributeMapper; diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/TimePointCache.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/TimePointCache.java new file mode 100644 index 0000000000..827a19a104 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/TimePointCache.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo; + +import com.alibaba.fastjson.JSONObject; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +@Data +public class TimePointCache { + private LocalDateTime timestamp; + private String queryData; + private Map pointIndex; +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/taskmanagement/scheduled/coretask/GenerateWorkOrderTask.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/taskmanagement/scheduled/coretask/GenerateWorkOrderTask.java index 0db8c6c2d1..e7d8d66ee0 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/taskmanagement/scheduled/coretask/GenerateWorkOrderTask.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/taskmanagement/scheduled/coretask/GenerateWorkOrderTask.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.mes.controller.admin.taskmanagement.scheduled.co import cn.iocoder.yudao.module.iot.controller.admin.device.enums.TaskTypeEnum; import cn.iocoder.yudao.module.iot.controller.admin.device.scheduled.core.Task; -import cn.iocoder.yudao.module.iot.service.device.TDengineService; import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; import cn.iocoder.yudao.module.mes.dal.dataobject.dvsubject.DvSubjectDO; import cn.iocoder.yudao.module.mes.dal.dataobject.subjectplan.SubjectPlanDO; diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java index b050953926..ecc92e70dd 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/energydevice/EnergyDeviceServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.mes.service.energydevice; +import cn.iocoder.yudao.framework.common.pojo.DeviceEdgeData; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -25,9 +26,7 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; -import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.util.*; @@ -249,972 +248,253 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { } @Override - public List queryDataRecords(EnergyDeviceConsumptionReqVO deviceConsumptionReqVO) { + public List queryDataRecords(EnergyDeviceConsumptionReqVO req) { List result = new ArrayList<>(); - List energyDeviceDO = energyDeviceMapper.selectList( - Wrappers.lambdaQuery() - .in(StringUtils.isNotBlank(deviceConsumptionReqVO.getIds()), - EnergyDeviceDO::getId, - StringUtils.isNotBlank(deviceConsumptionReqVO.getIds()) - ? Arrays.asList(deviceConsumptionReqVO.getIds().split(",")) - : null) - .like(StringUtils.isNotBlank(deviceConsumptionReqVO.getName()), EnergyDeviceDO::getName, deviceConsumptionReqVO.getName()) - .eq(deviceConsumptionReqVO.getOrgId() != null, EnergyDeviceDO::getOrgId, deviceConsumptionReqVO.getOrgId()) - .orderByDesc(EnergyDeviceDO::getCreateTime)); - if (energyDeviceDO == null || energyDeviceDO.isEmpty()) { - throw exception(ENERGY_LIST_NOT_EXISTS); - } - - for (EnergyDeviceDO deviceDO : energyDeviceDO) { - if (StringUtils.isBlank(deviceDO.getRules())) { - continue; - } + // 查询设备列表 + List devices = queryDevices(req); + if (devices.isEmpty()) return result; - // 解析规则 - List operationRulesVOList = JSON.parseArray(deviceDO.getRules(), OperationRulesVO.class); - if (operationRulesVOList == null || operationRulesVOList.isEmpty()) { - continue; - } - - try { - // 计算设备结果 - EnergyDeviceRespVO deviceRespVO = buildEnergyDeviceRespVO(deviceDO, operationRulesVOList, deviceConsumptionReqVO.getStartTime(), deviceConsumptionReqVO.getEndTime()); - if (deviceRespVO != null) { - result.add(deviceRespVO); - } - } catch (Exception e) { - log.error("计算设备结果失败, deviceId: {}", deviceDO.getId(), e); + // 解析规则 & 收集所有 ruleDeviceIds 和点位ID + Map> deviceRulesMap = new HashMap<>(); + Set ruleDeviceIds = new HashSet<>(); + Set pointIds = new HashSet<>(); + for (EnergyDeviceDO device : devices) { + List rules = parseRules(device); + if (!rules.isEmpty()) { + deviceRulesMap.put(device.getId(), rules); + rules.forEach(r -> { + if (r.getDeviceId() != null) ruleDeviceIds.add(r.getDeviceId()); + if (r.getPointId() != null) pointIds.add(r.getPointId()); + }); } } - return result; - } + if (ruleDeviceIds.isEmpty()) return result; - /** - * 构建EnergyDeviceRespVO对象 - */ - private EnergyDeviceRespVO buildEnergyDeviceRespVO(EnergyDeviceDO deviceDO, - List rules, - String startTime, String endTime) { - if (deviceDO == null || rules == null || rules.isEmpty()) { - return null; - } - - EnergyDeviceRespVO respVO = new EnergyDeviceRespVO(); - - // 设置设备基本信息 - respVO.setId(deviceDO.getId()); - respVO.setName(deviceDO.getName()); - respVO.setCode(deviceDO.getCode()); - respVO.setInfo(deviceDO.getInfo()); - respVO.setCheckCron(deviceDO.getCheckCron()); - respVO.setLastCheckTime(deviceDO.getLastCheckTime()); - respVO.setLastCheckValue(deviceDO.getLastCheckValue()); - respVO.setUnitName(deviceDO.getUnitName()); - respVO.setIsEnable(deviceDO.getIsEnable()); - respVO.setCreateTime(deviceDO.getCreateTime()); - respVO.setUpdateTime(deviceDO.getUpdateTime()); - respVO.setDeviceTypeId(deviceDO.getDeviceTypeId()); - respVO.setDeviceTypeName(deviceDO.getDeviceTypeName()); - respVO.setOrgId(deviceDO.getOrgId()); - respVO.setOrgName(deviceDO.getOrgName()); - respVO.setRules(deviceDO.getRules()); - respVO.setOperationRulesVOList(rules); - - // 计算数据结果 - Map calculationResult = calculateDeviceData(deviceDO, rules, startTime, endTime); - if (calculationResult == null) { - return respVO; - } + // TDengine 查询所有设备首末数据 + Map edgeDataMap = + tDengineService.queryDeviceFirstAndLast(ruleDeviceIds, req.getStartTime(), req.getEndTime()); - // 设置能耗总用量差值 - Double totalDifference = (Double) calculationResult.get("totalDifference"); - if (totalDifference != null) { - respVO.setEnergyConsumption(formatDouble(totalDifference)); - } else { - respVO.setEnergyConsumption("0.0"); - } + // 构建缓存 + Map firstCache = buildCache(edgeDataMap, true); + Map lastCache = buildCache(edgeDataMap, false); - // 设置最新和最晚数据时间 - if (StringUtils.isNotBlank(startTime)) { - respVO.setEarliestDataTime(formatDateTime(startTime, "yyyy-MM-dd HH:mm")); - } - if (StringUtils.isNotBlank(endTime)) { - respVO.setLatestDataTime(formatDateTime(endTime, "yyyy-MM-dd HH:mm")); - } + // 批量获取点位信息(名称/单位)避免 N+1 查询 + Map pointInfoMap = batchGetPointInfoFromLocalDB(new ArrayList<>(pointIds)); - // 构建点位详情列表 - List pointDetails = buildPointDetails(calculationResult, deviceDO, rules); - respVO.setPointDetails(pointDetails); + // 逐设备计算 EnergyDeviceRespVO + for (EnergyDeviceDO device : devices) { + List rules = deviceRulesMap.get(device.getId()); + if (rules == null || rules.isEmpty()) continue; - return respVO; - } - - /** - * 格式化时间为指定格式 - */ - private String formatDateTime(String time, String pattern) { - if (StringUtils.isBlank(time)) { - return ""; - } - - try { - // 常见时间格式列表 - String[] possiblePatterns = { - "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mm:ss.SSS", - "yyyy-MM-dd'T'HH:mm:ss", - "yyyy-MM-dd'T'HH:mm:ss.SSS", - "yyyy/MM/dd HH:mm:ss", - "yyyy-MM-dd HH:mm" - }; - - for (String possiblePattern : possiblePatterns) { - try { - SimpleDateFormat sdf = new SimpleDateFormat(possiblePattern); - Date date = sdf.parse(time); - SimpleDateFormat targetFormat = new SimpleDateFormat(pattern); - return targetFormat.format(date); - } catch (Exception e) { - continue; - } - } - - // 如果解析失败,尝试简单截取 - if (time.length() >= 16) { - return time.substring(0, 16); - } - - } catch (Exception e) { - log.warn("时间格式化失败: {}", time, e); - } - - return time; - } - /** - * 从数据中获取时间 - */ - /** - * 从数据中获取时间,格式化为 yyyy-MM-dd HH:mm:ss - */ - private String getTimeFromData(Map calculationResult, String dataKey) { - if (calculationResult == null || dataKey == null) { - return ""; + EnergyDeviceRespVO vo = calculateDevice(device, rules, firstCache, lastCache, pointInfoMap, req); + result.add(vo); } - Map timeData = (Map) calculationResult.get(dataKey); - if (timeData == null) { - return ""; - } - - Object timestamp = timeData.get("timestamp"); - if (timestamp == null) { - return ""; - } - - String timeStr = timestamp.toString(); - - // 如果已经是目标格式的长度,直接返回 - if (timeStr.length() == 19) { // yyyy-MM-dd HH:mm:ss - return timeStr; - } - - // 如果有毫秒部分,截取前19位 - if (timeStr.length() > 19 && timeStr.charAt(19) == '.') { - return timeStr.substring(0, 19); - } - - // 如果是其他格式,尝试解析 - try { - // 移除毫秒部分 - if (timeStr.contains(".")) { - timeStr = timeStr.substring(0, timeStr.indexOf(".")); - } - - // 确保格式正确 - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - SimpleDateFormat originalSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - - try { - // 尝试用带毫秒的格式解析 - Date date = originalSdf.parse(timeStr); - return sdf.format(date); - } catch (Exception e) { - // 尝试不带毫秒解析 - try { - Date date = sdf.parse(timeStr); - return sdf.format(date); - } catch (Exception e2) { - // 如果不是标准格式,只做简单截取 - if (timeStr.length() > 19) { - return timeStr.substring(0, Math.min(19, timeStr.length())); - } - return timeStr; - } - } - } catch (Exception e) { - return timeStr; - } - } - - /** - * 从查询结果中提取特定点位的数据 - */ - private Map getPointData(Map dataMap, Long pointId) { - if (dataMap == null || pointId == null) { - return null; - } - - try { - Object queryDataObj = dataMap.get("queryData"); - if (queryDataObj == null) { - return null; - } - - String queryData = queryDataObj.toString(); - if (StringUtils.isBlank(queryData)) { - return null; - } - -// // 处理编码问题 -// queryData = fixEncoding(queryData); - - // 解析JSON数组 - JSONArray jsonArray = JSON.parseArray(queryData); - - for (int i = 0; i < jsonArray.size(); i++) { - JSONObject jsonObj = jsonArray.getJSONObject(i); - - // 安全获取id - Long dataPointId = null; - try { - dataPointId = jsonObj.getLong("id"); - } catch (Exception e) { - continue; - } - - if (dataPointId == null) { - continue; - } - - // 匹配点位ID - if (pointId.equals(dataPointId)) { - Map pointData = new HashMap<>(); - - // 提取所有需要的字段 - pointData.put("id", dataPointId); - - // 安全获取字段 - pointData.put("address", getJsonString(jsonObj, "address")); - - // 处理值 - Object value = jsonObj.get("addressValue"); - Double doubleValue = null; - if (value != null) { - try { - if (value instanceof Number) { - doubleValue = ((Number) value).doubleValue(); - } else { - doubleValue = Double.parseDouble(value.toString()); - } - } catch (NumberFormatException e) { - log.warn("点位值无法转换为数字: pointId={}, value={}", pointId, value); - doubleValue = null; - } - } - pointData.put("value", doubleValue); - pointData.put("addressValue", value); - - pointData.put("attributeCode", getJsonString(jsonObj, "attributeCode")); - pointData.put("attributeName", getJsonString(jsonObj, "attributeName")); - pointData.put("attributeType", getJsonString(jsonObj, "attributeType")); - pointData.put("dataType", getJsonString(jsonObj, "dataType")); - pointData.put("dataUnit", getJsonString(jsonObj, "dataUnit")); - - // 安全获取ratio - try { - Float ratio = jsonObj.getFloat("ratio"); - pointData.put("ratio", ratio); - } catch (Exception e) { - pointData.put("ratio", null); - } - - return pointData; - } - } - - } catch (Exception e) { - log.error("解析点位数据失败, pointId: {}", pointId, e); - } - - return null; - } - - /** - * 从JSON对象安全获取字符串 - */ - private String getJsonString(JSONObject jsonObj, String key) { - try { - return jsonObj.getString(key); - } catch (Exception e) { - return ""; - } + return result; } - /** - * 格式化double值为字符串 - */ - private String formatDouble(Double value) { - if (value == null) { - return "0.0"; - } +// =================== 核心方法 =================== - DecimalFormat df = new DecimalFormat("#.##"); - return df.format(value); + /** 查询设备列表 */ + private List queryDevices(EnergyDeviceConsumptionReqVO req) { + return energyDeviceMapper.selectList( + Wrappers.lambdaQuery() + .in(StringUtils.isNotBlank(req.getIds()), + EnergyDeviceDO::getId, + StringUtils.isNotBlank(req.getIds()) + ? Arrays.asList(req.getIds().split(",")) + : null) + .like(StringUtils.isNotBlank(req.getName()), EnergyDeviceDO::getName, req.getName()) + .eq(req.getOrgId() != null, EnergyDeviceDO::getOrgId, req.getOrgId()) + .orderByDesc(EnergyDeviceDO::getCreateTime) + ); } - /** - * 计算设备数据 - */ - private Map calculateDeviceData(EnergyDeviceDO deviceDO, - List rules, - String startTime, String endTime) { - if (deviceDO == null || rules == null || rules.isEmpty()) { - return null; - } - - Map result = new HashMap<>(); - + /** 解析设备规则 */ + private List parseRules(EnergyDeviceDO device) { + if (device == null || StringUtils.isBlank(device.getRules())) return Collections.emptyList(); try { - // 分别计算最新和最晚时间的数据 - Map latestData = getTimePointData(rules, startTime, endTime, true); - Map earliestData = getTimePointData(rules, startTime, endTime, false); - - if (latestData == null || earliestData == null) { - return result; - } - - // 提取实际采集时间 - String latestTime = extractTimestamp(latestData); - String earliestTime = extractTimestamp(earliestData); - - // 使用相同的规则计算两个时间点的总值 - Double latestTotal = calculateTotalByRules(latestData, rules); - Double earliestTotal = calculateTotalByRules(earliestData, rules); - - // 计算每个点位的差值 - List> pointDifferences = calculatePointDifferences(latestData, earliestData, rules); - - result.put("latestData", latestData); - result.put("earliestData", earliestData); - result.put("latestTime", latestTime); - result.put("earliestTime", earliestTime); - result.put("latestTotal", latestTotal != null ? latestTotal : 0.0); - result.put("earliestTotal", earliestTotal != null ? earliestTotal : 0.0); - result.put("totalDifference", (latestTotal != null && earliestTotal != null) ? - latestTotal - earliestTotal : 0.0); - result.put("pointDifferences", pointDifferences != null ? pointDifferences : Collections.emptyList()); - + return JSON.parseArray(device.getRules(), OperationRulesVO.class); } catch (Exception e) { - log.error("计算设备数据失败, deviceId: {}", deviceDO.getId(), e); - } - - return result; - } - - /** - * 从数据中提取时间戳 - */ - private String extractTimestamp(Map timePointData) { - if (timePointData == null) { - return ""; - } - - Object timestamp = timePointData.get("timestamp"); - if (timestamp != null) { - return timestamp.toString(); - } - - return ""; - } - - /** - * 从设备数据中获取点位值 - */ - private Double getPointValueFromDataByDevice(Map timePointData, Long deviceId, Long pointId) { - if (timePointData == null || deviceId == null || pointId == null) { - return null; + log.error("解析规则失败, deviceId={}", device.getId(), e); + return Collections.emptyList(); } - - String deviceKey = "device_" + deviceId; - Map deviceData = (Map) timePointData.get(deviceKey); - if (deviceData == null) { - return null; - } - - return getPointValueFromData(deviceData, pointId); } - /** - * 获取点位值 - */ - private Double getPointValueFromData(Map dataMap, Long pointId) { - if (dataMap == null || pointId == null) { - return null; - } - - Map pointData = getPointData(dataMap, pointId); - if (pointData != null) { - Object value = pointData.get("value"); - if (value instanceof Double) { - return (Double) value; - } else if (value instanceof Number) { - return ((Number) value).doubleValue(); - } + /** 构建缓存 */ + private Map buildCache(Map dataMap, boolean first) { + Map 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 null; + return cache; } - /** - * 获取指定时间点的数据 - */ - private Map getTimePointData(List rules, - String startTime, String endTime, - boolean isLatest) { - if (rules == null || rules.isEmpty()) { - return null; - } - - Map timePointData = new HashMap<>(); - String timestamp = null; + /** 单设备计算 */ + private EnergyDeviceRespVO calculateDevice(EnergyDeviceDO device, + List rules, + Map firstCache, + Map lastCache, + Map pointInfoMap, + EnergyDeviceConsumptionReqVO req) { - for (OperationRulesVO rule : rules) { - if (rule == null || rule.getDeviceId() == null) { - continue; - } - - List> maps = null; - try { - maps = tDengineService.getstDeviceDataOrderByTimeDesc( - rule.getDeviceId(), startTime, endTime,null); - } catch (Exception e) { - log.error("查询设备数据失败, deviceId: {}", rule.getDeviceId(), e); - continue; - } - - if (maps == null || maps.isEmpty()) { - continue; - } - - Map dataMap = null; - try { - if (isLatest) { - dataMap = maps.get(0); - } else { - dataMap = maps.get(maps.size() - 1); - } - } catch (Exception e) { - log.error("获取时间点数据失败, deviceId: {}, isLatest: {}", rule.getDeviceId(), isLatest, e); - continue; - } - - if (dataMap == null) { - continue; - } + Double firstTotal = calculateByRules(rules, firstCache); + Double lastTotal = calculateByRules(rules, lastCache); - if (timestamp == null && dataMap.containsKey("timestamp")) { - Object tsObj = dataMap.get("timestamp"); - if (tsObj != null) { - timestamp = tsObj.toString(); - } - } + EnergyDeviceRespVO vo = new EnergyDeviceRespVO(); + vo.setId(device.getId()); + vo.setName(device.getName()); + vo.setCode(device.getCode()); + vo.setOrgId(device.getOrgId()); + vo.setOrgName(device.getOrgName()); + vo.setCreateTime(device.getCreateTime()); + vo.setUpdateTime(device.getUpdateTime()); + vo.setRules(device.getRules()); - String deviceKey = "device_" + rule.getDeviceId(); - if (!timePointData.containsKey(deviceKey)) { - timePointData.put(deviceKey, dataMap); - } - } + vo.setEnergyConsumption(formatDouble(lastTotal - firstTotal)); + vo.setEarliestDataTime(req.getStartTime()); + vo.setLatestDataTime(req.getEndTime()); - if (timestamp != null) { - timePointData.put("timestamp", timestamp); - } else { - timePointData.put("timestamp", ""); - } + // 构建点位详情 + vo.setPointDetails(buildPointDetails(rules, firstCache, lastCache, pointInfoMap)); - return timePointData.isEmpty() ? null : timePointData; + return vo; } - /** - * 根据规则计算总值 - */ - private Double calculateTotalByRules(Map timePointData, List rules) { - if (timePointData == null || timePointData.isEmpty() || rules == null || rules.isEmpty()) { - return 0.0; - } - + /** 根据规则计算总值 */ + private Double calculateByRules(List rules, Map cache) { Double total = null; - String lastOperator = null; + String lastOp = "+"; - for (int i = 0; i < rules.size(); i++) { - OperationRulesVO rule = rules.get(i); - if (rule == null || rule.getDeviceId() == null || rule.getPointId() == null) { - continue; - } + for (OperationRulesVO r : rules) { + TimePointCache c = cache.get(r.getDeviceId()); + if (c == null) continue; - String deviceKey = "device_" + rule.getDeviceId(); - Map deviceData = (Map) timePointData.get(deviceKey); - if (deviceData == null) { - continue; - } - - Double pointValue = getPointValueFromData(deviceData, rule.getPointId()); - if (pointValue == null) { - continue; - } + Double value = getPointValue(c, r.getPointId()); + if (value == null) continue; if (total == null) { - total = pointValue; + total = value; } else { - String operator = null; - if (rule.getOperator() != null) { - operator = rule.getOperator().trim(); - } - - if (StringUtils.isEmpty(operator)) { - operator = lastOperator; - } - - if (StringUtils.isNotEmpty(operator)) { - try { - total = applyOperator(total, pointValue, operator); - } catch (Exception e) { - log.error("应用运算符失败: current={}, value={}, operator={}", total, pointValue, operator, e); - total += pointValue; - } - } else { - total += pointValue; - } + total = applyOperator(total, value, StringUtils.defaultIfBlank(r.getOperator(), lastOp)); } - lastOperator = rule.getOperator(); + lastOp = r.getOperator(); } return total != null ? total : 0.0; } - /** - * 计算每个点位的差值 - */ - private List> calculatePointDifferences(Map latestData, - Map earliestData, - List rules) { - List> differences = new ArrayList<>(); - - if (rules == null || rules.isEmpty()) { - return differences; - } - - for (OperationRulesVO rule : rules) { - if (rule == null || rule.getDeviceId() == null || rule.getPointId() == null) { - continue; - } - - Map pointDiff = new HashMap<>(); - pointDiff.put("pointId", rule.getPointId()); - pointDiff.put("operator", rule.getOperator() != null ? rule.getOperator() : ""); - pointDiff.put("deviceId", rule.getDeviceId()); - - Double latestValue = getPointValueFromDataByDevice(latestData, rule.getDeviceId(), rule.getPointId()); - Double earliestValue = getPointValueFromDataByDevice(earliestData, rule.getDeviceId(), rule.getPointId()); - - pointDiff.put("latestValue", latestValue != null ? latestValue : 0.0); - pointDiff.put("earliestValue", earliestValue != null ? earliestValue : 0.0); - - if (latestValue != null && earliestValue != null) { - Double difference = latestValue - earliestValue; - pointDiff.put("difference", difference); - } else if (latestValue != null) { - pointDiff.put("difference", latestValue); - } else if (earliestValue != null) { - pointDiff.put("difference", -earliestValue); - } else { - pointDiff.put("difference", 0.0); - } - - differences.add(pointDiff); - } - - return differences; - } - - /** - * 应用运算符计算 - */ - private Double applyOperator(Double current, Double value, String operator) { - if (current == null || value == null || operator == null) { - return current; - } - - String op = operator.trim(); - switch (op) { - case "+": - return current + value; - case "-": - return current - value; - case "*": - return current * value; - case "/": - if (Math.abs(value) > 0.000001) { // 避免除零 - return current / value; - } else { - log.warn("除数不能为0: current={}, value={}", current, value); - return current; - } - default: - log.warn("不支持的操作符: {}, 使用加法", operator); - return current + value; - } - } - - /** - * 修复编码问题 - */ - private String fixEncoding(String str) { - if (StringUtils.isBlank(str)) { - return str; + /** 延迟解析 JSON 获取点位值 */ + private Double getPointValue(TimePointCache cache, Long pointId) { + if (cache == null || pointId == null) return null; + if (cache.getPointIndex() == null) { + cache.setPointIndex(buildPointIndex(cache.getQueryData())); } + JSONObject obj = cache.getPointIndex().get(pointId); + if (obj == null) return null; + Object v = obj.get("addressValue"); + if (v instanceof Number) return ((Number) v).doubleValue(); try { - // 尝试UTF-8解码 - byte[] bytes = str.getBytes(StandardCharsets.ISO_8859_1); - return new String(bytes, StandardCharsets.UTF_8); + return Double.parseDouble(String.valueOf(v)); } catch (Exception e) { - return str; + return null; } } + /** 构建 JSON 点位索引 */ + private Map buildPointIndex(String jsonData) { + Map index = new HashMap<>(); + if (StringUtils.isBlank(jsonData)) return index; - /** - * 构建点位详情列表 - * 包含:点位名称、最早值、最晚值、相差值、时间、单位等信息 - */ - private List buildPointDetails(Map calculationResult, - EnergyDeviceDO deviceDO, - List originalRules) { - List result = new ArrayList<>(); - - if (calculationResult == null || originalRules == null || originalRules.isEmpty()) { - return result; - } - - List> pointDifferences = (List>) calculationResult.get("pointDifferences"); - if (pointDifferences == null || pointDifferences.isEmpty()) { - return result; - } - - Map latestData = (Map) calculationResult.get("latestData"); - Map earliestData = (Map) calculationResult.get("earliestData"); - - // 收集所有点位ID - List allPointIds = new ArrayList<>(); - for (Map pointDiff : pointDifferences) { - Long pointId = (Long) pointDiff.get("pointId"); - if (pointId != null) { - allPointIds.add(pointId); - } - } - - // 批量从本地数据库获取点位信息 - Map pointInfoMap = batchGetPointInfoFromLocalDB(allPointIds); - - for (Map pointDiff : pointDifferences) { - Long pointId = (Long) pointDiff.get("pointId"); - Long deviceId = (Long) pointDiff.get("deviceId"); - - if (pointId == null) { - continue; - } - - // 获取点位详细信息 - PointDetailVO pointDetail = new PointDetailVO(); - pointDetail.setPointId(pointId); - pointDetail.setDeviceId(deviceId); - - // 设置运算符 - String operator = (String) pointDiff.get("operator"); - pointDetail.setOperator(operator != null ? operator : ""); - - // 从TD数据获取点位信息和实际采集时间 - setPointDataFromTD(pointDetail, latestData, earliestData, pointId, deviceId, pointDiff); - - // 如果没有从TD数据获取到点位名称,从本地数据库获取 - if (StringUtils.isBlank(pointDetail.getPointName())) { - setPointInfoFromLocalDB(pointDetail, pointInfoMap.get(pointId)); - } - - // 如果没有单位,尝试从本地数据库获取 - if (StringUtils.isBlank(pointDetail.getUnit())) { - setPointUnitFromLocalDB(pointDetail, pointInfoMap.get(pointId)); - } - - // 设置数值 - Double earliestValue = (Double) pointDiff.get("earliestValue"); - Double latestValue = (Double) pointDiff.get("latestValue"); - Double difference = (Double) pointDiff.get("difference"); - - pointDetail.setEarliestValue(formatDouble(earliestValue != null ? earliestValue : 0.0)); - pointDetail.setLatestValue(formatDouble(latestValue != null ? latestValue : 0.0)); - pointDetail.setDifference(formatDouble(difference != null ? difference : 0.0)); - - result.add(pointDetail); - } - - return result; - } - - /** - * 从TD数据库数据中获取点位详细信息 - */ - private void setPointDataFromTD(PointDetailVO pointDetail, - Map latestData, - Map earliestData, - Long pointId, - Long deviceId, - Map pointDiff) { - - // 获取最新数据的时间 - if (latestData != null) { - String deviceKey = "device_" + deviceId; - Map deviceData = (Map) latestData.get(deviceKey); - if (deviceData != null) { - Object timestamp = deviceData.get("timestamp"); - if (timestamp != null) { - pointDetail.setLatestTime(formatToFullTime(timestamp.toString())); - } - } + Object obj = JSON.parse(jsonData); + JSONArray array; + if (obj instanceof JSONArray) { + array = (JSONArray) obj; + } else if (obj instanceof String) { + // 外层字符串 -> 再解析一次 + array = JSON.parseArray((String) obj); + } else { + array = new JSONArray(); } - // 获取最早数据的时间 - if (earliestData != null) { - String deviceKey = "device_" + deviceId; - Map deviceData = (Map) earliestData.get(deviceKey); - if (deviceData != null) { - Object timestamp = deviceData.get("timestamp"); - if (timestamp != null) { - pointDetail.setEarliestTime(formatToFullTime(timestamp.toString())); - } - } - } - // 从最新数据获取点位名称和单位 - if (latestData != null) { - String deviceKey = "device_" + deviceId; - Map deviceData = (Map) latestData.get(deviceKey); - if (deviceData != null) { - Map pointData = getPointData(deviceData, pointId); - if (pointData != null) { - // 获取点位名称 - String pointName = (String) pointData.get("attributeName"); - if (StringUtils.isNotBlank(pointName)) { - pointDetail.setPointName(pointName); - } else { - pointName = (String) pointData.get("attributeCode"); - if (StringUtils.isNotBlank(pointName)) { - pointDetail.setPointName(pointName); - } - } - - // 获取单位 - String unit = (String) pointData.get("dataUnit"); - if (StringUtils.isNotBlank(unit)) { - pointDetail.setUnit(unit); - } - } - } + 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; } - /** - * 格式化为完整时间 yyyy-MM-dd HH:mm:ss - */ - private String formatToFullTime(String time) { - if (StringUtils.isBlank(time)) { - return ""; - } - - try { - String timeStr = time.trim(); - - // 1. 如果已经是 yyyy-MM-dd HH:mm:ss 格式 - if (timeStr.length() >= 19 && timeStr.charAt(4) == '-' && timeStr.charAt(10) == ' ') { - if (timeStr.length() == 19) { - return timeStr; // 直接返回 - } else { - return timeStr.substring(0, 19); // 截取前19位 - } - } - - // 2. ISO格式: 2024-01-15T14:30:00.000+08:00 - if (timeStr.contains("T")) { - timeStr = timeStr.replace("T", " "); - int dotIndex = timeStr.indexOf("."); - if (dotIndex > 0) { - timeStr = timeStr.substring(0, dotIndex); - } else { - int plusIndex = Math.min(timeStr.indexOf("+"), timeStr.indexOf("-", 11)); - if (plusIndex > 0) { - timeStr = timeStr.substring(0, plusIndex); - } else if (timeStr.contains("Z")) { - timeStr = timeStr.substring(0, timeStr.indexOf("Z")); - } - } - return timeStr.length() >= 19 ? timeStr.substring(0, 19) : timeStr; - } - - // 3. 尝试解析 - String[] patterns = { - "yyyy-MM-dd HH:mm:ss", - "yyyy-MM-dd HH:mm:ss.SSS", - "yyyy-MM-dd'T'HH:mm:ss", - "yyyy-MM-dd'T'HH:mm:ss.SSS", - "yyyy/MM/dd HH:mm:ss", - "yyyy/MM/dd HH:mm:ss.SSS" - }; - - for (String pattern : patterns) { - try { - SimpleDateFormat sdf = new SimpleDateFormat(pattern); - Date date = sdf.parse(timeStr); - SimpleDateFormat targetFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - return targetFormat.format(date); - } catch (Exception e) { - continue; - } - } - - } catch (Exception e) { - log.warn("时间格式化失败: {}", time, e); + /** 应用运算符 */ + private Double applyOperator(Double current, Double value, String operator) { + if (current == null || value == null || StringUtils.isBlank(operator)) return current; + switch (operator.trim()) { + case "+": return current + value; + case "-": return current - value; + case "*": return current * value; + case "/": return Math.abs(value) > 1e-6 ? current / value : current; + default: return current + value; } - - return time; } - /** - * 从时间戳中提取时分秒 - */ - private String extractHMSFromTimestamp(String timestamp) { - if (StringUtils.isBlank(timestamp)) { - return ""; - } - - try { - String timeStr = timestamp.trim(); - // 处理多种格式 - // 1. 标准格式: yyyy-MM-dd HH:mm:ss - if (timeStr.length() >= 19) { - return timeStr.substring(11, 19); // 提取 HH:mm:ss - } - - // 2. ISO格式: 2024-01-15T14:30:00.000+08:00 - if (timeStr.contains("T")) { - int tIndex = timeStr.indexOf("T"); - int dotIndex = timeStr.indexOf("."); - if (dotIndex > tIndex) { - return timeStr.substring(tIndex + 1, dotIndex); - } else { - int plusIndex = timeStr.indexOf("+"); - if (plusIndex > tIndex) { - return timeStr.substring(tIndex + 1, plusIndex); - } else if (timeStr.contains("Z") && timeStr.indexOf("Z") > tIndex) { - int zIndex = timeStr.indexOf("Z"); - return timeStr.substring(tIndex + 1, zIndex); - } - } - } + /** 构建点位详情列表并使用本地数据库批量获取点位信息 */ + private List buildPointDetails(List rules, + Map firstCache, + Map lastCache, + Map pointInfoMap) { + List result = new ArrayList<>(); + if (rules == null || rules.isEmpty()) return result; - // 3. 如果是 HH:mm:ss 格式 - if (timeStr.matches("\\d{1,2}:\\d{1,2}:\\d{1,2}")) { - return timeStr; - } + for (OperationRulesVO r : rules) { + PointDetailVO p = new PointDetailVO(); + p.setDeviceId(r.getDeviceId()); + p.setPointId(r.getPointId()); + p.setOperator(r.getOperator()); - // 4. 尝试解析 - try { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - Date date = sdf.parse(timeStr); - SimpleDateFormat hmsFormat = new SimpleDateFormat("HH:mm:ss"); - return hmsFormat.format(date); - } catch (Exception e) { - // 继续尝试其他格式 - } + Double firstVal = getPointValue(firstCache.get(r.getDeviceId()), r.getPointId()); + Double lastVal = getPointValue(lastCache.get(r.getDeviceId()), r.getPointId()); + p.setEarliestValue(formatDouble(firstVal)); + p.setLatestValue(formatDouble(lastVal)); + p.setDifference(formatDouble((lastVal != null ? lastVal : 0.0) - (firstVal != null ? firstVal : 0.0))); - try { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - Date date = sdf.parse(timeStr); - SimpleDateFormat hmsFormat = new SimpleDateFormat("HH:mm:ss"); - return hmsFormat.format(date); - } catch (Exception e) { - // 解析失败 + // 从本地数据库获取点位名称和单位 + DeviceContactModelDO pointInfo = pointInfoMap.get(r.getPointId()); + if (pointInfo != null) { + p.setPointName(StringUtils.isNotBlank(pointInfo.getAttributeName()) + ? pointInfo.getAttributeName() + : pointInfo.getAttributeCode()); + p.setUnit(pointInfo.getDataUnit()); } - } catch (Exception e) { - log.warn("时间格式解析失败: {}", timestamp, e); - } - - return timestamp; - } - - /** - * 从本地数据库获取点位信息 - */ - private void setPointInfoFromLocalDB(PointDetailVO pointDetail, DeviceContactModelDO pointInfo) { - if (pointDetail == null || pointInfo == null) { - return; - } - - if (StringUtils.isBlank(pointDetail.getPointName())) { - if (StringUtils.isNotBlank(pointInfo.getAttributeName())) { - pointDetail.setPointName(pointInfo.getAttributeName()); - } else if (StringUtils.isNotBlank(pointInfo.getAttributeCode())) { - pointDetail.setPointName(pointInfo.getAttributeCode()); - } else { - pointDetail.setPointName("点位" + pointDetail.getPointId()); - } + result.add(p); } + return result; } - /** - * 从本地数据库获取点位单位 - */ - private void setPointUnitFromLocalDB(PointDetailVO pointDetail, DeviceContactModelDO pointInfo) { - if (pointDetail == null || pointInfo == null) { - return; - } - - if (StringUtils.isBlank(pointDetail.getUnit())) { - if (StringUtils.isNotBlank(pointInfo.getDataUnit())) { - pointDetail.setUnit(pointInfo.getDataUnit()); - } - } + /** 格式化 double */ + private String formatDouble(Double value) { + if (value == null) return "0.0"; + return new DecimalFormat("#.##").format(value); } - /** - * 批量获取点位信息(优化性能) - */ + /** 批量获取点位信息(本地数据库) */ private Map batchGetPointInfoFromLocalDB(List pointIds) { Map result = new HashMap<>(); - - if (pointIds == null || pointIds.isEmpty()) { - return result; - } + if (pointIds == null || pointIds.isEmpty()) return result; try { - List deviceContacts = deviceContactModelMapper.selectBatchIds(pointIds); - if (deviceContacts != null && !deviceContacts.isEmpty()) { - for (DeviceContactModelDO deviceContact : deviceContacts) { - if (deviceContact != null && deviceContact.getId() != null) { - result.put(deviceContact.getId(), deviceContact); - } + List contacts = deviceContactModelMapper.selectBatchIds(pointIds); + if (contacts != null) { + for (DeviceContactModelDO c : contacts) { + if (c != null && c.getId() != null) result.put(c.getId(), c); } } } catch (Exception e) { @@ -1224,78 +504,8 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { return result; } - /** - * 从TD数据库数据中获取点位名称 - */ - private String getPointNameFromTDData(Map timePointData, Long pointId, Map pointDiff) { - if (timePointData == null || pointId == null || pointDiff == null) { - return null; - } - - Long deviceId = (Long) pointDiff.get("deviceId"); - if (deviceId == null) { - return null; - } - - String deviceKey = "device_" + deviceId; - Map deviceData = (Map) timePointData.get(deviceKey); - if (deviceData == null) { - return null; - } - - Map pointData = getPointData(deviceData, pointId); - if (pointData == null) { - return null; - } - - String attributeName = (String) pointData.get("attributeName"); - if (StringUtils.isNotBlank(attributeName)) { - return attributeName; - } - - String attributeCode = (String) pointData.get("attributeCode"); - if (StringUtils.isNotBlank(attributeCode)) { - return attributeCode; - } - return null; - } - - /** - * 批量获取点位名称(优化性能) - */ - private Map batchGetPointNamesFromLocalDB(List pointIds) { - Map result = new HashMap<>(); - if (pointIds == null || pointIds.isEmpty()) { - return result; - } - try { - // 批量查询点位信息 - List deviceContacts = deviceContactModelMapper.selectBatchIds(pointIds); - if (deviceContacts != null && !deviceContacts.isEmpty()) { - for (DeviceContactModelDO deviceContact : deviceContacts) { - if (deviceContact == null || deviceContact.getId() == null) { - continue; - } - - String pointName = null; - if (StringUtils.isNotBlank(deviceContact.getAttributeName())) { - pointName = deviceContact.getAttributeName(); - } else if (StringUtils.isNotBlank(deviceContact.getAttributeCode())) { - pointName = deviceContact.getAttributeCode(); - } - - if (StringUtils.isNotBlank(pointName)) { - result.put(deviceContact.getId(), pointName); - } - } - } - } catch (Exception e) { - log.error("批量查询点位信息失败, pointIds: {}", pointIds, e); - } - return result; - } } \ No newline at end of file