diff --git a/fastbee-admin/src/main/resources/application-dev.yml b/fastbee-admin/src/main/resources/application-dev.yml index 4bac352e..53d62bc0 100644 --- a/fastbee-admin/src/main/resources/application-dev.yml +++ b/fastbee-admin/src/main/resources/application-dev.yml @@ -127,7 +127,7 @@ emqx: ApiSecret: emqx.api.123 # sip 配置 sip: - enabled: true # 是否启用视频监控SIP,true为启用 + enabled: false # 是否启用视频监控SIP,true为启用 ## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP ## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP ip: 192.168.110.78 # 本服务启动的 IP diff --git a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java index e6fdf814..9d011c36 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java +++ b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java @@ -275,6 +275,11 @@ public interface FastBeeConstant { */ String DEVICE_OTA_DATA = "device:ota:"; + /** + * 设备上电触发指令缓存 + */ + String POWER_ON_TRIGGER_COMMAND = "power:on:trigger:command:"; + } interface PROTOCOL { diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java b/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java index 5763c344..83e77d17 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java +++ b/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java @@ -134,5 +134,13 @@ public class RedisKeyBuilder { return CacheConstants.DEVICE_OTA_DATA + serialNumber; } + /** + * 设备上电触发指令缓存key + * 用于存储设备上电触发任务下发的指令片段(索引2到11位) + */ + public static String buildPowerOnTriggerCommandKey(String serialNumber){ + return FastBeeConstant.REDIS.POWER_ON_TRIGGER_COMMAND + serialNumber.toUpperCase(); + } + } diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelValuesInput.java b/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelValuesInput.java index 173673e3..4d14b8f9 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelValuesInput.java +++ b/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelValuesInput.java @@ -22,6 +22,9 @@ public class ThingsModelValuesInput /** 设备物模型值的集合 **/ private List thingsModelSimpleItem; + /** 原始消息数据包(用于判断设备上电触发回包等) **/ + private Object sources; + public Long getDeviceId() { return deviceId; } @@ -53,4 +56,12 @@ public class ThingsModelValuesInput public void setThingsModelValueRemarkItem(List thingsModelSimpleItem) { this.thingsModelSimpleItem = thingsModelSimpleItem; } + + public Object getSources() { + return sources; + } + + public void setSources(Object sources) { + this.sources = sources; + } } diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/CaculateUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/CaculateUtils.java index 94b3745f..b27098b3 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/utils/CaculateUtils.java +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/CaculateUtils.java @@ -19,9 +19,9 @@ public class CaculateUtils { /** * /* - * 暂时只支持加减乘除及括号的应用 + * 支持加减乘除、幂运算、取余及括号的应用 */ - private static final String symbol = "+-,*/,(),%"; + private static final String symbol = "+-,*/,(),%^"; /** @@ -105,6 +105,10 @@ public class CaculateUtils { } int length = b.toString().split("\\.")[1].length(); return a.divide(b, length, BigDecimal.ROUND_HALF_UP); + case '^': + // 幂运算:a的b次方 + double result = Math.pow(a.doubleValue(), b.doubleValue()); + return BigDecimal.valueOf(result).stripTrailingZeros(); default: throw new RuntimeException("操作符号异常!"); } @@ -212,7 +216,19 @@ public class CaculateUtils { public static String caculateReplace(String str, Map map) { for (Map.Entry entry : map.entrySet()) { - str = str.replaceAll(entry.getKey(), entry.getValue()==null ? "1" : entry.getValue()); + // 对于包含特殊字符的占位符(如 {identifier}),需要转义正则表达式特殊字符 + String key = entry.getKey(); + String value = entry.getValue() == null ? "1" : entry.getValue(); + // 如果占位符包含正则表达式特殊字符,使用 Pattern.quote() 转义 + if (key.contains("{") || key.contains("}") || key.contains("(") || key.contains(")") + || key.contains("[") || key.contains("]") || key.contains("*") || key.contains("+") + || key.contains("?") || key.contains("^") || key.contains("$") || key.contains("|")) { + // 转义正则表达式特殊字符 + String escapedKey = java.util.regex.Pattern.quote(key); + str = str.replaceAll(escapedKey, value); + } else { + str = str.replaceAll(key, value); + } } return str; } diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java index 445e3b0f..a6dfe2d2 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java @@ -53,7 +53,10 @@ public class TraceIdUtils { public static long generateTraceId() { long id = getIdWorker().nextId(); // 使用绝对值确保TraceId为正数,避免负数显示 + // 注意:Math.abs(id) * 1000 可能会溢出,所以需要对最终结果再次使用Math.abs() long traceId = Math.abs(id) * 1000 + ThreadLocalRandom.current().nextInt(1000); + // 确保最终结果为非负数(处理溢出情况) + traceId = Math.abs(traceId); MDC.put(TRACE_ID_KEY, String.valueOf(traceId)); return traceId; } diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdUtils.java index 61d01526..ae8d5956 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdUtils.java +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdUtils.java @@ -54,7 +54,8 @@ public class IdUtils } if(lastTimestamp == timestamp) { - this.sequence = this.sequence + 1L & 4095L; + // 修复:使用括号确保正确的运算顺序 + this.sequence = (this.sequence + 1L) & 4095L; if(this.sequence == 0L) { timestamp = this.tilNextMillis(lastTimestamp); } @@ -63,7 +64,18 @@ public class IdUtils } lastTimestamp = timestamp; - return timestamp - 1288834974657L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence; + + // 修复:使用括号确保正确的运算符优先级,避免负数 + // 原代码:timestamp - 1288834974657L << 22 会被解析为 timestamp - (1288834974657L << 22) + // 修复后:((timestamp - 1288834974657L) << 22) 确保先减法后位移 + long id = ((timestamp - 1288834974657L) << 22) + | (this.datacenterId << 17) + | (this.workerId << 12) + | this.sequence; + + // 确保返回的ID为正数(通过清除符号位) + // 如果id为负数,说明计算结果超出了预期,使用绝对值 + return id < 0 ? Math.abs(id) : id; } private long tilNextMillis(long lastTimestamp) { diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdWorker.java b/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdWorker.java new file mode 100644 index 00000000..ae1945cc --- /dev/null +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdWorker.java @@ -0,0 +1,122 @@ +package com.fastbee.common.utils.uuid; + +import lombok.extern.slf4j.Slf4j; + +/** + * 系统编码生成器(改进版) + * 修复了原版本中可能导致负数的问题 + * + * @author fastbee + * @date 2025-01-20 + */ +@Slf4j +public class IdWorker { + + private final long workerId; + /** 起始时间戳:2015-01-20 00:00:00 */ + private final static long twepoch = 1421976968066L; + private long sequence = 0L; + /** 机器ID占用的位数 */ + private final static long workerIdBits = 2L; + /** 支持的最大机器ID */ + public final static long maxWorkerId = -1L ^ (-1L << workerIdBits); + /** 序列号占用的位数 */ + private final static long sequenceBits = 9L; + + /** 机器ID向左移的位数 */ + private final static long workerIdShift = sequenceBits; + /** 时间戳向左移的位数 */ + private final static long timestampLeftShift = sequenceBits + workerIdBits; + /** 生成序列的掩码 */ + public final static long sequenceMask = -1L ^ (-1L << sequenceBits); + + private long lastTimestamp = -1L; + + /** + * 构造函数 + * + * @param workerId 机器ID (0-3) + */ + public IdWorker(final long workerId) { + super(); + if (workerId > IdWorker.maxWorkerId || workerId < 0) { + throw new IllegalArgumentException(String.format( + "worker Id can't be greater than %d or less than 0", + IdWorker.maxWorkerId)); + } + this.workerId = workerId; + } + + /** + * 生成下一个ID + * 修复了原代码中的运算符优先级问题,确保不会产生负数 + * + * @return 生成的ID(始终为正数) + */ + public synchronized long nextId() { + long timestamp = this.timeGen(); + + // 时钟回拨检查 + if (timestamp < this.lastTimestamp) { + long offset = this.lastTimestamp - timestamp; + log.warn("Clock moved backwards. Refusing to generate id for {} milliseconds", offset); + // 时钟回拨时,等待时钟追上 + timestamp = this.tilNextMillis(this.lastTimestamp); + } + + if (this.lastTimestamp == timestamp) { + // 修复:使用括号确保正确的运算顺序 + // 原代码:this.sequence + 1 & IdWorker.sequenceMask 会被解析为 this.sequence + (1 & IdWorker.sequenceMask) + // 修复后:(this.sequence + 1) & IdWorker.sequenceMask 确保先加法后按位与 + this.sequence = (this.sequence + 1) & IdWorker.sequenceMask; + if (this.sequence == 0) { + // 序列号溢出,等待下一毫秒 + timestamp = this.tilNextMillis(this.lastTimestamp); + } + } else { + this.sequence = 0; + } + + this.lastTimestamp = timestamp; + + // 修复:使用括号确保正确的运算符优先级,避免负数 + // 原代码:timestamp - twepoch << timestampLeftShift 会被解析为 timestamp - (twepoch << timestampLeftShift) + // 修复后:((timestamp - twepoch) << timestampLeftShift) 确保先减法后位移 + long id = ((timestamp - twepoch) << timestampLeftShift) + | (this.workerId << IdWorker.workerIdShift) + | (this.sequence); + + // 确保返回的ID为正数 + // 正常情况下id应该为正数,但如果计算结果为负数,使用绝对值 + if (id < 0) { + log.warn("Generated negative ID: {}, using absolute value", id); + return Math.abs(id); + } + + return id; + } + + /** + * 等待下一毫秒 + * + * @param lastTimestamp 上次生成ID的时间戳 + * @return 新的时间戳 + */ + private long tilNextMillis(final long lastTimestamp) { + long timestamp = this.timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = this.timeGen(); + } + return timestamp; + } + + /** + * 获取当前时间戳(毫秒) + * + * @return 当前时间戳 + */ + private long timeGen() { + return System.currentTimeMillis(); + } +} + diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/aspect/DeviceDebugLogAspect.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/aspect/DeviceDebugLogAspect.java index 53f98732..565c940c 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/aspect/DeviceDebugLogAspect.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/aspect/DeviceDebugLogAspect.java @@ -58,6 +58,15 @@ import java.util.Objects; @Slf4j public class DeviceDebugLogAspect { + /** 日志值最大长度 */ + private static final int MAX_LOG_VALUE_LENGTH = 64; + /** 备注最大长度 */ + private static final int MAX_REMARK_LENGTH = 200; + /** 错误信息显示最大长度 */ + private static final int MAX_ERROR_MSG_DISPLAY_LENGTH = 50; + /** 其他模式 */ + private static final int MODE_OTHER = 3; + @Resource private ILogService logService; @@ -102,43 +111,21 @@ public class DeviceDebugLogAspect { */ @AfterThrowing(pointcut = "parseReportMsgPointcut()", throwing = "e") public void afterThrowingParseReportMsg(JoinPoint joinPoint, Throwable e) { - try { - Object[] args = joinPoint.getArgs(); - if (args != null && args.length > 0 && args[0] instanceof DeviceReportBo) { + handleException(joinPoint, e, (args) -> { + if (args[0] instanceof DeviceReportBo) { DeviceReportBo bo = (DeviceReportBo) args[0]; String serialNumber = bo.getSerialNumber(); if (StringUtils.isEmpty(serialNumber)) { - log.warn("设备编号为空,跳过错误日志记录"); - return; + return null; } - - // 处理原始数据,可能是byte[]或String - String sourceData = ""; - try { - if (bo.getData() != null && bo.getData().length > 0) { - sourceData = ByteBufUtil.hexDump(bo.getData()); - } else if (StringUtils.isNotEmpty(bo.getSources())) { - sourceData = bo.getSources(); - } - } catch (Exception ex) { - log.warn("处理原始数据失败: {}", ex.getMessage()); - sourceData = bo.getSources() != null ? bo.getSources() : ""; - } - - String errorMsg = e.getMessage(); - if (StringUtils.isEmpty(errorMsg)) { - errorMsg = e.getClass().getName(); - } - - // 直接从bo中获取协议编码,避免额外的查询 - String protocolCode = bo.getProtocolCode(); + String sourceData = extractSourceDataFromReportBo(bo); + String errorMsg = getErrorMessage(e); Long productId = bo.getProductId(); - - saveErrorLog(serialNumber, sourceData, errorMsg, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "DEBUG_ERROR", productId); + return new ErrorLogContext(serialNumber, sourceData, errorMsg, + DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "DEBUG_ERROR", productId); } - } catch (Exception ex) { - log.error("记录数据解析错误日志失败: {}", ex.getMessage(), ex); - } + return null; + }, "记录数据解析错误日志失败"); } /** @@ -146,58 +133,21 @@ public class DeviceDebugLogAspect { */ @AfterThrowing(pointcut = "messagePostPointcut()", throwing = "e") public void afterThrowingMessagePost(JoinPoint joinPoint, Throwable e) { - try { - Object[] args = joinPoint.getArgs(); - if (args != null && args.length > 0 && args[0] instanceof DeviceMessage) { + handleException(joinPoint, e, (args) -> { + if (args[0] instanceof DeviceMessage) { DeviceMessage deviceMessage = (DeviceMessage) args[0]; String serialNumber = deviceMessage.getSerialNumber(); if (StringUtils.isEmpty(serialNumber)) { - log.warn("设备编号为空,跳过错误日志记录"); - return; + return null; } - - // 处理原始数据 - String sourceData = ""; - try { - if (deviceMessage.getMessage() != null) { - Object msg = deviceMessage.getMessage(); - if (msg instanceof byte[]) { - sourceData = ByteBufUtil.hexDump((byte[]) msg); - } else { - String msgStr = msg.toString(); - // 如果dataType是ascii,需要将ASCII字符串转换为HEX格式用于显示 - if ("ascii".equalsIgnoreCase(deviceMessage.getDataType())) { - // ASCII字符串转HEX - sourceData = asciiToHex(msgStr); - } else { - // 其他情况直接使用原字符串(可能是HEX格式) - sourceData = msgStr; - } - } - } - } catch (Exception ex) { - log.warn("处理原始数据失败: {}", ex.getMessage()); - // 如果转换失败,至少保存原始字符串 - if (deviceMessage.getMessage() != null) { - sourceData = deviceMessage.getMessage().toString(); - } - } - - String errorMsg = e.getMessage(); - if (StringUtils.isEmpty(errorMsg)) { - errorMsg = e.getClass().getName(); - } - - // 获取协议编码和产品ID(通过设备编号查询,有缓存) - String protocolCode = getProtocolCodeBySerialNumber(serialNumber); + String sourceData = extractSourceDataFromDeviceMessage(deviceMessage); + String errorMsg = getErrorMessage(e); Long productId = getProductIdBySerialNumber(serialNumber); - - saveErrorLog(serialNumber, sourceData, errorMsg, + return new ErrorLogContext(serialNumber, sourceData, errorMsg, DeviceLogTypeEnum.COMMAND_SEND_ERROR.getType(), "SEND_ERROR", productId); } - } catch (Exception ex) { - log.error("记录指令发送错误日志失败: {}", ex.getMessage(), ex); - } + return null; + }, "记录指令发送错误日志失败"); } /** @@ -205,55 +155,24 @@ public class DeviceDebugLogAspect { */ @AfterThrowing(pointcut = "protocolDecodePointcut()", throwing = "e") public void afterThrowingProtocolDecode(JoinPoint joinPoint, Throwable e) { - try { - Object[] args = joinPoint.getArgs(); - if (args != null && args.length > 0 && args[0] instanceof DeviceData) { - DeviceData deviceData = - (DeviceData) args[0]; + handleException(joinPoint, e, (args) -> { + if (args[0] instanceof DeviceData) { + DeviceData deviceData = (DeviceData) args[0]; String serialNumber = deviceData.getSerialNumber(); if (StringUtils.isEmpty(serialNumber)) { - log.warn("设备编号为空,跳过错误日志记录"); - return; + return null; } - - // 处理原始数据 - String sourceData = ""; - try { - ByteBuf buf = deviceData.getBuf(); - if (buf != null) { - sourceData = ByteBufUtil.hexDump(buf); - } else if (deviceData.getData() != null && deviceData.getData().length > 0) { - sourceData = ByteBufUtil.hexDump(deviceData.getData()); - } - } catch (Exception ex) { - log.warn("处理原始数据失败: {}", ex.getMessage()); - } - - String errorMsg = e.getMessage(); - if (StringUtils.isEmpty(errorMsg)) { - errorMsg = e.getClass().getName(); - } - - // 获取协议编码和产品ID:优先从deviceData获取,否则通过设备编号查询 - String protocolCode = null; + String sourceData = extractSourceDataFromDeviceData(deviceData); + String errorMsg = getErrorMessage(e); Long productId = deviceData.getProductId(); - if (productId != null) { - protocolCode = getProtocolCodeByProductId(productId); + if (productId == null) { + productId = getProductIdBySerialNumber(serialNumber); } - if (StringUtils.isEmpty(protocolCode) && StringUtils.isNotEmpty(serialNumber)) { - protocolCode = getProtocolCodeBySerialNumber(serialNumber); - if (productId == null) { - productId = getProductIdBySerialNumber(serialNumber); - } - } - - // 协议解析错误也归类为数据解析错误 - saveErrorLog(serialNumber, sourceData, errorMsg, + return new ErrorLogContext(serialNumber, sourceData, errorMsg, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType(), "PARSE_ERROR", productId); } - } catch (Exception ex) { - log.error("记录协议解析错误日志失败: {}", ex.getMessage(), ex); - } + return null; + }, "记录协议解析错误日志失败"); } /** @@ -301,31 +220,140 @@ public class DeviceDebugLogAspect { return null; } + /** + * 统一异常处理入口 + */ + private void handleException(JoinPoint joinPoint, Throwable e, + java.util.function.Function contextExtractor, + String errorLogPrefix) { + try { + Object[] args = joinPoint.getArgs(); + if (args == null || args.length == 0) { + return; + } + ErrorLogContext context = contextExtractor.apply(args); + if (context == null || StringUtils.isEmpty(context.getSerialNumber())) { + log.warn("设备编号为空,跳过错误日志记录"); + return; + } + saveErrorLog(context); + } catch (Exception ex) { + log.error("{}: {}", errorLogPrefix, ex.getMessage(), ex); + } + } + + /** + * 从DeviceReportBo提取原始数据 + */ + private String extractSourceDataFromReportBo(DeviceReportBo bo) { + try { + if (bo.getData() != null && bo.getData().length > 0) { + return ByteBufUtil.hexDump(bo.getData()); + } else if (StringUtils.isNotEmpty(bo.getSources())) { + return bo.getSources(); + } + } catch (Exception ex) { + log.warn("处理原始数据失败: {}", ex.getMessage()); + } + return bo.getSources() != null ? bo.getSources() : ""; + } + + /** + * 从DeviceMessage提取原始数据 + */ + private String extractSourceDataFromDeviceMessage(DeviceMessage deviceMessage) { + try { + if (deviceMessage.getMessage() != null) { + Object msg = deviceMessage.getMessage(); + if (msg instanceof byte[]) { + return ByteBufUtil.hexDump((byte[]) msg); + } else { + String msgStr = msg.toString(); + if ("ascii".equalsIgnoreCase(deviceMessage.getDataType())) { + return asciiToHex(msgStr); + } else { + return msgStr; + } + } + } + } catch (Exception ex) { + log.warn("处理原始数据失败: {}", ex.getMessage()); + if (deviceMessage.getMessage() != null) { + return deviceMessage.getMessage().toString(); + } + } + return ""; + } + + /** + * 从DeviceData提取原始数据 + */ + private String extractSourceDataFromDeviceData(DeviceData deviceData) { + try { + ByteBuf buf = deviceData.getBuf(); + if (buf != null) { + return ByteBufUtil.hexDump(buf); + } else if (deviceData.getData() != null && deviceData.getData().length > 0) { + return ByteBufUtil.hexDump(deviceData.getData()); + } + } catch (Exception ex) { + log.warn("处理原始数据失败: {}", ex.getMessage()); + } + return ""; + } + + /** + * 获取错误信息 + */ + private String getErrorMessage(Throwable e) { + String errorMsg = e.getMessage(); + return StringUtils.isEmpty(errorMsg) ? e.getClass().getName() : errorMsg; + } + + /** + * 检查设备是否已授权 + */ + private boolean isDeviceAuthorized(String serialNumber, Long productId) { + if (productId == null) { + return true; // 产品ID为空,不进行授权校验 + } + try { + Product product = productService.selectProductByProductId(productId); + if (product == null || product.getIsAuthorize() == null || product.getIsAuthorize() != 1) { + return true; // 产品未开启授权码,允许记录 + } + // 产品开启了授权码,检查设备是否绑定了授权码 + ProductAuthorize queryParam = new ProductAuthorize(); + queryParam.setProductId(productId); + queryParam.setSerialNumber(serialNumber); + List authorizeList = productAuthorizeService.selectProductAuthorizeList(queryParam); + return authorizeList != null && !authorizeList.isEmpty(); + } catch (Exception ex) { + log.warn("授权码校验失败: {}, 设备编号: {}, 继续记录日志", ex.getMessage(), serialNumber); + return true; // 校验失败时继续记录日志,避免影响正常功能 + } + } + /** * 保存错误日志到iot_device_log表,并推送到前端页面 */ - private void saveErrorLog(String serialNumber, String sourceData, String errorMsg, - Integer logType, String identity, Long productId) { + private void saveErrorLog(ErrorLogContext context) { try { + String serialNumber = context.getSerialNumber(); if (StringUtils.isEmpty(serialNumber)) { log.warn("设备编号为空,无法保存错误日志"); return; } // 获取设备信息 - Device device = null; - try { - device = deviceService.selectDeviceBySerialNumber(serialNumber); - } catch (Exception ex) { - log.warn("获取设备信息失败: {}, 设备编号: {}", ex.getMessage(), serialNumber); - } - + Device device = getDeviceBySerialNumber(serialNumber); if (device == null) { log.warn("设备不存在,无法保存错误日志: {}", serialNumber); return; } - // 如果productId为空,尝试从设备信息中获取 + // 获取产品ID + Long productId = context.getProductId(); if (productId == null) { productId = device.getProductId(); } @@ -333,64 +361,66 @@ public class DeviceDebugLogAspect { productId = getProductIdBySerialNumber(serialNumber); } - // 授权码校验:如果产品开启了授权码,但设备未绑定授权码,不记录日志也不推送消息 - if (productId != null) { - try { - Product product = productService.selectProductByProductId(productId); - if (product != null && product.getIsAuthorize() != null && product.getIsAuthorize() == 1) { - // 产品开启了授权码,检查设备是否绑定了授权码 - ProductAuthorize queryParam = new ProductAuthorize(); - queryParam.setProductId(productId); - queryParam.setSerialNumber(serialNumber); - java.util.List authorizeList = productAuthorizeService.selectProductAuthorizeList(queryParam); - if (authorizeList == null || authorizeList.isEmpty()) { - log.debug("设备[{}]未绑定授权码,跳过错误日志记录和消息推送", serialNumber); - return; // 不记录日志,也不推送消息 - } - } - } catch (Exception ex) { - log.warn("授权码校验失败: {}, 设备编号: {}, 继续记录日志", ex.getMessage(), serialNumber); - // 校验失败时继续记录日志,避免影响正常功能 - } + // 授权码校验 + if (!isDeviceAuthorized(serialNumber, productId)) { + log.debug("设备[{}]未绑定授权码,跳过错误日志记录和消息推送", serialNumber); + return; } - DeviceLog deviceLog = new DeviceLog(); - deviceLog.setSerialNumber(serialNumber); - deviceLog.setDeviceId(device.getDeviceId()); - deviceLog.setDeviceName(device.getDeviceName()); - deviceLog.setLogType(logType); - // 存储原始数据包(限制64字符) - deviceLog.setLogValue(StringUtils.substring(sourceData, 0, 64)); - // 存储错误信息(限制500字符) - deviceLog.setRemark(StringUtils.substring(errorMsg, 0, 500)); - deviceLog.setIdentity(identity); - deviceLog.setModelName(Objects.equals(logType, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType()) ? - "数据解析错误" : "指令发送错误"); - deviceLog.setCreateTime(DateUtils.getNowDate()); - deviceLog.setUserId(device.getTenantId()); - deviceLog.setUserName(device.getTenantName()); - deviceLog.setTenantId(device.getTenantId()); - deviceLog.setTenantName(device.getTenantName()); - deviceLog.setIsMonitor(0); - deviceLog.setMode(3); // 3=其他 - - // 保存错误日志到数据库 + // 构建并保存设备日志 + DeviceLog deviceLog = buildDeviceLog(device, context); logService.saveDeviceLog(deviceLog); log.info("已保存错误日志到数据库 - 设备: {}, 错误类型: {}, 错误信息: {}", - serialNumber, identity, StringUtils.substring(errorMsg, 0, 50)); + serialNumber, context.getIdentity(), + StringUtils.substring(context.getErrorMsg(), 0, MAX_ERROR_MSG_DISPLAY_LENGTH)); // 推送错误信息到前端页面 - pushErrorToFrontend(serialNumber, productId, sourceData, errorMsg, logType, identity); + pushErrorToFrontend(serialNumber, productId, context); } catch (Exception ex) { log.error("保存错误日志失败: {}", ex.getMessage(), ex); } } + /** + * 获取设备信息 + */ + private Device getDeviceBySerialNumber(String serialNumber) { + try { + return deviceService.selectDeviceBySerialNumber(serialNumber); + } catch (Exception ex) { + log.warn("获取设备信息失败: {}, 设备编号: {}", ex.getMessage(), serialNumber); + return null; + } + } + + /** + * 构建设备日志对象 + */ + private DeviceLog buildDeviceLog(Device device, ErrorLogContext context) { + DeviceLog deviceLog = new DeviceLog(); + deviceLog.setSerialNumber(context.getSerialNumber()); + deviceLog.setDeviceId(device.getDeviceId()); + deviceLog.setDeviceName(device.getDeviceName()); + deviceLog.setLogType(context.getLogType()); + deviceLog.setLogValue(StringUtils.substring(context.getSourceData(), 0, MAX_LOG_VALUE_LENGTH)); + deviceLog.setRemark(StringUtils.substring(context.getErrorMsg(), 0, MAX_REMARK_LENGTH)); + deviceLog.setIdentity(context.getIdentity()); + deviceLog.setModelName(Objects.equals(context.getLogType(), DeviceLogTypeEnum.DATA_PARSE_ERROR.getType()) ? + "数据解析错误" : "指令发送错误"); + deviceLog.setCreateTime(DateUtils.getNowDate()); + deviceLog.setUserId(device.getTenantId()); + deviceLog.setUserName(device.getTenantName()); + deviceLog.setTenantId(device.getTenantId()); + deviceLog.setTenantName(device.getTenantName()); + deviceLog.setIsMonitor(0); + deviceLog.setMode(MODE_OTHER); + return deviceLog; + } + /** * 推送错误信息到前端页面 */ - private void pushErrorToFrontend(String serialNumber, Long productId, String sourceData, - String errorMsg, Integer logType, String identity) { + private void pushErrorToFrontend(String serialNumber, Long productId, ErrorLogContext context) { try { if (productId == null) { log.warn("产品ID为空,无法推送错误信息到前端: {}", serialNumber); @@ -398,42 +428,68 @@ public class DeviceDebugLogAspect { } // 构建错误消息的物模型数据 - List errorItems = new ArrayList<>(); ThingsModelSimpleItem errorItem = new ThingsModelSimpleItem(); - errorItem.setId("ERROR_" + identity); - errorItem.setName(Objects.equals(logType, DeviceLogTypeEnum.DATA_PARSE_ERROR.getType()) ? "数据解析错误" : "指令发送错误"); - errorItem.setValue(errorMsg); - errorItem.setRemark(errorMsg); + errorItem.setId("ERROR_" + context.getIdentity()); + errorItem.setName(Objects.equals(context.getLogType(), DeviceLogTypeEnum.DATA_PARSE_ERROR.getType()) + ? "数据解析错误" : "指令发送错误"); + errorItem.setValue(context.getErrorMsg()); + errorItem.setRemark(context.getErrorMsg()); errorItem.setTs(new Date()); + List errorItems = new ArrayList<>(); errorItems.add(errorItem); // 构建推送消息 PushMessageBo messageBo = new PushMessageBo(); messageBo.setTopic(topicsUtils.buildTopic(productId, serialNumber.toUpperCase(), TopicType.WS_SERVICE_INVOKE)); - // 将hex转换为ASCII - String sourceAscii = hexToAscii(sourceData); - + String sourceAscii = hexToAscii(context.getSourceData()); JSONObject pushObj = new JSONObject(); pushObj.put("message", errorItems); - pushObj.put("sources", StringUtils.isNotEmpty(sourceData) ? sourceData : ""); // HEX格式 - pushObj.put("sourcesAscii", sourceAscii); // ASCII格式 - pushObj.put("errorType", identity); // 错误类型:PARSE_ERROR 或 SEND_ERROR - pushObj.put("isError", true); // 标记为错误消息 + pushObj.put("sources", StringUtils.isNotEmpty(context.getSourceData()) ? context.getSourceData() : ""); + pushObj.put("sourcesAscii", sourceAscii); + pushObj.put("errorType", context.getIdentity()); + pushObj.put("isError", true); messageBo.setMessage(JSON.toJSONString(pushObj)); // 推送消息到MQTT mqttRemoteManager.pushCommon(messageBo); - log.info("已推送错误信息到前端 - 设备: {}, 产品ID: {}, 错误类型: {}, HEX: {}, ASCII: {}", - serialNumber, productId, identity, - StringUtils.isNotEmpty(sourceData) ? StringUtils.substring(sourceData, 0, 20) + "..." : "", - StringUtils.isNotEmpty(sourceAscii) ? StringUtils.substring(sourceAscii, 0, 20) + "..." : ""); + log.info("已推送错误信息到前端 - 设备: {}, 产品ID: {}, 错误类型: {}", + serialNumber, productId, context.getIdentity()); } catch (Exception ex) { log.error("推送错误信息到前端失败: {}", ex.getMessage(), ex); } } + /** + * 错误日志上下文对象 + */ + private static class ErrorLogContext { + private final String serialNumber; + private final String sourceData; + private final String errorMsg; + private final Integer logType; + private final String identity; + private final Long productId; + + public ErrorLogContext(String serialNumber, String sourceData, String errorMsg, + Integer logType, String identity, Long productId) { + this.serialNumber = serialNumber; + this.sourceData = sourceData; + this.errorMsg = errorMsg; + this.logType = logType; + this.identity = identity; + this.productId = productId; + } + + public String getSerialNumber() { return serialNumber; } + public String getSourceData() { return sourceData; } + public String getErrorMsg() { return errorMsg; } + public Integer getLogType() { return logType; } + public String getIdentity() { return identity; } + public Long getProductId() { return productId; } + } + /** * 将ASCII字符串转换为HEX字符串 * @param asciiStr ASCII字符串 diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DataHandlerImpl.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DataHandlerImpl.java index 538018fd..023294e5 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DataHandlerImpl.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DataHandlerImpl.java @@ -95,6 +95,8 @@ public class DataHandlerImpl implements IDataHandler { // 这里上报设备编号是转的大写,后面存缓存也是使用大写的,所以在查询物模型的值时添加把设备编号转大写后取值 input.setDeviceNumber(bo.getSerialNumber().toUpperCase()); input.setThingsModelValueRemarkItem(thingsModelSimpleItems); + // 传递原始消息数据包,用于判断设备上电触发回包等 + input.setSources(bo.getSources()); List result = deviceService.reportDeviceThingsModelValue(input, bo.getType(), bo.isShadow()); // 只有设备上报进入规则引擎流程 if (bo.isRuleEngine() && !bo.getSerialNumber().startsWith("server-")){ @@ -120,7 +122,7 @@ public class DataHandlerImpl implements IDataHandler { // log.error("=>上报属性信息给小度音箱异常", e); // } } catch (Exception e) { - log.error("接收属性数据,解析数据时异常 message={},e={}", e.getMessage(),e); + log.error("接收属性数据,解析数据时异常 message={}", e.getMessage(),e); } } diff --git a/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/modbus/codec/MessageAdapter.java b/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/modbus/codec/MessageAdapter.java index 1df16d3c..978a183b 100644 --- a/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/modbus/codec/MessageAdapter.java +++ b/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/modbus/codec/MessageAdapter.java @@ -55,7 +55,6 @@ public class MessageAdapter implements MessageDecoder, MessageEncoder{ //这里兼容一下TCP整包发送的数据(整包==设备编号,数据一起发送) if (clientId == null){ if (StringUtils.isNotEmpty(dump)) { - assert dump != null; if (dump.startsWith("68") && dump.endsWith("16")) { protocol = managerService.getProtocolByProtocolCode(FastBeeConstant.PROTOCOL.FlowMeter); }else if (dump.startsWith("7e")&& dump.endsWith("7e")){ diff --git a/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/modbus/codec/ModbusProtocol.java b/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/modbus/codec/ModbusProtocol.java index 905e7384..57d599ce 100644 --- a/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/modbus/codec/ModbusProtocol.java +++ b/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/modbus/codec/ModbusProtocol.java @@ -130,7 +130,7 @@ public class ModbusProtocol implements IProtocol { report.setSources(hexDump); return report; } catch (Exception e) { - log.error("=>解码异常[{}]", e, e.getMessage()); + log.error("=>解码异常[{}]", e.getMessage(), e); throw new RuntimeException(e); } } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ModbusJob.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ModbusJob.java index 89354593..498baa13 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ModbusJob.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ModbusJob.java @@ -80,4 +80,10 @@ public class ModbusJob extends BaseEntity { @ApiModelProperty("时间周期类型") private Integer cycleType; + /** + * 执行时间(仅设备上电触发任务使用,记录任务执行的时间) + */ + @ApiModelProperty("执行时间") + private java.util.Date executeTime; + } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ModbusJobMapper.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ModbusJobMapper.java index 6efcdb99..39f94c49 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ModbusJobMapper.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ModbusJobMapper.java @@ -45,6 +45,14 @@ public interface ModbusJobMapper */ public int updateModbusJob(ModbusJob modbusJob); + /** + * 清空执行时间 + * + * @param taskId 任务ID + * @return 结果 + */ + public int clearExecuteTime(Long taskId); + /** * 删除轮训任务列 * diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java index 667c9d6d..a334a28f 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java @@ -68,6 +68,7 @@ import java.math.BigDecimal; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.fastbee.common.utils.SecurityUtils.getLoginUser; @@ -513,6 +514,7 @@ public class DeviceServiceImpl implements IDeviceService { ThingsModelValueItem thingModels = thingsModelService.getSingleThingModels(input.getProductId(), identity); if (Objects.isNull(thingModels)) { log.warn("=>查询物模型为空[{}],标识符:[{}]", input.getProductId(), identity); + continue; // 跳过当前物模型的处理 } // 获取设备实时值 ValueItem cacheIdentifier = itslValueCache.getCacheIdentifier(input.getProductId(), input.getDeviceNumber(), thingModels.getId()); @@ -525,10 +527,50 @@ public class DeviceServiceImpl implements IDeviceService { /* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -开始 ★★★★★★★★★★★★★★★★★★★★★★*/ //有计算公式的,经过计算公式 - if (thingModels.getFormula() != null && !"".equals(thingModels.getFormula())) { + if (thingModels != null && thingModels.getFormula() != null && !"".equals(thingModels.getFormula())) { Map params = new HashMap<>(); + // %s 表示当前物模型的值(保持向后兼容) params.put("%s", value); - value = String.valueOf(CaculateUtils.execute(thingModels.getFormula(), params)); + + // 解析公式中的其他物模型标识符(格式:{identifier} 或直接使用标识符) + String formula = thingModels.getFormula(); + // 匹配 {identifier} 格式的占位符 + java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\{([^}]+)\\}"); + java.util.regex.Matcher matcher = pattern.matcher(formula); + Set referencedIdentifiers = new HashSet<>(); + while (matcher.find()) { + String identifier = matcher.group(1); + referencedIdentifiers.add(identifier); + } + + // 获取引用的其他物模型的值 + for (String refIdentifier : referencedIdentifiers) { + if (!refIdentifier.equals(identity)) { // 避免循环引用自己 + try { + ValueItem refValueItem = itslValueCache.getCacheIdentifier( + input.getProductId(), + input.getDeviceNumber(), + refIdentifier + ); + if (refValueItem != null && StringUtils.isNotEmpty(refValueItem.getValue())) { + // 使用 {identifier} 作为占位符 + params.put("{" + refIdentifier + "}", refValueItem.getValue()); + } else { + // 如果引用的物模型值不存在,使用0作为默认值 + params.put("{" + refIdentifier + "}", "0"); + log.warn("=>计算公式引用的物模型值不存在[{}],标识符:[{}],使用默认值0", + input.getProductId(), refIdentifier); + } + } catch (Exception e) { + log.error("=>获取计算公式引用的物模型值失败[{}],标识符:[{}],错误:{}", + input.getProductId(), refIdentifier, e.getMessage()); + params.put("{" + refIdentifier + "}", "0"); + } + } + } + + // 执行计算 + value = String.valueOf(CaculateUtils.execute(formula, params)); item.setValue(value); } /* ★★★★★★★★★★★★★★★★★★★★★★ 数据计算 -结束 ★★★★★★★★★★★★★★★★★★★★★★*/ @@ -642,8 +684,10 @@ public class DeviceServiceImpl implements IDeviceService { } // 缓存最新一条数据到redis redisCache.hashPutAll(key, maps); + // 判断是否是设备上电触发的回包,如果是则跳过保存历史数据 + boolean isPowerOnTriggerReply = isPowerOnTriggerReply(input.getSources(), input.getDeviceNumber()); //存储历史数据 - if (!CollectionUtils.isEmpty(deviceLogList) && !isShadow) { + if (!CollectionUtils.isEmpty(deviceLogList) && !isShadow && !isPowerOnTriggerReply) { for (DeviceLog deviceLog : deviceLogList) { logService.saveDeviceLog(deviceLog); } @@ -1355,10 +1399,26 @@ public class DeviceServiceImpl implements IDeviceService { // 执行所有相关的modbus任务 boolean hasActiveTask = false; + // 记录设备上电触发任务下发的指令片段(索引2到11位) + String serialNumber = device.getSerialNumber().toUpperCase(); + String cacheKey = RedisKeyBuilder.buildPowerOnTriggerCommandKey(serialNumber); + for (ModbusJob modbusJob : modbusJobList) { if ("0".equals(modbusJob.getStatus())) { // 只执行正常状态的任务 hasActiveTask = true; -// log.info("发现设备上电触发任务,设备编号:{},任务ID:{},指令:{}", device.getSerialNumber(), deviceJob.getJobId(), modbusJob.getCommand()); + // 记录下发的指令片段(索引2到11位,即第3到第12个字符) + String command = modbusJob.getCommand(); + if (StringUtils.isNotEmpty(command) && command.length() >= 12) { + // 去除空格并转为大写 + String cleanCommand = command.replaceAll("\\s+", "").toUpperCase(); + if (cleanCommand.length() >= 12) { + // 提取索引2到11位(即第3到第12个字符) + String commandSegment = cleanCommand.substring(2, 12); + // 存储到Redis缓存,设置过期时间为5分钟(设备上报应该在5分钟内完成) + redisCache.setCacheObject(cacheKey, commandSegment, 300, TimeUnit.SECONDS); + log.info("记录设备上电触发任务下发指令片段,设备编号:{},指令片段:{}", serialNumber, commandSegment); + } + } } } @@ -1391,6 +1451,7 @@ public class DeviceServiceImpl implements IDeviceService { return; } log.info("更新设备上电触发任务状态为暂停,任务数量:{}", modbusJobList.size()); + java.util.Date executeTime = DateUtils.getNowDate(); modbusJobList.forEach(modbusJob -> { // 只更新status=0的任务为status=1,如果已经是status=1则跳过 if (!"0".equals(modbusJob.getStatus())) { @@ -1400,10 +1461,12 @@ public class DeviceServiceImpl implements IDeviceService { ModbusJob job = new ModbusJob(); job.setStatus("1"); job.setTaskId(modbusJob.getTaskId()); + // 记录执行时间 + job.setExecuteTime(executeTime); try { - // 将任务状态改为暂停("1"=暂停,"0"=正常) + // 将任务状态改为暂停("1"=暂停,"0"=正常),并记录执行时间 modbusJobService.updateModbusJob(job); - log.info("设备上电触发任务已设置为暂停状态,任务ID:{}", modbusJob.getTaskId()); + log.info("设备上电触发任务已设置为暂停状态并记录执行时间,任务ID:{},执行时间:{}", modbusJob.getTaskId(), executeTime); } catch (Exception e) { log.error("更新设备上电触发任务状态异常,任务ID:{}", modbusJob.getTaskId(), e); } @@ -1972,4 +2035,71 @@ public class DeviceServiceImpl implements IDeviceService { return deviceMapper.selectSerialNumbersByProductId(productId); } + /** + * 判断是否是设备上电触发的回包 + * 从Redis缓存中获取下发的指令片段(索引2到11位),与上报的指令片段进行比较 + * + * @param sources 原始消息数据包 + * @param deviceNumber 设备编号 + * @return true表示是设备上电触发的回包 + */ + private boolean isPowerOnTriggerReply(Object sources, String deviceNumber) { + if (sources == null || StringUtils.isEmpty(deviceNumber)) { + return false; + } + + try { + // 从Redis缓存中获取下发的指令片段 + String serialNumber = deviceNumber.toUpperCase(); + String cacheKey = RedisKeyBuilder.buildPowerOnTriggerCommandKey(serialNumber); + String sentCommandSegment = redisCache.getCacheObject(cacheKey); + + // 如果缓存中没有下发的指令片段,说明不是设备上电触发的回包 + if (StringUtils.isEmpty(sentCommandSegment)) { + return false; + } + + String hexStr = null; + + // 将 sources 转换为十六进制字符串 + if (sources instanceof String) { + hexStr = (String) sources; + // 如果是字符串,可能需要去除空格 + hexStr = hexStr.replaceAll("\\s+", "").toUpperCase(); + } else if (sources instanceof byte[]) { + // 如果是字节数组,转换为十六进制字符串 + byte[] bytes = (byte[]) sources; + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X", b & 0xFF)); + } + hexStr = sb.toString(); + } else { + // 其他类型,尝试转换为字符串 + hexStr = sources.toString().replaceAll("\\s+", "").toUpperCase(); + } + + if (hexStr.length() < 12) { + return false; + } + + // 提取上报指令的索引2到11位(即第3到第12个字符) + String actualSegment = hexStr.substring(2, 12); + + // 与下发的指令片段进行比较 + boolean isMatch = sentCommandSegment.equals(actualSegment); + + if (isMatch) { + // 匹配成功,删除缓存(避免重复匹配) + redisCache.deleteObject(cacheKey); + log.info("检测到设备上电触发回包,设备编号:{},指令片段:{}", serialNumber, actualSegment); + } + + return isMatch; + } catch (Exception e) { + log.warn("判断设备上电触发回包时发生异常: {}", e.getMessage()); + return false; + } + } + } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ModbusJobServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ModbusJobServiceImpl.java index a03bb5f5..aa206858 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ModbusJobServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ModbusJobServiceImpl.java @@ -201,6 +201,14 @@ public class ModbusJobServiceImpl implements IModbusJobService { */ @Override public int updateModbusJob(ModbusJob modbusJob) { + // 如果状态从1(暂停)改为0(正常),需要清空执行时间 + if (modbusJob.getTaskId() != null && modbusJob.getStatus() != null && "0".equals(modbusJob.getStatus())) { + ModbusJob existingJob = modbusJobMapper.selectModbusJobByTaskId(modbusJob.getTaskId()); + if (existingJob != null && "1".equals(existingJob.getStatus())) { + // 状态从1改为0,清空执行时间 + modbusJobMapper.clearExecuteTime(modbusJob.getTaskId()); + } + } return modbusJobMapper.updateModbusJob(modbusJob); } diff --git a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ModbusJobMapper.xml b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ModbusJobMapper.xml index 51665faf..a22ee698 100644 --- a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ModbusJobMapper.xml +++ b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ModbusJobMapper.xml @@ -16,10 +16,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + - select task_id,job_name, sub_device_id, sub_serial_number, command,device_type, job_id, status, create_by, create_time, remark from iot_modbus_job + select task_id,job_name, sub_device_id, sub_serial_number, command,device_type, job_id, status, create_by, create_time, remark, execute_time from iot_modbus_job