From 814dcc0f5839381e85f18441140ceded4d114028 Mon Sep 17 00:00:00 2001 From: lld <15027638633@163.com> Date: Fri, 6 Mar 2026 22:12:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/agri/quartz/task/RollerAutoTask.java | 251 +++++++++++------- 1 file changed, 151 insertions(+), 100 deletions(-) diff --git a/agri-quartz/src/main/java/com/agri/quartz/task/RollerAutoTask.java b/agri-quartz/src/main/java/com/agri/quartz/task/RollerAutoTask.java index fe9e406..5daaf87 100644 --- a/agri-quartz/src/main/java/com/agri/quartz/task/RollerAutoTask.java +++ b/agri-quartz/src/main/java/com/agri/quartz/task/RollerAutoTask.java @@ -1,13 +1,8 @@ package com.agri.quartz.task; -import com.agri.common.core.domain.entity.SysUser; import com.agri.common.enums.TempCommandStatus; -import com.agri.common.utils.RollerTimeCalculator; -import com.agri.common.utils.TempJudgeUtil; -import com.agri.common.utils.TimeConvertUtil; -import com.agri.common.utils.TimeRangeUtil; +import com.agri.common.utils.*; import com.agri.framework.config.MqttConfig; -import com.agri.framework.interceptor.FrontendControlHandler; import com.agri.framework.manager.MqttAutoOffManager; import com.agri.system.domain.SysAgriInfo; import com.agri.system.domain.SysDevOperLog; @@ -16,9 +11,7 @@ import com.agri.system.service.ISysAgriInfoService; import com.agri.system.service.ISysDevOperLogService; import com.agri.system.service.ISysDtuDataService; import com.agri.system.service.ISysRollerParamService; -import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; import org.apache.commons.collections4.CollectionUtils; -import org.checkerframework.checker.units.qual.A; import org.eclipse.paho.client.mqttv3.MqttException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +26,6 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; /** @@ -207,122 +199,181 @@ public class RollerAutoTask { } /** + * 公共常量(可抽取到常量类) + */ + private static final String LOCK_PREFIX = "lock:"; + private static final String AUTO_MODE = "auto_mode"; + private static final String CREATE_BY = "条件控制"; + private static final int OP_SOURCE = 3; // 操作来源:条件控制 + private static final int LOCK_ACQUIRED = 1; // 是否获取锁 + + /** + * 发送开指令 + * @param imei 设备唯一标识 + * @param agriName 大棚名称 + * @param roller 卷膜标识 * @param isFirstRun 是否第一次执行 * @param vent 每次执行的风口大小 + * @param reservedLen 预留风口长度 */ private void sendOpenCommand(String imei, String agriName, String roller, boolean isFirstRun, BigDecimal vent, BigDecimal reservedLen) { - try { - // 默认 - BigDecimal openLen = vent; - // 是第一次,需要vent+预留风口 - if (isFirstRun) { - log.info("自动模式:设备【{}】开启自动模式。触发自动化条件,卷膜【{}】是今天第一次执行",imei,roller); - openLen = openLen.add(reservedLen); - } - String funcType = roller + "k1"; - - String message = "{\"" + funcType + "\":1}"; - String lockKey = "lock:" + imei + ":" + funcType; - Boolean lockSuccess = stringRedisTemplate.opsForValue().setIfAbsent( - lockKey, "auto_mode", dtuCtlLockTTL, TimeUnit.SECONDS // 延长至15秒,适配设备回执场景 - ); - if (lockSuccess == null || !lockSuccess) { - log.warn("【分布式锁】前端【自动模式】下触发【{}】操作设备【{}】的【{}】功能失败;可能其他用户正在操作此功能", roller, imei, funcType); - return; - } - - // 3. 记录日志 - log.info("【指令处理】前端【自动模式】下触发【{}】操作设备【{}】的【{}】功能, 指令:{}" - , roller, imei, funcType, message); - - - SysDevOperLog logDto = new SysDevOperLog(); - // 大棚名称 - logDto.setAgriName(agriName); - // imei - logDto.setImei(imei); - // 卷膜n - logDto.setFuncCode(funcType); - // 运行1 暂停0 - logDto.setOpType(1); - // 来源 条件控制 - logDto.setOpSource(3); - // 指令 - logDto.setPayload(message); - // 是否成功获取锁 - logDto.setLockAcquired(1); - // 锁持有者 - logDto.setLockHolder("auto_mode"); - // 操作人 - logDto.setCreateBy("条件控制"); - devOperLogService.save(logDto); - - - mqttMessageSender.publish("dtu/"+imei+"/down", message); - // 获取运行时间 - int runTime = RollerTimeCalculator.calculateRunTime(openLen); - if (runTime>0) { - autoOffManager.scheduleAutoOff(imei, roller+"k",runTime); - } - } catch (MqttException e) { - throw new RuntimeException(e); + // ========== 1. 前置参数校验(防御性判断) ========== + validateBaseParams(imei, agriName, roller); + if (vent == null) { + log.error("【开指令】设备{}卷膜{}风口大小为空,指令发送失败", imei, roller); + return; } + if (isFirstRun && reservedLen == null) { + log.error("【开指令】设备{}卷膜{}首次执行但预留风口为空,指令发送失败", imei, roller); + return; + } + + // ========== 2. 计算开指令风口长度 ========== + BigDecimal openLen = vent; + if (isFirstRun) { + log.info("【自动模式】设备【{}】卷膜【{}】今日首次执行开指令,叠加预留风口{}", imei, roller, reservedLen); + openLen = openLen.add(reservedLen); + } + String funcType = roller + "k1"; + String message = buildMqttMessage(funcType); + + // ========== 3. 执行核心指令逻辑 ========== + executeCommand(imei, agriName, roller, funcType, message, openLen, true); } /** + * 发送关指令 + * @param imei 设备唯一标识 + * @param agriName 大棚名称 + * @param roller 卷膜标识 * @param isFirstRun 是否第一次执行 * @param vent 每次执行的风口大小 */ private void sendCloseCommand(String imei, String agriName, String roller, boolean isFirstRun, BigDecimal vent) { + // ========== 1. 前置参数校验 ========== + validateBaseParams(imei, agriName, roller); + if (isFirstRun) { + log.info("【关指令】设备{}卷膜{}今日首次执行,直接返回不发送关指令", imei, roller); + return; + } + if (vent == null) { + log.error("【关指令】设备{}卷膜{}风口大小为空,指令发送失败", imei, roller); + return; + } + + // ========== 2. 构建关指令参数 ========== + String funcType = roller + "g1"; + String message = buildMqttMessage(funcType); + + // ========== 3. 执行核心指令逻辑 ========== + executeCommand(imei, agriName, roller, funcType, message, vent, false); + } + +// ========== 抽取公共方法:减少代码冗余 ========== + + /** + * 基础参数校验 + * @param imei 设备ID + * @param agriName 大棚名称 + * @param roller 卷膜标识 + */ + private void validateBaseParams(String imei, String agriName, String roller) { + if (StringUtils.isBlank(imei)) { + throw new IllegalArgumentException("设备IMEI不能为空"); + } + if (StringUtils.isBlank(agriName)) { + log.warn("【指令】设备{}卷膜{}大棚名称为空,仍继续执行指令", imei, roller); + } + if (StringUtils.isBlank(roller)) { + throw new IllegalArgumentException("卷膜标识不能为空"); + } + } + + /** + * 构建MQTT指令消息 + * @param funcType 功能类型(如roller1k1/roller1g1) + * @return 格式化的MQTT消息字符串 + */ + private String buildMqttMessage(String funcType) { + return String.format("{\"%s\":1}", funcType); + } + + /** + * 执行指令核心逻辑(分布式锁+日志记录+MQTT发布+自动关调度) + * @param imei 设备ID + * @param agriName 大棚名称 + * @param roller 卷膜标识 + * @param funcType 功能类型 + * @param message MQTT消息 + * @param len 风口长度(用于计算运行时间) + * @param isOpen 是否开指令 + */ + private void executeCommand(String imei, String agriName, String roller, String funcType, + String message, BigDecimal len, boolean isOpen) { + String lockKey = LOCK_PREFIX + imei + ":" + funcType; try { - // 如果是第一次直接返回 - if (isFirstRun) return; - String funcType = roller + "g1"; - String message = "{\"" + funcType + "\":1}"; - String lockKey = "lock:" + imei + ":" + funcType; + // ========== 1. 获取分布式锁 ========== Boolean lockSuccess = stringRedisTemplate.opsForValue().setIfAbsent( - lockKey, "auto_mode", dtuCtlLockTTL, TimeUnit.SECONDS // 延长至15秒,适配设备回执场景 + lockKey, AUTO_MODE, dtuCtlLockTTL, TimeUnit.SECONDS ); if (lockSuccess == null || !lockSuccess) { - log.warn("【分布式锁】前端【自动模式】下触发【{}】操作设备【{}】的【{}】功能失败;可能其他用户正在操作此功能", roller, imei, funcType); + log.warn("【分布式锁】自动模式下{}设备{}的{}功能失败,可能存在并发操作", + isOpen ? "开启" : "关闭", imei, funcType); return; } - // 3. 记录日志 - log.info("【指令处理】前端【自动模式】下触发【{}】操作设备【{}】的【{}】功能, 指令:{}" - , roller, imei, funcType, message); + // ========== 2. 记录操作日志 ========== + log.info("【指令处理】自动模式下触发{}设备{}的{}功能,指令:{}", + isOpen ? "开启" : "关闭", imei, funcType, message); + saveOperLog(imei, agriName, funcType, message, isOpen ? 1 : 0); + // ========== 3. 发布MQTT指令 ========== + mqttMessageSender.publish("dtu/" + imei + "/down", message); - SysDevOperLog logDto = new SysDevOperLog(); - // 大棚名称 - logDto.setAgriName(agriName); - // imei - logDto.setImei(imei); - // 卷膜n - logDto.setFuncCode(funcType); - // 运行1 暂停0 - logDto.setOpType(1); - // 来源 条件控制 - logDto.setOpSource(3); - // 指令 - logDto.setPayload(message); - // 是否成功获取锁 - logDto.setLockAcquired(1); - // 锁持有者 - logDto.setLockHolder("auto_mode"); - // 操作人 - logDto.setCreateBy("条件控制"); - devOperLogService.save(logDto); - - mqttMessageSender.publish("dtu/"+imei+"/down", "{\""+funcType+"\":1}"); - // 获取运行时间 - int runTime = RollerTimeCalculator.calculateRunTime(vent); + // ========== 4. 计算运行时间并调度自动关 ========== + int runTime = RollerTimeCalculator.calculateRunTime(len); if (runTime > 0) { - autoOffManager.scheduleAutoOff(imei, roller+"g",runTime); + String autoOffKey = roller + (isOpen ? "k" : "g"); + autoOffManager.scheduleAutoOff(imei, autoOffKey, runTime); + log.debug("【自动关调度】设备{}卷膜{}调度{}秒后自动关闭", imei, roller, runTime); } + } catch (MqttException e) { - throw new RuntimeException(e); + // ========== 异常处理:记录详细日志,不抛运行时异常 ========== + log.error("【MQTT异常】{}设备{}的{}功能指令发布失败,指令:{}", + isOpen ? "开启" : "关闭", imei, funcType, message, e); + } catch (Exception e) { + // 兜底异常捕获:避免未知异常导致锁无法释放(依赖TTL兜底) + log.error("【指令执行异常】{}设备{}的{}功能执行失败", + isOpen ? "开启" : "关闭", imei, funcType, e); + } + } + + /** + * 保存设备操作日志 + * @param imei 设备ID + * @param agriName 大棚名称 + * @param funcCode 功能码 + * @param payload 指令内容 + * @param opType 操作类型(1-开,0-关) + */ + private void saveOperLog(String imei, String agriName, String funcCode, String payload, int opType) { + SysDevOperLog logDto = new SysDevOperLog(); + logDto.setAgriName(agriName); + logDto.setImei(imei); + logDto.setFuncCode(funcCode); + logDto.setOpType(opType); + logDto.setOpSource(OP_SOURCE); + logDto.setPayload(payload); + logDto.setLockAcquired(LOCK_ACQUIRED); // 已获取锁 + logDto.setLockHolder(AUTO_MODE); + logDto.setCreateBy(CREATE_BY); + // 可选:增加异常捕获,避免日志保存失败影响指令执行 + try { + devOperLogService.save(logDto); + } catch (Exception e) { + log.error("【日志保存失败】设备{}功能{}日志保存失败", imei, funcCode, e); } } }