From 685d224ac1d252ccbfe02d3c4326aa53a5a6acc9 Mon Sep 17 00:00:00 2001 From: HuangHuiKang Date: Thu, 15 Jan 2026 17:14:52 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=AE=8C=E6=88=90=E8=83=BD?= =?UTF-8?q?=E8=80=97=E6=8A=A5=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device/DeviceController.java | 14 + .../device/scheduled/CronExpressionUtil.java | 160 ++++++ .../admin/device/scheduled/DeviceTask.java | 39 ++ .../device/scheduled/SchedulerConfig.java | 23 + .../scheduled/TaskSchedulerManager.java | 141 ++++++ .../iot/service/device/DeviceService.java | 5 + .../iot/service/device/DeviceServiceImpl.java | 68 ++- .../iot/service/device/TDengineService.java | 2 +- .../admin/dvrepair/DvRepairController.java | 8 +- .../admin/dvsubject/DvSubjectController.java | 4 +- .../energydevice/EnergyDeviceController.java | 23 +- .../vo/EnergyDeviceConsumptionReqVO.java | 2 + .../vo/EnergyDevicePageReqVO.java | 4 + .../vo/EnergyDeviceRecordRespVO.java | 41 ++ .../energydevice/vo/EnergyDeviceRespVO.java | 42 +- .../admin/energydevice/vo/PointDetailVO.java | 60 +++ .../moldrepair/MoldRepairController.java | 8 +- .../moldsubject/MoldSubjectController.java | 4 +- .../energydevice/EnergyDeviceMapper.java | 22 +- .../energydevice/EnergyDeviceServiceImpl.java | 463 ++++++++++++++++-- 20 files changed, 1079 insertions(+), 54 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/CronExpressionUtil.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/DeviceTask.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/SchedulerConfig.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/TaskSchedulerManager.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceRecordRespVO.java create mode 100644 yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/PointDetailVO.java 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 90ca470c1b..4acfd8c8c4 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 @@ -237,4 +237,18 @@ public class DeviceController { } + @PostMapping("/scheduledStart") + public CommonResult scheduledStart(@RequestParam("id") Long id) { + return success(deviceService.scheduledStart(id)); + } + + + @PostMapping("/scheduledStop") + public CommonResult scheduledStop(@RequestParam("id") Long id) { + return success(deviceService.scheduledStop(id)); + } + + + + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/CronExpressionUtil.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/CronExpressionUtil.java new file mode 100644 index 0000000000..1b88e81ee4 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/CronExpressionUtil.java @@ -0,0 +1,160 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.scheduled; + +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +public class CronExpressionUtil { + + /** + * 将秒数转换为cron表达式 + * @param intervalSeconds 间隔秒数 + * @return cron表达式 + */ + public static String secondsToCron(Double intervalSeconds) { + if (intervalSeconds == null || intervalSeconds <= 0) { + throw new IllegalArgumentException("间隔秒数必须大于0"); + } + + long seconds = Math.round(intervalSeconds); + + // 如果秒数小于60,使用秒级定时 + if (seconds < 60) { + return String.format("0/%d * * * * ?", seconds); + } + + // 如果秒数在60-3599之间,使用分钟级定时 + else if (seconds < 3600) { + long minutes = seconds / 60; + if (seconds % 60 != 0) { + // 如果不是整分钟,需要特殊处理 + return String.format("0 */%d * * * ?", minutes); + } + return String.format("0 0/%d * * * ?", minutes); + } + + // 如果秒数在3600-86399之间,使用小时级定时 + else if (seconds < 86400) { + long hours = seconds / 3600; + if (seconds % 3600 != 0) { + // 如果不是整小时,转换为分钟 + long minutes = seconds / 60; + return String.format("0 0/%d * * * ?", minutes); + } + return String.format("0 0 0/%d * * ?", hours); + } + + // 如果大于等于1天,使用天级定时 + else { + long days = seconds / 86400; + if (seconds % 86400 != 0) { + // 如果不是整天,转换为小时 + long hours = seconds / 3600; + return String.format("0 0 0/%d * * ?", hours); + } + return String.format("0 0 0 */%d * ?", days); + } + } + + /** + * 将秒数转换为固定延迟的cron表达式(从0秒开始) + * @param intervalSeconds 间隔秒数 + * @return cron表达式 + */ + public String secondsToFixedRateCron(Double intervalSeconds) { + if (intervalSeconds == null || intervalSeconds <= 0) { + throw new IllegalArgumentException("间隔秒数必须大于0"); + } + + long seconds = Math.round(intervalSeconds); + + // 计算分钟和剩余秒数 + long minutes = seconds / 60; + long remainingSeconds = seconds % 60; + + long remainingMinutes; + if (minutes == 0) { + // 小于1分钟,只使用秒 + return String.format("*/%d * * * * ?", seconds); + } else if (minutes < 60) { + // 小于1小时 + if (remainingSeconds == 0) { + return String.format("0 */%d * * * ?", minutes); + } else { + // 有剩余秒数,需要更复杂的表达式 + return String.format("%d */%d * * * ?", remainingSeconds, minutes); + } + } else if (minutes < 1440) { // 小于24小时 + long hours = minutes / 60; + remainingMinutes = minutes % 60; + + if (remainingMinutes == 0 && remainingSeconds == 0) { + return String.format("0 0 */%d * * ?", hours); + } else if (remainingSeconds == 0) { + return String.format("0 %d */%d * * ?", remainingMinutes, hours); + } else { + return String.format("%d %d */%d * * ?", remainingSeconds, remainingMinutes, hours); + } + } else { + // 大于等于1天 + long days = minutes / 1440; + long remainingHours = (minutes % 1440) / 60; + remainingMinutes = minutes % 60; + + if (remainingHours == 0 && remainingMinutes == 0 && remainingSeconds == 0) { + return String.format("0 0 0 */%d * ?", days); + } else { + // 简化处理,转换为小时 + long totalHours = (days * 24) + remainingHours; + if (remainingMinutes == 0 && remainingSeconds == 0) { + return String.format("0 0 */%d * * ?", totalHours); + } else { + return String.format("%d %d */%d * * ?", + remainingSeconds, remainingMinutes, totalHours); + } + } + } + } + + /** + * 验证cron表达式是否有效 + * @param cronExpression cron表达式 + * @return 是否有效 + */ + public boolean isValidCron(String cronExpression) { + if (!StringUtils.hasText(cronExpression)) { + return false; + } + + try { + new CronTrigger(cronExpression); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * 获取友好的时间描述 + * @param intervalSeconds 间隔秒数 + * @return 时间描述 + */ + public String getIntervalDescription(Double intervalSeconds) { + if (intervalSeconds == null) { + return "未设置"; + } + + long seconds = Math.round(intervalSeconds); + + if (seconds < 60) { + return seconds + "秒"; + } else if (seconds < 3600) { + return (seconds / 60) + "分" + (seconds % 60) + "秒"; + } else if (seconds < 86400) { + return (seconds / 3600) + "小时" + ((seconds % 3600) / 60) + "分"; + } else { + return (seconds / 86400) + "天" + ((seconds % 86400) / 3600) + "小时"; + } + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/DeviceTask.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/DeviceTask.java new file mode 100644 index 0000000000..6569557047 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/DeviceTask.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.scheduled; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.text.SimpleDateFormat; +import java.util.Date; + +@Component +public class DeviceTask { + private static final Logger logger = LoggerFactory.getLogger(DeviceTask.class); + + /** + * 具体的设备执行逻辑 + * @param deviceId 设备ID + * @param deviceCode 设备编码 + */ + public void executeDeviceLogic(Long deviceId, String deviceCode) { + try { + // 创建时间格式化对象 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String currentTime = sdf.format(new Date()); + + logger.info("执行设备任务,设备ID: {}, 设备编码: {}, 时间: {}", + deviceId, deviceCode, currentTime); + + // TODO: 这里编写具体的设备执行逻辑 + // 比如:读取设备数据、发送指令、处理响应等 + + } catch (Exception e) { + // 异常信息中也加入时间戳 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String errorTime = sdf.format(new Date()); + + logger.error("执行设备任务异常,设备ID: {}, 异常时间: {}", + deviceId, errorTime, e); + } + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/SchedulerConfig.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/SchedulerConfig.java new file mode 100644 index 0000000000..b268dfd49e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/SchedulerConfig.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.scheduled; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +@Configuration +@EnableScheduling +public class SchedulerConfig { + + @Bean + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(20); // 设置线程池大小 + scheduler.setThreadNamePrefix("scheduled-task-"); // 线程名前缀 + scheduler.setAwaitTerminationSeconds(60); // 等待任务完成的秒数 + scheduler.setWaitForTasksToCompleteOnShutdown(true); // 关闭时等待任务完成 + scheduler.initialize(); // 初始化 + return scheduler; + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/TaskSchedulerManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/TaskSchedulerManager.java new file mode 100644 index 0000000000..8f54e10eeb --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/scheduled/TaskSchedulerManager.java @@ -0,0 +1,141 @@ +// TaskSchedulerManager.java +package cn.iocoder.yudao.module.iot.controller.admin.device.scheduled; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; + +@Component +public class TaskSchedulerManager { + private static final Logger logger = LoggerFactory.getLogger(TaskSchedulerManager.class); + + private TaskScheduler taskScheduler; // 移除 static + private DeviceTask deviceTask; // 移除 static + private final Map> taskMap = new ConcurrentHashMap<>(); + + /** + * 通过构造器注入 TaskScheduler + * Spring 会自动在上下文中查找 TaskScheduler Bean + */ + @Autowired + public TaskSchedulerManager(TaskScheduler taskScheduler) { + this.taskScheduler = taskScheduler; + } + + /** + * 注入 DeviceTask + */ + @Autowired + public void setDeviceTask(DeviceTask deviceTask) { + this.deviceTask = deviceTask; + } + + /** + * 如果 Spring 容器中没有 TaskScheduler Bean, + * 可以在这里创建一个默认的 + */ + @PostConstruct + public void init() { + if (taskScheduler == null) { + logger.warn("TaskScheduler not found in context, creating default one"); + ThreadPoolTaskScheduler defaultScheduler = new ThreadPoolTaskScheduler(); + defaultScheduler.setPoolSize(10); + defaultScheduler.setThreadNamePrefix("device-task-"); + defaultScheduler.initialize(); + this.taskScheduler = defaultScheduler; + } + } + + /** + * 启动设备定时任务 + * 移除 static 修饰符 + */ + public boolean startDeviceTask(Long deviceId, String deviceCode, String cronExpression) { + try { + // 先停止已存在的任务 + stopDeviceTask(deviceId); + + // 验证 DeviceTask + if (deviceTask == null) { + logger.error("DeviceTask is not initialized"); + return false; + } + + // 创建新的定时任务 + ScheduledFuture future = taskScheduler.schedule( + () -> { + try { + deviceTask.executeDeviceLogic(deviceId, deviceCode); + } catch (Exception e) { + logger.error("Device task execution failed for deviceId: {}", deviceId, e); + } + }, + new CronTrigger(cronExpression) + ); + + taskMap.put(deviceId, future); + logger.info("启动设备定时任务成功,设备ID: {}, cron表达式: {}", deviceId, cronExpression); + return true; + + } catch (Exception e) { + logger.error("启动设备定时任务失败,设备ID: {}", deviceId, e); + return false; + } + } + + /** + * 停止设备定时任务 + * 移除 static 修饰符 + */ + public boolean stopDeviceTask(Long deviceId) { + try { + ScheduledFuture future = taskMap.get(deviceId); + if (future != null) { + future.cancel(true); + taskMap.remove(deviceId); + logger.info("停止设备定时任务成功,设备ID: {}", deviceId); + return true; + } else { + logger.info("设备定时任务不存在,设备ID: {}", deviceId); + return false; + } + } catch (Exception e) { + logger.error("停止设备定时任务失败,设备ID: {}", deviceId, e); + return false; + } + } + + /** + * 检查设备定时任务是否在运行 + */ + public boolean isTaskRunning(Long deviceId) { + ScheduledFuture future = taskMap.get(deviceId); + return future != null && !future.isCancelled() && !future.isDone(); + } + + /** + * 获取任务数量 + */ + public int getTaskCount() { + return taskMap.size(); + } + + /** + * 停止所有任务 + */ + public void stopAllTasks() { + logger.info("停止所有定时任务,共 {} 个", taskMap.size()); + for (Long deviceId : taskMap.keySet()) { + stopDeviceTask(deviceId); + } + } +} \ 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/device/DeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java index a882bc6e11..d333084b3e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java @@ -126,4 +126,9 @@ public interface DeviceService { Map> createDeviceDataMap(Long deviceId); List devicePointList(); + + Boolean scheduledStart(Long id); + + Boolean scheduledStop(Long id); + } \ 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/device/DeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java index c91206f06d..c11170a9cc 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java @@ -6,6 +6,8 @@ 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.common.util.opc.OpcUtils; +import cn.iocoder.yudao.module.iot.controller.admin.device.scheduled.CronExpressionUtil; +import cn.iocoder.yudao.module.iot.controller.admin.device.scheduled.TaskSchedulerManager; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*; import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.mqttdatarecord.vo.MqttDataRecordPageReqVO; @@ -87,6 +89,8 @@ public class DeviceServiceImpl implements DeviceService { @Resource private DeviceAttributeTypeMapper deviceAttributeTypeMapper; + @Resource + private TaskSchedulerManager taskSchedulerManager; @Override @Transactional(rollbackFor = Exception.class) @@ -234,7 +238,7 @@ public class DeviceServiceImpl implements DeviceService { for (DeviceContactModelDO record : records) { Map data = deviceDataMap.get(record.getId()); if (data != null) { - record.setAddressValue(data.get("addressValue")); // 设置 addressValue + record.setAddressValue(adjustByRatio(data.get("addressValue"), record.getRatio())); // 设置 addressValue record.setLatestCollectionTime((String) data.get("timestamp")); // 设置 latestCollectionTime } } @@ -242,6 +246,18 @@ public class DeviceServiceImpl implements DeviceService { return deviceModelAttributeDOPageResult; } + private Object adjustByRatio(Object value, Double ratio) { + + if (value == null) return null; + + try { + Double result = Double.parseDouble(value.toString()); + return (ratio != null && ratio != 0.0) ? result / ratio : result; + } catch (NumberFormatException e) { + return value; // 转换失败返回原值 + } + } + @Override public Map> createDeviceDataMap(Long deviceId) { // 创建结果Map:键为数据记录ID (Long),值为该条记录的详细信息 (Map) @@ -350,6 +366,7 @@ public class DeviceServiceImpl implements DeviceService { } + @Override public Long createDeviceAttribute(DeviceAttributeDO deviceAttribute) { deviceAttributeMapper.insert(deviceAttribute); @@ -666,4 +683,53 @@ public class DeviceServiceImpl implements DeviceService { return deviceMapper.selectBatchIds(ids); } + + + + @Override + public Boolean scheduledStart(Long id) { + try { + + // 1. 查询设备信息 + DeviceDO deviceDO = deviceMapper.selectById(id); + if (deviceDO==null) { + throw new RuntimeException("设备不存在"); + } + if (deviceDO.getSampleCycle() == null) { + throw new RuntimeException("设备cron表达式为空"); + } + + String cronExpression = CronExpressionUtil.secondsToCron(deviceDO.getSampleCycle()); + + // 2. 启动定时任务 + boolean success = taskSchedulerManager.startDeviceTask( + deviceDO.getId(), + deviceDO.getDeviceCode(), + cronExpression + ); + + if (!success) { + throw new RuntimeException("启动定时任务失败"); + } + + return true; + + } catch (Exception e) { + throw new RuntimeException("连接设备失败: " + e.getMessage(), e); + } + } + + @Override + public Boolean scheduledStop(Long id) { + try { + // 1. 停止定时任务 + taskSchedulerManager.stopDeviceTask(id); + + return true; + + } catch (Exception e) { + throw new RuntimeException("断开设备失败: " + e.getMessage(), e); + } + } + } \ 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/device/TDengineService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java index e7214d358c..184f6dcbf2 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 @@ -377,7 +377,7 @@ public class TDengineService { } }); } catch (Exception e) { - log.error("查询设备" + id + "的最新数据时发生异常", e); +// log.error("查询设备" + id + "的最新数据时发生异常", e); // e.printStackTrace(); return Collections.singletonList(createEmptyResult(id)); } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dvrepair/DvRepairController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dvrepair/DvRepairController.java index 244d0cbd35..bec45badf0 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dvrepair/DvRepairController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dvrepair/DvRepairController.java @@ -135,11 +135,15 @@ public class DvRepairController { for (DvRepairRespVO dvSubjectRespVO : dvSubjectRespVOPageResult.getList()) { if (dvSubjectRespVO.getAcceptedBy() !=null) { AdminUserRespDTO user = adminUserApi.getUser(Long.valueOf(dvSubjectRespVO.getAcceptedBy())); - dvSubjectRespVO.setAcceptedBy("(" + user.getUsername() + ")" + user.getNickname()); + if (user !=null ){ + dvSubjectRespVO.setAcceptedBy("(" + user.getUsername() + ")" + user.getNickname()); + } } if (dvSubjectRespVO.getConfirmBy() !=null) { AdminUserRespDTO user = adminUserApi.getUser(Long.valueOf(dvSubjectRespVO.getConfirmBy())); - dvSubjectRespVO.setConfirmBy("(" + user.getUsername() + ")" + user.getNickname()); + if (user !=null){ + dvSubjectRespVO.setConfirmBy("(" + user.getUsername() + ")" + user.getNickname()); + } } } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dvsubject/DvSubjectController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dvsubject/DvSubjectController.java index 206d320d1c..5542541ad0 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dvsubject/DvSubjectController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dvsubject/DvSubjectController.java @@ -149,7 +149,9 @@ public class DvSubjectController { private PageResult buildCreatorName(PageResult dvSubjectRespVOPageResult) { for (DvSubjectRespVO dvSubjectRespVO : dvSubjectRespVOPageResult.getList()) { AdminUserRespDTO user = adminUserApi.getUser(Long.valueOf(dvSubjectRespVO.getCreator())); - dvSubjectRespVO.setCreatorName("(" + user.getUsername()+ ")" + user.getNickname()); + if (user!=null) { + dvSubjectRespVO.setCreatorName("(" + user.getUsername() + ")" + user.getNickname()); + } } return dvSubjectRespVOPageResult; diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java index e5f422db5a..a419a9b237 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/EnergyDeviceController.java @@ -6,10 +6,7 @@ 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.mes.controller.admin.energydevice.vo.EnergyDeviceConsumptionReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDevicePageReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDeviceRespVO; -import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.EnergyDeviceSaveReqVO; +import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.energydevice.EnergyDeviceCheckRecordDO; import cn.iocoder.yudao.module.mes.dal.dataobject.energydevice.EnergyDeviceDO; import cn.iocoder.yudao.module.mes.service.energydevice.EnergyDeviceService; @@ -24,6 +21,7 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.IOException; +import java.net.URLEncoder; import java.util.List; import java.util.Map; @@ -108,6 +106,23 @@ public class EnergyDeviceController { + @GetMapping("/record-export-excel") + @Operation(summary = "导出数据记录 Excel") + @PreAuthorize("@ss.hasPermission('mes:energy-device:export')") + @ApiAccessLog(operateType = EXPORT) + public void recordExportExcel(@Valid EnergyDeviceConsumptionReqVO deviceConsumptionReqVO, + HttpServletResponse response) throws IOException { + List energyDeviceRespVOS = energyDeviceService.queryDataRecords(deviceConsumptionReqVO); + List energyDeviceRecordRespVOS = BeanUtils.toBean(energyDeviceRespVOS, EnergyDeviceRecordRespVO.class); + // 设置响应头 + response.setContentType("application/vnd.ms-excel;charset=UTF-8"); + response.setHeader("Content-Disposition", + "attachment;filename=" + URLEncoder.encode("能耗报表.xls", "UTF-8")); + response.setHeader("Content-Encoding", "identity"); + // 导出 Excel + ExcelUtils.write(response, "能耗报表.xls", "数据", EnergyDeviceRecordRespVO.class, energyDeviceRecordRespVOS); + } + // ==================== 子表(抄表记录) ==================== @GetMapping("/energy-device-check-record/page") diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceConsumptionReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceConsumptionReqVO.java index d5aead46bc..9d0f6fbc25 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceConsumptionReqVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceConsumptionReqVO.java @@ -20,5 +20,7 @@ public class EnergyDeviceConsumptionReqVO { @Schema(description = "结束时间") private String endTime; + @Schema(description = "ids导出集合用") + private String ids; } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDevicePageReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDevicePageReqVO.java index 6c2e4c0f1f..840821555c 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDevicePageReqVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDevicePageReqVO.java @@ -61,4 +61,8 @@ public class EnergyDevicePageReqVO extends PageParam { @Schema(description = "计算规则", example = "车间1") private String rules; + + @Schema(description = "ids导出集合用") + private String ids; + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceRecordRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceRecordRespVO.java new file mode 100644 index 0000000000..8ea039a569 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceRecordRespVO.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo; + + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 能耗管理-能耗报表导出 Response VO") +@Data +@ExcelIgnoreUnannotated +public class EnergyDeviceRecordRespVO { + + @Schema(description = "名称", example = "芋艿") + @ExcelProperty("名称") + private String name; + + @Schema(description = "能耗类型名称", example = "水") + @ExcelProperty("能耗类型名称") + private String deviceTypeName; + + @Schema(description = "所属区域名称", example = "车间1") + @ExcelProperty("所属区域名称") + private String orgName; + + @Schema(description = "能耗总用量") + @ExcelProperty("能耗总用量") + private String energyConsumption; + + @Schema(description = "最新数据时间") + @ExcelProperty("开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private String latestDataTime; + + @Schema(description = "最早数据时间") + @ExcelProperty("结束时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private String earliestDataTime; + +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceRespVO.java index a564d1bff6..09975d3727 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceRespVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/EnergyDeviceRespVO.java @@ -1,6 +1,11 @@ package cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo; import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.*; @@ -21,14 +26,14 @@ public class EnergyDeviceRespVO { // @ExcelProperty("ID") private Long id; - @Schema(description = "名称", example = "芋艿") - @ExcelProperty("名称") - private String name; - @Schema(description = "编码") @ExcelProperty("编码") private String code; + @Schema(description = "名称", example = "芋艿") + @ExcelProperty("名称") + private String name; + // @Schema(description = "设备类型", example = "1") // @ExcelProperty(value = "设备类型", converter = DictConvert.class) // @DictFormat("mes_energy_device_type") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中 @@ -54,26 +59,29 @@ public class EnergyDeviceRespVO { // @ExcelProperty("单位") private String unitName; + @Schema(description = "能耗类型名称", example = "水") + @ExcelProperty("能耗类型名称") + private String deviceTypeName; + @Schema(description = "是否启用", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("是否启用") private Boolean isEnable; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") - @ColumnWidth(20) +// @ExcelProperty("创建时间") +// @ColumnWidth(20) private LocalDateTime createTime; @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("更新时间") + @ColumnWidth(20) private LocalDateTime updateTime; @Schema(description = "能耗类型ID", example = "1") // @ExcelProperty("能耗类型ID") private Long deviceTypeId; - @Schema(description = "能耗类型名称", example = "水") - @ExcelProperty("能耗类型名称") - private String deviceTypeName; + @Schema(description = "所属区域ID", example = "1") // @ExcelProperty("所属区域ID") @@ -92,15 +100,25 @@ public class EnergyDeviceRespVO { @Schema(description = "能耗总用量") - @ExcelProperty("能耗总用量") +// @ExcelProperty("能耗总用量") private String energyConsumption; - @Schema(description = "子列表点位参数值") - @ExcelProperty("子列表点位参数值") private Map sublistPointList; @Schema(description = "点位差值列表") private Map operationRulesVOMap; + @Schema(description = "最新数据时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private String latestDataTime; + + @Schema(description = "最早数据时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private String earliestDataTime; + + @Schema(description = "点位详情列表") + private List pointDetails; + + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/PointDetailVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/PointDetailVO.java new file mode 100644 index 0000000000..ba7df48389 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/energydevice/vo/PointDetailVO.java @@ -0,0 +1,60 @@ +package cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PointDetailVO { + /** + * 点位名称 + */ + private String pointName; + + /** + * 点位ID + */ + private Long pointId; + + /** + * 设备ID + */ + private Long deviceId; + + /** + * 最早值 + */ + private String earliestValue; + + /** + * 最早时间 + */ + private String earliestTime; + + /** + * 最晚值 + */ + private String latestValue; + + /** + * 最晚时间 + */ + private String latestTime; + + /** + * 差值 + */ + private String difference; + + /** + * 运算符 + */ + private String operator; + + /** + * 单位 + */ + private String unit; +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/moldrepair/MoldRepairController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/moldrepair/MoldRepairController.java index 23d3cd5ad2..137199aa81 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/moldrepair/MoldRepairController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/moldrepair/MoldRepairController.java @@ -135,11 +135,15 @@ public class MoldRepairController { for (MoldRepairRespVO moldSubjectRespVO : moldSubjectRespVOPageResult.getList()) { if (moldSubjectRespVO.getAcceptedBy() !=null) { AdminUserRespDTO user = adminUserApi.getUser(Long.valueOf(moldSubjectRespVO.getAcceptedBy())); - moldSubjectRespVO.setAcceptedBy("(" + user.getUsername() + ")" + user.getNickname()); + if (user!=null) { + moldSubjectRespVO.setAcceptedBy("(" + user.getUsername() + ")" + user.getNickname()); + } } if (moldSubjectRespVO.getConfirmBy() !=null) { AdminUserRespDTO user = adminUserApi.getUser(Long.valueOf(moldSubjectRespVO.getConfirmBy())); - moldSubjectRespVO.setConfirmBy("(" + user.getUsername() + ")" + user.getNickname()); + if (user!=null) { + moldSubjectRespVO.setConfirmBy("(" + user.getUsername() + ")" + user.getNickname()); + } } } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/moldsubject/MoldSubjectController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/moldsubject/MoldSubjectController.java index 99e4e81c15..dc8bfb9dbc 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/moldsubject/MoldSubjectController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/moldsubject/MoldSubjectController.java @@ -149,7 +149,9 @@ public class MoldSubjectController { private PageResult buildCreatorName(PageResult moldSubjectRespVOPageResult) { for (MoldSubjectRespVO moldSubjectRespVO : moldSubjectRespVOPageResult.getList()) { AdminUserRespDTO user = adminUserApi.getUser(Long.valueOf(moldSubjectRespVO.getCreator())); - moldSubjectRespVO.setCreatorName("(" + user.getUsername()+ ")" + user.getNickname()); + if (user!=null) { + moldSubjectRespVO.setCreatorName("(" + user.getUsername() + ")" + user.getNickname()); + } } return moldSubjectRespVOPageResult; diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/energydevice/EnergyDeviceMapper.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/energydevice/EnergyDeviceMapper.java index 03a7b88306..184c141fc6 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/energydevice/EnergyDeviceMapper.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/energydevice/EnergyDeviceMapper.java @@ -1,11 +1,14 @@ package cn.iocoder.yudao.module.mes.dal.mysql.energydevice; import java.util.*; +import java.util.stream.Collectors; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; import cn.iocoder.yudao.module.mes.dal.dataobject.energydevice.EnergyDeviceDO; +import com.alibaba.excel.util.StringUtils; import org.apache.ibatis.annotations.Mapper; import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.*; @@ -18,7 +21,10 @@ import cn.iocoder.yudao.module.mes.controller.admin.energydevice.vo.*; public interface EnergyDeviceMapper extends BaseMapperX { default PageResult selectPage(EnergyDevicePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() + + + LambdaQueryWrapperX energyDeviceDOLambdaQueryWrapperX = new LambdaQueryWrapperX<>(); + energyDeviceDOLambdaQueryWrapperX .likeIfPresent(EnergyDeviceDO::getName, reqVO.getName()) .eqIfPresent(EnergyDeviceDO::getCode, reqVO.getCode()) .eqIfPresent(EnergyDeviceDO::getInfo, reqVO.getInfo()) @@ -28,7 +34,19 @@ public interface EnergyDeviceMapper extends BaseMapperX { .likeIfPresent(EnergyDeviceDO::getUnitName, reqVO.getUnitName()) .eqIfPresent(EnergyDeviceDO::getIsEnable, reqVO.getIsEnable()) .betweenIfPresent(EnergyDeviceDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(EnergyDeviceDO::getId)); + .orderByDesc(EnergyDeviceDO::getId); + + // 单独处理 ids 条件 + if (StringUtils.isNotBlank(reqVO.getIds())) { + List idList = Arrays.stream(reqVO.getIds().split(",")) + .map(String::trim) + .map(Long::valueOf) + .collect(Collectors.toList()); + energyDeviceDOLambdaQueryWrapperX.in(EnergyDeviceDO::getId, idList); + } + + + return selectPage(reqVO, energyDeviceDOLambdaQueryWrapperX); } } \ No newline at end of file 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 79734439a4..c7a828bc92 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 @@ -28,6 +28,7 @@ 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.*; @@ -253,8 +254,14 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { 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())); + .eq(deviceConsumptionReqVO.getOrgId() != null, EnergyDeviceDO::getOrgId, deviceConsumptionReqVO.getOrgId()) + .orderByDesc(EnergyDeviceDO::getCreateTime)); if (energyDeviceDO == null || energyDeviceDO.isEmpty()) { throw exception(ENERGY_LIST_NOT_EXISTS); @@ -330,13 +337,127 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { respVO.setEnergyConsumption("0.0"); } - // 设置每个点位参数的差值,key为点位参数名称 - Map operationRulesVOMap = buildPointDifferenceMap(calculationResult, deviceDO, rules); - respVO.setOperationRulesVOMap(operationRulesVOMap); + // 设置最新和最晚数据时间 + 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")); + } + + // 构建点位详情列表 + List pointDetails = buildPointDetails(calculationResult, deviceDO, rules); + respVO.setPointDetails(pointDetails); 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 ""; + } + + 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; + } + } /** * 从查询结果中提取特定点位的数据 @@ -357,8 +478,8 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { return null; } - // 处理编码问题 - queryData = fixEncoding(queryData); +// // 处理编码问题 +// queryData = fixEncoding(queryData); // 解析JSON数组 JSONArray jsonArray = JSON.parseArray(queryData); @@ -475,6 +596,10 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { return result; } + // 提取实际采集时间 + String latestTime = extractTimestamp(latestData); + String earliestTime = extractTimestamp(earliestData); + // 使用相同的规则计算两个时间点的总值 Double latestTotal = calculateTotalByRules(latestData, rules); Double earliestTotal = calculateTotalByRules(earliestData, rules); @@ -484,6 +609,8 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { 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) ? @@ -497,6 +624,22 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { return result; } + /** + * 从数据中提取时间戳 + */ + private String extractTimestamp(Map timePointData) { + if (timePointData == null) { + return ""; + } + + Object timestamp = timePointData.get("timestamp"); + if (timestamp != null) { + return timestamp.toString(); + } + + return ""; + } + /** * 从设备数据中获取点位值 */ @@ -754,12 +897,13 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { /** - * 优化:批量构建点位差值Map + * 构建点位详情列表 + * 包含:点位名称、最早值、最晚值、相差值、时间、单位等信息 */ - private Map buildPointDifferenceMap(Map calculationResult, - EnergyDeviceDO deviceDO, - List originalRules) { - Map result = new HashMap<>(); + private List buildPointDetails(Map calculationResult, + EnergyDeviceDO deviceDO, + List originalRules) { + List result = new ArrayList<>(); if (calculationResult == null || originalRules == null || originalRules.isEmpty()) { return result; @@ -770,8 +914,8 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { return result; } - // 获取最新数据用于获取点位名称 Map latestData = (Map) calculationResult.get("latestData"); + Map earliestData = (Map) calculationResult.get("earliestData"); // 收集所有点位ID List allPointIds = new ArrayList<>(); @@ -782,37 +926,300 @@ public class EnergyDeviceServiceImpl implements EnergyDeviceService { } } - // 批量从本地数据库获取点位名称 - Map pointNameMap = batchGetPointNamesFromLocalDB(allPointIds); + // 批量从本地数据库获取点位信息 + Map pointInfoMap = batchGetPointInfoFromLocalDB(allPointIds); for (Map pointDiff : pointDifferences) { Long pointId = (Long) pointDiff.get("pointId"); + Long deviceId = (Long) pointDiff.get("deviceId"); + if (pointId == null) { continue; } - // 获取点位名称 - String pointName = null; + // 获取点位详细信息 + PointDetailVO pointDetail = new PointDetailVO(); + pointDetail.setPointId(pointId); + pointDetail.setDeviceId(deviceId); + + // 设置运算符 + String operator = (String) pointDiff.get("operator"); + pointDetail.setOperator(operator != null ? operator : ""); - // 1. 尝试从TD数据库获取 - pointName = getPointNameFromTDData(latestData, pointId, pointDiff); + // 从TD数据获取点位信息和实际采集时间 + setPointDataFromTD(pointDetail, latestData, earliestData, pointId, deviceId, pointDiff); - // 2. 如果TD数据库没有,从本地数据库获取 - if (StringUtils.isBlank(pointName)) { - pointName = pointNameMap.get(pointId); + // 如果没有从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"); - String differenceStr = formatDouble(difference != null ? difference : 0.0); - // 以点位名称为key,差值为value - if (StringUtils.isNotBlank(pointName)) { - result.put(pointName, differenceStr); + 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())); + } + } + } + + // 获取最早数据的时间 + 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); + } + } + } + } + } + + /** + * 格式化为完整时间 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); + } + + 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); + } + } + } + + // 3. 如果是 HH:mm:ss 格式 + if (timeStr.matches("\\d{1,2}:\\d{1,2}:\\d{1,2}")) { + return timeStr; + } + + // 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) { + // 继续尝试其他格式 + } + + 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) { + // 解析失败 + } + + } 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 { - // 如果还没有点位名称,使用点位ID作为key - result.put("点位" + pointId, differenceStr); + pointDetail.setPointName("点位" + pointDetail.getPointId()); + } + } + } + + /** + * 从本地数据库获取点位单位 + */ + 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()); + } + } + } + + /** + * 批量获取点位信息(优化性能) + */ + private Map batchGetPointInfoFromLocalDB(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) { + result.put(deviceContact.getId(), deviceContact); + } + } } + } catch (Exception e) { + log.error("批量查询点位信息失败, pointIds: {}", pointIds, e); } return result;