From 44df912fc25d0bdfb310acd1fbf54d2f2b4429df Mon Sep 17 00:00:00 2001 From: George Date: Thu, 1 Jan 2026 22:31:56 +0800 Subject: [PATCH] =?UTF-8?q?20260101:=201=E3=80=81=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E9=A1=B5=E9=9D=A2=E9=92=A9=E5=AD=90=E5=87=BD?= =?UTF-8?q?=E6=95=B0=EF=BC=8C=E5=86=99=E5=85=A5redis=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E6=A0=87=E8=AF=86=EF=BC=8C=E5=8F=AF=E9=80=9A=E8=BF=87=E5=88=87?= =?UTF-8?q?=E9=9D=A2=E6=8E=A8=E9=80=81=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=88=B0=E5=89=8D=E7=AB=AF=E9=A1=B5=E9=9D=A2=EF=BC=8C=E7=A6=BB?= =?UTF-8?q?=E5=BC=80=E9=A1=B5=E9=9D=A2=E5=8F=96=E6=B6=88=E6=A0=87=E8=AF=86?= =?UTF-8?q?=EF=BC=9B=202=E3=80=81=E6=95=B0=E6=8D=AE=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=B8=80=E7=AC=94=E6=95=B0=E6=8D=AE=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E5=88=9B=E5=BB=BA=E6=97=A5=E6=9C=9F=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=AE=BE=E5=A4=87=E6=97=A5=E5=BF=97=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E4=BB=8Efor=E5=8D=95=E4=B8=AA=E6=94=B9=E4=B8=BA=E6=89=B9?= =?UTF-8?q?=E9=87=8F=203=E3=80=81=E4=BF=A1=E6=81=AF=E6=A0=8F=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0pdf=E6=96=87=E6=A1=A3=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/constant/CacheConstants.java | 6 + .../fastbee/common/utils/CaculateUtils.java | 7 +- .../iot/data/aspect/DeviceDebugLogAspect.java | 106 +++++++++------ .../executor/PowerOnTriggerTaskExecutor.java | 11 +- .../service/impl/DeviceJobServiceImpl.java | 8 +- .../impl/DeviceReportMessageServiceImpl.java | 10 +- .../controller/device/DeviceController.java | 40 ++++++ .../fastbee/modbus/codec/MessageAdapter.java | 2 +- .../fastbee/server/config/NettyConfig.java | 1 - .../server/handler/TCPMessageAdapter.java | 10 ++ .../iot/service/impl/DeviceServiceImpl.java | 73 +++++----- .../service/impl/ThingsModelServiceImpl.java | 4 +- .../iot/tdengine/service/ILogService.java | 4 + .../service/impl/MySqlLogServiceImpl.java | 17 ++- .../service/impl/TdengineLogServiceImpl.java | 47 +++++++ .../com/fastbee/system/domain/SysNotice.java | 19 ++- .../mapper/system/SysNoticeMapper.xml | 6 +- vue/src/api/iot/mqttTest.js | 16 +++ vue/src/views/dataCenter/history.vue | 50 +++++-- vue/src/views/index.vue | 127 +++++++++++++----- vue/src/views/iot/device/index.vue | 64 ++++----- .../views/iot/device/instruction-parsing.vue | 23 +++- vue/src/views/system/notice/index.vue | 66 ++++++++- 23 files changed, 538 insertions(+), 179 deletions(-) diff --git a/fastbee-common/src/main/java/com/fastbee/common/constant/CacheConstants.java b/fastbee-common/src/main/java/com/fastbee/common/constant/CacheConstants.java index cd9717aa..6c22e485 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/constant/CacheConstants.java +++ b/fastbee-common/src/main/java/com/fastbee/common/constant/CacheConstants.java @@ -70,4 +70,10 @@ public class CacheConstants { * 设备OTA升级实时数据 */ public static final String DEVICE_OTA_DATA = "device:ota:"; + + /** + * 设备调试日志记录标志 redis key + */ + public static final String DEVICE_DEBUG_USER_KEY = "device:debug:user:"; + } 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 b27098b3..3612814f 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 @@ -120,9 +120,10 @@ public class CaculateUtils { */ public static List suffixHandle(String exeStr) { StringBuilder buf = new StringBuilder(); - Stack stack = new Stack(); + //Stack stack = new Stack(); + Deque stack = new ArrayDeque<>(); char[] exeChars = exeStr.toCharArray(); - List res = new ArrayList(); + List res = new ArrayList<>(); for (char x : exeChars) { // 判断是不是操作符号 if (symbol.indexOf(x) > -1) { @@ -224,7 +225,7 @@ public class CaculateUtils { || key.contains("[") || key.contains("]") || key.contains("*") || key.contains("+") || key.contains("?") || key.contains("^") || key.contains("$") || key.contains("|")) { // 转义正则表达式特殊字符 - String escapedKey = java.util.regex.Pattern.quote(key); + String escapedKey = Pattern.quote(key); str = str.replaceAll(escapedKey, value); } else { str = str.replaceAll(key, value); 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 79890733..242433dc 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 @@ -2,21 +2,22 @@ package com.fastbee.iot.data.aspect; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.constant.CacheConstants; import com.fastbee.common.core.device.DeviceAndProtocol; import com.fastbee.common.core.mq.DeviceReportBo; import com.fastbee.common.core.mq.message.DeviceData; import com.fastbee.common.core.mq.message.DeviceMessage; +import com.fastbee.common.core.redis.RedisCache; import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; import com.fastbee.common.enums.DeviceLogTypeEnum; import com.fastbee.common.enums.TopicType; import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.SecurityUtils; import com.fastbee.common.utils.StringUtils; import com.fastbee.common.utils.gateway.mq.TopicsUtils; -import com.fastbee.iot.domain.Device; -import com.fastbee.iot.domain.DeviceLog; -import com.fastbee.iot.domain.Product; -import com.fastbee.iot.domain.ProductAuthorize; +import com.fastbee.iot.domain.*; import com.fastbee.iot.service.IDeviceService; +import com.fastbee.iot.service.IDeviceUserService; import com.fastbee.iot.service.IProductAuthorizeService; import com.fastbee.iot.service.IProductService; import com.fastbee.iot.tdengine.service.ILogService; @@ -94,6 +95,9 @@ public class DeviceDebugLogAspect { @Resource private TopicsUtils topicsUtils; + @Resource + private RedisCache redisCache; + /** * 拦截数据解析方法 */ @@ -184,36 +188,6 @@ public class DeviceDebugLogAspect { }, "记录协议解析错误日志失败"); } - /** - * 根据设备编号获取协议编码(有缓存) - */ - private String getProtocolCodeBySerialNumber(String serialNumber) { - try { - DeviceAndProtocol deviceAndProtocol = - deviceService.selectProtocolBySerialNumber(serialNumber); - if (deviceAndProtocol != null) { - return deviceAndProtocol.getProtocolCode(); - } - } catch (Exception ex) { - log.warn("获取协议编码失败: {}, 设备编号: {}", ex.getMessage(), serialNumber); - } - return null; - } - - /** - * 根据产品ID获取协议编码(有缓存) - */ - private String getProtocolCodeByProductId(Long productId) { - try { - if (productId != null) { - return productService.getProtocolByProductId(productId); - } - } catch (Exception ex) { - log.warn("根据产品ID获取协议编码失败: {}, 产品ID: {}", ex.getMessage(), productId); - } - return null; - } - /** * 根据设备编号获取产品ID(有缓存) */ @@ -245,6 +219,12 @@ public class DeviceDebugLogAspect { log.warn("设备编号为空,跳过错误日志记录"); return; } + + // 检查用户是否在数据调试页面 + if (!isUserInDebugPage(context.getSerialNumber())) { + return; + } + saveErrorLog(context); } catch (Exception ex) { log.error("{}: {}", errorLogPrefix, ex.getMessage(), ex); @@ -318,6 +298,50 @@ public class DeviceDebugLogAspect { String errorMsg = e.getMessage(); return StringUtils.isEmpty(errorMsg) ? e.getClass().getName() : errorMsg; } + @Resource + private IDeviceUserService deviceUserService; + /** + * 检查当前用户是否在数据调试页面 + * @return true-在调试页面,false-不在调试页面 + */ + private boolean isUserInDebugPage(String serialNumber) { + + // 1、从Redis中获取用户ID + + // 2、从SecurityContext中获取用户ID + try { + Long userId = SecurityUtils.getUserId(); + if (userId == null) { + return false; + } + String cacheKey = CacheConstants.DEVICE_DEBUG_USER_KEY + userId; + String flag = redisCache.getCacheObject(cacheKey); + return "1".equals(flag); + } catch (Exception e) { + // 如果获取用户信息失败,默认不记录日志,避免影响正常功能 + log.debug("无法从SecurityContext获取用户ID(可能在异步线程中)"); + } + + + // 3、从数据库中获取用户ID + try { + // 获取设备信息 + Device device = getDeviceBySerialNumber(serialNumber); + if (device == null || device.getTenantId() == null) { + return false; + } + String cacheKey = CacheConstants.DEVICE_DEBUG_USER_KEY + device.getTenantId(); + String flag = redisCache.getCacheObject(cacheKey); + if ("1".equals(flag)) { + log.debug("检测到设备[{}]的关联用户[{}]开启了调试标志", serialNumber, device.getTenantId()); + return true; + } + return false; + } catch (Exception e) { + log.warn("检查设备用户调试状态失败: {}, 设备编号: {}", e.getMessage(), serialNumber); + return false; + } + } /** * 检查设备是否已授权 @@ -377,13 +401,13 @@ public class DeviceDebugLogAspect { } // 构建并保存设备日志 - DeviceLog deviceLog = buildDeviceLog(device, context); - logService.saveDeviceLog(deviceLog); - if (log.isDebugEnabled()) { - log.debug("已保存错误日志到数据库 - 设备: {}, 错误类型: {}, 错误信息: {}", - serialNumber, context.getIdentity(), - StringUtils.substring(context.getErrorMsg(), 0, MAX_ERROR_MSG_DISPLAY_LENGTH)); - } +// DeviceLog deviceLog = buildDeviceLog(device, context); +// logService.saveDeviceLog(deviceLog); +// if (log.isDebugEnabled()) { +// log.debug("已保存错误日志到数据库 - 设备: {}, 错误类型: {}, 错误信息: {}", +// serialNumber, context.getIdentity(), +// StringUtils.substring(context.getErrorMsg(), 0, MAX_ERROR_MSG_DISPLAY_LENGTH)); +// } // 推送错误信息到前端页面 pushErrorToFrontend(serialNumber, productId, context); diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/executor/PowerOnTriggerTaskExecutor.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/executor/PowerOnTriggerTaskExecutor.java index e9d2028c..74e47042 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/executor/PowerOnTriggerTaskExecutor.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/executor/PowerOnTriggerTaskExecutor.java @@ -11,7 +11,6 @@ import com.fastbee.common.enums.ServerType; import com.fastbee.common.enums.TopicType; import com.fastbee.common.constant.HttpStatus; import com.fastbee.common.core.domain.AjaxResult; -import com.fastbee.common.utils.DateUtils; import com.fastbee.common.utils.StringUtils; import com.fastbee.common.utils.gateway.mq.TopicsUtils; import com.fastbee.common.utils.gateway.protocol.ByteUtils; @@ -134,6 +133,12 @@ public class PowerOnTriggerTaskExecutor { return; } + try { + Thread.sleep(1000); // 延迟1秒,等待设备上线 + } catch (InterruptedException e) { + log.error("设备上电触发任务执行失败", e); + } + String pendingKey = RedisKeyBuilder.buildPowerOnTriggerPendingKey(serialNumber); // 关键:使用原子删除操作来防止竞态条件 // 只有第一个成功删除 pendingKey 的线程才能继续执行任务 @@ -300,10 +305,6 @@ public class PowerOnTriggerTaskExecutor { continue; } - // 记录指令到Redis缓存(用于轮询任务管理) - String runtimeCacheKey = RedisKeyBuilder.buildModbusRuntimeCacheKey(serialNumber); - redisCache.zSetAdd(runtimeCacheKey, command, DateUtils.getTimestampSeconds()); - // 记录发送时间 long sendTime = System.currentTimeMillis(); boolean responseReceived; diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceJobServiceImpl.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceJobServiceImpl.java index 08bc7e9e..37658072 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceJobServiceImpl.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceJobServiceImpl.java @@ -15,11 +15,11 @@ import org.quartz.JobDataMap; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; +import javax.annotation.Resource; import java.util.List; /** @@ -30,13 +30,13 @@ import java.util.List; @Service @Slf4j public class DeviceJobServiceImpl implements IDeviceJobService { - @Autowired + @Resource private Scheduler scheduler; - @Autowired + @Resource private DeviceJobMapper jobMapper; - @Autowired + @Resource private SysJobMapper sysJobMapper; diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceReportMessageServiceImpl.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceReportMessageServiceImpl.java index 78947714..b9d36416 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceReportMessageServiceImpl.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceReportMessageServiceImpl.java @@ -145,10 +145,10 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi queryParam.setSerialNumber(serialNumber); List authorizeList = productAuthorizeService.selectProductAuthorizeList(queryParam); if (authorizeList == null || authorizeList.isEmpty()) { - log.warn("TCP数据上报-授权码校验失败: 设备[{}]未绑定授权码,拒绝数据上报", serialNumber); + //log.warn("TCP数据上报-授权码校验失败: 设备[{}]未绑定授权码,拒绝数据上报", serialNumber); return; // 拒绝数据上报 } - log.debug("TCP数据上报-授权码校验通过: 设备[{}]已绑定授权码", serialNumber); + //log.debug("TCP数据上报-授权码校验通过: 设备[{}]已绑定授权码", serialNumber); } //同步设备状态 @@ -285,10 +285,8 @@ public class DeviceReportMessageServiceImpl implements IDeviceReportMessageServi private void processNoSub(DeviceReport message, String topicName) { if (message.getServerType().equals(ServerType.MQTT)) { //处理topic以prop结尾上报的数据 (属性) - if (message.getServerType().equals(ServerType.MQTT)) { - if (!topicName.endsWith(TopicType.PROPERTY_POST.getTopicSuffix())) { - return; - } + if (!topicName.endsWith(TopicType.PROPERTY_POST.getTopicSuffix())) { + return; } } ReportDataBo report = new ReportDataBo(); diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/device/DeviceController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/device/DeviceController.java index b38d9565..456ed634 100644 --- a/fastbee-open-api/src/main/java/com/fastbee/controller/device/DeviceController.java +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/device/DeviceController.java @@ -1,12 +1,14 @@ package com.fastbee.controller.device; import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.CacheConstants; import com.fastbee.common.constant.HttpStatus; import com.fastbee.common.core.controller.BaseController; import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.core.domain.entity.SysRole; import com.fastbee.common.core.domain.model.LoginUser; import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.core.redis.RedisCache; import com.fastbee.common.enums.BusinessType; import com.fastbee.common.exception.ServiceException; import com.fastbee.common.utils.MessageUtils; @@ -34,6 +36,7 @@ import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -49,6 +52,8 @@ public class DeviceController extends BaseController { @Autowired private IDeviceService deviceService; + @Autowired + private RedisCache redisCache; /** * 查询设备列表 @@ -453,4 +458,39 @@ public class DeviceController extends BaseController { return rspData; } + /** + * 开启设备调试日志记录 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @PostMapping("/debug/enable") + @ApiOperation("开启设备调试日志记录") + public AjaxResult enableDebugLog() { + try { + Long userId = getUserId(); + String cacheKey = CacheConstants.DEVICE_DEBUG_USER_KEY + userId; + // 设置标志,过期时间30分钟(防止页面异常关闭导致标志未清除) + redisCache.setCacheObject(cacheKey, "1", 30, TimeUnit.MINUTES); + return AjaxResult.success("已开启调试日志记录"); + } catch (Exception e) { + return AjaxResult.error("开启调试日志记录失败:" + e.getMessage()); + } + } + + /** + * 关闭设备调试日志记录 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @PostMapping("/debug/disable") + @ApiOperation("关闭设备调试日志记录") + public AjaxResult disableDebugLog() { + try { + Long userId = getUserId(); + String cacheKey = CacheConstants.DEVICE_DEBUG_USER_KEY + userId; + redisCache.deleteObject(cacheKey); + return AjaxResult.success("已关闭调试日志记录"); + } catch (Exception e) { + return AjaxResult.error("关闭调试日志记录失败:" + e.getMessage()); + } + } + } 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 2f942159..2cb3f078 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 @@ -50,7 +50,7 @@ public class MessageAdapter implements MessageDecoder, MessageEncoder { Long productId = null; String devNum = new String(ByteBufUtil.getBytes(buf)); String dump = ByteBufUtil.hexDump(buf); - log.info("{}=>上报hex数据:{}", clientId, dump); + log.info("{}上报hex数据:{}", clientId, dump); //这里兼容一下TCP整包发送的数据(整包==设备编号,数据一起发送) if (clientId == null) { diff --git a/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/config/NettyConfig.java b/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/config/NettyConfig.java index affd2a87..e6d8ee65 100644 --- a/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/config/NettyConfig.java +++ b/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/config/NettyConfig.java @@ -127,7 +127,6 @@ public class NettyConfig { case COAP: this.name = name != null ? name : ServerType.COAP.name(); this.server = server; - ; this.server.config = this; break; default: diff --git a/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/handler/TCPMessageAdapter.java b/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/handler/TCPMessageAdapter.java index b49630f9..3586771f 100644 --- a/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/handler/TCPMessageAdapter.java +++ b/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/handler/TCPMessageAdapter.java @@ -5,6 +5,7 @@ import com.fastbee.base.session.Session; import com.fastbee.base.session.SessionManager; import com.fastbee.base.util.AttributeUtils; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; @@ -49,6 +50,15 @@ public class TCPMessageAdapter extends ChannelInboundHandlerAdapter { ByteBuf buf = (ByteBuf) msg; /*session存储*/ Session session = getSession(ctx); + // 临时记录设备原始数据日志 + // 针对特定设备记录原始数据日志 + String serialNumber = session.getClientId(); + if ("D3C611176213".equalsIgnoreCase(serialNumber) || "DEE3FBBF0132".equalsIgnoreCase(serialNumber)) { + ByteBuf copy = buf.duplicate(); + byte[] source = new byte[buf.writerIndex()]; + copy.readBytes(source); + log.warn("=>设备[{}]原始数据:{}", serialNumber, ByteBufUtil.hexDump(source)); + } session.access(); /*将设备端消息传递给下一个处理器*/ ctx.fireChannelRead(Packet.of(session, buf)); 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 13d27ae0..2e9a7e3c 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 @@ -71,7 +71,8 @@ import java.math.BigDecimal; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static com.fastbee.common.utils.SecurityUtils.getLoginUser; @@ -498,7 +499,7 @@ public class DeviceServiceImpl implements IDeviceService { @DSTransactional public List reportDeviceThingsModelValue(ThingsModelValuesInput input, int type, boolean isShadow) { String key = RedisKeyBuilder.buildTSLVCacheKey(input.getProductId(), input.getDeviceNumber()); - Map maps = new HashMap(); + Map maps = new HashMap<>(); List list = new ArrayList<>(); //属性存储集合 @@ -507,6 +508,8 @@ public class DeviceServiceImpl implements IDeviceService { List functionLogList = new ArrayList<>(); //事件存储集合 List eventLogList = new ArrayList<>(); + // 获取当前时间 + Date nowDate = DateUtils.getNowDate(); for (ThingsModelSimpleItem item : input.getThingsModelValueRemarkItem()) { String identity = item.getId(); String serialNumber = input.getDeviceNumber(); @@ -538,13 +541,7 @@ public class DeviceServiceImpl implements IDeviceService { // 解析公式中的其他物模型标识符(格式:{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); - } + Set referencedIdentifiers = parseFormulaPlaceholders(formula); // 获取引用的其他物模型的值 for (String refIdentifier : referencedIdentifiers) { @@ -561,8 +558,7 @@ public class DeviceServiceImpl implements IDeviceService { } else { // 如果引用的物模型值不存在,使用0作为默认值 params.put("{" + refIdentifier + "}", "0"); - log.warn("=>计算公式引用的物模型值不存在[{}],标识符:[{}],使用默认值0", - input.getProductId(), refIdentifier); + log.warn("=>计算公式引用的物模型值不存在[{}],标识符:[{}],使用默认值0", input.getProductId(), refIdentifier); } } catch (Exception e) { log.error("=>获取计算公式引用的物模型值失败[{}],标识符:[{}],错误:{}", @@ -641,7 +637,7 @@ public class DeviceServiceImpl implements IDeviceService { deviceLog.setLogValue(value); deviceLog.setRemark(item.getRemark()); deviceLog.setIdentity(id); - deviceLog.setCreateTime(DateUtils.getNowDate()); + deviceLog.setCreateTime(nowDate); deviceLog.setUserId(device.getTenantId()); deviceLog.setUserName(device.getTenantName()); deviceLog.setTenantId(device.getTenantId()); @@ -654,7 +650,7 @@ public class DeviceServiceImpl implements IDeviceService { case SERVICE: if (1 == thingModels.getIsHistory()) { FunctionLog function = new FunctionLog(); - function.setCreateTime(DateUtils.getNowDate()); + function.setCreateTime(nowDate); function.setFunValue(value); function.setSerialNumber(input.getDeviceNumber()); function.setIdentify(id); @@ -677,7 +673,7 @@ public class DeviceServiceImpl implements IDeviceService { event.setIsMonitor(0); event.setUserId(device.getTenantId()); event.setUserName(device.getTenantName()); - event.setCreateTime(DateUtils.getNowDate()); + event.setCreateTime(nowDate); // 1=影子模式,2=在线模式,3=其他 event.setMode(2); eventLogList.add(event); @@ -687,11 +683,12 @@ public class DeviceServiceImpl implements IDeviceService { } // 缓存最新一条数据到redis redisCache.hashPutAll(key, maps); - //存储历史数据 + // 存储历史数据——批量 if (!CollectionUtils.isEmpty(deviceLogList) && !isShadow) { - for (DeviceLog deviceLog : deviceLogList) { - logService.saveDeviceLog(deviceLog); - } +// for (DeviceLog deviceLog : deviceLogList) { +// logService.saveDeviceLog(deviceLog); +// } + logService.saveDeviceLogBatch(deviceLogList); } //指令存储,影子模式不存储 if (!CollectionUtils.isEmpty(functionLogList) && !isShadow) { @@ -720,7 +717,7 @@ public class DeviceServiceImpl implements IDeviceService { Map calculatedMaps = new HashMap<>(); List calculatedDeviceLogList = new ArrayList<>(); - calculateDependentThingsModels(input, updatedIdentifiers, key, calculatedMaps, calculatedDeviceLogList, type); + calculateDependentThingsModels(input, updatedIdentifiers, nowDate, calculatedMaps, calculatedDeviceLogList, type); // 将计算后的值更新到缓存 if (!calculatedMaps.isEmpty()) { @@ -731,9 +728,7 @@ public class DeviceServiceImpl implements IDeviceService { // 保存计算型物模型的历史数据 if (!CollectionUtils.isEmpty(calculatedDeviceLogList)) { - for (DeviceLog deviceLog : calculatedDeviceLogList) { - logService.saveDeviceLog(deviceLog); - } + logService.saveDeviceLogBatch(calculatedDeviceLogList); log.debug("=>自动计算依赖物模型,保存历史数据成功,设备编号:[{}],历史数据数量:[{}]", input.getDeviceNumber(), calculatedDeviceLogList.size()); } @@ -743,6 +738,26 @@ public class DeviceServiceImpl implements IDeviceService { return list; } + /** + * 解析公式中的占位符标识符 + * + * @param formula 计算公式 + * @return 占位符标识符集合 + */ + private Set parseFormulaPlaceholders(String formula) { + Set referencedIdentifiers = new HashSet<>(); + if (StringUtils.isEmpty(formula)) { + return referencedIdentifiers; + } + Pattern pattern = Pattern.compile("\\{([^}]+)}"); + Matcher matcher = pattern.matcher(formula); + while (matcher.find()) { + String identifier = matcher.group(1); + referencedIdentifiers.add(identifier); + } + return referencedIdentifiers; + } + /** * 查询设备列表 * @@ -1947,14 +1962,14 @@ public class DeviceServiceImpl implements IDeviceService { * * @param input 设备上报数据输入 * @param updatedIdentifiers 已更新的物模型标识符集合(设备上报的) - * @param key Redis缓存key + * @param nowDate 数据记录时间 * @param maps 物模型值映射(用于批量更新缓存) * @param deviceLogList 设备日志列表(用于存储历史数据) * @param type 物模型类型(1=属性,2=功能,3=事件) */ private void calculateDependentThingsModels(ThingsModelValuesInput input, Set updatedIdentifiers, - String key, + Date nowDate, Map maps, List deviceLogList, int type) { @@ -1991,13 +2006,7 @@ public class DeviceServiceImpl implements IDeviceService { } // 检查计算公式是否引用了已更新的物模型 - 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); - } + Set referencedIdentifiers = parseFormulaPlaceholders(formula); // 检查是否至少引用了一个已更新的物模型 boolean hasDependency = false; @@ -2074,7 +2083,7 @@ public class DeviceServiceImpl implements IDeviceService { deviceLog.setLogValue(calculatedValue); deviceLog.setRemark("自动计算"); // 标记为自动计算 deviceLog.setIdentity(dependentIdentifier); - deviceLog.setCreateTime(DateUtils.getNowDate()); + deviceLog.setCreateTime(nowDate); deviceLog.setUserId(device.getTenantId()); deviceLog.setUserName(device.getTenantName()); deviceLog.setTenantId(device.getTenantId()); diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java index d9533497..4856e4b9 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java @@ -627,8 +627,8 @@ public class ThingsModelServiceImpl implements IThingsModelService { public IdentifierVO revertObjectOrArrayIdentifier(String identifier) { IdentifierVO identifierVO = new IdentifierVO(); if (identifier.startsWith("array_")) { - int i1 = org.apache.commons.lang3.StringUtils.ordinalIndexOf(identifier, "_", 1); - int i2 = org.apache.commons.lang3.StringUtils.ordinalIndexOf(identifier, "_", 2); + int i1 = StringUtils.ordinalIndexOf(identifier, "_", 1); + int i2 = StringUtils.ordinalIndexOf(identifier, "_", 2); identifierVO.setIdentifier(identifier.substring(i2 + 1)); identifierVO.setIndex(Integer.parseInt(identifier.substring(++i1, i2))); } else { diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/ILogService.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/ILogService.java index 092e9189..99d4b9b2 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/ILogService.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/ILogService.java @@ -27,6 +27,10 @@ public interface ILogService { * 保存设备日志 **/ int saveDeviceLog(DeviceLog deviceLog); + /** + * 批量保存设备日志 + **/ + void saveDeviceLogBatch(List deviceLogList); /** * 批量保存日志 diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/MySqlLogServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/MySqlLogServiceImpl.java index 84851376..dac10214 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/MySqlLogServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/MySqlLogServiceImpl.java @@ -14,10 +14,11 @@ import com.fastbee.iot.mapper.DeviceLogMapper; import com.fastbee.iot.model.MonitorModel; import com.fastbee.iot.tdengine.service.model.TdLogDto; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import javax.annotation.Resource; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -33,11 +34,11 @@ import java.util.stream.Collectors; @ConditionalOnProperty(name = "spring.datasource.dynamic.datasource.taos.enabled", havingValue = "false") @Service("MySql") public class MySqlLogServiceImpl implements ILogService { - @Autowired + @Resource private DeviceLogMapper deviceLogMapper; - @Autowired + @Resource private EventLogMapper eventLogMapper; - @Autowired + @Resource private FunctionLogMapper functionLogMapper; @@ -55,6 +56,14 @@ public class MySqlLogServiceImpl implements ILogService { return deviceLogMapper.insertDeviceLog(deviceLog); } + @Override + public void saveDeviceLogBatch(List deviceLogList) { + if (CollectionUtils.isEmpty(deviceLogList)) { + return; + } + deviceLogMapper.saveBatch(deviceLogList); + } + @Override public int saveBatch(TdLogDto dto) { return deviceLogMapper.saveBatch(dto.getList()); diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/TdengineLogServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/TdengineLogServiceImpl.java index 1e019b0c..3263f8d2 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/TdengineLogServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/TdengineLogServiceImpl.java @@ -23,6 +23,7 @@ import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.springframework.util.CollectionUtils; /** * 类名: TdengineLogServiceImpl @@ -62,6 +63,52 @@ public class TdengineLogServiceImpl implements ILogService { return tDDeviceLogMapper.save(dbName, deviceLog); } + /*** + * 批量保存设备日志 + * @param deviceLogList 设备日志列表 + */ + @Override + public void saveDeviceLogBatch(List deviceLogList) { + if (CollectionUtils.isEmpty(deviceLogList)) { + log.debug("=>批量保存设备日志到TDengine:日志列表为空,跳过保存"); + return; + } + try { + // 按设备编号分组 + Map> groupedBySerialNumber = deviceLogList.stream() + .filter(log -> log.getSerialNumber() != null && !log.getSerialNumber().isEmpty()) + .collect(Collectors.groupingBy(DeviceLog::getSerialNumber)); + + if (groupedBySerialNumber.isEmpty()) { + log.warn("=>批量保存设备日志到TDengine:没有有效的设备编号,跳过保存"); + return; + } + + // 为每个设备批量保存日志 + int totalSaved = 0; + for (Map.Entry> entry : groupedBySerialNumber.entrySet()) { + String serialNumber = entry.getKey(); + List logs = entry.getValue(); + + try { + TdLogDto dto = new TdLogDto(serialNumber, logs); + int saved = tDDeviceLogMapper.saveBatch(dbName, dto); + totalSaved += saved; + log.debug("=>批量保存设备日志到TDengine成功,设备编号:[{}],保存{}条记录", serialNumber, saved); + } catch (Exception e) { + log.error("=>批量保存设备日志到TDengine失败,设备编号:[{}],共{}条记录,错误:{}", + serialNumber, logs.size(), e.getMessage(), e); + // 继续处理其他设备的日志,不中断整个流程 + } + } + + } catch (Exception e) { + log.error("=>批量保存设备日志到TDengine时发生异常,共{}条记录,错误:{}", + deviceLogList.size(), e.getMessage(), e); + throw new RuntimeException("批量保存设备日志到TDengine失败:" + e.getMessage(), e); + } + } + /** * 批量保存日志 */ diff --git a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysNotice.java b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysNotice.java index 4b7cdf34..c32075ae 100644 --- a/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysNotice.java +++ b/fastbee-service/fastbee-system-service/src/main/java/com/fastbee/system/domain/SysNotice.java @@ -32,9 +32,9 @@ public class SysNotice extends BaseEntity { private String noticeTitle; /** - * 公告类型(1通知 2公告) + * 公告类型(1通知 2公告 3文档) */ - @ApiModelProperty("公告类型(1通知 2公告)") + @ApiModelProperty("公告类型(1通知 2公告 3文档)") private String noticeType; /** @@ -43,6 +43,12 @@ public class SysNotice extends BaseEntity { @ApiModelProperty("公告内容") private String noticeContent; + /** + * 文件路径(文档类型使用) + */ + @ApiModelProperty("文件路径(文档类型使用)") + private String filePath; + /** * 公告状态(0正常 1关闭) */ @@ -84,6 +90,14 @@ public class SysNotice extends BaseEntity { this.noticeContent = noticeContent; } + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + public String getStatus() { return status; } @@ -99,6 +113,7 @@ public class SysNotice extends BaseEntity { .append("noticeTitle", getNoticeTitle()) .append("noticeType", getNoticeType()) .append("noticeContent", getNoticeContent()) + .append("filePath", getFilePath()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) diff --git a/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysNoticeMapper.xml b/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysNoticeMapper.xml index 66ee3bb6..87414229 100644 --- a/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysNoticeMapper.xml +++ b/fastbee-service/fastbee-system-service/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -9,6 +9,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -18,7 +19,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, create_by, create_time, update_by, update_time, remark + select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, file_path, status, create_by, create_time, update_by, update_time, remark from sys_notice @@ -47,6 +48,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" notice_title, notice_type, notice_content, + file_path, status, remark, create_by, @@ -55,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{noticeTitle}, #{noticeType}, #{noticeContent}, + #{filePath}, #{status}, #{remark}, #{createBy}, @@ -68,6 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" notice_title = #{noticeTitle}, notice_type = #{noticeType}, notice_content = #{noticeContent}, + file_path = #{filePath}, status = #{status}, update_by = #{updateBy}, update_time = sysdate() diff --git a/vue/src/api/iot/mqttTest.js b/vue/src/api/iot/mqttTest.js index 8be8e114..e429b8eb 100644 --- a/vue/src/api/iot/mqttTest.js +++ b/vue/src/api/iot/mqttTest.js @@ -61,3 +61,19 @@ export function delPreferences(data) { method: 'DELETE' }); } + +// 开启设备调试日志记录 +export function enableDebugLog() { + return request({ + url: '/iot/device/debug/enable', + method: 'post', + }); +} + +// 关闭设备调试日志记录 +export function disableDebugLog() { + return request({ + url: '/iot/device/debug/disable', + method: 'post', + }); +} diff --git a/vue/src/views/dataCenter/history.vue b/vue/src/views/dataCenter/history.vue index 2216ea84..5216e4f9 100644 --- a/vue/src/views/dataCenter/history.vue +++ b/vue/src/views/dataCenter/history.vue @@ -12,8 +12,8 @@ - - 全部勾选 + + {{ devIsAllSelected ? '全部反选' : '全部勾选' }} @@ -80,8 +80,8 @@ - - 全部勾选 + + {{ sceneIsAllSelected ? '全部反选' : '全部勾选' }} @@ -270,6 +270,20 @@ export default { const end = start + this.scenePageSize; return this.sceneTableComTemp.slice(start, end); }, + // 判断设备变量是否全部选中 + devIsAllSelected() { + if (this.devIdentifierList.length === 0) return false; + const allIdentifiers = this.devIdentifierList.map(item => item.identifier); + return allIdentifiers.length > 0 && + allIdentifiers.every(id => this.devQueryParams.identifiers.includes(id)); + }, + // 判断场景变量是否全部选中 + sceneIsAllSelected() { + if (this.sceneIdentifierList.length === 0) return false; + const allIdentifiers = this.sceneIdentifierList.map(item => item.identifier); + return allIdentifiers.length > 0 && + allIdentifiers.every(id => this.sceneQueryParams.identifiers.includes(id)); + }, }, methods: { // 获取设备列表 @@ -292,12 +306,22 @@ export default { }, // 设备变量选择变化 handleDevIdentifierChange(val) { - // 如果选择了"全部勾选",则选中所有变量 + // 如果选择了"全部勾选",则切换全选/全不选 const index = val.indexOf('__selectAll__'); if (index > -1) { const allIdentifiers = this.devIdentifierList.map(item => item.identifier); + // 判断当前是否已经全部选中(基于当前值,不是新值) + const isAllSelected = allIdentifiers.length > 0 && + allIdentifiers.every(id => this.devQueryParams.identifiers.includes(id)); + this.$nextTick(() => { - this.devQueryParams.identifiers = [...allIdentifiers]; + if (isAllSelected) { + // 如果已经全部选中,则清空选择(反选) + this.devQueryParams.identifiers = []; + } else { + // 如果没有全部选中,则选中所有(勾选) + this.devQueryParams.identifiers = [...allIdentifiers]; + } }); } }, @@ -398,12 +422,22 @@ export default { }, // 场景变量选择变化 handleSceneIdentifierChange(val) { - // 如果选择了"全部勾选",则选中所有变量 + // 如果选择了"全部勾选",则切换全选/全不选 const index = val.indexOf('__selectAll__'); if (index > -1) { const allIdentifiers = this.sceneIdentifierList.map(item => item.identifier); + // 判断当前是否已经全部选中(基于当前值,不是新值) + const isAllSelected = allIdentifiers.length > 0 && + allIdentifiers.every(id => this.sceneQueryParams.identifiers.includes(id)); + this.$nextTick(() => { - this.sceneQueryParams.identifiers = [...allIdentifiers]; + if (isAllSelected) { + // 如果已经全部选中,则清空选择(反选) + this.sceneQueryParams.identifiers = []; + } else { + // 如果没有全部选中,则选中所有(勾选) + this.sceneQueryParams.identifiers = [...allIdentifiers]; + } }); } }, diff --git a/vue/src/views/index.vue b/vue/src/views/index.vue index 66e3126c..2bfbd6e0 100644 --- a/vue/src/views/index.vue +++ b/vue/src/views/index.vue @@ -177,24 +177,31 @@ - -

- - {{ $t('home.information') }} -

-
-
-
- 公告 - 信息 - {{ item.noticeTitle }} -
-
- - {{ parseTime(item.createTime, '{y}-{m}-{d}') }} -
-
+ +

+ + {{ $t('home.information') }} +

+
+
+
+ 公告 + 文档 + 信息 + {{ item.noticeTitle }} +
+
+ + {{ parseTime(item.createTime, '{y}-{m}-{d}') }} +
+
@@ -238,7 +245,14 @@ {{ notice.createTime }}
-
+ +
+ + 下载文档 + +
+ +
- - - + + + - + - + + + + + {{ item.deviceName }} - - {{ item.deviceName }} @@ -189,38 +191,38 @@
- - -
- - - - {{ item.protocolCode }} + + +
+ + + {{ item.protocolCode }} - {{ item.transport }} - + {{ item.transport }} + {{ $t('device.index.105953-53') }}{{ item.alertCount.unprocessedCount }} - -
- + - {{ item.serialNumber }} + + {{ item.serialNumber }} + - {{ item.productName }} + + {{ item.productName }} + {{ parseTime(item.activeTime, '{y}-{m}-{d}') }}
- +
- - + + {{ $t('device.index.105953-35') }} - + {{ $t('device.index.105953-36') }} - + {{ $t('device.index.105953-40') }} diff --git a/vue/src/views/iot/device/instruction-parsing.vue b/vue/src/views/iot/device/instruction-parsing.vue index b42bdaa2..b9a86fbd 100644 --- a/vue/src/views/iot/device/instruction-parsing.vue +++ b/vue/src/views/iot/device/instruction-parsing.vue @@ -375,7 +375,7 @@