From 7aef1ba8e3fc20faaf57a25674c77331453f4610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A5=88=E7=A7=91=E6=96=AF=EF=BC=88NexIoT=EF=BC=89?= <16120872+nexiot@user.noreply.gitee.com> Date: Tue, 23 Sep 2025 10:56:07 +0800 Subject: [PATCH] =?UTF-8?q?chore(release):=20=E7=89=88=E6=9C=AC=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=B1=87=E6=80=BB=20(2025-08-10=20=E8=87=B3=202025-09?= =?UTF-8?q?-23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 核心功能优化: - 新增UDP模块并默认开启,TCP基础上增加UDP适配 - 支持网关绑定多产品子设备,优化设备重连机制 - 新增Ezviz(萤石)接入模块,调整imoulife支持自有平台账号 性能与稳定性: - 优化设备影子热数据存储,增加强制刷盘功能 - 产品列表及MQTT查询增加多级缓存策略 - 强化TCP安全机制,新增异常攻击封禁策略 功能完善: - 集成rulego可视化规则引擎基础框架 - Web-IDE新增20+ modbus/hex封装方法 - 优化乐城播放器界面及TCP连接信息展示 问题修复: - 解决系统间强依赖及注入相关问题 - 修复时间格式化、设备创建时间显示问题 - 完善日志处理及应用推送相关异常 其他改进: - 重构TCP粘包处理,删除冗余代码 - 增加Docker部署配置 - 细化权限控制策略 --- cn-universal-admin/pom.xml | 7 +- .../admin/network/doc/NetworkApiDoc.java | 82 ----- .../network/service/INetworkService.java | 10 - .../service/impl/NetworkServiceImpl.java | 297 ++++++++++++++--- .../admin/network/utils/NetworkTypeUtil.java | 2 +- .../admin/network/web/NetworkController.java | 7 +- .../platform/service/IIoTDeviceService.java | 9 +- .../platform/service/IIoTProductService.java | 4 +- .../impl/IoTDeviceGroupServiceImpl.java | 39 ++- .../service/impl/IoTDeviceServiceImpl.java | 34 +- .../service/impl/IoTProductServiceImpl.java | 183 +++++++++-- .../platform/web/IoTDeviceController.java | 29 +- .../platform/web/IoTProductController.java | 307 +++++++++--------- .../platform/web/IoTUserAppController.java | 44 ++- .../service/IIoTUserApplicationService.java | 11 + .../impl/IoTUserApplicationServiceImpl.java | 120 +++++++ 16 files changed, 807 insertions(+), 378 deletions(-) delete mode 100644 cn-universal-admin/src/main/java/cn/universal/admin/network/doc/NetworkApiDoc.java diff --git a/cn-universal-admin/pom.xml b/cn-universal-admin/pom.xml index 6bb577f..4e13709 100644 --- a/cn-universal-admin/pom.xml +++ b/cn-universal-admin/pom.xml @@ -114,7 +114,12 @@ ${project.version} compile - + + cn.universal.iot + cn-universal-notice + ${project.version} + compile + \ No newline at end of file diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/network/doc/NetworkApiDoc.java b/cn-universal-admin/src/main/java/cn/universal/admin/network/doc/NetworkApiDoc.java deleted file mode 100644 index 8066702..0000000 --- a/cn-universal-admin/src/main/java/cn/universal/admin/network/doc/NetworkApiDoc.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * - * Copyright (c) 2025, IoT-Universal. All Rights Reserved. - * - * @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。 - * @Author: Aleo - * @Email: wo8335224@gmail.com - * @Wechat: outlookFil - * - * - */ - -package cn.universal.admin.network.doc; - -/** - * 网络组件API文档 - * - * @version 1.0 @Author Aleo - * @since 2025/1/20 - */ -public class NetworkApiDoc { - - /** - * 网络组件管理API文档 - * - *

基础路径: /admin/network - * - *

1. 查询网络组件列表 GET /admin/network/list 参数: - page: 页码 (可选,默认1) - size: 每页大小 (可选,默认10) - type: - * 网络类型 (可选,单个类型,如 TCP_CLIENT) - types: 网络类型列表 (可选,多个类型,如 ["MQTT_CLIENT", "MQTT_SERVER"]) - name: - * 网络组件名称 (可选,模糊查询) - productKey: 产品Key (可选) - state: 状态 (可选,true/false) - unionId: 唯一标识 (可选) - * - *

2. 查询网络组件列表(支持多个类型) GET /admin/network/list/multi-type 参数: - page: 页码 (可选,默认1) - size: 每页大小 - * (可选,默认10) - type: 网络类型 (可选,支持逗号分隔多个类型,如 "MQTT_CLIENT,MQTT_SERVER") - name: 网络组件名称 (可选,模糊查询) - - * productKey: 产品Key (可选) - state: 状态 (可选,true/false) - unionId: 唯一标识 (可选) - * - *

3. 根据ID查询网络组件 GET /admin/network/{id} 参数: - id: 网络组件ID (路径参数) - * - *

4. 新增网络组件 POST /admin/network 请求体: { "type": "TCP_SERVER", "unionId": "unique_id", - * "productKey": "product_key", "name": "网络组件名称", "description": "详细描述", "state": false, - * "configuration": "{\"host\":\"0.0.0.0\",\"port\":6372,\"ssl\":false}", "createUser": "admin" } - * - *

5. 修改网络组件 PUT /admin/network 请求体: { "id": 1, "type": "TCP_SERVER", "unionId": "unique_id", - * "productKey": "product_key", "name": "网络组件名称", "description": "详细描述", "state": false, - * "configuration": "{\"host\":\"0.0.0.0\",\"port\":6372,\"ssl\":false}" } - * - *

6. 删除网络组件 DELETE /admin/network/{id} 参数: - id: 网络组件ID (路径参数) - * - *

7. 批量删除网络组件 DELETE /admin/network/batch/{ids} 参数: - ids: 网络组件ID数组 (路径参数,逗号分隔) - * - *

8. 启动网络组件 POST /admin/network/start/{id} 参数: - id: 网络组件ID (路径参数) - * - *

9. 停止网络组件 POST /admin/network/stop/{id} 参数: - id: 网络组件ID (路径参数) - * - *

10. 重启网络组件 POST /admin/network/restart/{id} 参数: - id: 网络组件ID (路径参数) - * - *

11. 获取网络类型列表 GET /admin/network/types - * - *

12. 验证网络组件配置 POST /admin/network/validate 请求体: { "type": "TCP_SERVER", "unionId": - * "unique_id", "name": "网络组件名称", "configuration": - * "{\"host\":\"0.0.0.0\",\"port\":6372,\"ssl\":false}" } - * - *

响应格式: { "code": 200, "msg": "操作成功", "data": { // 具体数据 } } - * - *

网络类型说明: - TCP_CLIENT: TCP客户端 - TCP_SERVER: TCP服务端 - MQTT_CLIENT: MQTT客户端 - MQTT_SERVER: - * MQTT服务端 - * - *

配置示例: - * - *

TCP_SERVER配置: { "allIdleTime": 0, "allowInsert": false, "alwaysPreDecode": false, - * "decoderType": "STRING", "host": "0.0.0.0", "idleInterval": 0, "onlyCache": false, - * "parserConfiguration": { "byteOrderLittle": true, "delimited": "]", "delimitedMaxlength": 1024, - * "failFast": true }, "parserType": "DELIMITED", "port": 6372, "productKey": - * "630477f1cd68445d90b04653", "readerIdleTime": 360, "readTimeout": 0, "sendTimeout": 0, "ssl": - * false, "writerIdleTime": 0 } - * - *

MQTT_SERVER配置: { "autoReconnect": true, "cleanSession": true, "clientIdPrefix": "univ_cli_", - * "defaultQos": 1, "enabled": true, "host": "tcp://125.210.48.96:1883", "id": - * "6863896d75386b5c1e7e908c", "keepAliveInterval": 60, "password": "univiot@2025!", "productKey": - * "local", "ssl": false, "subscribeTopics": "$univ_cli/up/property/+/+", "username": "univ_cli", - * "connectTimeout": 30 } - */ -} diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/network/service/INetworkService.java b/cn-universal-admin/src/main/java/cn/universal/admin/network/service/INetworkService.java index 08dbe55..f91ba2c 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/network/service/INetworkService.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/network/service/INetworkService.java @@ -29,26 +29,16 @@ import java.util.List; */ public interface INetworkService { - boolean start(String productKey); - - boolean stop(String productKey); - - Object get(String productKey); - boolean del(String productKey); List selectNetworkList(NetworkBO bo); - NetworkVO getDetail(Long id); - List queryMileSightList(NetworkBO bo); R insertDevInstance(IoTDeviceBO devInstancebo); R deleteDevInstanceByIds(String[] ids); - void reloadTcpClient(String applicationId); - /** * 查询网络组件列表 * diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/network/service/impl/NetworkServiceImpl.java b/cn-universal-admin/src/main/java/cn/universal/admin/network/service/impl/NetworkServiceImpl.java index 5f4038c..b3726ee 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/network/service/impl/NetworkServiceImpl.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/network/service/impl/NetworkServiceImpl.java @@ -12,6 +12,7 @@ package cn.universal.admin.network.service.impl; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; @@ -25,12 +26,15 @@ import cn.universal.common.domain.R; import cn.universal.common.enums.NetworkType; import cn.universal.common.exception.IoTException; import cn.universal.core.service.IoTDownlFactory; +import cn.universal.dm.device.service.protocol.ProtocolClusterService; +import cn.universal.dm.device.service.protocol.ProtocolServerManager; import cn.universal.mqtt.protocol.third.ThirdMQTTServerManager; import cn.universal.persistence.entity.IoTDevice; import cn.universal.persistence.entity.IoTProduct; import cn.universal.persistence.entity.IoTUser; import cn.universal.persistence.entity.Network; import cn.universal.persistence.entity.bo.IoTDeviceBO; +import cn.universal.persistence.entity.bo.IoTProductBO; import cn.universal.persistence.entity.bo.NetworkBO; import cn.universal.persistence.entity.vo.IoTProductVO; import cn.universal.persistence.entity.vo.NetworkVO; @@ -49,6 +53,7 @@ import java.util.Map; import java.util.Set; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tk.mybatis.mapper.entity.Example; @@ -63,11 +68,8 @@ import tk.mybatis.mapper.entity.Example; @Service public class NetworkServiceImpl implements INetworkService { - @Autowired - private NetworkMapper networkMapper; - @Resource - private NetworkMapper netWorkMapper; + private NetworkMapper networkMapper; @Resource private IoTDeviceMapper ioTDeviceMapper; @Resource @@ -75,42 +77,31 @@ public class NetworkServiceImpl implements INetworkService { @Resource private IoTDeviceFenceRelMapper ioTDeviceFenceRelMapper; + // 直接注入 TCP 和 MQTT 服务 + @Qualifier("tcpClusterService") + @Autowired(required = false) + private ProtocolClusterService tcpClusterService; + + @Autowired(required = false) + private ProtocolServerManager tcpServerManager; + + @Qualifier("udpClusterService") + @Autowired(required = false) + private ProtocolClusterService udpClusterService; @Autowired(required = false) private ThirdMQTTServerManager mqttServerManager; - @Override - public boolean start(String productKey) { - - return true; - } - - @Override - public boolean stop(String productKey) { - - return true; - } - - @Override - public Object get(String productKey) { - return null; - } - @Override public boolean del(String productKey) { Example ex = new Example(Network.class); ex.createCriteria().andEqualTo("productKey", productKey); - return netWorkMapper.deleteByExample(ex) > 0; + return networkMapper.deleteByExample(ex) > 0; } @Override public List selectNetworkList(NetworkBO bo) { - return netWorkMapper.selectNetworkListV1(bo); - } - - @Override - public NetworkVO getDetail(Long id) { - return netWorkMapper.selectById(id); + return networkMapper.selectNetworkListV1(bo); } @Override @@ -182,10 +173,6 @@ public class NetworkServiceImpl implements INetworkService { return R.ok("删除成功"); } - @Override - public void reloadTcpClient(String applicationId) { - } - @Override public List selectNetworkList(NetworkQuery query) { // 验证类型参数 @@ -214,9 +201,32 @@ public class NetworkServiceImpl implements INetworkService { String unionId = bo.getUnionId(); if (NetworkType.TCP_CLIENT.getId().equals(type) || NetworkType.TCP_SERVER.getId() .equals(type)) { + if (tcpClusterService != null && tcpServerManager != null) { + Object serverInstance = tcpServerManager.getServerInstance(productKey); + if (serverInstance != null && tcpServerManager.isAlive(serverInstance)) { + bo.setStateName("已启动"); + bo.setRunning(Boolean.TRUE); + } else { + bo.setStateName("未启动"); + bo.setRunning(Boolean.FALSE); + } + } else { + bo.setStateName("未启动"); + bo.setRunning(Boolean.FALSE); + } + // 查出哪些TCP-Server绑定了产品 + if (NetworkType.TCP_SERVER.getId().equals(type)) { + IoTProductBO ioTProductBO = ioTProductMapper.selectTcpProductsUseNetwork( + bo.getProductKey()); + bo.setBindTcpServerProductCount(ioTProductBO == null ? 0 : 1); + bo.setBindTcpServerProducts(ioTProductBO); + } } else if (NetworkType.MQTT_CLIENT.getId().equals(type) || NetworkType.MQTT_SERVER.getId() .equals(type)) { + List ioTProductBOS = ioTProductMapper.selectMqttProductsUseNetwork(unionId); + bo.setBindMqttServerProductCount(CollUtil.size(ioTProductBOS)); + bo.setBindMqttServerProducts(ioTProductBOS); if (mqttServerManager != null && mqttServerManager.isConnected(unionId)) { bo.setStateName("已启动"); bo.setRunning(Boolean.TRUE); @@ -224,6 +234,14 @@ public class NetworkServiceImpl implements INetworkService { bo.setStateName("未启动"); bo.setRunning(Boolean.FALSE); } + } else if (NetworkType.UDP.getId().equals(type)) { + if (udpClusterService != null && udpClusterService.isProductServerAlive(productKey)) { + bo.setStateName("已启动"); + bo.setRunning(Boolean.TRUE); + } else { + bo.setStateName("未启动"); + bo.setRunning(Boolean.FALSE); + } } else { bo.setStateName("未知"); bo.setRunning(Boolean.FALSE); @@ -264,6 +282,9 @@ public class NetworkServiceImpl implements INetworkService { case NetworkTypeConstants.MQTT_SERVER: vo.setTypeName(NetworkType.MQTT_SERVER.getDescription()); break; + case NetworkTypeConstants.UDP: + vo.setTypeName(NetworkType.UDP.getDescription()); + break; default: vo.setTypeName(network.getType()); } @@ -275,7 +296,19 @@ public class NetworkServiceImpl implements INetworkService { String unionId = network.getUnionId(); if (NetworkType.TCP_CLIENT.getId().equals(type) || NetworkType.TCP_SERVER.getId() .equals(type)) { - //TODO 开源版本无 + if (tcpClusterService != null && tcpServerManager != null) { + Object serverInstance = tcpServerManager.getServerInstance(productKey); + if (serverInstance != null && tcpServerManager.isAlive(serverInstance)) { + vo.setStateName("已启动"); + vo.setRunning(Boolean.TRUE); + } else { + vo.setStateName("未启动"); + vo.setRunning(Boolean.FALSE); + } + } else { + vo.setStateName("未启动"); + vo.setRunning(Boolean.FALSE); + } } else if (NetworkType.MQTT_CLIENT.getId().equals(type) || NetworkType.MQTT_SERVER.getId() .equals(type)) { @@ -286,6 +319,14 @@ public class NetworkServiceImpl implements INetworkService { vo.setStateName("未启动"); vo.setRunning(Boolean.FALSE); } + } else if (NetworkType.UDP.getId().equals(type)) { + if (udpClusterService != null && udpClusterService.isProductServerAlive(productKey)) { + vo.setStateName("已启动"); + vo.setRunning(Boolean.TRUE); + } else { + vo.setStateName("未启动"); + vo.setRunning(Boolean.FALSE); + } } else { vo.setStateName("未知"); vo.setRunning(Boolean.FALSE); @@ -347,11 +388,11 @@ public class NetworkServiceImpl implements INetworkService { network.setUnionId(IdUtil.objectId()); network.setState(false); - // 新增TCP_SERVER时,必须指定productKey,并且不能重复绑定 + // 新增TCP_SERVER/UDP时,必须指定productKey,并且不能重复绑定 if (NetworkType.TCP_SERVER.getId().equals(network.getType()) || NetworkType.TCP_CLIENT.getId() - .equals(network.getType())) { + .equals(network.getType()) || NetworkType.UDP.getId().equals(network.getType())) { if (StrUtil.isBlank(network.getProductKey())) { - throw new RuntimeException("TCP服务组件必须选择产品"); + throw new RuntimeException("TCP/UDP服务组件必须选择产品"); } network.setUnionId(network.getProductKey()); // 检查是否已被绑定 @@ -391,8 +432,8 @@ public class NetworkServiceImpl implements INetworkService { } String type = existNetwork.getType(); - if ((NetworkType.TCP_SERVER.getId().equals(type) || NetworkType.TCP_CLIENT.getId() - .equals(type))) { + if ((NetworkType.TCP_SERVER.getId().equals(type) || NetworkType.TCP_CLIENT.getId().equals(type) + || NetworkType.UDP.getId().equals(type))) { // 只有当unionId发生变化时,才做"已被产品关联且产品下有设备时禁止修改"的校验 String oldProductKey = existNetwork.getProductKey(); String networkProductKey = network.getProductKey(); @@ -423,9 +464,9 @@ public class NetworkServiceImpl implements INetworkService { throw new RuntimeException("网络组件不存在"); } String type = network.getType(); - // 只对TCP_SERVER和TCP_CLIENT做判断 - if (NetworkType.TCP_SERVER.getId().equals(type) || NetworkType.TCP_CLIENT.getId() - .equals(type)) { + // 只对TCP_SERVER、TCP_CLIENT和UDP做判断 + if (NetworkType.TCP_SERVER.getId().equals(type) || NetworkType.TCP_CLIENT.getId().equals(type) + || NetworkType.UDP.getId().equals(type)) { // 查找是否有关联产品 String productKey = ioTProductMapper.findProductKeyByNetworkUnionId(network.getUnionId()); if (productKey != null) { @@ -466,6 +507,11 @@ public class NetworkServiceImpl implements INetworkService { if (StrUtil.isBlank(host)) { throw new RuntimeException("MQTT网络组件启动/重启时host不能为空"); } + } else if (NetworkType.UDP.getId().equals(type)) { + Integer port = config.getInt("port"); + if (port == null) { + throw new RuntimeException("UDP网络组件启动/重启时端口不能为空"); + } } try { // 根据网络类型调用相应的启动逻辑 @@ -527,6 +573,11 @@ public class NetworkServiceImpl implements INetworkService { if (StrUtil.isBlank(host)) { throw new RuntimeException("MQTT网络组件启动/重启时host不能为空"); } + } else if (NetworkType.UDP.getId().equals(type)) { + Integer port = config.getInt("port"); + if (port == null) { + throw new RuntimeException("UDP网络组件启动/重启时端口不能为空"); + } } try { // 根据网络类型调用相应的重启逻辑 @@ -548,7 +599,7 @@ public class NetworkServiceImpl implements INetworkService { @Override public List getNetworkTypes() { return Arrays.asList(NetworkType.TCP_CLIENT.getId(), NetworkType.TCP_SERVER.getId(), - NetworkType.MQTT_CLIENT.getId(), NetworkType.MQTT_SERVER.getId()); + NetworkType.MQTT_CLIENT.getId(), NetworkType.MQTT_SERVER.getId(), NetworkType.UDP.getId()); } /** @@ -608,9 +659,9 @@ public class NetworkServiceImpl implements INetworkService { try { JSONObject config = JSONUtil.parseObj(network.getConfiguration()); String type = network.getType(); - // TCP类型检查端口唯一性 - if (NetworkType.TCP_CLIENT.getId().equals(type) || NetworkType.TCP_SERVER.getId() - .equals(type)) { + // TCP/UDP类型检查端口唯一性 + if (NetworkType.TCP_CLIENT.getId().equals(type) || NetworkType.TCP_SERVER.getId().equals(type) + || NetworkType.UDP.getId().equals(type)) { Integer port = config.getInt("port"); if (port != null) { List existingNetworks = networkMapper.selectTcpNetworkByPort(port, excludeId); @@ -663,6 +714,8 @@ public class NetworkServiceImpl implements INetworkService { case NetworkTypeConstants.MQTT_CLIENT: case NetworkTypeConstants.MQTT_SERVER: return startMqttNetwork(network); + case NetworkTypeConstants.UDP: + return startUdpNetwork(network); default: log.warn("不支持的网络类型: {}", type); return false; @@ -690,6 +743,8 @@ public class NetworkServiceImpl implements INetworkService { case NetworkTypeConstants.MQTT_CLIENT: case NetworkTypeConstants.MQTT_SERVER: return stopMqttNetwork(network); + case NetworkTypeConstants.UDP: + return stopUdpNetwork(network); default: log.warn("不支持的网络类型: {}", type); return false; @@ -708,7 +763,6 @@ public class NetworkServiceImpl implements INetworkService { */ private boolean restartNetworkByType(Network network) { String type = network.getType(); - try { switch (type) { case NetworkTypeConstants.TCP_CLIENT: @@ -717,6 +771,8 @@ public class NetworkServiceImpl implements INetworkService { case NetworkTypeConstants.MQTT_CLIENT: case NetworkTypeConstants.MQTT_SERVER: return restartMqttNetwork(network); + case NetworkTypeConstants.UDP: + return restartUdpNetwork(network); default: log.warn("不支持的网络类型: {}", type); return false; @@ -734,11 +790,28 @@ public class NetworkServiceImpl implements INetworkService { * @return 是否成功 */ private boolean startTcpNetwork(Network network) { + if (tcpClusterService == null) { + log.error("TCP服务管理器未注入,无法启动TCP网络组件: {}", network.getName()); + return false; + } + + String productKey = network.getProductKey(); + if (StrUtil.isBlank(productKey)) { + log.error("TCP网络组件缺少productKey: {}", network.getName()); + return false; + } + try { - //TODO 开源版本无 - return true; + // 使用新的方法,从数据库加载配置并启动 + boolean success = tcpClusterService.start(productKey); + if (success) { + log.info("TCP网络组件启动成功: productKey={}", productKey); + } else { + log.error("TCP网络组件启动失败: productKey={}", productKey); + } + return success; } catch (Exception e) { - log.error("启动TCP网络组件失败: productKey={}", "", e); + log.error("启动TCP网络组件失败: productKey={}", productKey, e); return false; } } @@ -750,17 +823,27 @@ public class NetworkServiceImpl implements INetworkService { * @return 是否成功 */ private boolean stopTcpNetwork(Network network) { + if (tcpClusterService == null) { + log.error("TCP服务管理器未注入,无法停止TCP网络组件: {}", network.getName()); + return false; + } String productKey = network.getProductKey(); if (StrUtil.isBlank(productKey)) { log.error("TCP网络组件缺少productKey: {}", network.getName()); return false; } - try { - return true; + try { + boolean success = tcpClusterService.stop(productKey); + if (success) { + log.info("TCP网络组件停止成功: productKey={}", productKey); + } else { + log.error("TCP网络组件停止失败: productKey={}", productKey); + } + return success; } catch (Exception e) { - log.error("停止TCP网络组件失败: productKey={}", "", e); + log.error("停止TCP网络组件失败: productKey={}", productKey, e); return false; } } @@ -772,11 +855,27 @@ public class NetworkServiceImpl implements INetworkService { * @return 是否成功 */ private boolean restartTcpNetwork(Network network) { + if (tcpClusterService == null) { + log.error("TCP服务管理器未注入,无法重启TCP网络组件: {}", network.getName()); + return false; + } + + String productKey = network.getProductKey(); + if (StrUtil.isBlank(productKey)) { + log.error("TCP网络组件缺少productKey: {}", network.getName()); + return false; + } try { - return true; + boolean success = tcpClusterService.restart(productKey); + if (success) { + log.info("TCP网络组件重启成功: productKey={}", productKey); + } else { + log.error("TCP网络组件重启失败: productKey={}", productKey); + } + return success; } catch (Exception e) { - log.error("重启TCP网络组件失败: productKey={}", "", e); + log.error("重启TCP网络组件失败: productKey={}", productKey, e); return false; } } @@ -881,4 +980,94 @@ public class NetworkServiceImpl implements INetworkService { public IoTDevice getDeviceById(String id) { return ioTDeviceMapper.selectDevInstanceById(id); } + + /** + * 启动UDP网络组件 + * + * @param network 网络组件 + * @return 是否成功 + */ + private boolean startUdpNetwork(Network network) { + String productKey = network.getProductKey(); + if (StrUtil.isBlank(productKey)) { + log.error("UDP网络组件缺少productKey: {}", network.getName()); + return false; + } + + try { + boolean success; + if (udpClusterService != null) { + // 使用集群服务启动(会广播到其他节点) + success = udpClusterService.start(productKey); + log.info("UDP网络组件集群启动: productKey={}, success={}", productKey, success); + } else { + log.error("UDP集群服务未注入,无法启动UDP网络组件: {}", network.getName()); + return false; + } + return success; + } catch (Exception e) { + log.error("启动UDP网络组件失败: productKey={}", productKey, e); + return false; + } + } + + /** + * 停止UDP网络组件 + * + * @param network 网络组件 + * @return 是否成功 + */ + private boolean stopUdpNetwork(Network network) { + String productKey = network.getProductKey(); + if (StrUtil.isBlank(productKey)) { + log.error("UDP网络组件缺少productKey: {}", network.getName()); + return false; + } + + try { + boolean success; + if (udpClusterService != null) { + // 使用集群服务停止(会广播到其他节点) + success = udpClusterService.stop(productKey); + log.info("UDP网络组件集群停止: productKey={}, success={}", productKey, success); + } else { + log.error("UDP集群服务未注入,无法停止UDP网络组件: {}", network.getName()); + return false; + } + return success; + } catch (Exception e) { + log.error("停止UDP网络组件失败: productKey={}", productKey, e); + return false; + } + } + + /** + * 重启UDP网络组件 + * + * @param network 网络组件 + * @return 是否成功 + */ + private boolean restartUdpNetwork(Network network) { + String productKey = network.getProductKey(); + if (StrUtil.isBlank(productKey)) { + log.error("UDP网络组件缺少productKey: {}", network.getName()); + return false; + } + + try { + boolean success; + if (udpClusterService != null) { + // 使用集群服务重启(会广播到其他节点) + success = udpClusterService.restart(productKey); + log.info("UDP网络组件集群重启: productKey={}, success={}", productKey, success); + } else { + log.error("UDP集群服务未注入,无法重启UDP网络组件: {}", network.getName()); + return false; + } + return success; + } catch (Exception e) { + log.error("重启UDP网络组件失败: productKey={}", productKey, e); + return false; + } + } } diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/network/utils/NetworkTypeUtil.java b/cn-universal-admin/src/main/java/cn/universal/admin/network/utils/NetworkTypeUtil.java index dbeb0d0..b84fcbb 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/network/utils/NetworkTypeUtil.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/network/utils/NetworkTypeUtil.java @@ -29,7 +29,7 @@ public class NetworkTypeUtil { /** 支持的网络类型列表 */ public static final List SUPPORTED_TYPES = - Arrays.asList("TCP_CLIENT", "TCP_SERVER", "MQTT_CLIENT", "MQTT_SERVER"); + Arrays.asList("TCP_CLIENT", "TCP_SERVER", "MQTT_CLIENT", "MQTT_SERVER", "UDP"); /** * 解析网络类型字符串,支持逗号分隔的多个类型 diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/network/web/NetworkController.java b/cn-universal-admin/src/main/java/cn/universal/admin/network/web/NetworkController.java index c6e2bab..1cd0310 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/network/web/NetworkController.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/network/web/NetworkController.java @@ -329,7 +329,7 @@ public class NetworkController extends BaseController { } /** 查询绑定指定星纵网关的设备 */ - @GetMapping("/milesight/list") + @GetMapping("/xz/gateway/list") public TableDataInfo queryMileSightList(NetworkBO bo) { startPage(); // 设置用户权限过滤 @@ -339,7 +339,7 @@ public class NetworkController extends BaseController { } /** 新增设备 */ - @PostMapping("/milesight/add") + @PostMapping("/xz/gateway/add") @Log(title = "新增设备", businessType = BusinessType.INSERT) public R add(@RequestBody IoTDeviceBO devInstancebo) { // 设置创建用户 @@ -356,7 +356,7 @@ public class NetworkController extends BaseController { } /** 删除设备 */ - @DeleteMapping("/milesight/{ids}") + @DeleteMapping("/xz/gateway/{ids}") @Log(title = "删除设备", businessType = BusinessType.DELETE) public R remove(@PathVariable String[] ids) { // 校验用户权限 @@ -385,7 +385,6 @@ public class NetworkController extends BaseController { public R reloadTcpClient(@PathVariable("applicationId") String applicationId) { // 校验用户权限 - 需要根据applicationId获取相关的信息进行权限校验 // 这里可能需要额外的业务逻辑来实现权限校验 - iNetworkService.reloadTcpClient(applicationId); return R.ok(); } diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/IIoTDeviceService.java b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/IIoTDeviceService.java index 0eeb243..9803565 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/IIoTDeviceService.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/IIoTDeviceService.java @@ -27,6 +27,7 @@ import cn.universal.persistence.entity.vo.IoTDeviceModelVO; import cn.universal.persistence.entity.vo.IoTDeviceVO; import com.github.pagehelper.Page; import java.util.List; +import java.util.Map; /** * 设备Service接口 @@ -147,9 +148,15 @@ public interface IIoTDeviceService { List selectGwIotDevices(IoTGwDeviceBO bo); - IoTDevice selectDeviceByDeviceId(String deviceId); + IoTDevice selectIoTDevice(String productKey, String deviceId); List selectDevInstanceListWithTags(IoTDeviceBO ioTDeviceBO, IoTUser iotUser); List selectProductListInBatchFunction(Long applicationId, IoTUser iotUser); + + /** 新:按第三方平台过滤设备列表(不影响旧接口) */ + List selectDevInstanceListByPlatform( + IoTDevice ioTDevice, IoTUser iotUser, String thirdPlatform); + + } diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/IIoTProductService.java b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/IIoTProductService.java index aa2317f..b6b5078 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/IIoTProductService.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/IIoTProductService.java @@ -178,7 +178,9 @@ public interface IIoTProductService { Map countDevNumberByProductKey(String unionId); - AjaxResult selectDevProductByKey(String key); + AjaxResult selectIoTProductByKey(String key); + + AjaxResult> selectGatewaySubProductsByKey(String gwProductKey); int updateDevProductOtherConfig(String otherConfig); diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTDeviceGroupServiceImpl.java b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTDeviceGroupServiceImpl.java index c922b2a..ad08cc7 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTDeviceGroupServiceImpl.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTDeviceGroupServiceImpl.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -84,7 +85,7 @@ public class IoTDeviceGroupServiceImpl extends BaseServiceImpl implements IIoTDe // 生成设备分组树 List roots = collect.get(0L); ioTDeviceGroupVO.setChildren(roots); - buildGroupTree(roots, collect); + groupTree(roots, collect); return groupVos; } @@ -192,12 +193,23 @@ public class IoTDeviceGroupServiceImpl extends BaseServiceImpl implements IIoTDe } /** - * 绑定设备到分组 + * 绑定设备到分组 iot_dev_action * * @param devGroup * @return */ @Override + @CacheEvict( + cacheNames = { + "iot_dev_instance_bo", + "iot_dev_metadata_bo", + "iot_dev_shadow_bo", + "iot_dev_action", + "selectDevCount", + "iot_dev_product_list", + "iot_product_device" + }, + allEntries = true) public AjaxResult bindDevToGroup(IoTDeviceGroupBO devGroup) { // 判断分组是否存在 Long groupId = devGroup.getId(); @@ -292,6 +304,17 @@ public class IoTDeviceGroupServiceImpl extends BaseServiceImpl implements IIoTDe */ @Override @Transactional + @CacheEvict( + cacheNames = { + "iot_dev_instance_bo", + "iot_dev_metadata_bo", + "iot_dev_shadow_bo", + "iot_dev_action", + "selectDevCount", + "iot_dev_product_list", + "iot_product_device" + }, + allEntries = true) public AjaxResult unBindDevByGroupId(String groupId, String[] devId) { for (int i = 0; i < devId.length; i++) { ioTDeviceTagsMapper.delete(IoTDeviceTags.builder().iotId(devId[i]).value(groupId).build()); @@ -299,14 +322,8 @@ public class IoTDeviceGroupServiceImpl extends BaseServiceImpl implements IIoTDe return AjaxResult.success(); } - /** - * 生成设备分组树 - * - * @param ioTDeviceGroupVO - * @param groups - * @return - */ - private void buildGroupTree( + /** 生成设备分组列表 */ + private void groupTree( List ioTDeviceGroupVO, Map> groups) { if (ioTDeviceGroupVO == null) { return; @@ -315,7 +332,7 @@ public class IoTDeviceGroupServiceImpl extends BaseServiceImpl implements IIoTDe if (ioTDeviceGroupVO.get(i).getHasChild() == 1) { List devGroups = groups.get(ioTDeviceGroupVO.get(i).getId()); ioTDeviceGroupVO.get(i).setChildren(devGroups); - buildGroupTree(devGroups, groups); + groupTree(devGroups, groups); } } return; diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTDeviceServiceImpl.java b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTDeviceServiceImpl.java index b907e9d..a695f67 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTDeviceServiceImpl.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTDeviceServiceImpl.java @@ -45,7 +45,6 @@ import cn.universal.persistence.mapper.IoTDeviceProtocolMapper; import cn.universal.persistence.mapper.IoTDeviceShadowMapper; import cn.universal.persistence.mapper.IoTDeviceTagsMapper; import cn.universal.persistence.mapper.IoTProductMapper; -import cn.universal.persistence.query.IoTDeviceQuery; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import jakarta.annotation.Resource; @@ -149,6 +148,13 @@ public class IoTDeviceServiceImpl extends BaseServiceImpl implements IIoTDeviceS ioTDevice, iotUser.isAdmin() ? null : iotUser.getUnionId()); } + @Override + public List selectDevInstanceListByPlatform( + IoTDevice ioTDevice, IoTUser iotUser, String thirdPlatform) { + return ioTDeviceMapper.selectDevInstanceListByPlatform( + ioTDevice, thirdPlatform, iotUser.isAdmin() ? null : iotUser.getUnionId()); + } + /** * 查询设备列表 * @@ -229,8 +235,8 @@ public class IoTDeviceServiceImpl extends BaseServiceImpl implements IIoTDeviceS } @Override - public IoTDevice selectDeviceByDeviceId(String deviceId) { - return ioTDeviceMapper.getOneByDeviceId(IoTDeviceQuery.builder().deviceId(deviceId).build()); + public IoTDevice selectIoTDevice(String productKey, String deviceId) { + return ioTDeviceMapper.selectIoTDevice(productKey, deviceId); } @Override @@ -270,11 +276,12 @@ public class IoTDeviceServiceImpl extends BaseServiceImpl implements IIoTDeviceS if (ioTDeviceBO.getOtherParams().containsKey("deviceKey")) { ioTDeviceBO.setSecretKey(ioTDeviceBO.getOtherParams().get("deviceKey").toString()); } + if (cn.universal.common.utils.StringUtils.isEmpty(ioTDeviceBO.getLatitude())) { - ioTDeviceBO.setLatitude("30.4484801"); + ioTDeviceBO.setLatitude("44.61081"); } if (cn.universal.common.utils.StringUtils.isEmpty(ioTDeviceBO.getLongitude())) { - ioTDeviceBO.setLongitude("120.2922654"); + ioTDeviceBO.setLongitude("80.990372"); } if (StrUtil.isNotBlank(deviceId)) { ioTDeviceBO.setGwProductKey(deviceId); @@ -333,6 +340,17 @@ public class IoTDeviceServiceImpl extends BaseServiceImpl implements IIoTDeviceS } @Override + @CacheEvict( + cacheNames = { + "iot_dev_instance_bo", + "iot_dev_metadata_bo", + "iot_dev_shadow_bo", + "iot_dev_action", + "selectDevCount", + "iot_dev_product_list", + "iot_product_device" + }, + allEntries = true) public int bindApp(String ids, String appUniqueId) { String[] a = ids.split(","); return ioTDeviceMapper.bindApp(a, appUniqueId); @@ -362,7 +380,7 @@ public class IoTDeviceServiceImpl extends BaseServiceImpl implements IIoTDeviceS downRequest.set("detail", devInstancebo.getDetail()); // 网关产品ProductKey downRequest.set("gwProductKey", devInstancebo.getGwProductKey()); - downRequest.set("gwDeviceId", devInstancebo.getExtDeviceId()); + downRequest.set("gwDeviceId", devInstancebo.getGwDeviceId()); downRequest.set("extDeviceId", devInstancebo.getExtDeviceId()); JSONObject ob = new JSONObject(); // 设备实例名称 @@ -506,9 +524,7 @@ public class IoTDeviceServiceImpl extends BaseServiceImpl implements IIoTDeviceS public R iotFunctionsDown(String productKey, String downRequest) { JSONObject jsonObject = JSONUtil.parseObj(downRequest); String deviceId = jsonObject.getStr("deviceId"); - IoTDevice ioTDeviceBo = - ioTDeviceMapper.getOneByDeviceId( - IoTDeviceQuery.builder().deviceId(deviceId).productKey(productKey).build()); + IoTDevice ioTDeviceBo = ioTDeviceMapper.selectIoTDevice(productKey, deviceId); if (ioTDeviceBo == null) { return R.error("设备不存在"); } diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTProductServiceImpl.java b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTProductServiceImpl.java index d471513..72c2323 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTProductServiceImpl.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/platform/service/impl/IoTProductServiceImpl.java @@ -25,10 +25,12 @@ import cn.hutool.http.HttpStatus; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; +import cn.universal.dm.device.service.protocol.ProtocolClusterService; import cn.universal.admin.common.service.BaseServiceImpl; import cn.universal.admin.common.utils.SecurityUtils; import cn.universal.admin.platform.service.IIoTProductService; import cn.universal.admin.system.service.IoTDeviceProtocolService; +import cn.universal.cache.annotation.MultiLevelCacheable; import cn.universal.common.constant.IoTConstant; import cn.universal.common.constant.IoTConstant.DeviceNode; import cn.universal.common.constant.IoTConstant.ProductFlushType; @@ -192,31 +194,54 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc } @Override - // @Cacheable(cacheNames = "iot_dev_product_list", unless = "#result == null", keyGenerator = - // "redisKeyGenerate") + // 分页查询不使用缓存,因为分页参数会导致缓存键不同,效率低下 public List selectDevProductV4List(IoTProductQuery ioTProductQuery) { - Page page = - PageHelper.startPage(ioTProductQuery.getPageNum(), ioTProductQuery.getPageSize()); + // 设置分页参数 + PageHelper.startPage(ioTProductQuery.getPageNum(), ioTProductQuery.getPageSize()); + + // 优化:一次SQL查询获取产品列表和设备数量,避免N+1查询 List devProductVOS = ioTProductMapper.selectDevProductV3List(ioTProductQuery); - List results = - ioTProductMapper.countDevNumberByProductKey(ioTProductQuery.getCreatorId()); - if (CollUtil.isNotEmpty(results)) { - final Map collect = - results.stream() - .collect(Collectors.toMap(IoTProductVO::getProductKey, IoTProductVO::getDevNum)); + + // 处理图片URL + if (CollUtil.isNotEmpty(devProductVOS)) { for (IoTProductVO devProduct : devProductVOS) { - devProduct.setDevNum(collect.getOrDefault(devProduct.getProductKey(), 0)); if (JSONUtil.isTypeJSON(devProduct.getPhotoUrl())) { JSONObject photo = JSONUtil.parseObj(devProduct.getPhotoUrl()); devProduct.setImage(photo.getStr("img", "")); } } } - // 按设备数量倒序排序 - // if (CollUtil.isNotEmpty(devProductVOS)) { - // devProductVOS.sort(Comparator.comparingInt(IoTProductVO::getDevNum).reversed()); - // } - return page; + + return devProductVOS; + } + + /** 获取不分页的产品列表(用于缓存) 缓存基础产品数据,不包含分页信息 */ + @MultiLevelCacheable( + cacheNames = "selectDevProductV4ListNoPage", + keyGenerator = "redisKeyGenerate", + unless = "#result == null", + l1Expire = 30) + public List selectDevProductV4ListNoPage(IoTProductQuery ioTProductQuery) { + // 创建不分页的查询条件 + IoTProductQuery noPageQuery = new IoTProductQuery(); + BeanUtil.copyProperties(ioTProductQuery, noPageQuery); + noPageQuery.setPageNum(1); + noPageQuery.setPageSize(Integer.MAX_VALUE); // 获取所有数据 + + // 优化:一次SQL查询获取产品列表和设备数量,避免N+1查询 + List devProductVOS = ioTProductMapper.selectDevProductV3List(noPageQuery); + + // 处理图片URL + if (CollUtil.isNotEmpty(devProductVOS)) { + for (IoTProductVO devProduct : devProductVOS) { + if (JSONUtil.isTypeJSON(devProduct.getPhotoUrl())) { + JSONObject photo = JSONUtil.parseObj(devProduct.getPhotoUrl()); + devProduct.setImage(photo.getStr("img", "")); + } + } + } + + return devProductVOS; } @Override @@ -237,7 +262,9 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc cacheNames = { "iot_all_dev_product_list", "iot_dev_product_list", - "selectAllEnableNetworkProductKey" + "selectAllEnableNetworkProductKey", + "selectDevProductV4ListNoPage", + "countDevNumberByProductKey" }, allEntries = true) public int insertDevProduct(IoTProduct ioTProduct) { @@ -257,6 +284,9 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc if (StrUtil.isBlank(ioTProduct.getProductSecret())) { ioTProduct.setProductSecret(IdUtil.fastSimpleUUID()); } + if (StrUtil.isBlank(ioTProduct.getConfiguration())) { + ioTProduct.setConfiguration(buildProductCfg(ioTProduct)); + } ioTProduct.setProductKey(productKey); ioTProduct.setMetadata(defaultMetadata); ioTProduct.setThirdConfiguration(thirdConfig); @@ -268,9 +298,30 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc return i; } + /** 默认创建mqtt设置自动注册 */ + private String buildProductCfg(IoTProduct ioTProduct) { + JSONObject cfg = new JSONObject(); + boolean isMqtt = false; + String thirdPlatform = ioTProduct == null ? null : ioTProduct.getThirdPlatform(); + if (thirdPlatform == null) { + return JSONUtil.toJsonStr(cfg); + } + if (ProtocolModule.tcp.name().equalsIgnoreCase(thirdPlatform) + || ProtocolModule.mqtt.name().equalsIgnoreCase(thirdPlatform) + || ProtocolModule.udp.name().equalsIgnoreCase(thirdPlatform)) { + cfg.set(IoTConstant.ALLOW_INSERT, true); + } + return JSONUtil.toJsonStr(cfg); + } + @Override @CacheEvict( - cacheNames = {"iot_all_dev_product_list", "iot_dev_product_list"}, + cacheNames = { + "iot_all_dev_product_list", + "iot_dev_product_list", + "selectDevProductV4ListNoPage", + "countDevNumberByProductKey" + }, allEntries = true) public int insertList(List ioTProductList) { int i = ioTProductMapper.insertList(ioTProductList); @@ -280,7 +331,13 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc /** 产品协议导入 */ @Override @CacheEvict( - cacheNames = {"iot_all_dev_product_list", "iot_dev_product_list", "supportMQTTNetwork"}, + cacheNames = { + "iot_all_dev_product_list", + "iot_dev_product_list", + "supportMQTTNetwork", + "selectDevProductV4ListNoPage", + "countDevNumberByProductKey" + }, allEntries = true) public String importProduct(List productImportBos, String unionId) { Long timeMillis = System.currentTimeMillis(); @@ -357,7 +414,9 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc devProduct -> { R r = IoTDownlFactory.safeInvokeDown( - ProtocolModule.ctaiot.name(), "downPro", devProduct.getThirdDownRequest()); + ProtocolModule.ctaiot.name(), + IoTConstant.DOWN_TO_THIRD_PLATFORM, + devProduct.getThirdDownRequest()); if (!R.SUCCESS.equals(r.getCode())) { failedList.add(devProduct); } else { @@ -395,7 +454,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc ioTProductAction.create(null, unionId); String result = String.format( - "导入普通产品总数:%s 个,成功:%s个;导入电信产品总数:%s 个,成功:%s 个;导入协议总数:%s 个,成功:%s 个。", + "导入普通产品总数:%s 个,成功:%s个;导入电信ctwing产品总数:%s 个,成功:%s 个;导入协议总数:%s 个,成功:%s 个。", customProductList.size() + duplicateNum.get(), customSuccess, ctwingProductList.size(), @@ -413,7 +472,13 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc */ @Override @CacheEvict( - cacheNames = {"iot_all_dev_product_list", "iot_dev_product_list", "supportMQTTNetwork"}, + cacheNames = { + "iot_all_dev_product_list", + "iot_dev_product_list", + "supportMQTTNetwork", + "selectDevProductV4ListNoPage", + "countDevNumberByProductKey" + }, allEntries = true) public int updateDevProduct(IoTProduct ioTProduct) { IoTUser iotUser = queryIotUser(SecurityUtils.getUnionId()); @@ -423,7 +488,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc String appUnionId = iotUser.getUnionId(); IoTProduct pro = ioTProductMapper.selectByPrimaryKey(ioTProduct.getId()); if (!appUnionId.equals(pro.getCreatorId()) && !iotUser.isAdmin()) { - throw new IoTException("您没有权限操作此产品!"); + throw new IoTException("没有产品权限"); } if (Objects.nonNull(ioTProduct.getClassifiedId())) { IoTProductSort ioTProductSort = @@ -432,7 +497,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc } int count = ioTProductMapper.updateDevProduct(ioTProduct); - log.info("修改产品记录数={},修改人={}", count, appUnionId); + log.info("updateDevProduct,操作成功={},userId={}", count, appUnionId); ioTProductAction.update(ioTProduct); return count; } @@ -445,7 +510,13 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc */ @Override @CacheEvict( - cacheNames = {"iot_all_dev_product_list", "iot_dev_product_list", "supportMQTTNetwork"}, + cacheNames = { + "iot_all_dev_product_list", + "iot_dev_product_list", + "supportMQTTNetwork", + "selectDevProductV4ListNoPage", + "countDevNumberByProductKey" + }, allEntries = true) public int deleteDevProductByIds(String[] ids) { String appUnionId = queryIotUser(SecurityUtils.getUnionId()).getUnionId(); @@ -453,7 +524,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc for (String id : ids) { IoTProduct ioTProduct = ioTProductMapper.selectDevProductById(id); if (!appUnionId.equals(ioTProduct.getCreatorId())) { - throw new IoTException("您没有权限操作此产品!"); + throw new IoTException("无产品权限"); } count = +deleteDevProductById(id); } @@ -468,7 +539,13 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc */ @Override @CacheEvict( - cacheNames = {"iot_all_dev_product_list", "iot_dev_product_list", "supportMQTTNetwork"}, + cacheNames = { + "iot_all_dev_product_list", + "iot_dev_product_list", + "supportMQTTNetwork", + "selectDevProductV4ListNoPage", + "countDevNumberByProductKey" + }, allEntries = true) public int deleteDevProductById(String id) { String appUnionId = queryIotUser(SecurityUtils.getUnionId()).getUnionId(); @@ -477,7 +554,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc throw new IoTException("产品不存在"); } if (!appUnionId.equals(ioTProduct.getCreatorId())) { - throw new IoTException("您没有权限操作此产品!"); + throw new IoTException("无产品权限"); } IoTDevice ioTDevice = IoTDevice.builder().productKey(ioTProduct.getProductKey()).build(); int count = ioTDeviceMapper.selectCount(ioTDevice); @@ -507,7 +584,8 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc "iot_dev_action", "selectDevCount", "supportMQTTNetwork", - "iot_dev_product_list" + "iot_dev_product_list", + "getProductConfiguration" }, allEntries = true) public int updateDevProductConfig(IoTProductVO devProduct) { @@ -518,7 +596,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc throw new IoTException("产品不存在"); } if (!appUnionId.equals(product.getCreatorId())) { - throw new IoTException("您没有权限操作此产品!"); + throw new IoTException("无产品权限"); } // 获取新的产品配置信息 JSONObject newConfig = JSONUtil.parseObj(devProduct.getConfiguration()); @@ -802,7 +880,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc String appUnionId = queryIotUser(SecurityUtils.getUnionId()).getUnionId(); IoTProduct product = ioTProductMapper.getProductByProductKey(ioTProductBO.getProductKey()); if (!appUnionId.equals(product.getCreatorId())) { - throw new IoTException("您没有权限操作此产品!"); + throw new IoTException("无产品权限"); } ioTProductBO.setPath(getPath(ioTProductBO)); int delCount = ioTProductMapper.deleteMetadata(ioTProductBO); @@ -840,7 +918,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc String appUnionId = queryIotUser(SecurityUtils.getUnionId()).getUnionId(); IoTProduct product = ioTProductMapper.getProductByProductKey(ioTProductBO.getProductKey()); if (!appUnionId.equals(product.getCreatorId())) { - throw new IoTException("您没有权限操作此产品!"); + throw new IoTException("无产品权限"); } ioTProductBO.beanToJson(); // if(getMetadata(ioTProductBO)!=null){ @@ -884,19 +962,41 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc } @Override + @MultiLevelCacheable( + cacheNames = "countDevNumberByProductKey", + keyGenerator = "redisKeyGenerate", + unless = "#result == null", + l1Expire = 30) public Map countDevNumberByProductKey(String unionId) { List results = ioTProductMapper.countDevNumberByProductKey(unionId); return results.stream() .collect(Collectors.toMap(IoTProductVO::getProductKey, IoTProductVO::getDevNum)); } + /** 设备数量变化时清除相关缓存 当设备新增、删除、修改时调用此方法 */ + @CacheEvict( + cacheNames = {"countDevNumberByProductKey", "selectDevProductV4ListNoPage"}, + allEntries = true) + public void evictDeviceCountCache() { + // 此方法用于手动清除设备数量相关缓存 + // 当设备数据发生变化时调用 + } + @Override - public AjaxResult selectDevProductByKey(String key) { + public AjaxResult selectIoTProductByKey(String key) { IoTProduct ioTProduct = new IoTProduct(); ioTProduct.setProductKey(key); return AjaxResult.success(ioTProductMapper.selectOne(ioTProduct)); } + @Override + public AjaxResult> selectGatewaySubProductsByKey(String gwProductKey) { + IoTProduct ioTProduct = new IoTProduct(); + ioTProduct.setGwProductKey(gwProductKey); + ioTProduct.setState((byte) 0); + return AjaxResult.success(ioTProductMapper.select(ioTProduct)); + } + /** 修改产品其他配置信息 */ @Override @CacheEvict( @@ -917,7 +1017,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc IoTProduct product = ioTProductMapper.selectDevProductById(newConfig.getStr("productId")); IoTUser iotUser = queryIotUser(SecurityUtils.getUnionId()); if (!product.getCreatorId().equals(iotUser.getUnionId())) { - throw new IoTException("您没有权限操作此产品!"); + throw new IoTException("无产品权限"); } // 获取第三方配置 String thirdConfiguration = product.getThirdConfiguration(); @@ -1042,7 +1142,20 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc @Override public void flushNettyServer(String config, String productKey, TcpFlushType type) { - + JSONObject object = new JSONObject(); + object.set("type", ProductFlushType.server.name()); + object.set("productKey", productKey); + object.set("customType", type.name()); + eventPublisher.publishEvent(EventTopics.PRODUCT_CONFIG_UPDATED, object); + // 使用ProtocolClusterService进行集群操作 + ProtocolClusterService tcpClusterService = SpringUtil.getBean(ProtocolClusterService.class); + if (tcpClusterService != null) { + switch (type) { + case start -> tcpClusterService.start(productKey); + case reload -> tcpClusterService.restart(productKey); + case close -> tcpClusterService.stop(productKey); + } + } } @Override diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTDeviceController.java b/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTDeviceController.java index 84efb18..d9eb512 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTDeviceController.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTDeviceController.java @@ -90,6 +90,18 @@ public class IoTDeviceController extends BaseController { return getDataTable(list); } + /** 新:按第三方平台过滤的设备列表(用于萤石/乐橙) */ + @GetMapping("/listByPlatform") + public TableDataInfo listByPlatform( + IoTDevice ioTDevice, + @RequestParam(value = "thirdPlatform", required = false) String thirdPlatform) { + IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId()); + startPage(); + List list = + iIotDeviceService.selectDevInstanceListByPlatform(ioTDevice, iotUser, thirdPlatform); + return getDataTable(list); + } + /** 查询分组未绑定设备列表 */ @GetMapping("/list/{groupId}") public TableDataInfo list(@PathVariable("groupId") String groupId, IoTDevice ioTDevice) { @@ -152,27 +164,18 @@ public class IoTDeviceController extends BaseController { /** 获取设备详细信息 */ @GetMapping(value = "/{id}") - public AjaxResult getInfo(@PathVariable("id") String id) { - + public AjaxResult getIotDeviceInfo(@PathVariable("id") String id) { IoTDevice ioTDevice = iIotDeviceService.selectDevInstanceById(id); - // if (ObjectUtil.isEmpty(ioTDevice)) { - // throw new IoTException("设备不存在"); - // } - // IoTUser iotUser = checkParent(SecurityUtils.getUnionId()); - // if (!iotUser.isAdmin() && !ioTDevice.getCreatorId() - // .equals(iotUser.getUnionId())) { - // throw new IoTException("你无权操作"); - // } - // 是否有编解码插件 int count = ioTDeviceProtocolService.countProtocol(ioTDevice.getProductKey()); IoTDeviceBO ioTDeviceBO = new IoTDeviceBO(); BeanUtil.copyProperties(ioTDevice, ioTDeviceBO); // 查询网关产品信息 if (StrUtil.isNotBlank(ioTDeviceBO.getGwProductKey())) { IoTDevice parentDevice = - iIotDeviceService.selectDeviceByDeviceId(ioTDevice.getGwProductKey()); + iIotDeviceService.selectIoTDevice( + ioTDeviceBO.getGwProductKey(), ioTDevice.getExtDeviceId()); if (ObjectUtil.isNotNull(parentDevice)) { - ioTDeviceBO.setParentName( + ioTDeviceBO.setGwDeviceInfo( parentDevice.getDeviceId() + "(" + parentDevice.getDeviceName() + ")"); } } diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTProductController.java b/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTProductController.java index bab3f26..b13b752 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTProductController.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTProductController.java @@ -20,16 +20,22 @@ import cn.universal.admin.common.annotation.Log; import cn.universal.admin.common.enums.BusinessType; import cn.universal.admin.common.utils.SecurityUtils; import cn.universal.admin.network.service.INetworkService; +import cn.universal.admin.platform.dto.ConnectionInfoDTO; +import cn.universal.admin.platform.service.ConnectionInfoService; import cn.universal.admin.platform.service.IIoTDeviceService; import cn.universal.admin.platform.service.IIoTProductService; import cn.universal.admin.platform.service.impl.IoTProductServiceImpl; import cn.universal.admin.system.service.ISysDictTypeService; import cn.universal.admin.system.service.IoTDeviceProtocolService; import cn.universal.admin.system.web.BaseController; +import cn.universal.common.constant.IoTConstant; import cn.universal.common.constant.IoTConstant.ProtocolModule; import cn.universal.common.constant.IoTConstant.TcpFlushType; +import cn.universal.common.event.EventTopics; +import cn.universal.common.event.processer.EventPublisher; import cn.universal.common.exception.IoTException; import cn.universal.core.service.IoTDownlFactory; +import cn.universal.ossm.service.ISysOssService; import cn.universal.persistence.entity.IoTDevice; import cn.universal.persistence.entity.IoTDeviceEvents; import cn.universal.persistence.entity.IoTDeviceFunction; @@ -55,7 +61,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -77,29 +82,20 @@ import org.springframework.web.multipart.MultipartFile; @RequestMapping("/admin/v1/product") public class IoTProductController extends BaseController { - @Autowired - private IIoTProductService devProductService; - @Autowired - private IIoTDeviceService iIotDeviceService; - @Autowired - private ISysDictTypeService dictTypeService; - @Resource - private INetworkService iNetworkService; - @Resource - private IoTDeviceProtocolService ioTDeviceProtocolService; - @Resource - private IoTProductMapper ioTProductMapper; - @Autowired - private IoTProductServiceImpl ioTProductServiceImpl; - @Autowired - private ApplicationEventPublisher eventPublisher; + @Autowired private IIoTProductService devProductService; + @Autowired private IIoTDeviceService iIotDeviceService; + @Autowired private ISysDictTypeService dictTypeService; + @Resource private INetworkService iNetworkService; + @Resource private IoTDeviceProtocolService ioTDeviceProtocolService; + @Resource private IoTProductMapper ioTProductMapper; + @Autowired private IoTProductServiceImpl ioTProductServiceImpl; + @Autowired private EventPublisher eventPublisher; + @Autowired private ISysOssService iSysOssService; + @Autowired private ConnectionInfoService connectionInfoService; - /** - * 查询设备产品列表 - */ + /** 查询设备产品列表 */ @GetMapping("/list") public TableDataInfo list(IoTProductQuery query) { - // 只能看自己设备,除非是特殊设置和超管 IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId()); if (!iotUser.viewAllProduct()) { query.setSelf(true); @@ -118,20 +114,21 @@ public class IoTProductController extends BaseController { return getDataTable(list); } - /** - * 获取产品详情通过 ProductKey - */ + /** 获取产品详情通过 ProductKey */ @GetMapping("/get/pro/{key}") public AjaxResult getProByKey(@PathVariable("key") String key) { - return devProductService.selectDevProductByKey(key); + return devProductService.selectIoTProductByKey(key); } - /** - * 查询设备产品列表 - */ + /** 获取网关绑定的子产品列表 */ + @GetMapping("/gateway/subProducts/{key}") + public AjaxResult> gatewaySubProducts(@PathVariable("key") String gwProductKey) { + return devProductService.selectGatewaySubProductsByKey(gwProductKey); + } + + /** 查询设备产品列表 */ @GetMapping("/v2/list") public TableDataInfo v2List(IoTProductQuery query) { - // 只能看自己设备,除非是特殊设置和超管 IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId()); if (!iotUser.viewAllProduct()) { query.setSelf(true); @@ -150,12 +147,10 @@ public class IoTProductController extends BaseController { return getDataTable(list); } - /** - * 查询设备产品列表 - */ + /** 查询设备产品列表 */ @GetMapping("/v3/list") public TableDataInfo v3List(IoTProductQuery query) { - // 只能看自己设备,除非是特殊设置和超管 + // IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId()); if (!iotUser.viewAllProduct()) { query.setSelf(true); @@ -175,12 +170,10 @@ public class IoTProductController extends BaseController { return getDataTable(list); } - /** - * 查询设备产品列表 - */ + /** 查询设备产品列表 */ @GetMapping("/v4/list") public TableDataInfo v4List(IoTProductQuery query) { - // 只能看自己设备,除非是特殊设置和超管 + // IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId()); if (!iotUser.viewAllProduct()) { query.setSelf(true); @@ -200,70 +193,47 @@ public class IoTProductController extends BaseController { return getDataTable(list); } - /** - * 查询所有设备产品列表 - */ + /** 查询所有设备产品列表 */ @GetMapping("/all/list") public TableDataInfo v3List() { List list = devProductService.selectDevProductAllList(); return getDataTable(list); } - /** - * 物模型新增内容 - */ + /** 物模型新增内容 */ @PostMapping("/metadata/add") @Log(title = "物模型新增内容", businessType = BusinessType.INSERT) public AjaxResult metadataAdd(@RequestBody IoTProductBO ioTProductBO) { return toAjax(devProductService.insertMetadata(ioTProductBO)); } - /** - * 物模型修改内容 - */ + /** 物模型修改内容 */ @PostMapping("/metadata/update") @Log(title = "物模型修改内容", businessType = BusinessType.UPDATE) public AjaxResult metadataUpdate(@RequestBody IoTProductBO ioTProductBO) { return toAjax(devProductService.updateMetadata(ioTProductBO)); } - /** - * 物模型删除内容 - */ + /** 物模型删除内容 */ @PostMapping("/metadata/delete") @Log(title = "物模型删除内容", businessType = BusinessType.DELETE) public AjaxResult metadataDelete(@RequestBody IoTProductBO ioTProductBO) { return toAjax(devProductService.deleteMetadata(ioTProductBO)); } - /** - * 物模型查询内容 - */ + /** 物模型查询内容 */ @PostMapping("/metadata/get") public AjaxResult metadataGet(@RequestBody IoTProductBO ioTProductBO) { return AjaxResult.success(devProductService.getMetadata(ioTProductBO)); } - /** 导出设备产品列表 */ - // @PostMapping("/export") - // public void export(HttpServletResponse response, IoTProduct ioTProduct) - // { - // List list = devProductService.selectDevProductList(ioTProduct); - // ExcelUtil util = new ExcelUtil(IoTProduct.class); - // util.exportExcel(response, list, "设备产品数据"); - // } - - /** - * 获取设备产品详细信息 - */ + /** 获取设备产品详细信息 */ @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") String id) { return AjaxResult.success(devProductService.selectDevProductById(id)); } - /** - * 查询可绑定网关列表 - */ + /** 查询可绑定网关列表 */ @Operation(summary = "查询可绑定网关产品列表") @GetMapping("/gateway/list") public AjaxResult> getGatewayList() { @@ -271,20 +241,16 @@ public class IoTProductController extends BaseController { return AjaxResult.success(list); } - /** - * 查询可绑定网关子设备产品列表 - */ + /** 查询可绑定网关子设备产品列表 */ @Operation(summary = "查询可绑定网关子设备产品列表") - @GetMapping("/subdevice/{productKey}") + @GetMapping("/sub/device/{productKey}") public AjaxResult> getGatewaySubDeviceList( @PathVariable("productKey") String productKey) { List list = devProductService.getGatewaySubDeviceList(productKey); return AjaxResult.success(list); } - /** - * 查询电信公共产品列表 - */ + /** 查询电信公共产品列表 */ @PostMapping(value = "/ctwing/pubpro") public AjaxResult getCtwingPubPro(@RequestBody String downRequest) { String creatorId = loginIoTUnionUser(SecurityUtils.getUnionId()).getUnionId(); @@ -299,23 +265,27 @@ public class IoTProductController extends BaseController { downProRequest.set("data", data); return AjaxResult.success( IoTDownlFactory.safeInvokeDown( - ProtocolModule.ctaiot.name(), "downPro", downProRequest.toString())); + ProtocolModule.ctaiot.name(), + IoTConstant.DOWN_TO_THIRD_PLATFORM, + downProRequest.toString())); } - /** - * 新增设备产品 - */ + /** 新增设备产品 */ @PostMapping @Log(title = "新增产品", businessType = BusinessType.INSERT) public AjaxResult add(@RequestBody IoTProduct ioTProduct) { String unionId = loginIoTUnionUser(SecurityUtils.getUnionId()).getUnionId(); ioTProduct.setCreatorId(unionId); + // 发布产品配置更新事件 + Map eventData = new HashMap<>(); + eventData.put("type", "product"); + eventData.put("productKey", ioTProduct.getProductKey()); + eventData.put("operation", "ADD"); + eventPublisher.publishEvent(EventTopics.PRODUCT_CONFIG_UPDATED, eventData); return toAjax(devProductService.insertDevProduct(ioTProduct)); } - /** - * 新增设备产品NB - */ + /** 新增设备产品NB */ @PostMapping(value = "/nb") @Log(title = "新增产品NB", businessType = BusinessType.INSERT) public AjaxResult addNb(@RequestBody String downRequest) { @@ -343,12 +313,12 @@ public class IoTProductController extends BaseController { downProRequest.set("data", data); return AjaxResult.success( IoTDownlFactory.safeInvokeDown( - ProtocolModule.ctaiot.name(), "downPro", downProRequest.toString())); + ProtocolModule.ctaiot.name(), + IoTConstant.DOWN_TO_THIRD_PLATFORM, + downProRequest.toString())); } - /** - * 新增电信公共产品 - */ + /** 新增电信公共产品 */ @PostMapping(value = "/ctwing/pubproadd") @Log(title = "新增电信公共产品", businessType = BusinessType.INSERT) public AjaxResult addCtwingPubPro(@RequestBody String downRequest) { @@ -367,12 +337,12 @@ public class IoTProductController extends BaseController { downProRequest.set("data", data); return AjaxResult.success( IoTDownlFactory.safeInvokeDown( - ProtocolModule.ctaiot.name(), "downPro", downProRequest.toString())); + ProtocolModule.ctaiot.name(), + IoTConstant.DOWN_TO_THIRD_PLATFORM, + downProRequest.toString())); } - /** - * 修改设备产品 - */ + /** 修改设备产品 */ @PostMapping(value = "/updateNetworkUnionId") @Log(title = "修改网络组件", businessType = BusinessType.UPDATE) public AjaxResult editNetwork(@RequestBody IoTProductBO ioTProductBO) { @@ -387,9 +357,7 @@ public class IoTProductController extends BaseController { ioTProductBO.getId(), ioTProductBO.getNetworkUnionId())); } - /** - * 子设备绑定网关 - */ + /** 子设备绑定网关 */ @Operation(summary = "子设备绑定网关") @PutMapping("/gateway") @Log(title = "子设备绑定网关", businessType = BusinessType.UPDATE) @@ -407,9 +375,7 @@ public class IoTProductController extends BaseController { return toAjax(devProductService.updateDevProduct(productVO)); } - /** - * 修改设备产品 - */ + /** 修改设备产品 */ @PutMapping @Log(title = "修改设备产品", businessType = BusinessType.UPDATE) public AjaxResult edit(@RequestBody IoTProductBO ioTProductBO) { @@ -434,42 +400,45 @@ public class IoTProductController extends BaseController { downProRequest.set("productKey", ioTProductBO.getProductKey()); return AjaxResult.success( IoTDownlFactory.safeInvokeDown( - ProtocolModule.ctaiot.name(), "downPro", downProRequest.toString())); + ProtocolModule.ctaiot.name(), + IoTConstant.DOWN_TO_THIRD_PLATFORM, + downProRequest.toString())); } IoTProduct ioTProduct = BeanUtil.toBean(ioTProductBO, IoTProduct.class); - return toAjax(devProductService.updateDevProduct(ioTProduct)); + int result = devProductService.updateDevProduct(ioTProduct); + if (result > 0) { + // 发布产品配置更新事件 + Map eventData = new HashMap<>(); + eventData.put("type", "product"); + eventData.put("productKey", ioTProduct.getProductKey()); + eventData.put("operation", "UPDATE"); + eventPublisher.publishEvent(EventTopics.PRODUCT_CONFIG_UPDATED, eventData); + } + return toAjax(result); } - /** - * 修改产品配置信息 - */ + /** 修改产品配置信息 */ @PutMapping("/config") @Log(title = "修改产品配置信息", businessType = BusinessType.UPDATE) public AjaxResult editConfig(@RequestBody IoTProductVO devProduct) { return toAjax(devProductService.updateDevProductConfig(devProduct)); } - /** - * 修改产品其他配置信息 - */ + /** 修改产品其他配置信息 */ @PutMapping("/otherConfig") @Log(title = "修改产品其他配置信息", businessType = BusinessType.UPDATE) public AjaxResult editOtherConfig(@RequestBody String otherConfig) { return toAjax(devProductService.updateDevProductOtherConfig(otherConfig)); } - /** - * 修改产品存储策略 - */ + /** 修改产品存储策略 */ @PutMapping("/storeConfig") @Log(title = "修改产品存储策略", businessType = BusinessType.UPDATE) public AjaxResult editOtherConfig(@RequestBody IoTProductVO devProduct) { return toAjax(devProductService.updateDevProductStoreConfig(devProduct)); } - /** - * 删除设备产品 - */ + /** 删除设备产品 */ @DeleteMapping("/{ids}") @Log(title = "删除设备产品", businessType = BusinessType.DELETE) public AjaxResult remove(@PathVariable String[] ids) { @@ -480,7 +449,7 @@ public class IoTProductController extends BaseController { throw new IoTException("没有该产品"); } if (!iotUser.isAdmin() && !ioTProduct.getCreatorId().equals(iotUser.getUnionId())) { - throw new IoTException("您没有权限操作此产品!"); + throw new IoTException("没有产品权限"); } IoTDevice ioTDevice = IoTDevice.builder().productKey(ioTProduct.getProductKey()).build(); List ioTDeviceList = @@ -501,7 +470,9 @@ public class IoTProductController extends BaseController { return AjaxResult.success( IoTDownlFactory.safeInvokeDown( - ProtocolModule.ctaiot.name(), "downPro", downProRequest.toString())); + ProtocolModule.ctaiot.name(), + IoTConstant.DOWN_TO_THIRD_PLATFORM, + downProRequest.toString())); } // tcp产品删除时同时删除network表 if (ProtocolModule.tcp.name().equals(ioTProduct.getThirdPlatform())) { @@ -512,22 +483,25 @@ public class IoTProductController extends BaseController { } // 删除协议 ioTDeviceProtocolService.deleteDevProtocolById(ioTProduct.getProductKey()); + + // 发布产品删除事件 + Map eventData = new HashMap<>(); + eventData.put("type", "product"); + eventData.put("productKey", ioTProduct.getProductKey()); + eventData.put("operation", "DELETE"); + eventPublisher.publishEvent(EventTopics.PRODUCT_CONFIG_UPDATED, eventData); } return toAjax(devProductService.deleteDevProductByIds(ids)); } - /** - * 查询设备产品物模型属性列表 - */ + /** 查询设备产品物模型属性列表 */ @GetMapping("/properties/{id}") public AjaxResult getPropertiesList(@PathVariable("id") String id) { List list = devProductService.selectDevProperties(id); return AjaxResult.success(list); } - /** - * 查询设备产品物模型属性列表(功能下发属性) - */ + /** 查询设备产品物模型属性列表(功能下发属性) */ @GetMapping("/functionProperties/{id}") public AjaxResult getFunctionPropertiesList(@PathVariable("id") String id) { List list = devProductService.selectDevProperties(id); @@ -535,7 +509,8 @@ public class IoTProductController extends BaseController { List dataRw = list.stream().filter((item) -> "rw".equals(item.getMode())).collect(Collectors.toList()); // 获取设备公共字段 - List data = dictTypeService.selectDictDataByType("device_common_property"); + List data = + dictTypeService.selectDictDataByType(IoTConstant.DEVICE_SHADOW_CUSTOMIZED_PROPERTY); // 根据公共字段过滤 if (Validator.isNotNull(data)) { for (SysDictData datum : data) { @@ -558,27 +533,21 @@ public class IoTProductController extends BaseController { return AjaxResult.success(dataRw); } - /** - * 查询设备产品物模型事件列表 - */ + /** 查询设备产品物模型事件列表 */ @GetMapping("/events/{id}") public AjaxResult getEventsList(@PathVariable("id") String id) { List list = devProductService.selectDevEvents(id); return AjaxResult.success(list); } - /** - * 查询设备产品物模型方法列表 - */ + /** 查询设备产品物模型方法列表 */ @GetMapping("/functions/{id}") public AjaxResult getFunctionsList(@PathVariable("id") String id) { List list = devProductService.selectDevFunctions(id); return AjaxResult.success(list); } - /** - * 根据产品key查model配置 - */ + /** 根据产品key查model配置 */ @GetMapping("/model/{id}") public AjaxResult getmodel(@PathVariable("id") String id) { IoTDeviceModelVO ioTDeviceModelVO = devProductService.getModelByProductKey(id); @@ -590,7 +559,7 @@ public class IoTProductController extends BaseController { return AjaxResult.success(devProductService.selectMetadataByDevId(devId)); } - @GetMapping("/devnumber") + @GetMapping("/device/count") public AjaxResult countDevNumberByProductKey() { IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId()); Map products = @@ -599,13 +568,11 @@ public class IoTProductController extends BaseController { return AjaxResult.success(products); } - /** - * 导出设备产品列表 - */ + /** 导出设备产品列表 */ @PostMapping("/export") @Log(title = "产品导出", businessType = BusinessType.EXPORT) public void export(HttpServletResponse response, IoTProductQuery query) { - // 只能看自己设备,除非是特殊设置和超管 + // IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId()); query.setSelf(!iotUser.isAdmin()); if (query.isSelf()) { @@ -646,15 +613,13 @@ public class IoTProductController extends BaseController { } } - /** - * 批量导入产品 - */ + /** 批量导入产品 */ @PostMapping("/import") @Log(title = "产品批量导入", businessType = BusinessType.IMPORT) @Transactional(rollbackFor = Exception.class) public AjaxResult importProduct(MultipartFile file) throws Exception { // 模板 - // 只能看自己设备,除非是特殊设置和超管 + // String unionId = loginIoTUnionUser(SecurityUtils.getUnionId()).getUnionId(); try { // 读取JSON文件内容 @@ -675,9 +640,41 @@ public class IoTProductController extends BaseController { } } - /** - * 下载导入模版 - */ + /** 上传产品图片并保存到产品信息 */ + @Operation(summary = "上传产品图片") + @PostMapping("/uploadImage") + @Log(title = "上传产品图片", businessType = BusinessType.UPDATE) + public AjaxResult> uploadProductImage( + @RequestParam("id") String id, @RequestParam("file") MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new IoTException("上传文件不能为空"); + } + IoTProductVO product = devProductService.selectDevProductById(id); + if (product == null) { + throw new IoTException("产品不存在"); + } + String unionId = loginIoTUnionUser(SecurityUtils.getUnionId()).getUnionId(); + // 上传到 OSS,沿用系统统一上传逻辑 + var oss = iSysOssService.upload(file, unionId); + String url = oss.getUrl(); + + // 更新产品 photoUrl 字段(保持前端期望结构) + IoTProduct update = new IoTProduct(); + update.setId(product.getId()); + JSONObject photo = new JSONObject(); + photo.set("img", url); + photo.set("detail", ""); + update.setPhotoUrl(photo.toString()); + devProductService.updateDevProduct(update); + + Map data = new HashMap<>(2); + data.put("url", url); + String name = oss.getOriginalName(); + data.put("fileName", name.substring(name.lastIndexOf('/') + 1, name.lastIndexOf("."))); + return AjaxResult.success(data); + } + + /** 下载导入模版 */ @PostMapping("/import/template") public void downloadTemplate(HttpServletResponse response) { List list = new ArrayList<>(); @@ -723,9 +720,7 @@ public class IoTProductController extends BaseController { return getDataTable(maps); } - /** - * 绑定证书到产品 - */ + /** 绑定证书到产品 */ @PostMapping("/bindCertificate") @Log(title = "绑定证书", businessType = BusinessType.UPDATE) public AjaxResult bindCertificate(@RequestParam String productKey, @RequestParam String sslKey) { @@ -733,13 +728,35 @@ public class IoTProductController extends BaseController { return toAjax(result); } - /** - * 解绑证书 - */ + /** 解绑证书 */ @PostMapping("/unbindCertificate") @Log(title = "解绑证书", businessType = BusinessType.UPDATE) public AjaxResult unbindCertificate(@RequestParam String productKey) { int result = devProductService.unbindCertificate(productKey); return toAjax(result); } + + /** 查看连接信息 */ + @PostMapping("/connect/info") + @Log(title = "查看连接信息", businessType = BusinessType.OTHER) + public AjaxResult connectInfo(@RequestParam String productKey) { + try { + ConnectionInfoDTO connectionInfo = connectionInfoService.getConnectionInfo(productKey); + return AjaxResult.success(connectionInfo); + } catch (Exception e) { + return AjaxResult.error("获取连接信息失败:" + e.getMessage()); + } + } + + /** 查看MQTT密码 */ + @PostMapping("/connect/mqtt/password") + @Log(title = "查看连接信息密码", businessType = BusinessType.GRANT) + public AjaxResult connectInfoPassword(@RequestParam String productKey) { + try { + ConnectionInfoDTO passwordInfo = connectionInfoService.getMqttPasswordInfo(productKey); + return AjaxResult.success(passwordInfo); + } catch (Exception e) { + return AjaxResult.error("获取密码信息失败:" + e.getMessage()); + } + } } diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTUserAppController.java b/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTUserAppController.java index a6164d5..1cf010f 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTUserAppController.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/platform/web/IoTUserAppController.java @@ -16,11 +16,12 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; + import cn.hutool.http.Header; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; + import cn.hutool.json.JSONUtil; import cn.universal.admin.common.annotation.Log; import cn.universal.admin.common.enums.BusinessType; @@ -137,6 +138,28 @@ public class IoTUserAppController extends BaseController { util.exportExcel(response, list, "用户应用信息数据"); } + /** 获取用户应用信息详细信息 */ + @Operation(summary = "启用推送") + @PostMapping(value = "/{appUniqueId}/{pushType}/{enable}") + public AjaxResult enableOrDisablePush( + @PathVariable("appUniqueId") String appUniqueId, + @PathVariable("pushType") String pushType, + @PathVariable("enable") boolean enable) { + IoTUserApplication application = + iotUserApplicationService.selectIotUserApplicationById(appUniqueId); + IoTUser user = loginIoTUnionUser(SecurityUtils.getUnionId()); + if (!user.isAdmin() + && ObjectUtil.isNotEmpty(application) + && !application.getUnionId().equals(user.getUnionId())) { + throw new IoTException("你无权操作"); + } + boolean flag = iotUserApplicationService.enableOrDisablePushCfg(appUniqueId, pushType, enable); + if (flag) { + return AjaxResult.success(); + } + return AjaxResult.error("失败"); + } + /** 获取用户应用信息详细信息 */ @Operation(summary = "获取用户应用信息详细信息") @GetMapping(value = "/{appUniqueId}") @@ -198,21 +221,21 @@ public class IoTUserAppController extends BaseController { @Transactional @Log(title = "重置密钥", businessType = BusinessType.UPDATE) public AjaxResult resetSecret(@RequestBody IoTUserApplication iotUserApplication) { - IoTUserApplication oldapplication = - iotUserApplicationService.selectIotUserApplicationById(iotUserApplication.getAppUniqueId()); IoTUser user = loginIoTUnionUser(SecurityUtils.getUnionId()); + + // 权限检查 + IoTUserApplication oldApplication = + iotUserApplicationService.selectIotUserApplicationById(iotUserApplication.getAppUniqueId()); if (!user.isAdmin() - && ObjectUtil.isNotEmpty(oldapplication) - && !oldapplication.getUnionId().equals(user.getUnionId())) { + && ObjectUtil.isNotEmpty(oldApplication) + && !oldApplication.getUnionId().equals(user.getUnionId())) { throw new IoTException("你无权操作"); } - // iotUserApplication.setAppId(RandomUtil.randomString(16)); - iotUserApplication.setAppSecret(RandomUtil.randomString(32)); - iotUserApplication.setUnionId(user.getUnionId()); - if (iotUserApplicationService.updateIotUserApplication(iotUserApplication) == 0) { + // 调用service层方法完成密钥重置和MQTT配置同步 + if (!iotUserApplicationService.resetAppSecretAndSyncMqtt( + iotUserApplication.getAppUniqueId(), user.getUnionId())) { throw new IoTException("应用密钥重置失败"); } - return AjaxResult.success(); } @@ -238,7 +261,6 @@ public class IoTUserAppController extends BaseController { Map map = new HashMap<>(); map.put("message", "success"); iotUserApplication.setUnionId(app.getUnionId()); - iotUserApplication.setUpTopic(app.getUnionId() + StrUtil.C_SLASH + app.getAppId()); iotUserApplicationService.updateIotUserApplication(iotUserApplication); return AjaxResult.success(map); } diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/system/service/IIoTUserApplicationService.java b/cn-universal-admin/src/main/java/cn/universal/admin/system/service/IIoTUserApplicationService.java index cafd4a5..8aa4ea9 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/system/service/IIoTUserApplicationService.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/system/service/IIoTUserApplicationService.java @@ -67,4 +67,15 @@ public interface IIoTUserApplicationService { int deleteIotUserApplicationById(String appUniqueId); List selectApplicationList(IoTUserApplication application, IoTUser iotUser); + + boolean enableOrDisablePushCfg(String appUniqueId, String pushType, boolean isEnable); + + /** + * 重置应用密钥并同步更新MQTT配置 + * + * @param appUniqueId 应用唯一标识 + * @param unionId 用户唯一标识 + * @return 是否成功 + */ + boolean resetAppSecretAndSyncMqtt(String appUniqueId, String unionId); } diff --git a/cn-universal-admin/src/main/java/cn/universal/admin/system/service/impl/IoTUserApplicationServiceImpl.java b/cn-universal-admin/src/main/java/cn/universal/admin/system/service/impl/IoTUserApplicationServiceImpl.java index 6586b10..de64d02 100644 --- a/cn-universal-admin/src/main/java/cn/universal/admin/system/service/impl/IoTUserApplicationServiceImpl.java +++ b/cn-universal-admin/src/main/java/cn/universal/admin/system/service/impl/IoTUserApplicationServiceImpl.java @@ -13,6 +13,9 @@ package cn.universal.admin.system.service.impl; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import cn.universal.admin.common.service.BaseServiceImpl; import cn.universal.admin.common.utils.SecurityUtils; import cn.universal.admin.system.service.IIoTUserApplicationService; @@ -28,9 +31,12 @@ import cn.universal.persistence.mapper.OauthClientDetailsMapper; import jakarta.annotation.Resource; import java.util.List; import java.util.Objects; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import tk.mybatis.mapper.entity.Example; /** @@ -39,6 +45,7 @@ import tk.mybatis.mapper.entity.Example; * @since 2025-12-30 */ @Service +@Slf4j public class IoTUserApplicationServiceImpl extends BaseServiceImpl implements IIoTUserApplicationService { @@ -47,6 +54,12 @@ public class IoTUserApplicationServiceImpl extends BaseServiceImpl @Resource private OauthClientDetailsMapper oauthClientDetailsMapper; + @Value("${mqtt.cfg.enable:true}") + private boolean sysMqttEnabled; + + @Value("${mqtt.cfg.host:tcp://localhost:1883}") + private String sysMqttHost; + @Override public boolean EnDisableIoTUser(String unionId, boolean isEnable) { List select = @@ -212,4 +225,111 @@ public class IoTUserApplicationServiceImpl extends BaseServiceImpl application.setUnionId(iotUser.isAdmin() ? null : iotUser.getUnionId()); return iotUserApplicationMapper.selectApplicationList(application); } + + @Override + public boolean enableOrDisablePushCfg(String appUniqueId, String pushType, boolean isEnable) { + IoTUserApplication application = + iotUserApplicationMapper.selectOne( + IoTUserApplication.builder().appUniqueId(appUniqueId).build()); + if (application == null) { + return false; + } + // 获取当前推送配置 + String pushCfgStr = application.getCfg(); + JSONObject pushCfg; + if (StrUtil.isBlank(pushCfgStr)) { + pushCfg = new JSONObject(); + } else { + pushCfg = JSONUtil.parseObj(pushCfgStr); + } + + switch (pushType) { + case "mqtt": + if (isEnable) { + // MQTT启用:设置url为sysMqttHost、support为true、enable为true + // password为app_secret、username为app_id + JSONObject mqttConfig = new JSONObject(); + mqttConfig.set("enable", true); + mqttConfig.set("support", true); + mqttConfig.set("url", sysMqttHost); + mqttConfig.set("password", application.getAppSecret()); + mqttConfig.set("username", application.getAppId()); + mqttConfig.set("clientId", application.getAppId()); + + pushCfg.set("mqtt", mqttConfig); + } else { + // MQTT禁用:只设置enable为false + if (pushCfg.containsKey("mqtt")) { + JSONObject mqttConfig = pushCfg.getJSONObject("mqtt"); + mqttConfig.set("enable", false); + pushCfg.set("mqtt", mqttConfig); + } + } + break; + case "http": + if (isEnable) { + // HTTP启用:设置enable和support为true + JSONObject httpConfig = pushCfg.getJSONObject("http"); + httpConfig.set("enable", true); + httpConfig.set("support", true); + pushCfg.set("http", httpConfig); + } else { + // HTTP禁用:设置enable为false + if (pushCfg.containsKey("http")) { + JSONObject httpConfig = pushCfg.getJSONObject("http"); + httpConfig.set("enable", false); + pushCfg.set("http", httpConfig); + } + } + break; + default: + return false; + } + // 更新推送配置 + application.setCfg(pushCfg.toString()); + int result = iotUserApplicationMapper.updateIotUserApplication(application); + return result > 0; + } + + @Override + @Transactional + public boolean resetAppSecretAndSyncMqtt(String appUniqueId, String unionId) { + // 查询应用信息 + IoTUserApplication application = + iotUserApplicationMapper.selectOne( + IoTUserApplication.builder().appUniqueId(appUniqueId).build()); + + if (application == null) { + return false; + } + // 生成新的应用密钥 + String newAppSecret = cn.hutool.core.util.RandomUtil.randomString(32); + application.setAppSecret(newAppSecret); + + // 更新应用信息 + if (iotUserApplicationMapper.updateByPrimaryKeySelective(application) == 0) { + return false; + } + // 检查MQTT是否开启,如果开启则同步更新MQTT配置中的密码 + String oldCfg = application.getCfg(); + if (StrUtil.isNotBlank(oldCfg)) { + JSONObject cfg = JSONUtil.parseObj(oldCfg); + if (cfg.containsKey("mqtt")) { + JSONObject mqttConfig = cfg.getJSONObject("mqtt"); + if (mqttConfig.getBool("enable", false)) { + // MQTT已启用,更新密码 + mqttConfig.set("password", newAppSecret); + cfg.set("mqtt", mqttConfig); + + // 更新配置 + IoTUserApplication updateCfg = new IoTUserApplication(); + updateCfg.setAppUniqueId(appUniqueId); + updateCfg.setCfg(cfg.toString()); + iotUserApplicationMapper.updateByPrimaryKeySelective(updateCfg); + log.info("应用 {} 密钥重置后,MQTT配置密码已同步更新", appUniqueId); + } + } + } + return true; + } }