From 66bc902a0765131988f65ac1557301ce72efbfa0 Mon Sep 17 00:00:00 2001 From: HuangHuiKang Date: Wed, 4 Feb 2026 14:55:14 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9=E9=87=87=E9=9B=86?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/scheduled/coretask/DeviceTask.java | 68 +++++++++---------- .../iot/service/device/DeviceServiceImpl.java | 14 ++++ 2 files changed, 47 insertions(+), 35 deletions(-) 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 4da6014705..55e235b1d3 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 @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.enums.DeviceBasicStat import cn.iocoder.yudao.module.iot.controller.admin.device.enums.DeviceStatusEnum; 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.controller.admin.device.vo.DeviceSaveReqVO; import cn.iocoder.yudao.module.iot.controller.admin.devicemodelrules.vo.PointRulesRespVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO; @@ -17,6 +18,7 @@ import cn.iocoder.yudao.module.iot.dal.mysql.devicecontactmodel.DeviceContactMod import cn.iocoder.yudao.module.iot.dal.mysql.deviceoperationrecord.DeviceOperationRecordMapper; import cn.iocoder.yudao.module.iot.dal.mysql.devicepointrules.DevicePointRulesMapper; import cn.iocoder.yudao.module.iot.dal.mysql.devicewarinningrecord.DeviceWarinningRecordMapper; +import cn.iocoder.yudao.module.iot.service.device.DeviceService; import cn.iocoder.yudao.module.iot.service.device.TDengineService; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -48,6 +50,9 @@ public class DeviceTask implements Task { @Resource private TDengineService tDengineService; + @Resource + private DeviceService deviceService; + @Resource private DevicePointRulesMapper devicePointRulesMapper; @@ -103,13 +108,6 @@ public class DeviceTask implements Task { logger.info("执行设备任务,任务ID: {}, 参数: {}, 时间: {}", taskId, taskParam, currentTime); - // 解析参数,假设格式为 deviceId:deviceCode -// String[] params = taskParam.split(":"); -// if (params.length >= 2) { -// Long deviceId = Long.parseLong(params[0]); -// String deviceCode = params[1]; -// executeDeviceLogic(deviceId, deviceCode); -// } executeDeviceLogic(taskId,taskParam); @@ -148,32 +146,6 @@ public class DeviceTask implements Task { OpcUtils.disconnect(); } - /** - * 具体的设备执行逻辑 - */ -// private void executeDeviceLogic(Long sourceDeviceId, String param) { -// logger.info("执行设备逻辑,源设备ID: {},参数: {}", sourceDeviceId, param); -// -// // 1. 计算实际设备ID -// Long deviceId = sourceDeviceId - 1000000L; -// logger.info("处理后设备ID: {}", deviceId); -// -// if (deviceId == null) { -// throw new RuntimeException("设备ID不能为空"); -// } -// -// // 2. 获取设备信息 -// DeviceDO device = getDeviceInfo(deviceId); -// -// // 3. 连接OPC服务器 -// connectOpcServer(device); -// -// // 4. 处理数据读取和入库 -// processDeviceData(deviceId, device); -// -// // 5. 断开连接 -// OpcUtils.disconnect(); -// } /** * 获取设备信息 @@ -201,6 +173,8 @@ public class DeviceTask implements Task { boolean connected = OpcUtils.connect(device.getUrl(), username, password, 10); if (!connected) { + //停止任务 + deviceService.connectDevice(new DeviceSaveReqVO().setId(device.getId()).setIsConnect(2)); throw new RuntimeException("连接OPC服务器失败,URL: " + device.getUrl()); } } @@ -213,11 +187,35 @@ public class DeviceTask implements Task { List points = getDevicePoints(deviceId); if (CollectionUtils.isEmpty(points)) { logger.warn("设备 {} 未配置点位", deviceId); + //更新状态为待机中 + DeviceOperationRecordDO record = new DeviceOperationRecordDO(); + record.setDeviceId(device.getId()); + record.setRule(DeviceStatusEnum.STANDBY.getCode()); + //TODO 创建人和更新人为内置默认管理员 + record.setCreator("1"); + record.setUpdater("1"); + deviceOperationRecordMapper.insert(record); return; } logger.info("设备 {} 需要读取 {} 个点位", deviceId, points.size()); + + DevicePointRulesDO devicePointRulesDO = devicePointRulesMapper.selectOne(Wrappers.lambdaQuery() + .eq(DevicePointRulesDO::getDeviceId, deviceId) + .eq(DevicePointRulesDO::getIdentifier, DeviceBasicStatusEnum.RUNNING)); + if(devicePointRulesDO !=null && devicePointRulesDO.getFieldRule() == null ){ + //更新状态为待机中 + DeviceOperationRecordDO record = new DeviceOperationRecordDO(); + record.setDeviceId(device.getId()); + record.setRule(DeviceStatusEnum.STANDBY.getCode()); + //TODO 创建人和更新人为内置默认管理员 + record.setCreator("1"); + record.setUpdater("1"); + deviceOperationRecordMapper.insert(record); + } + + // 2. 读取并处理数据 int successCount = 0; List validDataList = new ArrayList<>(); @@ -333,9 +331,9 @@ public class DeviceTask implements Task { // 2. 遍历规则 for (DevicePointRulesDO devicePointRulesDO : devicePointRulesDOList) { - if (StringUtils.isBlank(devicePointRulesDO.getFieldRule())) { + if (StringUtils.isBlank(devicePointRulesDO.getFieldRule())) { continue; - } + } // 3. 解析规则列表 List pointRulesVOList = JSON.parseArray( 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 4f15ac6780..f881fc7d6d 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 @@ -523,9 +523,16 @@ public class DeviceServiceImpl implements DeviceService { }else if(Objects.equals(createReqVO.getIsConnect(), DeviceConnectionStatusEnum.DISCONNECTED.getStatus())){ boolean disconnect = OpcUtils.disconnect(); if (disconnect){ + //更新连接状态 deviceDO.setStatus(String.valueOf(DeviceConnectionStatusEnum.DISCONNECTED.getStatus())); deviceMapper.updateById(deviceDO); + //停止定时任务 taskSchedulerManager.stopDeviceTask(deviceDO.getId()); + //更新运行状态 + updateOperationalStatus(deviceDO); + + + }else { throw exception(OPC_CLOSE_CONNECT_FAILURE); } @@ -536,6 +543,13 @@ public class DeviceServiceImpl implements DeviceService { return Boolean.TRUE; } + private void updateOperationalStatus(DeviceDO deviceDO) { + DeviceOperationRecordDO deviceOperationRecordDO = new DeviceOperationRecordDO(); + deviceOperationRecordDO.setDeviceId(deviceDO.getId()); + deviceOperationRecordDO.setRule(DeviceStatusEnum.OFFLINE.getCode()); + deviceOperationRecordMapper.insert(deviceOperationRecordDO); + } + private DeviceDO validateConnectRequest(DeviceSaveReqVO createReqVO) { if(createReqVO.getId() == null){ throw exception(DEVICE_ID_DOES_NOT_EXIST); From 23bb0016ba18c93c8f7b7104a0e382e227d87ce8 Mon Sep 17 00:00:00 2001 From: HuangHuiKang Date: Wed, 4 Feb 2026 16:02:32 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9=E9=87=87=E9=9B=86?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1-?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=BF=9E=E6=8E=A5=E4=B8=AD=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E6=B7=BB=E5=8A=A0=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E9=BB=98=E8=AE=A4=E4=B8=BA=E5=BE=85=E6=9C=BA?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/common/util/opc/OpcUtils.java | 8 ++- .../device/scheduled/coretask/DeviceTask.java | 62 +++++++++++++++++-- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/opc/OpcUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/opc/OpcUtils.java index a9e6d816d3..ed78711033 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/opc/OpcUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/opc/OpcUtils.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.common.util.opc; +import lombok.extern.slf4j.Slf4j; import org.eclipse.milo.opcua.sdk.client.OpcUaClient; import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig; import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder; @@ -24,6 +25,7 @@ import java.util.function.Predicate; * OPC UA连接静态工具类 - 适用于Eclipse Milo 0.6.9 * 提供OPC UA服务器的连接、断开、数据读写等功能(静态方法版本) */ +@Slf4j public class OpcUtils { // 静态成员变量,所有实例共享 @@ -46,14 +48,15 @@ public class OpcUtils { public static boolean connect(String url, String username, String password, int timeoutSeconds) { if (isConnected) { - System.out.println(LOG_PREFIX + "客户端已连接,无需重复连接"); + log.info(" {} 客户端已连接,无需重复连接",LOG_PREFIX); return true; } serverUrl = url; try { - System.out.println(LOG_PREFIX + "正在连接到OPC UA服务器: " + url); + + log.info(" {} 正在连接到OPC UA服务器 {}",LOG_PREFIX,url); // 提取主机和端口 final String targetHost = extractHostFromUrl(url); @@ -62,6 +65,7 @@ public class OpcUtils { System.out.println(LOG_PREFIX + "目标主机: " + targetHost + ", 端口: " + targetPort + ", 路径: " + path); + // 将主机名解析为IP地址 final String ipAddress = resolveToIpAddress(targetHost); System.out.println(LOG_PREFIX + "解析为IP地址: " + ipAddress); 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 55e235b1d3..39295bccf7 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 @@ -1,11 +1,13 @@ // DeviceTask.java - 原有设备任务 package cn.iocoder.yudao.module.iot.controller.admin.device.scheduled.coretask; +import cn.iocoder.yudao.framework.common.enums.DeviceConnectionStatusEnum; import cn.iocoder.yudao.framework.common.util.opc.OpcUtils; import cn.iocoder.yudao.module.iot.controller.admin.device.enums.DeviceBasicStatusEnum; import cn.iocoder.yudao.module.iot.controller.admin.device.enums.DeviceStatusEnum; 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.controller.admin.device.scheduled.scheduler.TaskSchedulerManager; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceSaveReqVO; import cn.iocoder.yudao.module.iot.controller.admin.devicemodelrules.vo.PointRulesRespVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO; @@ -28,6 +30,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -51,6 +54,7 @@ public class DeviceTask implements Task { private TDengineService tDengineService; @Resource + @Lazy private DeviceService deviceService; @Resource @@ -62,6 +66,10 @@ public class DeviceTask implements Task { @Resource private DeviceWarinningRecordMapper deviceWarinningRecordMapper; + @Resource + @Lazy + private TaskSchedulerManager taskSchedulerManager; + @Override public String getTaskType() { return TaskTypeEnum.DEVICE.getCode(); @@ -171,29 +179,72 @@ public class DeviceTask implements Task { String username = StringUtils.defaultString(device.getUsername()); String password = StringUtils.defaultString(device.getPassword()); - boolean connected = OpcUtils.connect(device.getUrl(), username, password, 10); - if (!connected) { - //停止任务 - deviceService.connectDevice(new DeviceSaveReqVO().setId(device.getId()).setIsConnect(2)); - throw new RuntimeException("连接OPC服务器失败,URL: " + device.getUrl()); + boolean connected = false; + + + try { + connected = OpcUtils.connect(device.getUrl(), username, password, 10); + if (!connected) { + + log.error("设备 {} 连接OPC服务器失败,URL: {}", device.getId(), device.getUrl()); + device.setStatus(String.valueOf(DeviceConnectionStatusEnum.DISCONNECTED.getStatus())); + deviceMapper.updateById(device); + taskSchedulerManager.stopDeviceTask(device.getId()); + DeviceOperationRecordDO deviceOperationRecordDO = new DeviceOperationRecordDO(); + deviceOperationRecordDO.setDeviceId(device.getId()); + deviceOperationRecordDO.setRule(DeviceStatusEnum.OFFLINE.getCode()); + //TODO 默认内置管理员 + deviceOperationRecordDO.setCreator("1"); + deviceOperationRecordDO.setUpdater("1"); + deviceOperationRecordMapper.insert(deviceOperationRecordDO); + //抛出异常终止任务 + throw new RuntimeException("连接opcuv服务器异常"); + + } + + log.info("设备 {} 成功连接OPC服务器,URL: {}", device.getId(), device.getUrl()); + + } catch (Exception e) { + log.error("设备 {} 连接OPC服务器异常,URL: {}", device.getId(), device.getUrl(), e); + device.setStatus(String.valueOf(DeviceConnectionStatusEnum.DISCONNECTED.getStatus())); + deviceMapper.updateById(device); + taskSchedulerManager.stopDeviceTask(device.getId()); + DeviceOperationRecordDO deviceOperationRecordDO = new DeviceOperationRecordDO(); + deviceOperationRecordDO.setDeviceId(device.getId()); + deviceOperationRecordDO.setRule(DeviceStatusEnum.OFFLINE.getCode()); + //TODO 默认内置管理员 + deviceOperationRecordDO.setCreator("1"); + deviceOperationRecordDO.setUpdater("1"); + deviceOperationRecordMapper.insert(deviceOperationRecordDO); + //抛出异常终止任务 + throw new RuntimeException(e); } + + } /** * 处理设备数据 */ private void processDeviceData(Long deviceId, DeviceDO device) { + DeviceDO deviceDO = deviceMapper.selectById(deviceId); // 1. 查询点位配置 List points = getDevicePoints(deviceId); if (CollectionUtils.isEmpty(points)) { + + + logger.warn("设备 {} 未配置点位", deviceId); //更新状态为待机中 DeviceOperationRecordDO record = new DeviceOperationRecordDO(); record.setDeviceId(device.getId()); record.setRule(DeviceStatusEnum.STANDBY.getCode()); + record.setTotalStandbyTime(deviceDO.getSampleCycle()); //TODO 创建人和更新人为内置默认管理员 record.setCreator("1"); record.setUpdater("1"); + + deviceOperationRecordMapper.insert(record); return; } @@ -209,6 +260,7 @@ public class DeviceTask implements Task { DeviceOperationRecordDO record = new DeviceOperationRecordDO(); record.setDeviceId(device.getId()); record.setRule(DeviceStatusEnum.STANDBY.getCode()); + record.setTotalStandbyTime(deviceDO.getSampleCycle()); //TODO 创建人和更新人为内置默认管理员 record.setCreator("1"); record.setUpdater("1"); From 1e060796a049904bf6e6683cac72a646f3611332 Mon Sep 17 00:00:00 2001 From: HuangHuiKang Date: Wed, 4 Feb 2026 16:25:04 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix:=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86-?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=AE=A1=E7=90=86-=E6=96=B0=E5=A2=9Ecorn?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E4=B8=8D=E8=83=BD=E5=B0=8F=E4=BA=8E?= =?UTF-8?q?=E4=B8=80=E5=B0=8F=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-iot/yudao-module-iot-biz/pom.xml | 6 +++ .../module/mes/enums/ErrorCodeConstants.java | 2 + .../TaskManagementServiceImpl.java | 42 +++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml index 60e41dee82..3284ebf3e7 100644 --- a/yudao-module-iot/yudao-module-iot-biz/pom.xml +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -52,6 +52,12 @@ yudao-spring-boot-starter-mybatis + + com.cronutils + cron-utils + 9.2.0 + + cn.iocoder.boot diff --git a/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java index 6618f4adf1..cb6adedc8f 100644 --- a/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java +++ b/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java @@ -143,6 +143,8 @@ public interface ErrorCodeConstants { ErrorCode SUBJECT_EXISTS = new ErrorCode(1002000010, "项目编码已存在"); ErrorCode TASK_MANAGEMENT_NOT_EXISTS = new ErrorCode(1002000011, "设备类型不存在"); ErrorCode TASK_CORN_NOT_EXISTS = new ErrorCode(1002000011, "设备corn表达式为空"); + ErrorCode TASK_CORN_NOT_LE_HOUR = new ErrorCode(1002000011, "corn表达式不能小于一小时"); + ErrorCode TICKET_MANAGEMENT_NOT_EXISTS = new ErrorCode(1002000012, "工单管理不存在"); ErrorCode TICKET_RESULTS_NOT_EXISTS = new ErrorCode(1002000013, "工单检验结果不存在"); ErrorCode TICKET_RESULTS_ID_NOT_NULL = new ErrorCode(1002000014, "工单检验结果Id不存在"); diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/taskmanagement/TaskManagementServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/taskmanagement/TaskManagementServiceImpl.java index e39cc002bc..035dd42143 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/taskmanagement/TaskManagementServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/taskmanagement/TaskManagementServiceImpl.java @@ -16,6 +16,11 @@ import cn.iocoder.yudao.module.mes.dal.mysql.ticketmanagement.TicketManagementMa import cn.iocoder.yudao.module.mes.dal.mysql.ticketresults.TicketResultsMapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.cronutils.model.Cron; +import com.cronutils.model.CronType; +import com.cronutils.model.definition.CronDefinitionBuilder; +import com.cronutils.model.time.ExecutionTime; +import com.cronutils.parser.CronParser; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -23,6 +28,8 @@ import org.springframework.validation.annotation.Validated; import org.springframework.transaction.annotation.Transactional; import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.ZonedDateTime; import java.util.*; import java.util.stream.Collectors; @@ -73,6 +80,12 @@ public class TaskManagementServiceImpl implements TaskManagementService { @Override public Long createTaskManagement(TaskManagementSaveReqVO createReqVO) { + String cronExpr = createReqVO.getCronExpression(); + + // 校验 cron 表达式最小间隔 + isCronIntervalAtLeastOneHour(cronExpr); + + // 插入 TaskManagementDO taskManagement = BeanUtils.toBean(createReqVO, TaskManagementDO.class); taskManagementMapper.insert(taskManagement); @@ -80,6 +93,35 @@ public class TaskManagementServiceImpl implements TaskManagementService { return taskManagement.getId(); } + private void isCronIntervalAtLeastOneHour(String cronExpr) { + + try { + if (StringUtils.isBlank(cronExpr)){ + return; + } + + CronParser parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ)); + Cron cron = parser.parse(cronExpr); + cron.validate(); + + ExecutionTime executionTime = ExecutionTime.forCron(cron); + + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime next1 = executionTime.nextExecution(now).get(); + ZonedDateTime next2 = executionTime.nextExecution(next1).get(); + + Duration duration = Duration.between(next1, next2); + + if (duration.toHours() < 1) { + throw exception(TASK_CORN_NOT_LE_HOUR); + } + + } catch (Exception e) { + throw exception(TASK_CORN_NOT_LE_HOUR); + } + + } + @Override public void updateTaskManagement(TaskManagementSaveReqVO updateReqVO) { // 校验存在 From 778fe392da1d110019834d250001eff2448c2203 Mon Sep 17 00:00:00 2001 From: HuangHuiKang Date: Wed, 4 Feb 2026 18:29:49 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9=E9=85=8D=E6=96=B9?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/recipe/vo/RecipeRespVO.java | 3 + .../admin/recipe/vo/RecipeSaveReqVO.java | 3 + .../RecipeDeviceRecordController.java | 77 ++++++++++++++----- .../vo/RecipeDeviceRecordPageReqVO.java | 1 + .../vo/RecipePlanDetailSaveReqVO.java | 2 +- .../iot/dal/dataobject/recipe/RecipeDO.java | 7 ++ .../DeviceContactModelService.java | 2 + .../DeviceContactModelServiceImpl.java | 5 ++ .../RecipeDeviceRecordService.java | 3 + .../RecipeDeviceRecordServiceImpl.java | 14 ++++ .../RecipePointRecordServiceImpl.java | 7 ++ 11 files changed, 103 insertions(+), 21 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipe/vo/RecipeRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipe/vo/RecipeRespVO.java index 80dec1c437..6bbee8c9f0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipe/vo/RecipeRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipe/vo/RecipeRespVO.java @@ -56,4 +56,7 @@ public class RecipeRespVO { // @ExcelProperty("关联设备ID") private Long deviceId; + @Schema(description = "设备Id") + private Long machineId; + } \ 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/recipe/vo/RecipeSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipe/vo/RecipeSaveReqVO.java index c432c6b283..3826accc3a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipe/vo/RecipeSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipe/vo/RecipeSaveReqVO.java @@ -37,4 +37,7 @@ public class RecipeSaveReqVO { @Schema(description = "数据单位") private String dataUnit; + @Schema(description = "设备Id") + private Long machineId; + } \ 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/recipedevicerecord/RecipeDeviceRecordController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedevicerecord/RecipeDeviceRecordController.java index a0ba78a9d4..8c412b939f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedevicerecord/RecipeDeviceRecordController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedevicerecord/RecipeDeviceRecordController.java @@ -1,9 +1,15 @@ package cn.iocoder.yudao.module.iot.controller.admin.recipedevicerecord; +import cn.iocoder.yudao.framework.common.util.opc.OpcUtils; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceRespVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.recipe.RecipeDO; import cn.iocoder.yudao.module.iot.dal.dataobject.recipeplandetail.RecipePlanDetailDO; import cn.iocoder.yudao.module.iot.service.device.DeviceService; +import cn.iocoder.yudao.module.iot.service.devicecontactmodel.DeviceContactModelService; import cn.iocoder.yudao.module.iot.service.recipe.RecipeService; import cn.iocoder.yudao.module.iot.service.recipeplandetail.RecipePlanDetailService; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import org.springframework.validation.annotation.Validated; @@ -16,17 +22,23 @@ import javax.validation.*; import javax.servlet.http.*; import java.util.*; import java.io.IOException; +import java.util.function.Function; +import java.util.stream.Collectors; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_NOT_EXISTS; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RECIPE_NOT_EXISTS; import cn.iocoder.yudao.module.iot.controller.admin.recipedevicerecord.vo.*; import cn.iocoder.yudao.module.iot.dal.dataobject.recipedevicerecord.RecipeDeviceRecordDO; @@ -73,6 +85,9 @@ public class RecipeDeviceRecordController { @Resource private DeviceService deviceService; + @Resource + private DeviceContactModelService deviceContactModelService; + @PostMapping("/create") @@ -134,7 +149,6 @@ public class RecipeDeviceRecordController { /** * 批量创建设备点位采集记录和配方点位记录 * @param recipeId 配方ID - * @param pointList 点位列表 [{id: 1, refer: "参考值1"}, {id: 2, refer: "参考值2"}] * @return 创建结果 * @throws JsonProcessingException JSON处理异常 */ @@ -152,28 +166,51 @@ public class RecipeDeviceRecordController { // ========== 第一步:查询配方关联的点位属性信息 ========== // 1.1 根据recipeId查询iot_recipe_device_attribute表记录 - recipePlanDetailDO.setRecipeId(32L); + recipePlanDetailDO.setRecipeId(recipeId); List attributeList = recipeDeviceAttributeService.getByRecipeId(recipePlanDetailDO.getRecipeId()); -// if (CollectionUtils.isEmpty(attributeList)) { -// return success(false); // 无关联属性,直接返回 -// } - Map> deviceDataMap = deviceService.createDeviceDataMap(103L);//recipeRespVO.getDeviceId() - - for (RecipeDeviceAttributeDO attributeDO : attributeList) { - Map data = deviceDataMap.get(attributeDO.getAttributeId()); - if (data != null ) { - // 创建 - RecipeDeviceRecordDO recipeDeviceRecordDO = new RecipeDeviceRecordDO(); - recipeDeviceRecordDO.setRecipeId(recipeId); - recipeDeviceRecordDO.setAttributeCode(attributeDO.getAttributeName()); - recipeDeviceRecordDO.setDataType(attributeDO.getDataType()); - recipeDeviceRecordDO.setDataUnit(attributeDO.getDataUnit()); - if (data.get("addressValue") != null && data.get("addressValue").toString() != null) { - recipeDeviceRecordDO.setValue(data.get("addressValue").toString()); - } - recipeDeviceRecordService.createRecipeDeviceRecord(BeanUtils.toBean(recipeDeviceRecordDO, RecipeDeviceRecordSaveReqVO.class)); + //先删除在添加 + List recipeDeviceRecordDOS = recipeDeviceRecordService.getListByRecipeId(recipeId); + if (!recipeDeviceRecordDOS.isEmpty()){ + recipeDeviceRecordService.deleteByIds(recipeDeviceRecordDOS); + } + RecipeDO recipe = recipeService.getRecipe(recipeId); + if (recipe == null){ + throw exception(RECIPE_NOT_EXISTS); + } + + DeviceRespVO device = deviceService.getDevice(recipe.getMachineId()); + if (device== null ){ + throw exception(DEVICE_NOT_EXISTS); + } + + Map deviceContactModelMap = new HashMap<>(); + List deviceContactModelDOS = deviceContactModelService.selectListByDeviceId(device.getId()); + if (!deviceContactModelDOS.isEmpty()){ + deviceContactModelMap = deviceContactModelDOS.stream() + .collect(Collectors.toMap( + DeviceContactModelDO::getId, + Function.identity() + )); + } + + OpcUtils.connect(device.getUrl(),device.getUsername(),device.getPassword(),10); + + for (RecipeDeviceAttributeDO attributeDO : attributeList) { + DeviceContactModelDO deviceContactModelDO = deviceContactModelMap.get(attributeDO.getAttributeId()); + if (deviceContactModelDO == null){ + continue; } + // 创建 + RecipeDeviceRecordDO recipeDeviceRecordDO = new RecipeDeviceRecordDO(); + recipeDeviceRecordDO.setRecipeId(recipeId); + recipeDeviceRecordDO.setAttributeCode(deviceContactModelDO.getAttributeName()); + recipeDeviceRecordDO.setDataType(deviceContactModelDO.getDataType()); + recipeDeviceRecordDO.setDataUnit(deviceContactModelDO.getDataUnit()); + recipeDeviceRecordDO.setValue((String) OpcUtils.readValue(deviceContactModelDO.getAddress())); + + recipeDeviceRecordService.createRecipeDeviceRecord(BeanUtils.toBean(recipeDeviceRecordDO, RecipeDeviceRecordSaveReqVO.class)); + } return success(true); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedevicerecord/vo/RecipeDeviceRecordPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedevicerecord/vo/RecipeDeviceRecordPageReqVO.java index 3cc8aeefd6..880ce1901b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedevicerecord/vo/RecipeDeviceRecordPageReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipedevicerecord/vo/RecipeDeviceRecordPageReqVO.java @@ -52,4 +52,5 @@ public class RecipeDeviceRecordPageReqVO extends PageParam { @Schema(description = "配方id", example = "32535") private Long recipeId; + } \ 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/recipeplandetail/vo/RecipePlanDetailSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipeplandetail/vo/RecipePlanDetailSaveReqVO.java index 2598ec191b..4bfda9dafa 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipeplandetail/vo/RecipePlanDetailSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/recipeplandetail/vo/RecipePlanDetailSaveReqVO.java @@ -25,7 +25,7 @@ public class RecipePlanDetailSaveReqVO { private Long recipeId; @Schema(description = "关联计划(关联mes_plan表的id)", requiredMode = Schema.RequiredMode.REQUIRED, example = "28398") - @NotNull(message = "关联计划(关联mes_plan表的id)不能为空") +// @NotNull(message = "关联计划(关联mes_plan表的id)不能为空") private Long planId; @Schema(description = "来源(新增/生产中)") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/recipe/RecipeDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/recipe/RecipeDO.java index c3c943865a..81e54841b7 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/recipe/RecipeDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/recipe/RecipeDO.java @@ -60,4 +60,11 @@ public class RecipeDO extends BaseDO { */ private String dataUnit; + /** + * 数据单位 + */ + private Long machineId; + + + } \ 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/devicecontactmodel/DeviceContactModelService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelService.java index 8eca7be5bc..5daff8797e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelService.java @@ -53,4 +53,6 @@ public interface DeviceContactModelService { PageResult getDeviceContactModelPage(DeviceContactModelPageReqVO pageReqVO); List getDeviceContactModelList(Long id); + + List selectListByDeviceId(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/devicecontactmodel/DeviceContactModelServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelServiceImpl.java index b6cbb5f719..0108420cbd 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelServiceImpl.java @@ -99,4 +99,9 @@ public class DeviceContactModelServiceImpl implements DeviceContactModelService .orderByDesc(DeviceContactModelDO::getCreateTime)); } + @Override + public List selectListByDeviceId(Long id) { + return deviceContactModelMapper.selectList(Wrappers.lambdaQuery().eq(DeviceContactModelDO::getDeviceId,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/recipedevicerecord/RecipeDeviceRecordService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedevicerecord/RecipeDeviceRecordService.java index b05b6d8371..9b4e3107a6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedevicerecord/RecipeDeviceRecordService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedevicerecord/RecipeDeviceRecordService.java @@ -52,4 +52,7 @@ public interface RecipeDeviceRecordService { */ PageResult getRecipeDeviceRecordPage(RecipeDeviceRecordPageReqVO pageReqVO); + List getListByRecipeId(Long recipeId); + + void deleteByIds(List recipeDeviceRecordDOS); } \ 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/recipedevicerecord/RecipeDeviceRecordServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedevicerecord/RecipeDeviceRecordServiceImpl.java index 45e7db22ba..e3ab5db969 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedevicerecord/RecipeDeviceRecordServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipedevicerecord/RecipeDeviceRecordServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.recipedevicerecord; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.springframework.stereotype.Service; import javax.annotation.Resource; import org.springframework.validation.annotation.Validated; @@ -31,6 +32,7 @@ public class RecipeDeviceRecordServiceImpl implements RecipeDeviceRecordService @Override public Long createRecipeDeviceRecord(RecipeDeviceRecordSaveReqVO createReqVO) { + // 插入 RecipeDeviceRecordDO recipeDeviceRecord = BeanUtils.toBean(createReqVO, RecipeDeviceRecordDO.class); recipeDeviceRecordMapper.insert(recipeDeviceRecord); @@ -71,4 +73,16 @@ public class RecipeDeviceRecordServiceImpl implements RecipeDeviceRecordService return recipeDeviceRecordMapper.selectPage(pageReqVO); } + @Override + public List getListByRecipeId(Long recipeId) { + return recipeDeviceRecordMapper.selectList(Wrappers.lambdaQuery().eq(RecipeDeviceRecordDO::getRecipeId, recipeId)); + + } + + @Override + public void deleteByIds(List recipeDeviceRecordDOS) { + recipeDeviceRecordMapper.deleteByIds(recipeDeviceRecordDOS); + + } + } \ 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/recipepointrecord/RecipePointRecordServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipepointrecord/RecipePointRecordServiceImpl.java index c08cb7bfc5..a07dbee010 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipepointrecord/RecipePointRecordServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/recipepointrecord/RecipePointRecordServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.recipepointrecord; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.springframework.stereotype.Service; import javax.annotation.Resource; import org.springframework.validation.annotation.Validated; @@ -31,6 +32,12 @@ public class RecipePointRecordServiceImpl implements RecipePointRecordService { @Override public Long createRecipePointRecord(RecipePointRecordSaveReqVO createReqVO) { + List recipePointRecordDOS = recipePointRecordMapper.selectList(Wrappers.lambdaQuery().eq(RecipePointRecordDO::getRecipeId, createReqVO.getRecipeId())); + if (!recipePointRecordDOS.isEmpty()){ + recipePointRecordMapper.deleteByIds(recipePointRecordDOS); + } + + // 插入 RecipePointRecordDO recipePointRecord = BeanUtils.toBean(createReqVO, RecipePointRecordDO.class); recipePointRecordMapper.insert(recipePointRecord);