From 12fe073189eeee1f4fc57475d6b2e823c03478e4 Mon Sep 17 00:00:00 2001 From: lld <15027638633@163.com> Date: Wed, 11 Mar 2026 16:12:57 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=85=B3=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=88=9D=E6=AD=A5=E9=AA=8C=E8=AF=81=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/agri/common/constant/Constants.java | 6 + .../common/utils/RollerTimeCalculator.java | 11 +- .../interceptor/FrontendConfigHandler.java | 2 +- .../interceptor/FrontendControlHandler.java | 8 +- .../framework/manager/MqttAutoOffManager.java | 39 +-- .../web/dispatcher/MqttMessageDispatcher.java | 2 +- .../com/agri/quartz/task/RollerAutoTask.java | 245 ++++++++++-------- .../agri/system/mapper/SysDtuDataMapper.java | 4 + .../impl/SysDevOperLogServiceImpl.java | 3 +- .../service/impl/SysDtuDataServiceImpl.java | 27 +- .../mapper/system/SysDtuDataMapper.xml | 16 ++ .../mapper/system/SysRollerParamMapper.xml | 2 +- 12 files changed, 206 insertions(+), 159 deletions(-) diff --git a/agri-common/src/main/java/com/agri/common/constant/Constants.java b/agri-common/src/main/java/com/agri/common/constant/Constants.java index b83e6a4..7f266be 100644 --- a/agri-common/src/main/java/com/agri/common/constant/Constants.java +++ b/agri-common/src/main/java/com/agri/common/constant/Constants.java @@ -1,7 +1,9 @@ package com.agri.common.constant; +import java.math.BigDecimal; import java.util.Locale; import io.jsonwebtoken.Claims; +import org.springframework.beans.factory.annotation.Value; /** * 通用常量信息 @@ -173,4 +175,8 @@ public class Constants public static final String JM1G = "jm1g"; + public static final BigDecimal PER_LAP_LEN = BigDecimal.valueOf(2.13); + + public static final BigDecimal PER_LAP_SEC = BigDecimal.valueOf(18); + } diff --git a/agri-common/src/main/java/com/agri/common/utils/RollerTimeCalculator.java b/agri-common/src/main/java/com/agri/common/utils/RollerTimeCalculator.java index 202ed72..17e2f0f 100644 --- a/agri-common/src/main/java/com/agri/common/utils/RollerTimeCalculator.java +++ b/agri-common/src/main/java/com/agri/common/utils/RollerTimeCalculator.java @@ -8,6 +8,7 @@ package com.agri.common.utils; * @Version 1.0 */ +import com.agri.common.constant.Constants; import org.springframework.beans.factory.annotation.Value; import java.math.BigDecimal; @@ -17,12 +18,6 @@ import java.math.RoundingMode; * 卷膜时间计算工具类(企业标准写法:单例+常量固化) */ public class RollerTimeCalculator { - // 基础常量:固化到工具类,统一维护 - @Value("${agri.per-lap.len}") - private static BigDecimal perLapLen; - - @Value("${agri.per-lap.sec}") - private static BigDecimal perLapSec; // 私有化构造器,禁止实例化 private RollerTimeCalculator() {} @@ -38,8 +33,8 @@ public class RollerTimeCalculator { return 0; } // 核心公式:时间 = (长度 / 每圈长度) × 每圈时间 - BigDecimal cycleCount = targetLen.divide(perLapLen,2, RoundingMode.HALF_UP); - return cycleCount.multiply(perLapSec).setScale(2, RoundingMode.HALF_UP).intValue(); // 四舍五入取整 + BigDecimal cycleCount = targetLen.divide(Constants.PER_LAP_LEN,2, RoundingMode.HALF_UP); + return cycleCount.multiply(Constants.PER_LAP_SEC).setScale(2, RoundingMode.HALF_UP).intValue(); // 四舍五入取整 } /** diff --git a/agri-framework/src/main/java/com/agri/framework/interceptor/FrontendConfigHandler.java b/agri-framework/src/main/java/com/agri/framework/interceptor/FrontendConfigHandler.java index befa4fd..edccd57 100644 --- a/agri-framework/src/main/java/com/agri/framework/interceptor/FrontendConfigHandler.java +++ b/agri-framework/src/main/java/com/agri/framework/interceptor/FrontendConfigHandler.java @@ -89,7 +89,7 @@ public class FrontendConfigHandler { } // 转发前端指令 String deviceTopic = "dtu/" + deviceId + "/down"; -// mqttMessageSender.publish(deviceTopic, payload); + mqttMessageSender.publish(deviceTopic, payload); LocalDateTime currentTime = LocalDateTime.now(); // 3. 记录日志 log.info("【指令处理】前端{}于{}控制设备{}的{}功能,指令:{}", diff --git a/agri-framework/src/main/java/com/agri/framework/interceptor/FrontendControlHandler.java b/agri-framework/src/main/java/com/agri/framework/interceptor/FrontendControlHandler.java index 1cdd71b..de2b240 100644 --- a/agri-framework/src/main/java/com/agri/framework/interceptor/FrontendControlHandler.java +++ b/agri-framework/src/main/java/com/agri/framework/interceptor/FrontendControlHandler.java @@ -128,13 +128,13 @@ public class FrontendControlHandler { }); } catch (Exception e) { log.error("【指令处理】功能码解析失败,payload={}", payload, e); - // String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener"; - // mqttMessageSender.publish(errorTopic, "{\"msg\":\"指令格式错误\"}"); + String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener"; + mqttMessageSender.publish(errorTopic, "{\"msg\":\"指令格式错误\"}"); return; } if (funcCodeMap == null || funcCodeMap.isEmpty()) { - // String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener"; - // mqttMessageSender.publish(errorTopic, "{\"msg\":\"功能码不能为空\"}"); + String errorTopic = "frontend/" + clientId + "/dtu/" + deviceId + "/listener"; + mqttMessageSender.publish(errorTopic, "{\"msg\":\"功能码不能为空\"}"); log.warn("【指令处理】前端{}操作设备{}失败:功能码为空", clientId, deviceId); return; } diff --git a/agri-framework/src/main/java/com/agri/framework/manager/MqttAutoOffManager.java b/agri-framework/src/main/java/com/agri/framework/manager/MqttAutoOffManager.java index a75e6b9..bcaac5e 100644 --- a/agri-framework/src/main/java/com/agri/framework/manager/MqttAutoOffManager.java +++ b/agri-framework/src/main/java/com/agri/framework/manager/MqttAutoOffManager.java @@ -280,25 +280,26 @@ public class MqttAutoOffManager { JSONObject down = new JSONObject(); down.put(funcType, 0); log.info("触发自动化条件"); -// mqttMessageSender.publish(deviceTopic, down.toJSONString()); -// SysAgriInfo agriInfo = sysAgriInfoService.lambdaQuery() -// .eq(SysAgriInfo::getImei, deviceId) -// .one(); -// String agriName = (agriInfo!=null && ObjectUtils.isNotEmpty(agriInfo.getAgriName()))?agriInfo.getAgriName():null; -// SysDevOperLog logDto = new SysDevOperLog(); -// logDto.setAgriName(agriName); -// logDto.setImei(deviceId); -// logDto.setFuncCode(funcType); -// logDto.setOpType(0); -// logDto.setOpSource(2); -// logDto.setPayload(down.toJSONString()); -// logDto.setLockAcquired(1); -// logDto.setLockHolder("autoOff"); -// logDto.setExecResult(1); -// logDto.setLatestState(latest); -// logDto.setCreateBy("自动关"); -// logDto.setTaskStatus(getFutureStatus().toString()); -// sysDevOperLogService.save(logDto); + //todo + mqttMessageSender.publish(deviceTopic, down.toJSONString()); + SysAgriInfo agriInfo = sysAgriInfoService.lambdaQuery() + .eq(SysAgriInfo::getImei, deviceId) + .one(); + String agriName = (agriInfo!=null && ObjectUtils.isNotEmpty(agriInfo.getAgriName()))?agriInfo.getAgriName():null; + SysDevOperLog logDto = new SysDevOperLog(); + logDto.setAgriName(agriName); + logDto.setImei(deviceId); + logDto.setFuncCode(funcType); + logDto.setOpType(0); + logDto.setOpSource(2); + logDto.setPayload(down.toJSONString()); + logDto.setLockAcquired(1); + logDto.setLockHolder("autoOff"); + logDto.setExecResult(1); + logDto.setLatestState(latest); + logDto.setCreateBy("自动关"); + logDto.setTaskStatus(getFutureStatus().toString()); + sysDevOperLogService.save(logDto); log.info("【自动关任务】检测仍在运行,已下发关闭:deviceId={}, funcType={}, payload={}", deviceId, funcType, down.toJSONString()); } diff --git a/agri-framework/src/main/java/com/agri/framework/web/dispatcher/MqttMessageDispatcher.java b/agri-framework/src/main/java/com/agri/framework/web/dispatcher/MqttMessageDispatcher.java index f5f3f1c..79a58f7 100644 --- a/agri-framework/src/main/java/com/agri/framework/web/dispatcher/MqttMessageDispatcher.java +++ b/agri-framework/src/main/java/com/agri/framework/web/dispatcher/MqttMessageDispatcher.java @@ -59,7 +59,7 @@ public class MqttMessageDispatcher { public void handleMessage(String topic, String payload) { try { // log.info("【MQTT接收】topic={}, payload={}", topic, payload); -// if (env.acceptsProfiles("dev")) return; + if (env.acceptsProfiles("dev")) return; // 设备状态主题:dtu/{deviceId}/up if (topic.matches("dtu/\\w+/\\w+")) { deviceStatusHandler.handle(topic, payload); 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 1bd500d..c2dedf2 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,7 +1,9 @@ package com.agri.quartz.task; +import cn.hutool.core.map.MapUtil; import com.agri.common.enums.TempCommandStatus; import com.agri.common.utils.*; +import com.agri.common.utils.wechat.WxUtil; import com.agri.framework.config.MqttConfig; import com.agri.framework.manager.MqttAutoOffManager; import com.agri.system.domain.SysAgriInfo; @@ -23,6 +25,8 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -73,6 +77,9 @@ public class RollerAutoTask { @Value("${spring.mqtt.dtu-ctl-lock-ttl}") private int dtuCtlLockTTL; + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); + + // ========== 常量定义(新增) ========== private static final int WORK_MODE_AUTO = 1; // 自动模式 private static final int NOT_DELETED = 0; // 未删除 @@ -83,125 +90,151 @@ public class RollerAutoTask { private static final int LOCK_ACQUIRED = 1; // 是否获取锁 public void checkAutoTerm() { + try { + log.info("=============【定时任务】大棚自动模式监测开始执行,时间:{}=============", LocalDateTime.now()); + // 查询自动模式的大棚 + List agriInfos = agriInfoService.lambdaQuery() + .select(SysAgriInfo::getImei, SysAgriInfo::getAgriName) + .eq(SysAgriInfo::getWorkMode, WORK_MODE_AUTO) + .eq(SysAgriInfo::getIsDeleted, NOT_DELETED) + .list(); + if (CollectionUtils.isEmpty(agriInfos)) return; + // 取imei集合 + List imeiList = agriInfos.stream().map(SysAgriInfo::getImei).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(imeiList)) return; - // 查询自动模式的大棚 - List agriInfos = agriInfoService.lambdaQuery() - .select(SysAgriInfo::getImei, SysAgriInfo::getAgriName) - .eq(SysAgriInfo::getWorkMode, WORK_MODE_AUTO) - .eq(SysAgriInfo::getIsDeleted, NOT_DELETED) - .list(); - if (CollectionUtils.isEmpty(agriInfos)) return; - // 取imei集合 - List imeiList = agriInfos.stream().map(SysAgriInfo::getImei).collect(Collectors.toList()); - if (CollectionUtils.isEmpty(imeiList)) return; + // 根据imei 查询dtu_data最后一条温度数据 + List> dtuDataList = dtuDataService.getLastDtuDataByImeiList(imeiList); + if (CollectionUtils.isEmpty(dtuDataList)) return; + // 根据温湿度imei分组 + Map>> dtuDataByImeiMap + = dtuDataList.stream().collect(Collectors.groupingBy(map -> (String) map.get("imei"))); - // 根据imei 查询dtu_data最后一条温度数据 - List> dtuDataList = dtuDataService.getLastDtuDataByImeiList(imeiList); - if (CollectionUtils.isEmpty(dtuDataList)) return; - // 根据温湿度imei分组 - Map>> dtuDataByImeiMap - = dtuDataList.stream().collect(Collectors.groupingBy(map -> (String) map.get("imei"))); - - // 获取所有开启自动化模式的大棚列表的卷膜参数和条件设置 - List rollerTermList = rollerParamService.getRollerTerms(imeiList); - if (CollectionUtils.isEmpty(rollerTermList)) { - // todo 无参数设置和条件列表直接返回 - log.error("【定时任务-卷膜自动化控制】无参数设置和条件列表直接返回!"); - return; - } - // 按imei分组 → 再按roller分组(一步到位) - Map>> rollerTermMap = rollerTermList.stream() - .collect(Collectors.groupingBy( - RollerTermVO::getImei, - Collectors.groupingBy(RollerTermVO::getRoller) - )); - - // 查询每个IMEI今天的第一条日志 - Map> todayLogCountByImeiMap = devOperLogService.getTodayLogCountByImeiMap(imeiList); - // 循环所有开启自动化的大棚 - for (SysAgriInfo agriInfo : agriInfos) { - String imei = agriInfo.getImei(); - String agriName = agriInfo.getAgriName(); - // 获取温湿度指定imei的数据 - List> dtuDataInfo = dtuDataByImeiMap.get(imei); - // 该大棚温湿度不存在 - if (CollectionUtils.isEmpty(dtuDataInfo)) { - // todo 该大棚下1分钟内无最新温湿度,怀疑离线 - log.error("【定时任务-卷膜自动化控制】大棚『{}』1分钟内无最新温湿度,怀疑离线",imei); - continue; + // 获取所有开启自动化模式的大棚列表的卷膜参数和条件设置 + List rollerTermList = rollerParamService.getRollerTerms(imeiList); + if (CollectionUtils.isEmpty(rollerTermList)) { + // todo 无参数设置和条件列表直接返回 + log.error("【定时任务-卷膜自动化控制】无参数设置和条件列表直接返回!"); + return; } + // 按imei分组 → 再按roller分组(一步到位) + Map>> rollerTermMap = rollerTermList.stream() + .collect(Collectors.groupingBy( + RollerTermVO::getImei, + Collectors.groupingBy(RollerTermVO::getRoller) + )); - // 最后一条对应imei对应温度 及时间 - Map dtuData = dtuDataInfo.get(0); - // 求温度上报时间 - LocalDateTime dtuTime = TimeConvertUtil.strToLocalDateTimeSafe((String) dtuData.get("time")); - if (dtuTime == null) { - // todo 当前大棚温湿度时间为空 跳过 - log.error("【定时任务-卷膜自动化控制】大棚『{}』温湿度时间「{}」为空, 跳过",imei, LocalDateTime.now().minusMinutes(1)); - continue; - } - - // 获取当前imei下的所有参数设置以及卷膜自动化条件设置 - Map> configTermByRollerMap = rollerTermMap.get(imei); - if (configTermByRollerMap.isEmpty()) { - // todo 当前大棚下没有设置条件或者参数 - log.error("【定时任务-卷膜自动化控制】大棚『{}』当前大棚下没有设置条件或者参数",imei); - continue; - } - - // 获取今天对应imei的日志 - Map todayLogByRoller = todayLogCountByImeiMap.get(imei); - - for (Map.Entry> configEntry : configTermByRollerMap.entrySet()) { - String roller = configEntry.getKey(); // 当前卷膜 - List terms = configEntry.getValue(); // 当前卷膜条件 - // 每个卷膜分组只会有一个卷膜参数设置,所有取第一个即可 - if (terms == null || terms.isEmpty()) { - // todo 当前卷膜 无参数设置,跳过当前roller - log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」无参数设置,跳过当前roller",imei, roller); + // 查询每个IMEI今天的第一条日志 + Map> todayLogCountByImeiMap = devOperLogService.getTodayLogCountByImeiMap(imeiList); + // 循环所有开启自动化的大棚 + for (SysAgriInfo agriInfo : agriInfos) { + String imei = agriInfo.getImei(); + String agriName = agriInfo.getAgriName(); + // 获取温湿度指定imei的数据 + List> dtuDataInfo = dtuDataByImeiMap.get(imei); + // 该大棚温湿度不存在 + if (CollectionUtils.isEmpty(dtuDataInfo)) { + // todo 该大棚下1分钟内无最新温湿度,怀疑离线 + log.error("【定时任务-卷膜自动化控制】大棚『{}』1分钟内无最新温湿度,怀疑离线", imei); continue; } - // 获取卷膜参数 - RollerTermVO rollerConfig = terms.get(0); - String refTempCode = rollerConfig.getRefTempCode(); // 参考温度 - BigDecimal ventTotalLen = rollerConfig.getVentTotalLen(); // 风口总长 - BigDecimal reservedLen = rollerConfig.getReservedLen(); // 预留风口 - // 防御性判断:避免dtuData.get(refTempCode)为null - Object tempObj = dtuData.get(refTempCode); - if (tempObj == null) { - // todo 当前卷膜参考温度设置为空 - log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」参考温度设置为空",imei, roller); + // 最后一条对应imei对应温度 及时间 + Map dtuData = dtuDataInfo.get(0); + // 求温度上报时间 + LocalDateTime dtuTime = (LocalDateTime) dtuData.get("time"); + if (dtuTime == null) { + // todo 当前大棚温湿度时间为空 跳过 + log.error("【定时任务-卷膜自动化控制】大棚『{}』温湿度时间「{}」为空, 跳过", imei, LocalDateTime.now().minusMinutes(1)); continue; } - // 优化:明确标注为当前roller的参考温度快照(仅解析一次) - BigDecimal currentTemp = new BigDecimal(tempObj.toString()); - // 每个roller单独定义isFirstRun(作用域:当前roller) - boolean isFirstRun = todayLogByRoller.getOrDefault(roller, 0) == 0; + // 获取当前imei下的所有参数设置以及卷膜自动化条件设置 + Map> configTermByRollerMap = rollerTermMap.get(imei); + if (MapUtil.isEmpty(configTermByRollerMap)) { + // todo 当前大棚下没有设置条件或者参数 + log.error("【定时任务-卷膜自动化控制】大棚『{}』当前大棚下没有设置条件或者参数, 跳过", imei); + continue; + } - // 遍历当前roller的所有term(改用普通for循环,可读性更高) - for (RollerTermVO term : terms) { - // 判断该温度上报时间是否在该条件设置的时间范围内 - boolean inRange = TimeRangeUtil.isTimeInRange(dtuTime, term.getStartTime(), term.getEndTime()); - // 在范围内 - if (inRange) { + // 获取今天对应imei的日志 + Map todayLogByRoller = todayLogCountByImeiMap.get(imei); + for (Map.Entry> configEntry : configTermByRollerMap.entrySet()) { + String roller = configEntry.getKey(); // 当前卷膜 + List terms = configEntry.getValue(); // 当前卷膜条件 + // 每个卷膜分组只会有一个卷膜参数设置,所有取第一个即可 + if (terms == null || terms.isEmpty()) { + // todo 当前卷膜 无参数设置,跳过当前roller + log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」无参数设置,跳过当前roller", imei, roller); + continue; + } + LocalDateTime startTime = terms.stream() + .max(Comparator.comparing(RollerTermVO::getStartTime)) + .get() + .getStartTime(); + // 获取卷膜参数 + RollerTermVO rollerConfig = terms.get(0); + String refTempCode = rollerConfig.getRefTempCode(); // 参考温度 + BigDecimal ventTotalLen = rollerConfig.getVentTotalLen(); // 风口总长 + BigDecimal reservedLen = rollerConfig.getReservedLen(); // 预留风口 + + // 防御性判断:避免dtuData.get(refTempCode)为null + Object tempObj = dtuData.get(refTempCode); + if (tempObj == null) { + // todo 当前卷膜参考温度设置为空 + log.error("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」参考温度设置为空,跳过!", imei, roller); + continue; + } + // 优化:明确标注为当前roller的参考温度快照(仅解析一次) + BigDecimal currentTemp = new BigDecimal(tempObj.toString()); + + // 遍历当前roller的所有term(改用普通for循环,可读性更高) + for (RollerTermVO term : terms) { + // 判断该温度上报时间是否在该条件设置的时间范围内 + boolean inRange = TimeRangeUtil.isTimeInRange(dtuTime, term.getStartTime(), term.getEndTime()); + boolean isCancelOff = startTime.equals(term.getStartTime()); + log.info("\n【定时任务-卷膜自动化控制】正在监测大棚『{}』-卷膜『{}』:" + + "条件设置详情,监控时间范围:『{}~{}』,适宜温度:{}℃,运行风口:{}cm。「{}」", + imei,roller,term.getStartTime().format(FORMATTER),term.getEndTime().format(FORMATTER), + term.getTemp(),term.getVent(), isCancelOff?"当前为卷膜「"+roller+"」最后一条自动化规则":""); + if (!inRange) { + log.info("【定时任务-卷膜自动化控制】大棚『{}』当前卷膜「{}」温湿度时间:「{}」不在监控时间范围内「{}~{}」,跳过!", + imei, roller, dtuTime,term.getStartTime().format(FORMATTER),term.getEndTime().format(FORMATTER)); + continue; + } + // 在范围内 //判断温度是否在适宜温度内 TempCommandStatus tempCommandStatus = TempJudgeUtil.judgeTempCommand(currentTemp, term.getTemp()); // todo 开关指令需要通知用户 推送主题 && 更新数据 前端重新请求消息表 if (tempCommandStatus == TempCommandStatus.OPEN) { + log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度:『{}℃』,适宜温度为:「{}℃」,触发自动化条件,即将执行『开』指令!", + imei, roller, currentTemp, term.getTemp()); + // 判断是否首次开 + Integer openLen = todayLogByRoller.getOrDefault(roller + "k1", 0); // 开指令 - sendOpenCommand(imei,agriName, roller, isFirstRun, term.getVent(), reservedLen); - isFirstRun = false; + sendOpenCommand(imei, agriName, roller, openLen == 0, term.getVent(), reservedLen); + // 每次后,数量累计+1 不需要因为只循环到一次 } else if (tempCommandStatus == TempCommandStatus.CLOSE) { + log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度:『{}℃』,适宜温度为:「{}℃」,触发自动化条件,即将执行『关』指令!", + imei, roller, currentTemp, term.getTemp()); + // 判断是否首次开 + Integer closeLen = todayLogByRoller.getOrDefault(roller + "g1", 0); // 关指令 - sendCloseCommand(imei, agriName, roller, isFirstRun, term.getVent()); - isFirstRun = false; + sendCloseCommand(imei, agriName, roller, closeLen == 0, term.getVent(), isCancelOff); + // 每次后,数量累计+1 + } else { + log.info("【定时任务-卷膜自动化控制】大棚『{}』-卷膜『{}』当前温湿度:『{}℃』,适宜温度为:「{}℃」,温度适宜,无需操作!", + imei, roller, currentTemp, term.getTemp()); } + } } } + log.info("=============【定时任务】大棚自动模式监测执行完毕,时间:{}=============", LocalDateTime.now()); + } catch (Exception e) { + WxUtil.pushText("【定时任务】大棚自动模式监测执行终端, \n发生错误.错误原因:"+e+",\n时间:"+LocalDateTime.now()); + log.error("\n=============【定时任务】大棚自动模式监测执行终端,\n发生错误.错误原因:{},\n时间:{}=============", e, LocalDateTime.now()); } } @@ -238,7 +271,7 @@ public class RollerAutoTask { String message = buildMqttMessage(funcType); // ========== 3. 执行核心指令逻辑 ========== - executeCommand(imei, agriName, roller, funcType, message, openLen, true); + executeCommand(imei, agriName, roller, funcType, message, openLen, true, false); } /** @@ -249,7 +282,8 @@ public class RollerAutoTask { * @param isFirstRun 是否第一次执行 * @param vent 每次执行的风口大小 */ - private void sendCloseCommand(String imei, String agriName, String roller, boolean isFirstRun, BigDecimal vent) { + private void sendCloseCommand(String imei, String agriName, String roller, + boolean isFirstRun, BigDecimal vent, boolean isCancelOff) { // ========== 1. 前置参数校验 ========== validateBaseParams(imei, agriName, roller); if (isFirstRun) { @@ -266,7 +300,7 @@ public class RollerAutoTask { String message = buildMqttMessage(funcType); // ========== 3. 执行核心指令逻辑 ========== - executeCommand(imei, agriName, roller, funcType, message, vent, false); + executeCommand(imei, agriName, roller, funcType, message, vent, false, isCancelOff); } // ========== 抽取公共方法:减少代码冗余 ========== @@ -309,7 +343,7 @@ public class RollerAutoTask { * @param isOpen 是否开指令 */ private void executeCommand(String imei, String agriName, String roller, String funcType, - String message, BigDecimal len, boolean isOpen) { + String message, BigDecimal len, boolean isOpen, boolean isCancelOff) { String lockKey = LOCK_PREFIX + imei + ":" + funcType; try { // ========== 1. 获取分布式锁 ========== @@ -327,15 +361,18 @@ public class RollerAutoTask { isOpen ? "开启" : "关闭", imei, funcType, message); saveOperLog(imei, agriName, funcType, message, isOpen ? 1 : 0); - // ========== 3. 发布MQTT指令 ========== -// mqttMessageSender.publish("dtu/" + imei + "/down", message); - + // ========== 3. 发布MQTT指令 ==========todo + mqttMessageSender.publish("dtu/" + imei + "/down", message); + if (isCancelOff) { + log.debug("【自动关调度】设备{}卷膜{}:触发最后一条自动化条件,下发关闭指令,无需暂停!时间:{}", imei, roller, LocalDateTime.now()); + return; + } // ========== 4. 计算运行时间并调度自动关 ========== int runTime = RollerTimeCalculator.calculateRunTime(len); if (runTime > 0) { String autoOffKey = roller + (isOpen ? "k" : "g"); autoOffManager.scheduleAutoOff(imei, autoOffKey, runTime); - log.debug("【自动关调度】设备{}卷膜{}调度{}秒后自动关闭", imei, roller, runTime); + log.debug("【自动关调度】设备{}卷膜「{}:{}」调度{}秒后自动关闭", imei, roller,(isOpen?"开":"关"), runTime); } } diff --git a/agri-system/src/main/java/com/agri/system/mapper/SysDtuDataMapper.java b/agri-system/src/main/java/com/agri/system/mapper/SysDtuDataMapper.java index c3e8953..a1a3738 100644 --- a/agri-system/src/main/java/com/agri/system/mapper/SysDtuDataMapper.java +++ b/agri-system/src/main/java/com/agri/system/mapper/SysDtuDataMapper.java @@ -1,10 +1,12 @@ package com.agri.system.mapper; import java.util.List; +import java.util.Map; import com.agri.system.domain.SysAgriAlarmRelation; import com.agri.system.domain.SysDtuData; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; /** * DTU温湿度上报数据Mapper接口 @@ -63,4 +65,6 @@ public interface SysDtuDataMapper extends BaseMapper * @return 结果 */ public int deleteSysDtuDataByIds(Long[] ids); + + List> getLastDtuDataByImeiList(@Param("imeiList") List imeiList); } diff --git a/agri-system/src/main/java/com/agri/system/service/impl/SysDevOperLogServiceImpl.java b/agri-system/src/main/java/com/agri/system/service/impl/SysDevOperLogServiceImpl.java index a7c3797..b59faf7 100644 --- a/agri-system/src/main/java/com/agri/system/service/impl/SysDevOperLogServiceImpl.java +++ b/agri-system/src/main/java/com/agri/system/service/impl/SysDevOperLogServiceImpl.java @@ -122,6 +122,7 @@ public class SysDevOperLogServiceImpl extends ServiceImpl()); } // 4.2 填充统计结果 for (Map row : sqlResult) { diff --git a/agri-system/src/main/java/com/agri/system/service/impl/SysDtuDataServiceImpl.java b/agri-system/src/main/java/com/agri/system/service/impl/SysDtuDataServiceImpl.java index c688ca4..780a4ee 100644 --- a/agri-system/src/main/java/com/agri/system/service/impl/SysDtuDataServiceImpl.java +++ b/agri-system/src/main/java/com/agri/system/service/impl/SysDtuDataServiceImpl.java @@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -115,28 +116,14 @@ public class SysDtuDataServiceImpl extends ServiceImpl> getLastDtuDataByImeiList(List imeiList) { - // 1. 计算最后一分钟的起始时间(当前时间 - 1分钟) - LocalDateTime lastOneMinute = LocalDateTime.now().minusMinutes(1); - // 2. 子查询:按imei分组 + 最后一分钟过滤 + 取每组最大ID - QueryWrapper subQuery = new QueryWrapper<>(); - subQuery.select("imei", "MAX(id) as max_id") // 雪花ID的MAX(id)就是最新记录 - .in("imei", imeiList) - .ge("time", lastOneMinute) - .groupBy("imei"); + if (imeiList == null || imeiList.isEmpty()) { + log.error("imeiList列表为空"); + return Collections.emptyList(); + } - // 3. 主查询:通过MAX(id)匹配完整数据 + 指定字段 - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.inSql(SysDtuData::getId, - "SELECT max_id FROM (" + subQuery.getCustomSqlSegment() + ") t") - .select(SysDtuData::getImei, - SysDtuData::getTemp1, - SysDtuData::getTemp2, - SysDtuData::getTemp3, - SysDtuData::getTemp4, - SysDtuData::getTime); - - return baseMapper.selectMaps(queryWrapper); + // 3. 执行查询(如果用 ServiceImpl,直接调 list(mainQueryWrapper) 即可) + return baseMapper.getLastDtuDataByImeiList(imeiList); } } diff --git a/agri-system/src/main/resources/mapper/system/SysDtuDataMapper.xml b/agri-system/src/main/resources/mapper/system/SysDtuDataMapper.xml index 078c51a..331ee2b 100644 --- a/agri-system/src/main/resources/mapper/system/SysDtuDataMapper.xml +++ b/agri-system/src/main/resources/mapper/system/SysDtuDataMapper.xml @@ -136,4 +136,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by `time` desc limit 1 + \ No newline at end of file diff --git a/agri-system/src/main/resources/mapper/system/SysRollerParamMapper.xml b/agri-system/src/main/resources/mapper/system/SysRollerParamMapper.xml index 618a037..19e8c02 100644 --- a/agri-system/src/main/resources/mapper/system/SysRollerParamMapper.xml +++ b/agri-system/src/main/resources/mapper/system/SysRollerParamMapper.xml @@ -107,7 +107,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" at.start_time, at.end_time FROM sys_roller_param rp - LEFT JOIN sys_auto_term at + LEFT JOIN sys_auto_term `at` ON rp.imei = at.imei AND rp.roller = at.roller -- 关键:按imei+roller双字段关联(精准匹配) WHERE rp.imei IN