mirror of
https://gitee.com/NexIoT/Universal-IoT-Java.git
synced 2026-10-31 07:25:09 +08:00
@@ -114,7 +114,12 @@
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-notice</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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文档
|
||||
*
|
||||
* <p>基础路径: /admin/network
|
||||
*
|
||||
* <p>1. 查询网络组件列表 GET /admin/network/list 参数: - page: 页码 (可选,默认1) - size: 每页大小 (可选,默认10) - type:
|
||||
* 网络类型 (可选,单个类型,如 TCP_CLIENT) - types: 网络类型列表 (可选,多个类型,如 ["MQTT_CLIENT", "MQTT_SERVER"]) - name:
|
||||
* 网络组件名称 (可选,模糊查询) - productKey: 产品Key (可选) - state: 状态 (可选,true/false) - unionId: 唯一标识 (可选)
|
||||
*
|
||||
* <p>2. 查询网络组件列表(支持多个类型) GET /admin/network/list/multi-type 参数: - page: 页码 (可选,默认1) - size: 每页大小
|
||||
* (可选,默认10) - type: 网络类型 (可选,支持逗号分隔多个类型,如 "MQTT_CLIENT,MQTT_SERVER") - name: 网络组件名称 (可选,模糊查询) -
|
||||
* productKey: 产品Key (可选) - state: 状态 (可选,true/false) - unionId: 唯一标识 (可选)
|
||||
*
|
||||
* <p>3. 根据ID查询网络组件 GET /admin/network/{id} 参数: - id: 网络组件ID (路径参数)
|
||||
*
|
||||
* <p>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" }
|
||||
*
|
||||
* <p>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}" }
|
||||
*
|
||||
* <p>6. 删除网络组件 DELETE /admin/network/{id} 参数: - id: 网络组件ID (路径参数)
|
||||
*
|
||||
* <p>7. 批量删除网络组件 DELETE /admin/network/batch/{ids} 参数: - ids: 网络组件ID数组 (路径参数,逗号分隔)
|
||||
*
|
||||
* <p>8. 启动网络组件 POST /admin/network/start/{id} 参数: - id: 网络组件ID (路径参数)
|
||||
*
|
||||
* <p>9. 停止网络组件 POST /admin/network/stop/{id} 参数: - id: 网络组件ID (路径参数)
|
||||
*
|
||||
* <p>10. 重启网络组件 POST /admin/network/restart/{id} 参数: - id: 网络组件ID (路径参数)
|
||||
*
|
||||
* <p>11. 获取网络类型列表 GET /admin/network/types
|
||||
*
|
||||
* <p>12. 验证网络组件配置 POST /admin/network/validate 请求体: { "type": "TCP_SERVER", "unionId":
|
||||
* "unique_id", "name": "网络组件名称", "configuration":
|
||||
* "{\"host\":\"0.0.0.0\",\"port\":6372,\"ssl\":false}" }
|
||||
*
|
||||
* <p>响应格式: { "code": 200, "msg": "操作成功", "data": { // 具体数据 } }
|
||||
*
|
||||
* <p>网络类型说明: - TCP_CLIENT: TCP客户端 - TCP_SERVER: TCP服务端 - MQTT_CLIENT: MQTT客户端 - MQTT_SERVER:
|
||||
* MQTT服务端
|
||||
*
|
||||
* <p>配置示例:
|
||||
*
|
||||
* <p>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 }
|
||||
*
|
||||
* <p>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 }
|
||||
*/
|
||||
}
|
||||
-10
@@ -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<NetworkVO> selectNetworkList(NetworkBO bo);
|
||||
|
||||
NetworkVO getDetail(Long id);
|
||||
|
||||
List<IoTDevice> queryMileSightList(NetworkBO bo);
|
||||
|
||||
R insertDevInstance(IoTDeviceBO devInstancebo);
|
||||
|
||||
R deleteDevInstanceByIds(String[] ids);
|
||||
|
||||
void reloadTcpClient(String applicationId);
|
||||
|
||||
/**
|
||||
* 查询网络组件列表
|
||||
*
|
||||
|
||||
+243
-54
@@ -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<NetworkVO> 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<NetworkBO> 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<IoTProductBO> 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<String> 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<Network> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ public class NetworkTypeUtil {
|
||||
|
||||
/** 支持的网络类型列表 */
|
||||
public static final List<String> SUPPORTED_TYPES =
|
||||
Arrays.asList("TCP_CLIENT", "TCP_SERVER", "MQTT_CLIENT", "MQTT_SERVER");
|
||||
Arrays.asList("TCP_CLIENT", "TCP_SERVER", "MQTT_CLIENT", "MQTT_SERVER", "UDP");
|
||||
|
||||
/**
|
||||
* 解析网络类型字符串,支持逗号分隔的多个类型
|
||||
|
||||
+3
-4
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -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<GatewayDeviceVo> selectGwIotDevices(IoTGwDeviceBO bo);
|
||||
|
||||
IoTDevice selectDeviceByDeviceId(String deviceId);
|
||||
IoTDevice selectIoTDevice(String productKey, String deviceId);
|
||||
|
||||
List<IoTDevice> selectDevInstanceListWithTags(IoTDeviceBO ioTDeviceBO, IoTUser iotUser);
|
||||
|
||||
List<IoTProduct> selectProductListInBatchFunction(Long applicationId, IoTUser iotUser);
|
||||
|
||||
/** 新:按第三方平台过滤设备列表(不影响旧接口) */
|
||||
List<IoTDevice> selectDevInstanceListByPlatform(
|
||||
IoTDevice ioTDevice, IoTUser iotUser, String thirdPlatform);
|
||||
|
||||
|
||||
}
|
||||
|
||||
+3
-1
@@ -178,7 +178,9 @@ public interface IIoTProductService {
|
||||
|
||||
Map<String, Integer> countDevNumberByProductKey(String unionId);
|
||||
|
||||
AjaxResult<IoTProduct> selectDevProductByKey(String key);
|
||||
AjaxResult<IoTProduct> selectIoTProductByKey(String key);
|
||||
|
||||
AjaxResult<List<IoTProduct>> selectGatewaySubProductsByKey(String gwProductKey);
|
||||
|
||||
int updateDevProductOtherConfig(String otherConfig);
|
||||
|
||||
|
||||
+28
-11
@@ -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<IoTDeviceGroupVO> 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<Void> 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<Void> 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> ioTDeviceGroupVO, Map<Long, List<IoTDeviceGroupVO>> groups) {
|
||||
if (ioTDeviceGroupVO == null) {
|
||||
return;
|
||||
@@ -315,7 +332,7 @@ public class IoTDeviceGroupServiceImpl extends BaseServiceImpl implements IIoTDe
|
||||
if (ioTDeviceGroupVO.get(i).getHasChild() == 1) {
|
||||
List<IoTDeviceGroupVO> devGroups = groups.get(ioTDeviceGroupVO.get(i).getId());
|
||||
ioTDeviceGroupVO.get(i).setChildren(devGroups);
|
||||
buildGroupTree(devGroups, groups);
|
||||
groupTree(devGroups, groups);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
+25
-9
@@ -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<IoTDevice> 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("设备不存在");
|
||||
}
|
||||
|
||||
+148
-35
@@ -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<IoTProductVO> selectDevProductV4List(IoTProductQuery ioTProductQuery) {
|
||||
Page<IoTProductVO> page =
|
||||
PageHelper.startPage(ioTProductQuery.getPageNum(), ioTProductQuery.getPageSize());
|
||||
// 设置分页参数
|
||||
PageHelper.startPage(ioTProductQuery.getPageNum(), ioTProductQuery.getPageSize());
|
||||
|
||||
// 优化:一次SQL查询获取产品列表和设备数量,避免N+1查询
|
||||
List<IoTProductVO> devProductVOS = ioTProductMapper.selectDevProductV3List(ioTProductQuery);
|
||||
List<IoTProductVO> results =
|
||||
ioTProductMapper.countDevNumberByProductKey(ioTProductQuery.getCreatorId());
|
||||
if (CollUtil.isNotEmpty(results)) {
|
||||
final Map<String, Integer> 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<IoTProductVO> selectDevProductV4ListNoPage(IoTProductQuery ioTProductQuery) {
|
||||
// 创建不分页的查询条件
|
||||
IoTProductQuery noPageQuery = new IoTProductQuery();
|
||||
BeanUtil.copyProperties(ioTProductQuery, noPageQuery);
|
||||
noPageQuery.setPageNum(1);
|
||||
noPageQuery.setPageSize(Integer.MAX_VALUE); // 获取所有数据
|
||||
|
||||
// 优化:一次SQL查询获取产品列表和设备数量,避免N+1查询
|
||||
List<IoTProductVO> 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<IoTProduct> 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<IoTProductImportBO> 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<String, Integer> countDevNumberByProductKey(String unionId) {
|
||||
List<IoTProductVO> 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<IoTProduct> selectDevProductByKey(String key) {
|
||||
public AjaxResult<IoTProduct> selectIoTProductByKey(String key) {
|
||||
IoTProduct ioTProduct = new IoTProduct();
|
||||
ioTProduct.setProductKey(key);
|
||||
return AjaxResult.success(ioTProductMapper.selectOne(ioTProduct));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AjaxResult<List<IoTProduct>> 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
|
||||
|
||||
+16
-13
@@ -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<IoTDevice> 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() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
+162
-145
@@ -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<IoTProduct> getProByKey(@PathVariable("key") String key) {
|
||||
return devProductService.selectDevProductByKey(key);
|
||||
return devProductService.selectIoTProductByKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询设备产品列表
|
||||
*/
|
||||
/** 获取网关绑定的子产品列表 */
|
||||
@GetMapping("/gateway/subProducts/{key}")
|
||||
public AjaxResult<List<IoTProduct>> 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<IoTProductVO> list = devProductService.selectDevProductAllList();
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 物模型新增内容
|
||||
*/
|
||||
/** 物模型新增内容 */
|
||||
@PostMapping("/metadata/add")
|
||||
@Log(title = "物模型新增内容", businessType = BusinessType.INSERT)
|
||||
public AjaxResult<Void> metadataAdd(@RequestBody IoTProductBO ioTProductBO) {
|
||||
return toAjax(devProductService.insertMetadata(ioTProductBO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 物模型修改内容
|
||||
*/
|
||||
/** 物模型修改内容 */
|
||||
@PostMapping("/metadata/update")
|
||||
@Log(title = "物模型修改内容", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult<Void> metadataUpdate(@RequestBody IoTProductBO ioTProductBO) {
|
||||
return toAjax(devProductService.updateMetadata(ioTProductBO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 物模型删除内容
|
||||
*/
|
||||
/** 物模型删除内容 */
|
||||
@PostMapping("/metadata/delete")
|
||||
@Log(title = "物模型删除内容", businessType = BusinessType.DELETE)
|
||||
public AjaxResult<Void> metadataDelete(@RequestBody IoTProductBO ioTProductBO) {
|
||||
return toAjax(devProductService.deleteMetadata(ioTProductBO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 物模型查询内容
|
||||
*/
|
||||
/** 物模型查询内容 */
|
||||
@PostMapping("/metadata/get")
|
||||
public AjaxResult<IoTProductBO> metadataGet(@RequestBody IoTProductBO ioTProductBO) {
|
||||
return AjaxResult.success(devProductService.getMetadata(ioTProductBO));
|
||||
}
|
||||
|
||||
/** 导出设备产品列表 */
|
||||
// @PostMapping("/export")
|
||||
// public void export(HttpServletResponse response, IoTProduct ioTProduct)
|
||||
// {
|
||||
// List<IoTProduct> list = devProductService.selectDevProductList(ioTProduct);
|
||||
// ExcelUtil<IoTProduct> util = new ExcelUtil<IoTProduct>(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<List<IoTProductVO>> 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<List<IoTProductVO>> getGatewaySubDeviceList(
|
||||
@PathVariable("productKey") String productKey) {
|
||||
List<IoTProductVO> 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<String, Object> 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<String, Object> 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<IoTDevice> 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<String, Object> 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<IoTDeviceProperties> list = devProductService.selectDevProperties(id);
|
||||
return AjaxResult.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询设备产品物模型属性列表(功能下发属性)
|
||||
*/
|
||||
/** 查询设备产品物模型属性列表(功能下发属性) */
|
||||
@GetMapping("/functionProperties/{id}")
|
||||
public AjaxResult getFunctionPropertiesList(@PathVariable("id") String id) {
|
||||
List<IoTDeviceProperties> list = devProductService.selectDevProperties(id);
|
||||
@@ -535,7 +509,8 @@ public class IoTProductController extends BaseController {
|
||||
List<IoTDeviceProperties> dataRw =
|
||||
list.stream().filter((item) -> "rw".equals(item.getMode())).collect(Collectors.toList());
|
||||
// 获取设备公共字段
|
||||
List<SysDictData> data = dictTypeService.selectDictDataByType("device_common_property");
|
||||
List<SysDictData> 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<IoTDeviceEvents> list = devProductService.selectDevEvents(id);
|
||||
return AjaxResult.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询设备产品物模型方法列表
|
||||
*/
|
||||
/** 查询设备产品物模型方法列表 */
|
||||
@GetMapping("/functions/{id}")
|
||||
public AjaxResult getFunctionsList(@PathVariable("id") String id) {
|
||||
List<IoTDeviceFunction> 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<String, Integer> 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<Void> 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<Map<String, String>> 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<String, String> 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<IoTProductImportBO> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-11
@@ -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<Void> 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<Void> 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<String, String> 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);
|
||||
}
|
||||
|
||||
+11
@@ -67,4 +67,15 @@ public interface IIoTUserApplicationService {
|
||||
int deleteIotUserApplicationById(String appUniqueId);
|
||||
|
||||
List<IoTUserApplicationVO> 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);
|
||||
}
|
||||
|
||||
+120
@@ -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<IoTUserApplication> 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;
|
||||
}
|
||||
}
|
||||
|
||||
+206
-22
@@ -20,16 +20,23 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Redisson 配置类 用于配置分布式锁和 Redis 连接
|
||||
* 支持单机模式、集群模式、哨兵模式、主从模式
|
||||
*
|
||||
* @version 1.0 @Author Aleo
|
||||
* @version 2.0 @Author Aleo
|
||||
* @since 2025/1/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class RedissonConfig {
|
||||
|
||||
// ================================
|
||||
// 基础配置
|
||||
// ================================
|
||||
@Value("${spring.data.redis.host:localhost}")
|
||||
private String redisHost;
|
||||
|
||||
@@ -45,6 +52,9 @@ public class RedissonConfig {
|
||||
@Value("${spring.data.redis.timeout:3000}")
|
||||
private int redisTimeout;
|
||||
|
||||
// ================================
|
||||
// 连接池配置
|
||||
// ================================
|
||||
@Value("${spring.data.redis.lettuce.pool.max-active:8}")
|
||||
private int maxActive;
|
||||
|
||||
@@ -57,7 +67,12 @@ public class RedissonConfig {
|
||||
@Value("${spring.data.redis.lettuce.pool.max-wait:-1}")
|
||||
private long maxWait;
|
||||
|
||||
// ================================
|
||||
// Redisson 特定配置
|
||||
// ================================
|
||||
@Value("${redisson.mode:single}")
|
||||
private String redissonMode;
|
||||
|
||||
@Value("${redisson.lock.watchdog-timeout:30000}")
|
||||
private int lockWatchdogTimeout;
|
||||
|
||||
@@ -82,43 +97,212 @@ public class RedissonConfig {
|
||||
@Value("${redisson.connection.retry-interval:1500}")
|
||||
private int redissonRetryInterval;
|
||||
|
||||
// ================================
|
||||
// 集群模式配置
|
||||
// ================================
|
||||
@Value("${redisson.cluster.nodes:}")
|
||||
private String clusterNodes;
|
||||
|
||||
@Value("${redisson.cluster.scan-interval:2000}")
|
||||
private int clusterScanInterval;
|
||||
|
||||
@Value("${redisson.cluster.slave-connection-minimum-idle-size:1}")
|
||||
private int clusterSlaveMinIdle;
|
||||
|
||||
@Value("${redisson.cluster.slave-connection-pool-size:64}")
|
||||
private int clusterSlavePoolSize;
|
||||
|
||||
@Value("${redisson.cluster.master-connection-minimum-idle-size:1}")
|
||||
private int clusterMasterMinIdle;
|
||||
|
||||
@Value("${redisson.cluster.master-connection-pool-size:64}")
|
||||
private int clusterMasterPoolSize;
|
||||
|
||||
// ================================
|
||||
// 哨兵模式配置
|
||||
// ================================
|
||||
@Value("${redisson.sentinel.master-name:mymaster}")
|
||||
private String sentinelMasterName;
|
||||
|
||||
@Value("${redisson.sentinel.nodes:}")
|
||||
private String sentinelNodes;
|
||||
|
||||
@Value("${redisson.sentinel.password:}")
|
||||
private String sentinelPassword;
|
||||
|
||||
@Value("${redisson.sentinel.database:0}")
|
||||
private int sentinelDatabase;
|
||||
|
||||
// ================================
|
||||
// 主从模式配置
|
||||
// ================================
|
||||
@Value("${redisson.master-slave.master-address:}")
|
||||
private String masterAddress;
|
||||
|
||||
@Value("${redisson.master-slave.slave-addresses:}")
|
||||
private String slaveAddresses;
|
||||
|
||||
@Value("${redisson.master-slave.database:0}")
|
||||
private int masterSlaveDatabase;
|
||||
|
||||
/** 配置 Redisson 客户端 */
|
||||
@Bean
|
||||
@Primary
|
||||
public RedissonClient redissonClient() {
|
||||
try {
|
||||
Config config = new Config();
|
||||
|
||||
// 单机模式配置
|
||||
config
|
||||
.useSingleServer()
|
||||
.setAddress("redis://" + redisHost + ":" + redisPort)
|
||||
.setDatabase(redisDatabase)
|
||||
.setConnectionPoolSize(redissonPoolSize)
|
||||
.setConnectionMinimumIdleSize(redissonMinIdle)
|
||||
.setConnectTimeout(redissonTimeout)
|
||||
.setIdleConnectionTimeout(redissonTimeout)
|
||||
.setRetryAttempts(redissonRetryAttempts)
|
||||
.setRetryInterval(redissonRetryInterval)
|
||||
.setKeepAlive(true)
|
||||
.setTcpNoDelay(true);
|
||||
|
||||
// 如果设置了密码
|
||||
if (redisPassword != null && !redisPassword.trim().isEmpty()) {
|
||||
config.useSingleServer().setPassword(redisPassword);
|
||||
|
||||
// 根据配置模式选择不同的连接方式
|
||||
switch (redissonMode.toLowerCase()) {
|
||||
case "cluster":
|
||||
configClusterMode(config);
|
||||
break;
|
||||
case "sentinel":
|
||||
configSentinelMode(config);
|
||||
break;
|
||||
case "master-slave":
|
||||
configMasterSlaveMode(config);
|
||||
break;
|
||||
case "single":
|
||||
default:
|
||||
configSingleMode(config);
|
||||
break;
|
||||
}
|
||||
|
||||
// 分布式锁配置
|
||||
config.setLockWatchdogTimeout(lockWatchdogTimeout); // 看门狗超时时间
|
||||
config.setLockWatchdogTimeout(lockWatchdogTimeout);
|
||||
|
||||
RedissonClient redissonClient = Redisson.create(config);
|
||||
log.info("Redisson 客户端初始化成功: {}:{}", redisHost, redisPort);
|
||||
log.info("Redisson 客户端初始化成功: mode={}, host={}:{}", redissonMode, redisHost, redisPort);
|
||||
|
||||
return redissonClient;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Redisson 客户端初始化失败: {}:{}, error={}", redisHost, redisPort, e.getMessage(), e);
|
||||
log.error("Redisson 客户端初始化失败: mode={}, host={}:{}, error={}",
|
||||
redissonMode, redisHost, redisPort, e.getMessage(), e);
|
||||
throw new RuntimeException("Redisson 初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** 配置单机模式 */
|
||||
private void configSingleMode(Config config) {
|
||||
log.info("配置 Redisson 单机模式: {}:{}", redisHost, redisPort);
|
||||
|
||||
config.useSingleServer()
|
||||
.setAddress("redis://" + redisHost + ":" + redisPort)
|
||||
.setDatabase(redisDatabase)
|
||||
.setConnectionPoolSize(redissonPoolSize)
|
||||
.setConnectionMinimumIdleSize(redissonMinIdle)
|
||||
.setConnectTimeout(redissonTimeout)
|
||||
.setIdleConnectionTimeout(redissonTimeout)
|
||||
.setRetryAttempts(redissonRetryAttempts)
|
||||
.setRetryInterval(redissonRetryInterval)
|
||||
.setKeepAlive(true)
|
||||
.setTcpNoDelay(true);
|
||||
|
||||
// 如果设置了密码
|
||||
if (redisPassword != null && !redisPassword.trim().isEmpty()) {
|
||||
config.useSingleServer().setPassword(redisPassword);
|
||||
}
|
||||
}
|
||||
|
||||
/** 配置集群模式 */
|
||||
private void configClusterMode(Config config) {
|
||||
if (clusterNodes == null || clusterNodes.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("集群模式需要配置 redisson.cluster.nodes");
|
||||
}
|
||||
|
||||
log.info("配置 Redisson 集群模式: {}", clusterNodes);
|
||||
|
||||
List<String> nodes = Arrays.asList(clusterNodes.split(","));
|
||||
config.useClusterServers()
|
||||
.setScanInterval(clusterScanInterval)
|
||||
.setMasterConnectionMinimumIdleSize(clusterMasterMinIdle)
|
||||
.setMasterConnectionPoolSize(clusterMasterPoolSize)
|
||||
.setSlaveConnectionMinimumIdleSize(clusterSlaveMinIdle)
|
||||
.setSlaveConnectionPoolSize(clusterSlavePoolSize)
|
||||
.setConnectTimeout(redissonTimeout)
|
||||
.setIdleConnectionTimeout(redissonTimeout)
|
||||
.setRetryAttempts(redissonRetryAttempts)
|
||||
.setRetryInterval(redissonRetryInterval)
|
||||
.setKeepAlive(true)
|
||||
.setTcpNoDelay(true);
|
||||
|
||||
// 添加集群节点
|
||||
for (String node : nodes) {
|
||||
config.useClusterServers().addNodeAddress("redis://" + node.trim());
|
||||
}
|
||||
|
||||
// 如果设置了密码
|
||||
if (redisPassword != null && !redisPassword.trim().isEmpty()) {
|
||||
config.useClusterServers().setPassword(redisPassword);
|
||||
}
|
||||
}
|
||||
|
||||
/** 配置哨兵模式 */
|
||||
private void configSentinelMode(Config config) {
|
||||
if (sentinelNodes == null || sentinelNodes.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("哨兵模式需要配置 redisson.sentinel.nodes");
|
||||
}
|
||||
|
||||
log.info("配置 Redisson 哨兵模式: master={}, sentinels={}", sentinelMasterName, sentinelNodes);
|
||||
|
||||
List<String> sentinels = Arrays.asList(sentinelNodes.split(","));
|
||||
config.useSentinelServers()
|
||||
.setMasterName(sentinelMasterName)
|
||||
.setDatabase(sentinelDatabase)
|
||||
.setConnectTimeout(redissonTimeout)
|
||||
.setIdleConnectionTimeout(redissonTimeout)
|
||||
.setRetryAttempts(redissonRetryAttempts)
|
||||
.setRetryInterval(redissonRetryInterval)
|
||||
.setKeepAlive(true)
|
||||
.setTcpNoDelay(true);
|
||||
|
||||
// 添加哨兵节点
|
||||
for (String sentinel : sentinels) {
|
||||
config.useSentinelServers().addSentinelAddress("redis://" + sentinel.trim());
|
||||
}
|
||||
|
||||
// 如果设置了密码
|
||||
if (redisPassword != null && !redisPassword.trim().isEmpty()) {
|
||||
config.useSentinelServers().setPassword(redisPassword);
|
||||
}
|
||||
|
||||
// 如果设置了哨兵密码
|
||||
if (sentinelPassword != null && !sentinelPassword.trim().isEmpty()) {
|
||||
config.useSentinelServers().setSentinelPassword(sentinelPassword);
|
||||
}
|
||||
}
|
||||
|
||||
/** 配置主从模式 */
|
||||
private void configMasterSlaveMode(Config config) {
|
||||
if (masterAddress == null || masterAddress.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("主从模式需要配置 redisson.master-slave.master-address");
|
||||
}
|
||||
|
||||
log.info("配置 Redisson 主从模式: master={}, slaves={}", masterAddress, slaveAddresses);
|
||||
|
||||
config.useMasterSlaveServers()
|
||||
.setMasterAddress("redis://" + masterAddress.trim())
|
||||
.setDatabase(masterSlaveDatabase)
|
||||
.setConnectTimeout(redissonTimeout)
|
||||
.setIdleConnectionTimeout(redissonTimeout)
|
||||
.setRetryAttempts(redissonRetryAttempts)
|
||||
.setRetryInterval(redissonRetryInterval)
|
||||
.setKeepAlive(true)
|
||||
.setTcpNoDelay(true);
|
||||
|
||||
// 添加从节点
|
||||
if (slaveAddresses != null && !slaveAddresses.trim().isEmpty()) {
|
||||
List<String> slaves = Arrays.asList(slaveAddresses.split(","));
|
||||
for (String slave : slaves) {
|
||||
config.useMasterSlaveServers().addSlaveAddress("redis://" + slave.trim());
|
||||
}
|
||||
}
|
||||
|
||||
// 如果设置了密码
|
||||
if (redisPassword != null && !redisPassword.trim().isEmpty()) {
|
||||
config.useMasterSlaveServers().setPassword(redisPassword);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
package cn.universal.dm.device.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Redisson 配置测试类
|
||||
* 用于验证不同模式的连接是否正常
|
||||
*
|
||||
* @version 1.0 @Author Aleo
|
||||
* @since 2025/1/20
|
||||
*/
|
||||
@Slf4j
|
||||
//@Component
|
||||
public class RedissonConfigTest implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
log.info("开始测试 Redisson 连接...");
|
||||
|
||||
try {
|
||||
// 测试基本连接
|
||||
testBasicConnection();
|
||||
|
||||
// 测试分布式锁
|
||||
testDistributedLock();
|
||||
|
||||
// 测试Redis操作
|
||||
testRedisOperations();
|
||||
|
||||
log.info("Redisson 连接测试完成,所有测试通过!");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Redisson 连接测试失败: {}", e.getMessage(), e);
|
||||
// 不抛出异常,避免影响应用启动
|
||||
}
|
||||
}
|
||||
|
||||
/** 测试基本连接 */
|
||||
private void testBasicConnection() {
|
||||
log.info("测试基本连接...");
|
||||
|
||||
// 测试基本操作
|
||||
String testKey = "test:redisson:connection";
|
||||
redissonClient.getBucket(testKey).set("test");
|
||||
Object value = redissonClient.getBucket(testKey).get();
|
||||
log.info("Redis 连接测试通过: {} = {}", testKey, value);
|
||||
|
||||
// 清理测试数据
|
||||
redissonClient.getBucket(testKey).delete();
|
||||
}
|
||||
|
||||
/** 测试分布式锁 */
|
||||
private void testDistributedLock() {
|
||||
log.info("测试分布式锁...");
|
||||
|
||||
String lockKey = "test:redisson:lock";
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
|
||||
try {
|
||||
// 尝试获取锁
|
||||
boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
|
||||
if (locked) {
|
||||
log.info("成功获取分布式锁: {}", lockKey);
|
||||
|
||||
// 模拟业务处理
|
||||
Thread.sleep(1000);
|
||||
|
||||
// 释放锁
|
||||
lock.unlock();
|
||||
log.info("成功释放分布式锁: {}", lockKey);
|
||||
} else {
|
||||
log.warn("获取分布式锁失败: {}", lockKey);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("分布式锁测试被中断: {}", lockKey);
|
||||
} catch (Exception e) {
|
||||
log.error("分布式锁测试失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 测试Redis操作 */
|
||||
private void testRedisOperations() {
|
||||
log.info("测试Redis操作...");
|
||||
|
||||
try {
|
||||
// 测试字符串操作
|
||||
String testKey = "test:redisson:string";
|
||||
String testValue = "Hello Redisson!";
|
||||
|
||||
redissonClient.getBucket(testKey).set(testValue);
|
||||
String retrievedValue = redissonClient.getBucket(testKey).get().toString();
|
||||
|
||||
if (testValue.equals(retrievedValue)) {
|
||||
log.info("字符串操作测试通过: {} = {}", testKey, retrievedValue);
|
||||
} else {
|
||||
log.warn("字符串操作测试失败: 期望={}, 实际={}", testValue, retrievedValue);
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
redissonClient.getBucket(testKey).delete();
|
||||
|
||||
// 测试Hash操作
|
||||
String hashKey = "test:redisson:hash";
|
||||
redissonClient.getMap(hashKey).put("field1", "value1");
|
||||
redissonClient.getMap(hashKey).put("field2", "value2");
|
||||
|
||||
int hashSize = redissonClient.getMap(hashKey).size();
|
||||
log.info("Hash操作测试通过: {} 包含 {} 个字段", hashKey, hashSize);
|
||||
|
||||
// 清理测试数据
|
||||
redissonClient.getMap(hashKey).delete();
|
||||
|
||||
// 测试Set操作
|
||||
String setKey = "test:redisson:set";
|
||||
redissonClient.getSet(setKey).add("item1");
|
||||
redissonClient.getSet(setKey).add("item2");
|
||||
redissonClient.getSet(setKey).add("item3");
|
||||
|
||||
int setSize = redissonClient.getSet(setKey).size();
|
||||
log.info("Set操作测试通过: {} 包含 {} 个元素", setKey, setSize);
|
||||
|
||||
// 清理测试数据
|
||||
redissonClient.getSet(setKey).delete();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Redis操作测试失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package cn.universal.dm.device.service;
|
||||
|
||||
import cn.universal.dm.device.service.impl.IoTProductDeviceService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @version 1.0 @Author Aleo
|
||||
* @since 2025/6/26 20:10
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractCodecService {
|
||||
|
||||
@Resource private IoTProductDeviceService iotProductDeviceService;
|
||||
}
|
||||
+14
-6
@@ -12,7 +12,7 @@
|
||||
|
||||
package cn.universal.dm.device.service;
|
||||
|
||||
import cn.universal.core.service.ICodec;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.universal.core.service.ICodecService;
|
||||
import cn.universal.core.service.IDown;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -25,9 +25,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
* @since 2025/8/9 10:50
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractDownService<T> extends AbstratIoTService implements IDown, ICodec {
|
||||
public abstract class AbstractDownService<T> extends AbstratIoTService implements IDown {
|
||||
|
||||
@Autowired protected ICodecService codecService;
|
||||
@Autowired protected ICodecService iCodecService;
|
||||
|
||||
/**
|
||||
* 消息转换和编解码
|
||||
@@ -37,9 +37,17 @@ public abstract class AbstractDownService<T> extends AbstratIoTService implement
|
||||
*/
|
||||
protected abstract T convert(String request);
|
||||
|
||||
@Override
|
||||
public String spliceDown(String productKey, String payload) {
|
||||
protected String encodeWithShadow(String productKey, String deviceId, String payload) {
|
||||
JSONObject jsonObject = getProductConfiguration(productKey);
|
||||
JSONObject context = null;
|
||||
if (jsonObject != null) {
|
||||
// 上行报文是否需要附加影子
|
||||
Boolean requireDownShadow = jsonObject.getBool("requireDownShadow", false);
|
||||
if (requireDownShadow) {
|
||||
context = iotDeviceShadowService.getDeviceShadowObj(productKey, deviceId);
|
||||
}
|
||||
}
|
||||
// 使用新的统一编解码服务
|
||||
return codecService.encode(productKey, payload);
|
||||
return iCodecService.encode(productKey, payload, context);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-8
@@ -15,7 +15,6 @@ package cn.universal.dm.device.service;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.universal.common.constant.IoTConstant;
|
||||
import cn.universal.core.message.UPRequest;
|
||||
import cn.universal.core.service.ICodec;
|
||||
import cn.universal.core.service.ICodecService;
|
||||
import cn.universal.core.service.IUP;
|
||||
import java.util.List;
|
||||
@@ -33,7 +32,7 @@ import org.springframework.scheduling.annotation.Async;
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractUPService<T extends UPRequest> extends AbstratIoTService
|
||||
implements IUP, ICodec {
|
||||
implements IUP {
|
||||
|
||||
@Autowired protected ICodecService codecService;
|
||||
|
||||
@@ -112,10 +111,4 @@ public abstract class AbstractUPService<T extends UPRequest> extends AbstratIoTS
|
||||
|
||||
/** 当前iot组件名称 */
|
||||
protected abstract String currentComponent();
|
||||
|
||||
@Override
|
||||
public UPRequest preDecode(String productKey, String message) {
|
||||
// 使用新的统一编解码服务
|
||||
return codecService.preDecode(productKey, message);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-107
@@ -16,22 +16,15 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.common.constant.IoTConstant;
|
||||
import cn.universal.common.constant.IoTConstant.DeviceNode;
|
||||
import cn.universal.common.constant.IoTConstant.MessageType;
|
||||
import cn.universal.common.utils.AESOperator;
|
||||
import cn.universal.core.message.UPRequest;
|
||||
import cn.universal.core.metadata.AbstractEventMetadata;
|
||||
import cn.universal.core.metadata.AbstractFunctionMetadata;
|
||||
import cn.universal.core.metadata.DeviceMetadata;
|
||||
import cn.universal.core.protocol.jar.ProtocolCodecJar;
|
||||
import cn.universal.core.protocol.jscrtipt.ProtocolCodecJscript;
|
||||
import cn.universal.core.protocol.magic.ProtocolCodecMagic;
|
||||
import cn.universal.core.protocol.support.ProtocolCodecSupport;
|
||||
import cn.universal.core.protocol.support.ProtocolSupportDefinition;
|
||||
import cn.universal.core.service.ICodecService;
|
||||
import cn.universal.dm.device.service.action.IoTDeviceActionAfterService;
|
||||
import cn.universal.dm.device.service.impl.IoTDeviceService;
|
||||
@@ -94,6 +87,47 @@ public abstract class AbstratIoTService {
|
||||
protected IoTProduct getProduct(String productKey) {
|
||||
return iotProductDeviceService.getProduct(productKey);
|
||||
}
|
||||
|
||||
protected JSONObject getProductConfiguration(String productKey) {
|
||||
return iotProductDeviceService.getProductConfiguration(productKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全解析产品配置JSON
|
||||
*
|
||||
* <p>处理以下情况:
|
||||
* <ul>
|
||||
* <li>配置为null</li>
|
||||
* <li>配置为空字符串</li>
|
||||
* <li>配置不是有效的JSON格式</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param product 产品信息
|
||||
* @return 解析后的JSONObject,如果解析失败返回空的JSONObject
|
||||
*/
|
||||
protected JSONObject parseProductConfigurationSafely(IoTProduct product) {
|
||||
if (product == null) {
|
||||
log.warn("产品信息为空,返回空配置");
|
||||
return new JSONObject();
|
||||
}
|
||||
|
||||
String configuration = product.getConfiguration();
|
||||
if (StrUtil.isBlank(configuration)) {
|
||||
log.debug("产品配置为空,返回空配置: productKey={}", product.getProductKey());
|
||||
return new JSONObject();
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject config = JSONUtil.parseObj(configuration);
|
||||
log.debug("产品配置解析成功: productKey={}", product.getProductKey());
|
||||
return config;
|
||||
} catch (Exception e) {
|
||||
log.error("产品配置解析失败,返回空配置: productKey={}, configuration={}",
|
||||
product.getProductKey(), configuration, e);
|
||||
return new JSONObject();
|
||||
}
|
||||
}
|
||||
|
||||
protected IoTDeviceDTO getIoTDeviceDTO(IoTDeviceQuery query) {
|
||||
IoTDeviceDTO instanceBO = iotDeviceService.lifeCycleDevInstance(query);
|
||||
if (instanceBO == null) {
|
||||
@@ -101,17 +135,6 @@ public abstract class AbstratIoTService {
|
||||
}
|
||||
return instanceBO;
|
||||
}
|
||||
/**
|
||||
* 查询产品是否配置了离线周期阈值
|
||||
*
|
||||
* <p>用于判断产品是否需要监控设备离线状态
|
||||
*
|
||||
* @param productKey 产品唯一标识
|
||||
* @return true表示配置了离线周期,false表示未配置
|
||||
*/
|
||||
protected boolean offlineThreshold(String productKey) {
|
||||
return iotProductDeviceService.offlineThreshold(productKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询产品的消息订阅URL列表
|
||||
@@ -185,30 +208,6 @@ public abstract class AbstratIoTService {
|
||||
return instanceBO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询产品的协议解码定义
|
||||
*
|
||||
* <p>获取产品配置的协议解码规则,用于消息的编解码处理
|
||||
*
|
||||
* @param productKey 产品唯一标识
|
||||
* @return 协议支持定义,包含解码规则和配置
|
||||
*/
|
||||
protected ProtocolSupportDefinition selectProtocolDef(String productKey) {
|
||||
return iotProductDeviceService.selectProtocolDef(productKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询产品的协议解码定义(不带超长脚本)
|
||||
*
|
||||
* <p>获取产品配置的协议解码规则,用于消息的编解码处理
|
||||
*
|
||||
* @param productKey 产品唯一标识
|
||||
* @return 协议支持定义,包含解码规则和配置
|
||||
*/
|
||||
protected ProtocolSupportDefinition selectProtocolDefNoScript(String productKey) {
|
||||
return iotProductDeviceService.selectProtocolDefNoScript(productKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存设备上行日志和影子数据
|
||||
*
|
||||
@@ -231,73 +230,8 @@ public abstract class AbstratIoTService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编解码插件提供者
|
||||
*
|
||||
* <p>根据支持类型获取对应的协议编解码实现,支持: - jar: Java插件模式 - jscript: JavaScript脚本模式 - magic: 魔法字节模式
|
||||
*
|
||||
* @param supportType 支持类型
|
||||
* @return 协议编解码支持实例,如果类型不支持返回null
|
||||
*/
|
||||
protected ProtocolCodecSupport getProtocolCodecProvider(String supportType) {
|
||||
if (supportType == null) {
|
||||
return null;
|
||||
}
|
||||
if (supportType.equalsIgnoreCase("jar")) {
|
||||
return ProtocolCodecJar.getInstance();
|
||||
} else if (supportType.equalsIgnoreCase("jscript")) {
|
||||
return ProtocolCodecJscript.getInstance();
|
||||
} else if (supportType.equalsIgnoreCase("magic")) {
|
||||
return ProtocolCodecMagic.getInstance();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 透传上层应用原始报文AES加密
|
||||
*
|
||||
* <p>使用设备唯一标识作为密钥对原始报文进行AES加密 密钥生成规则:使用iotId的MD5值作为密钥
|
||||
*
|
||||
* @param payload 原始报文内容
|
||||
* @param iotId 设备唯一标识
|
||||
* @return 加密后的报文
|
||||
*/
|
||||
protected String playloadEncode(String payload, String iotId) {
|
||||
return AESOperator.getInstance()
|
||||
.encrypt(payload, DigestUtil.md5Hex16(iotId), DigestUtil.md5Hex(iotId));
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
*
|
||||
* <p>使用设备唯一标识作为密钥对加密报文进行AES解密 密钥生成规则:使用iotId的MD5值作为密钥
|
||||
*
|
||||
* @param payload 加密的报文内容
|
||||
* @param iotId 设备唯一标识
|
||||
* @return 解密后的原始报文
|
||||
*/
|
||||
protected String playloadDecode(String payload, String iotId) {
|
||||
return AESOperator.getInstance()
|
||||
.decrypt(payload, DigestUtil.md5Hex16(iotId), DigestUtil.md5Hex(iotId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据设备序列号查询设备信息
|
||||
*
|
||||
* <p>通过设备序列号获取设备的完整信息,包括设备状态、配置等
|
||||
*
|
||||
* @param deviceId 设备序列号
|
||||
* @return 设备实例信息
|
||||
*/
|
||||
public IoTDeviceDTO getIotDeviceByDeviceIdLimitOne(String deviceId) {
|
||||
IoTDeviceDTO devInstance =
|
||||
iotDeviceService.lifeCycleDevInstance(IoTDeviceQuery.builder().deviceId(deviceId).build());
|
||||
return devInstance;
|
||||
}
|
||||
|
||||
protected <R> List<R> decode(
|
||||
String productKey, String payload, Object context, Class<R> elementType) {
|
||||
// 使用新的统一编解码服务
|
||||
return codecService.decode(productKey, payload, context, elementType);
|
||||
}
|
||||
|
||||
@@ -309,6 +243,14 @@ public abstract class AbstratIoTService {
|
||||
return decode(productKey, payload, null, UPRequest.class);
|
||||
}
|
||||
|
||||
protected String encode(String productKey, String payload, Object context) {
|
||||
return codecService.encode(productKey, payload, context);
|
||||
}
|
||||
|
||||
protected String encode(String productKey, String payload) {
|
||||
return encode(productKey, payload, null);
|
||||
}
|
||||
|
||||
protected void buildCodecNotNullBean(
|
||||
BaseUPRequest hasData, BaseUPRequest.BaseUPRequestBuilder builder) {
|
||||
if (CollUtil.isNotEmpty(hasData.getProperties())) {
|
||||
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.dm.device.service;
|
||||
|
||||
import cn.universal.core.message.UPRequest;
|
||||
import cn.universal.core.protocol.support.ProtocolCodecSupport.CodecMethod;
|
||||
import cn.universal.core.service.ICodecService;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 编解码服务使用示例
|
||||
*
|
||||
* <p>展示如何使用统一的编解码服务进行各种编解码操作
|
||||
*
|
||||
* @version 1.0 @Author Aleo
|
||||
* @since 2025/01/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CodecServiceExample {
|
||||
|
||||
@Autowired private ICodecService codecService;
|
||||
|
||||
/**
|
||||
* 示例:解码设备上报数据
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param payload 原始数据
|
||||
* @return 解码后的UPRequest列表
|
||||
*/
|
||||
public List<UPRequest> decodeDeviceData(String productKey, String payload) {
|
||||
log.info("开始解码设备数据: productKey={}, payload={}", productKey, payload);
|
||||
|
||||
// 使用统一的解码服务
|
||||
List<UPRequest> result = codecService.decode(productKey, payload);
|
||||
|
||||
log.info("解码完成: 结果数量={}", result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:编码下行指令
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param payload 原始指令数据
|
||||
* @return 编码后的指令
|
||||
*/
|
||||
public String encodeDownCommand(String productKey, String payload) {
|
||||
log.info("开始编码下行指令: productKey={}, payload={}", productKey, payload);
|
||||
|
||||
// 使用统一的编码服务
|
||||
String result = codecService.encode(productKey, payload);
|
||||
|
||||
log.info("编码完成: result={}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:预解码TCP数据
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param payload 原始TCP数据
|
||||
* @return 预解码后的UPRequest
|
||||
*/
|
||||
public UPRequest preDecodeTcpData(String productKey, String payload) {
|
||||
log.info("开始预解码TCP数据: productKey={}, payload={}", productKey, payload);
|
||||
|
||||
// 使用统一的预解码服务
|
||||
UPRequest result = codecService.preDecode(productKey, payload);
|
||||
|
||||
log.info("预解码完成: result={}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:通用编解码方法
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param payload 原始数据
|
||||
* @param codecMethod 编解码方法类型
|
||||
* @return 编解码结果
|
||||
*/
|
||||
public String generalCodec(String productKey, String payload, CodecMethod codecMethod) {
|
||||
log.info("开始通用编解码: productKey={}, payload={}, method={}", productKey, payload, codecMethod);
|
||||
|
||||
// 使用统一的通用编解码服务
|
||||
String result = codecService.codec(productKey, payload, codecMethod);
|
||||
|
||||
log.info("通用编解码完成: result={}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:检查是否支持特定编解码方法
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param codecMethod 编解码方法类型
|
||||
* @return 是否支持
|
||||
*/
|
||||
public boolean checkSupport(String productKey, CodecMethod codecMethod) {
|
||||
boolean supported = codecService.isSupported(productKey, codecMethod);
|
||||
log.info("检查编解码支持: productKey={}, method={}, supported={}", productKey, codecMethod, supported);
|
||||
return supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:泛型解码
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param payload 原始数据
|
||||
* @param elementType 目标类型
|
||||
* @param <T> 泛型类型
|
||||
* @return 解码后的对象列表
|
||||
*/
|
||||
public <T> List<T> decodeWithType(String productKey, String payload, Class<T> elementType) {
|
||||
log.info("开始泛型解码: productKey={}, payload={}, elementType={}", productKey, payload, elementType);
|
||||
|
||||
// 使用统一的泛型解码服务
|
||||
List<T> result = codecService.decode(productKey, payload, elementType);
|
||||
|
||||
log.info("泛型解码完成: 结果数量={}", result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:IoT到第三方数据转换
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param payload 原始数据
|
||||
* @return 转换后的数据
|
||||
*/
|
||||
public String iotToYour(String productKey, String payload) {
|
||||
log.info("开始IoT到第三方数据转换: productKey={}, payload={}", productKey, payload);
|
||||
|
||||
// 使用统一的IoT到第三方转换服务
|
||||
String result = codecService.iotToYour(productKey, payload);
|
||||
|
||||
log.info("IoT到第三方数据转换完成: result={}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:第三方到IoT数据转换
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @param payload 原始数据
|
||||
* @return 转换后的数据
|
||||
*/
|
||||
public String yourToIot(String productKey, String payload) {
|
||||
log.info("开始第三方到IoT数据转换: productKey={}, payload={}", productKey, payload);
|
||||
|
||||
// 使用统一的第三方到IoT转换服务
|
||||
String result = codecService.yourToIot(productKey, payload);
|
||||
|
||||
log.info("第三方到IoT数据转换完成: result={}", result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -60,7 +60,8 @@ public class DashboardStatisticsTask implements ApplicationRunner {
|
||||
|
||||
// 如果本地缓存中没有平台数据,使用默认的平台列表
|
||||
if (platforms.isEmpty()) {
|
||||
platforms = Arrays.asList("ctaiot", "lvzhou", "ezviz", "onenet", "lechen", "tcp", "snitcp");
|
||||
platforms =
|
||||
Arrays.asList("ctaiot", "lvzhou", "ezviz", "onenet", "imoulife", "tcp", "snitcp");
|
||||
log.warn("[仪表盘统计] 本地缓存中无活跃平台,使用默认平台列表");
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ public class DashboardStatisticsTask implements ApplicationRunner {
|
||||
} catch (Exception e) {
|
||||
log.error("[仪表盘统计] 获取活跃平台失败", e);
|
||||
// 异常时返回默认平台列表
|
||||
return Arrays.asList("ctaiot", "lvzhou", "ezviz", "onenet", "lechen", "tcp", "snitcp");
|
||||
return Arrays.asList("ctaiot", "lvzhou", "ezviz", "onenet", "imoulife", "tcp", "snitcp");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -162,7 +162,7 @@ public abstract class IoTUPPushAdapter<T extends BaseUPRequest> {
|
||||
|
||||
String applicationId = deviceDTO.getApplicationId();
|
||||
if (applicationId == null || applicationId.trim().isEmpty()) {
|
||||
log.warn("[分组] 应用ID为空,设备ID: {}, 丢弃消息", deviceDTO.getIotId());
|
||||
log.warn("[分组] 应用ID为空,设备ID: {}, 丢弃消息", deviceDTO.getDeviceId());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
+3
-4
@@ -38,6 +38,7 @@ import cn.universal.persistence.mapper.IoTDeviceMapper;
|
||||
import cn.universal.persistence.mapper.IoTUserMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -444,14 +445,12 @@ public class IoTDeviceActionAfterService extends IoTUPPushAdapter<BaseUPRequest>
|
||||
.deviceId(instanceBO.getDeviceId())
|
||||
.deviceName(instanceBO.getDeviceName())
|
||||
.productKey(instanceBO.getProductKey())
|
||||
// .extDeviceId(instanceBO.getExtDeviceId())
|
||||
.messageType(MessageType.FUNCTIONS.name())
|
||||
.iotId(instanceBO.getIotId())
|
||||
.createId(instanceBO.getUserUnionId())
|
||||
.createTime(DateUtil.currentSeconds())
|
||||
.createTime(LocalDateTime.now())
|
||||
.commandStatus(0)
|
||||
// 使用此字段作为校验标识
|
||||
.commandId(StrUtil.length(commandId) > 10 ? StrUtil.sub(commandId, 0, 10) : commandId)
|
||||
.commandId(commandId)
|
||||
.build();
|
||||
return ioTDeviceLog;
|
||||
}
|
||||
|
||||
+5
@@ -44,6 +44,7 @@ public class IoTProductActionService implements IoTProductAction {
|
||||
"iot_dev_action",
|
||||
"selectDevCount",
|
||||
"iot_dev_product_list",
|
||||
"selectDevProductV4List",
|
||||
"iot_product_device"
|
||||
},
|
||||
allEntries = true)
|
||||
@@ -62,6 +63,7 @@ public class IoTProductActionService implements IoTProductAction {
|
||||
"iot_dev_action",
|
||||
"selectDevCount",
|
||||
"iot_dev_product_list",
|
||||
"selectDevProductV4List",
|
||||
"iot_product_device"
|
||||
},
|
||||
allEntries = true)
|
||||
@@ -76,6 +78,7 @@ public class IoTProductActionService implements IoTProductAction {
|
||||
"iot_dev_action",
|
||||
"selectDevCount",
|
||||
"iot_dev_product_list",
|
||||
"selectDevProductV4List",
|
||||
"iot_product_device"
|
||||
},
|
||||
allEntries = true)
|
||||
@@ -99,6 +102,7 @@ public class IoTProductActionService implements IoTProductAction {
|
||||
"iot_dev_action",
|
||||
"selectDevCount",
|
||||
"iot_dev_product_list",
|
||||
"selectDevProductV4List",
|
||||
"iot_product_device"
|
||||
},
|
||||
allEntries = true)
|
||||
@@ -116,6 +120,7 @@ public class IoTProductActionService implements IoTProductAction {
|
||||
"iot_dev_action",
|
||||
"selectDevCount",
|
||||
"iot_dev_product_list",
|
||||
"selectDevProductV4List",
|
||||
"iot_product_device"
|
||||
},
|
||||
allEntries = true)
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.dm.device.service.codec;
|
||||
|
||||
import cn.universal.core.message.UPRequest;
|
||||
import cn.universal.core.service.ICodec;
|
||||
import cn.universal.core.service.ICodecService;
|
||||
import cn.universal.dm.device.service.AbstratIoTService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @version 1.0 @Author Aleo
|
||||
* @since 2023/6/20
|
||||
*/
|
||||
@Service("codecImpl")
|
||||
@Slf4j
|
||||
public class CodecImpl extends AbstratIoTService implements ICodec {
|
||||
|
||||
@Autowired private ICodecService codecService;
|
||||
|
||||
@Override
|
||||
public UPRequest preDecode(String productKey, String message) {
|
||||
// 使用新的统一编解码服务
|
||||
return codecService.preDecode(productKey, message);
|
||||
}
|
||||
}
|
||||
+1
@@ -43,6 +43,7 @@ public class IoTCacheRemoveService {
|
||||
"iot_dev_shadow_bo",
|
||||
"iot_dev_action",
|
||||
"iot_dev_product_list",
|
||||
"selectDevProductV4List",
|
||||
"selectDevCount"
|
||||
},
|
||||
allEntries = true)
|
||||
|
||||
+114
-66
@@ -43,35 +43,33 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
||||
/**
|
||||
* 设备和产品鉴权
|
||||
*
|
||||
* @version 1.0 @Author Aleo
|
||||
* @since 2025/8/12 16:10
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IoTDeviceService {
|
||||
|
||||
@Resource
|
||||
private SupportMapAreasMapper supportMapAreasMapper;
|
||||
@Resource
|
||||
private IoTDeviceMapper ioTDeviceMapper;
|
||||
@Resource private SupportMapAreasMapper supportMapAreasMapper;
|
||||
@Resource private IoTDeviceMapper ioTDeviceMapper;
|
||||
|
||||
@Resource
|
||||
private IoTDeviceTagsMapper ioTDeviceTagsMapper;
|
||||
@Resource private IoTDeviceTagsMapper ioTDeviceTagsMapper;
|
||||
|
||||
@Resource
|
||||
private IoTDeviceShadowMapper ioTDeviceShadowMapper;
|
||||
@Resource private IoTDeviceShadowMapper ioTDeviceShadowMapper;
|
||||
|
||||
@Resource
|
||||
private IoTDeviceSubscribeMapper ioTDeviceSubscribeMapper;
|
||||
@Resource
|
||||
private IoTUserApplicationMapper iotUserApplicationMapper;
|
||||
@Resource
|
||||
private IoTDeviceFenceRelMapper ioTDeviceFenceRelMapper;
|
||||
@Resource private IoTDeviceSubscribeMapper ioTDeviceSubscribeMapper;
|
||||
@Resource private IoTUserApplicationMapper iotUserApplicationMapper;
|
||||
@Resource private IoTDeviceFenceRelMapper ioTDeviceFenceRelMapper;
|
||||
|
||||
@Resource
|
||||
private IoTCacheRemoveService iotCacheRemoveService;
|
||||
@Resource private IoTCacheRemoveService iotCacheRemoveService;
|
||||
|
||||
public Page<IoTDeviceVO> apiDeviceList(IoTAPIQuery iotAPIQuery) {
|
||||
Page<IoTDeviceVO> page = PageHelper.startPage(iotAPIQuery.getPage(), iotAPIQuery.getSize());
|
||||
@@ -79,12 +77,21 @@ public class IoTDeviceService {
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备绑定应用 对外API
|
||||
*/
|
||||
/** 设备绑定应用 对外API */
|
||||
@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 apiAppBind(String appid, String iotId) {
|
||||
IoTUserApplication iotUserApplication = iotUserApplicationMapper.selectIotUserApplicationByAppId(
|
||||
appid);
|
||||
IoTUserApplication iotUserApplication =
|
||||
iotUserApplicationMapper.selectIotUserApplicationByAppId(appid);
|
||||
if (iotUserApplication == null) {
|
||||
throw new IoTException("应用不存在");
|
||||
}
|
||||
@@ -92,9 +99,18 @@ public class IoTDeviceService {
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备解绑应用 对外API
|
||||
*/
|
||||
/** 设备解绑应用 对外API */
|
||||
@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 apiAppUnBind(String iotId) {
|
||||
if (StrUtil.isBlank(iotId)) {
|
||||
throw new IoTException("设备编号不能为空");
|
||||
@@ -102,16 +118,17 @@ public class IoTDeviceService {
|
||||
return ioTDeviceMapper.apiUnBindApp(iotId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备信息
|
||||
*/
|
||||
/** 更新设备信息 */
|
||||
public Map<String, Object> apiUpdateDevInfo(IoTAPIQuery iotAPIQuery) {
|
||||
IoTDevice instance = IoTDevice.builder().creatorId(iotAPIQuery.getIotUnionId())
|
||||
.iotId(iotAPIQuery.getIotId()).build();
|
||||
IoTDevice instance =
|
||||
IoTDevice.builder()
|
||||
.creatorId(iotAPIQuery.getIotUnionId())
|
||||
.iotId(iotAPIQuery.getIotId())
|
||||
.build();
|
||||
instance = ioTDeviceMapper.selectOne(instance);
|
||||
if (instance == null) {
|
||||
throw new IoTException(IoTErrorCode.DEV_NOT_FIND.getName(),
|
||||
IoTErrorCode.DEV_NOT_FIND.getCode());
|
||||
throw new IoTException(
|
||||
IoTErrorCode.DEV_NOT_FIND.getName(), IoTErrorCode.DEV_NOT_FIND.getCode());
|
||||
}
|
||||
if (StrUtil.isNotBlank(iotAPIQuery.getDeviceName())) {
|
||||
instance.setDeviceName(iotAPIQuery.getDeviceName());
|
||||
@@ -119,18 +136,18 @@ public class IoTDeviceService {
|
||||
if (StrUtil.isNotBlank(iotAPIQuery.getDetail())) {
|
||||
instance.setDetail(iotAPIQuery.getDetail());
|
||||
}
|
||||
if (StrUtil.isNotBlank(iotAPIQuery.getLatitude()) && StrUtil.isNotBlank(
|
||||
iotAPIQuery.getLongitude())) {
|
||||
if (StrUtil.isNotBlank(iotAPIQuery.getLatitude())
|
||||
&& StrUtil.isNotBlank(iotAPIQuery.getLongitude())) {
|
||||
|
||||
instance.setCoordinate(
|
||||
StrUtil.join(",", iotAPIQuery.getLongitude(), iotAPIQuery.getLatitude()));
|
||||
|
||||
// 更新设备所属经纬度
|
||||
SupportMapAreas supportMapAreas = supportMapAreasMapper.selectMapAreas(
|
||||
iotAPIQuery.getLongitude(), iotAPIQuery.getLatitude());
|
||||
SupportMapAreas supportMapAreas =
|
||||
supportMapAreasMapper.selectMapAreas(
|
||||
iotAPIQuery.getLongitude(), iotAPIQuery.getLatitude());
|
||||
if (supportMapAreas == null) {
|
||||
log.info("查询区域id为空,lot={},lat={}", iotAPIQuery.getLongitude(),
|
||||
iotAPIQuery.getLatitude());
|
||||
log.info("查询区域id为空,lot={},lat={}", iotAPIQuery.getLongitude(), iotAPIQuery.getLatitude());
|
||||
} else {
|
||||
instance.setAreasId(supportMapAreas.getId());
|
||||
}
|
||||
@@ -144,20 +161,31 @@ public class IoTDeviceService {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Cacheable(cacheNames = "selectDevCount", unless = "#result == null", keyGenerator = "redisKeyGenerate")
|
||||
@Cacheable(
|
||||
cacheNames = "selectDevCount",
|
||||
unless = "#result == null",
|
||||
keyGenerator = "redisKeyGenerate")
|
||||
public boolean selectDevCount(IoTAPIQuery iotAPIQuery) {
|
||||
if (iotAPIQuery == null) {
|
||||
throw new IoTException("参数不能为空");
|
||||
}
|
||||
IoTDevice instance = IoTDevice.builder().creatorId(iotAPIQuery.getIotUnionId())
|
||||
.deviceId(iotAPIQuery.getDeviceId()).application(iotAPIQuery.getApplicationId())
|
||||
.productKey(iotAPIQuery.getProductKey()).iotId(iotAPIQuery.getIotId()).build();
|
||||
IoTDevice instance =
|
||||
IoTDevice.builder()
|
||||
.creatorId(iotAPIQuery.getIotUnionId())
|
||||
.deviceId(iotAPIQuery.getDeviceId())
|
||||
.application(iotAPIQuery.getApplicationId())
|
||||
.productKey(iotAPIQuery.getProductKey())
|
||||
.iotId(iotAPIQuery.getIotId())
|
||||
.build();
|
||||
|
||||
int count = ioTDeviceMapper.selectCount(instance);
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
@Cacheable(cacheNames = "iot_dev_action", unless = "#result == null", keyGenerator = "redisKeyGenerate")
|
||||
@Cacheable(
|
||||
cacheNames = "iot_dev_action",
|
||||
unless = "#result == null",
|
||||
keyGenerator = "redisKeyGenerate")
|
||||
public IoTDeviceDTO lifeCycleDevInstance(IoTDeviceQuery query) {
|
||||
if (query == null || query.emptyParams()) {
|
||||
log.warn("no query condition");
|
||||
@@ -177,19 +205,24 @@ public class IoTDeviceService {
|
||||
*
|
||||
* <p>productKey 产品唯一编号
|
||||
*/
|
||||
@Cacheable(cacheNames = "iot_dev_instance_bo", unless = "#result == null", keyGenerator = "redisKeyGenerate")
|
||||
@Cacheable(
|
||||
cacheNames = "iot_dev_instance_bo",
|
||||
unless = "#result == null",
|
||||
keyGenerator = "redisKeyGenerate")
|
||||
public IoTDeviceDTO selectDevInstanceBO(Map<String, Object> map) {
|
||||
if (map == null || map.isEmpty()) {
|
||||
throw new IoTException("deviceId can not be null");
|
||||
}
|
||||
IoTDeviceDTO ioTDeviceDTO = ioTDeviceMapper.selectIoTDeviceBO(map);
|
||||
if (ioTDeviceDTO != null) {
|
||||
}
|
||||
if (ioTDeviceDTO != null) {}
|
||||
|
||||
return ioTDeviceDTO;
|
||||
}
|
||||
|
||||
@MultiLevelCacheable(cacheNames = "apiIoTDeviceVOInfo", unless = "#result == null", keyGenerator = "redisKeyGenerate")
|
||||
@MultiLevelCacheable(
|
||||
cacheNames = "apiIoTDeviceVOInfo",
|
||||
unless = "#result == null",
|
||||
keyGenerator = "redisKeyGenerate")
|
||||
public IoTDeviceVO apiIoTDeviceVOInfo(IoTAPIQuery query) {
|
||||
IoTDeviceVO ioTDeviceVO = ioTDeviceMapper.apiDeviceInfo(query);
|
||||
return ioTDeviceVO;
|
||||
@@ -221,8 +254,10 @@ public class IoTDeviceService {
|
||||
*
|
||||
* <p>productKey 产品唯一编号
|
||||
*/
|
||||
@Cacheable(cacheNames = "iot_dev_instance_bo", unless = "#result == null", key =
|
||||
"'selectDevInstanceBO" + ":'+#productKey+#deviceId")
|
||||
@Cacheable(
|
||||
cacheNames = "iot_dev_instance_bo",
|
||||
unless = "#result == null",
|
||||
key = "'selectDevInstanceBO" + ":'+#productKey+#deviceId")
|
||||
public IoTDeviceDTO selectDevInstanceBO(String productKey, String deviceId) {
|
||||
if (StrUtil.isEmpty(productKey) || StrUtil.isEmpty(deviceId)) {
|
||||
throw new IoTException("productKey or deviceId can not be null");
|
||||
@@ -237,7 +272,10 @@ public class IoTDeviceService {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Cacheable(cacheNames = "iot_dev_instance_bo", unless = "#result == null", key = "''+#productKey+#deviceId")
|
||||
@Cacheable(
|
||||
cacheNames = "iot_dev_instance_bo",
|
||||
unless = "#result == null",
|
||||
key = "''+#productKey+#deviceId")
|
||||
public IoTDevice selectDevInstance(String productKey, String deviceId) {
|
||||
if (StrUtil.isBlank(productKey) || StrUtil.isBlank(deviceId)) {
|
||||
return null;
|
||||
@@ -267,8 +305,9 @@ public class IoTDeviceService {
|
||||
if (StrUtil.isBlank(iotId)) {
|
||||
throw new IoTException("iotId can not be null");
|
||||
}
|
||||
IoTDeviceDTO ioTDeviceDTO = ioTDeviceMapper.selectIoTDeviceBO(
|
||||
BeanUtil.beanToMap(IoTAPIQuery.builder().iotId(iotId).build()));
|
||||
IoTDeviceDTO ioTDeviceDTO =
|
||||
ioTDeviceMapper.selectIoTDeviceBO(
|
||||
BeanUtil.beanToMap(IoTAPIQuery.builder().iotId(iotId).build()));
|
||||
return ioTDeviceDTO;
|
||||
}
|
||||
|
||||
@@ -278,9 +317,7 @@ public class IoTDeviceService {
|
||||
return metadataBO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 extDeviceId 删除设备信息
|
||||
*/
|
||||
/** 根据 extDeviceId 删除设备信息 */
|
||||
@Transactional
|
||||
public int delDevInstance(String iotId) {
|
||||
if (StrUtil.isBlank(iotId)) {
|
||||
@@ -290,11 +327,16 @@ public class IoTDeviceService {
|
||||
int dev = ioTDeviceMapper.delete(IoTDevice.builder().iotId(iotId).build());
|
||||
int tags = ioTDeviceTagsMapper.delete(IoTDeviceTags.builder().iotId(iotId).build());
|
||||
int sha = ioTDeviceShadowMapper.delete(IoTDeviceShadow.builder().iotId(iotId).build());
|
||||
int subscribe = ioTDeviceSubscribeMapper.delete(
|
||||
IoTDeviceSubscribe.builder().iotId(iotId).build());
|
||||
int subscribe =
|
||||
ioTDeviceSubscribeMapper.delete(IoTDeviceSubscribe.builder().iotId(iotId).build());
|
||||
int fence = ioTDeviceFenceRelMapper.deleteFenceInstance(iotId);
|
||||
log.info("删除设备 tag={},platform={},shadow={},subscribe={},fence={}", tags, dev, sha,
|
||||
subscribe, fence);
|
||||
log.info(
|
||||
"删除设备 tag={},platform={},shadow={},subscribe={},fence={}",
|
||||
tags,
|
||||
dev,
|
||||
sha,
|
||||
subscribe,
|
||||
fence);
|
||||
iotCacheRemoveService.removeDevProtocolCache();
|
||||
return dev;
|
||||
}
|
||||
@@ -302,13 +344,19 @@ public class IoTDeviceService {
|
||||
public void addDevHistory(String iotId) {
|
||||
IoTDevice ioTDevice = ioTDeviceMapper.selectOne(IoTDevice.builder().iotId(iotId).build());
|
||||
if (ioTDevice != null && ioTDevice.getRegistryTime() != null) {
|
||||
IoTDeviceHistoryBO ioTDeviceHistoryBO = IoTDeviceHistoryBO.builder()
|
||||
.deviceId(ioTDevice.getDeviceId()).deviceName(ioTDevice.getDeviceName())
|
||||
.productKey(ioTDevice.getProductKey())
|
||||
.firstOnlineTime(ioTDevice.getRegistryTime().longValue())
|
||||
.creater(ioTDevice.getCreatorId()).createTime(ioTDevice.getCreateTime())
|
||||
.deleteTime(System.currentTimeMillis()).coordinate(ioTDevice.getCoordinate()).build();
|
||||
// ioTDeviceMapper.insertDevInstanceHistory(ioTDeviceHistoryBO);
|
||||
IoTDeviceHistoryBO ioTDeviceHistoryBO =
|
||||
IoTDeviceHistoryBO.builder()
|
||||
.deviceId(ioTDevice.getDeviceId())
|
||||
.deviceName(ioTDevice.getDeviceName())
|
||||
.productKey(ioTDevice.getProductKey())
|
||||
.firstOnlineTime(ioTDevice.getRegistryTime().longValue())
|
||||
.creater(ioTDevice.getCreatorId())
|
||||
.createTime(ioTDevice.getCreateTime())
|
||||
.deleteTime(System.currentTimeMillis())
|
||||
.coordinate(ioTDevice.getCoordinate())
|
||||
.build();
|
||||
// ioTDeviceMapper.insertDevInstanceHistory(ioTDeviceHistoryBO);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+255
-66
@@ -41,10 +41,13 @@ import java.util.concurrent.TimeUnit;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备影子处理
|
||||
*
|
||||
* @version 1.0 @Author Aleo
|
||||
* @since 2025/9/17
|
||||
*/
|
||||
@@ -67,14 +70,128 @@ public class IoTDeviceShadowService {
|
||||
// 注入优化版服务
|
||||
@Resource private IoTDeviceShadowServiceOptimized ioTDeviceShadowServiceOptimized;
|
||||
|
||||
public IoTDeviceShadow getDeviceShadow(String iotId) {
|
||||
return ioTDeviceShadowMapper.getDeviceShadow(iotId);
|
||||
@Value("${shadow.cache.enabled:true}")
|
||||
private boolean shadowCacheEnabled;
|
||||
|
||||
@Value("${shadow.cache.key-prefix:shadow}")
|
||||
private String shadowKeyPrefix;
|
||||
|
||||
@Value("${shadow.cache.ttl-seconds:604800}") // 7 days
|
||||
private long shadowCacheTtlSeconds;
|
||||
|
||||
@Value("${shadow.flush.zset-key:shadow:flush}")
|
||||
private String shadowFlushZsetKey;
|
||||
|
||||
@Value("${shadow.flush.base-interval-ms:7200000}") // 24h
|
||||
private long flushBaseIntervalMs;
|
||||
|
||||
@Value("${shadow.flush.jitter-rate:0.2}") // ±20%
|
||||
private double flushJitterRate;
|
||||
|
||||
// 强制刷盘配置 - 使用默认值,不增加环境配置
|
||||
private static final long MAX_DELAY_MS = 86400000L; // 24小时最大延迟
|
||||
private static final long MIN_INTERVAL_MS = 3600000L; // 最小间隔:1小时
|
||||
private static final int VERSION_THRESHOLD = 10; // 版本号阈值:10
|
||||
private static final long FORCE_FLUSH_DELAY_MS = 30000L; // 强制刷盘延迟:30秒(确保能被5分钟扫描间隔捕获)
|
||||
|
||||
private String buildShadowKey(String iotId) {
|
||||
return shadowKeyPrefix + ":" + iotId;
|
||||
}
|
||||
|
||||
private Long calcNextFlushAtMs(String iotId) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// 获取当前已设置的刷盘时间
|
||||
Double currentScore = stringRedisTemplate.opsForZSet().score(shadowFlushZsetKey, iotId);
|
||||
|
||||
if (currentScore != null) {
|
||||
long currentFlushTime = currentScore.longValue();
|
||||
long timeSinceLastFlush = now - (currentFlushTime - flushBaseIntervalMs);
|
||||
|
||||
// 1. 最大延迟时间检查
|
||||
if (timeSinceLastFlush > MAX_DELAY_MS) {
|
||||
log.info("[ShadowFlush] 最大延迟强制刷盘: iotId={}, 延迟时间={}ms", iotId, timeSinceLastFlush);
|
||||
return now + FORCE_FLUSH_DELAY_MS;
|
||||
}
|
||||
|
||||
// 2. 最小间隔检查
|
||||
if (timeSinceLastFlush >= MIN_INTERVAL_MS) {
|
||||
log.info("[ShadowFlush] 最小间隔强制刷盘: iotId={}, 间隔时间={}ms", iotId, timeSinceLastFlush);
|
||||
return now + FORCE_FLUSH_DELAY_MS;
|
||||
}
|
||||
|
||||
// 3. 版本号检查
|
||||
if (checkVersionThreshold(iotId)) {
|
||||
log.info("[ShadowFlush] 版本号强制刷盘: iotId={}", iotId);
|
||||
return now + FORCE_FLUSH_DELAY_MS;
|
||||
}
|
||||
|
||||
// 如果当前刷盘时间还没到,保持原时间
|
||||
if (currentFlushTime > now) {
|
||||
return currentFlushTime;
|
||||
}
|
||||
}
|
||||
|
||||
// 正常计算新的刷盘时间
|
||||
double jitter = (Math.random() * 2 * flushJitterRate) - flushJitterRate;
|
||||
long offset = (long) (flushBaseIntervalMs * (1 + jitter));
|
||||
return now + Math.max(0L, offset);
|
||||
}
|
||||
|
||||
private Shadow readShadowFromCache(String iotId) {
|
||||
if (!shadowCacheEnabled) {
|
||||
return null;
|
||||
}
|
||||
final String json = buildAndGetFromRedis(iotId);
|
||||
if (json == null) return null;
|
||||
try {
|
||||
return JSONUtil.toBean(json, Shadow.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("[Shadow][Cache] parse error iotId={}, err={}", iotId, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String buildAndGetFromRedis(String iotId) {
|
||||
String key = buildShadowKey(iotId);
|
||||
String json = stringRedisTemplate.opsForValue().get(key);
|
||||
if (StrUtil.isBlank(json)) {
|
||||
return null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
private void writeShadowToCache(String iotId, Shadow shadow) {
|
||||
if (!shadowCacheEnabled || shadow == null) {
|
||||
return;
|
||||
}
|
||||
String key = buildShadowKey(iotId);
|
||||
stringRedisTemplate
|
||||
.opsForValue()
|
||||
.set(key, JSONUtil.toJsonStr(shadow), shadowCacheTtlSeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void markShadowDirtyForFlush(String iotId) {
|
||||
if (!shadowCacheEnabled) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Long nextAt = calcNextFlushAtMs(iotId);
|
||||
stringRedisTemplate.opsForZSet().add(shadowFlushZsetKey, iotId, nextAt.doubleValue());
|
||||
} catch (Exception e) {
|
||||
log.warn("[Shadow][Flush] mark dirty failed iotId={}, err={}", iotId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject getDeviceShadowObj(String productKey, String deviceId) {
|
||||
if (StrUtil.isBlank(productKey) || StrUtil.isBlank(deviceId)) {
|
||||
return null;
|
||||
}
|
||||
String s = buildAndGetFromRedis(productKey + deviceId);
|
||||
// 缓存拿到,直接返回缓存的
|
||||
if (StrUtil.isNotBlank(s)) {
|
||||
return JSONUtil.parseObj(s);
|
||||
}
|
||||
String shadowMetadata = ioTDeviceShadowMapper.getShadowMetadata(productKey, deviceId);
|
||||
if (StrUtil.isBlank(shadowMetadata)) {
|
||||
return null;
|
||||
@@ -82,20 +199,34 @@ public class IoTDeviceShadowService {
|
||||
return JSONUtil.parseObj(shadowMetadata);
|
||||
}
|
||||
|
||||
// @Cacheable(cacheNames = "iot_dev_shadow_bo", key = "''+#iotId", unless = "#result==null")
|
||||
/** 格式化的属性 */
|
||||
public List<IoTDevicePropertiesBO> getDevState(String iotId) {
|
||||
List<IoTDevicePropertiesBO> result = new ArrayList<>();
|
||||
IoTDeviceShadow ioTDeviceShadow =
|
||||
ioTDeviceShadowMapper.selectOne(IoTDeviceShadow.builder().iotId(iotId).build());
|
||||
if (ioTDeviceShadow == null) {
|
||||
// throw new IoTException("设备=[" + iotId + "]影子不存在,清检查");
|
||||
log.warn("设备=[" + iotId + "]影子不存在,清检查");
|
||||
return result;
|
||||
}
|
||||
Shadow shadow = JSONUtil.toBean(ioTDeviceShadow.getMetadata(), Shadow.class);
|
||||
|
||||
JSONObject properties = shadow.getState().getReported();
|
||||
JSONObject desireProperties = shadow.getState().getDesired();
|
||||
// Cache-first
|
||||
Shadow cached = readShadowFromCache(iotId);
|
||||
Shadow shadow = null;
|
||||
IoTDeviceShadow ioTDeviceShadow = null;
|
||||
if (cached != null) {
|
||||
shadow = cached;
|
||||
} else {
|
||||
ioTDeviceShadow =
|
||||
ioTDeviceShadowMapper.selectOne(IoTDeviceShadow.builder().iotId(iotId).build());
|
||||
if (ioTDeviceShadow == null) {
|
||||
log.warn("设备=[{}]影子不存在,清检查", iotId);
|
||||
return result;
|
||||
}
|
||||
shadow = JSONUtil.toBean(ioTDeviceShadow.getMetadata(), Shadow.class);
|
||||
// backfill cache
|
||||
writeShadowToCache(iotId, shadow);
|
||||
}
|
||||
JSONObject properties =
|
||||
shadow.getState() != null ? shadow.getState().getReported() : new JSONObject();
|
||||
JSONObject desireProperties =
|
||||
shadow.getState() != null ? shadow.getState().getDesired() : new JSONObject();
|
||||
Shadow finalShadow = shadow;
|
||||
JSONObject finalProperties = properties;
|
||||
JSONObject finalDesireProperties = desireProperties;
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("iotId", iotId);
|
||||
IoTDeviceDTO ioTDeviceDTO = ioTDeviceMapper.selectIoTDeviceBO(map);
|
||||
@@ -106,24 +237,32 @@ public class IoTDeviceShadowService {
|
||||
}
|
||||
LogStorePolicyDTO storePolicyDTO =
|
||||
iotProductDeviceService.getProductLogStorePolicy(ioTDeviceDTO.getProductKey());
|
||||
// 过滤没有上报过的属性
|
||||
propertyMetadataList.stream()
|
||||
.filter(s -> properties.get(s.getId()) != null)
|
||||
.filter(s -> finalProperties.get(s.getId()) != null)
|
||||
.forEach(
|
||||
s -> {
|
||||
Object obj = properties.get(s.getId());
|
||||
Object obj = finalProperties.get(s.getId());
|
||||
IoTDevicePropertiesBO entity = new IoTDevicePropertiesBO();
|
||||
entity.setDesireValue(desireProperties.get(s.getId()));
|
||||
Long ts =
|
||||
finalShadow.getMetadata() != null
|
||||
&& finalShadow.getMetadata().getReported() != null
|
||||
&& finalShadow.getMetadata().getReported().getJSONObject(s.getId())
|
||||
!= null
|
||||
? finalShadow
|
||||
.getMetadata()
|
||||
.getReported()
|
||||
.getJSONObject(s.getId())
|
||||
.getLong("timestamp")
|
||||
: DateUtil.currentSeconds();
|
||||
entity.setDesireValue(finalDesireProperties.get(s.getId()));
|
||||
entity.withValue(s.getValueType(), obj);
|
||||
entity.setPropertyName(s.getName());
|
||||
entity.setIotId(iotId);
|
||||
entity.setDeviceId(ioTDeviceDTO.getDeviceId());
|
||||
entity.setTimestamp(
|
||||
shadow.getMetadata().getReported().getJSONObject(s.getId()).getLong("timestamp"));
|
||||
entity.setTimestamp(ts);
|
||||
entity.setProperty(s.getId());
|
||||
entity.setStoragePolicy(storePolicyDTO.getProperties().containsKey(s.getId()));
|
||||
// 期望值
|
||||
if (desireProperties.get(s.getId()) != null) {
|
||||
if (finalDesireProperties.get(s.getId()) != null) {
|
||||
entity.setCustomized(IoTConstant.DEVICE_SHADOW_DESIRED_PROPERTY);
|
||||
}
|
||||
result.add(entity);
|
||||
@@ -132,26 +271,26 @@ public class IoTDeviceShadowService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 数据为空也返回结果 */
|
||||
/** 数据为空也返回结果 - cache first */
|
||||
public List<IoTDevicePropertiesBO> getDevStateWithNullResult(String iotId) {
|
||||
List<IoTDevicePropertiesBO> result = new ArrayList<>();
|
||||
IoTDeviceShadow ioTDeviceShadow =
|
||||
ioTDeviceShadowMapper.selectOne(IoTDeviceShadow.builder().iotId(iotId).build());
|
||||
Shadow shadow = null;
|
||||
JSONObject properties = null;
|
||||
JSONObject desireProperties = null;
|
||||
|
||||
if (ioTDeviceShadow == null) {
|
||||
// throw new IoTException("设备=[" + iotId + "]影子不存在,清检查");
|
||||
log.warn("设备=[" + iotId + "]影子不存在,清检查");
|
||||
shadow = Shadow.builder().build();
|
||||
properties = new JSONObject();
|
||||
desireProperties = new JSONObject();
|
||||
} else {
|
||||
shadow = JSONUtil.toBean(ioTDeviceShadow.getMetadata(), Shadow.class);
|
||||
properties = shadow.getState().getReported();
|
||||
desireProperties = shadow.getState().getDesired();
|
||||
Shadow shadow = readShadowFromCache(iotId);
|
||||
if (shadow == null) {
|
||||
IoTDeviceShadow ioTDeviceShadow =
|
||||
ioTDeviceShadowMapper.selectOne(IoTDeviceShadow.builder().iotId(iotId).build());
|
||||
if (ioTDeviceShadow != null && StrUtil.isNotBlank(ioTDeviceShadow.getMetadata())) {
|
||||
shadow = JSONUtil.toBean(ioTDeviceShadow.getMetadata(), Shadow.class);
|
||||
writeShadowToCache(iotId, shadow);
|
||||
} else {
|
||||
shadow = Shadow.builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject properties =
|
||||
shadow.getState() != null ? shadow.getState().getReported() : new JSONObject();
|
||||
JSONObject desireProperties =
|
||||
shadow.getState() != null ? shadow.getState().getDesired() : new JSONObject();
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("iotId", iotId);
|
||||
IoTDeviceDTO ioTDeviceDTO = ioTDeviceMapper.selectIoTDeviceBO(map);
|
||||
@@ -163,7 +302,6 @@ public class IoTDeviceShadowService {
|
||||
if (CollectionUtil.isEmpty(propertyMetadataList)) {
|
||||
return result;
|
||||
}
|
||||
// 过滤没有上报过的属性
|
||||
Shadow finalShadow = shadow;
|
||||
JSONObject finalProperties = properties;
|
||||
JSONObject finalDesireProperties = desireProperties;
|
||||
@@ -171,25 +309,26 @@ public class IoTDeviceShadowService {
|
||||
s -> {
|
||||
Object obj = finalProperties.get(s.getId());
|
||||
IoTDevicePropertiesBO entity = new IoTDevicePropertiesBO();
|
||||
long times = 0;
|
||||
if (obj != null) {
|
||||
entity.withValue(s.getValueType(), obj);
|
||||
entity.setTimestamp(
|
||||
finalShadow
|
||||
.getMetadata()
|
||||
.getReported()
|
||||
.getJSONObject(s.getId())
|
||||
.getLong("timestamp"));
|
||||
finalShadow.getMetadata() != null
|
||||
&& finalShadow.getMetadata().getReported() != null
|
||||
&& finalShadow.getMetadata().getReported().getJSONObject(s.getId()) != null
|
||||
? finalShadow
|
||||
.getMetadata()
|
||||
.getReported()
|
||||
.getJSONObject(s.getId())
|
||||
.getLong("timestamp")
|
||||
: DateUtil.currentSeconds());
|
||||
} else {
|
||||
entity.withValue(s.getValueType(), null);
|
||||
entity.setTimestamp(System.currentTimeMillis());
|
||||
entity.setTimestamp(DateUtil.currentSeconds());
|
||||
}
|
||||
entity.withValue(s.getValueType(), obj);
|
||||
entity.setPropertyName(s.getName());
|
||||
entity.setIotId(iotId);
|
||||
entity.setDeviceId(ioTDeviceDTO.getDeviceId());
|
||||
entity.setProperty(s.getId());
|
||||
// 期望值
|
||||
if (finalDesireProperties.get(s.getId()) != null) {
|
||||
entity.setDesireValue(finalDesireProperties.get(s.getId()));
|
||||
entity.setCustomized(IoTConstant.DEVICE_SHADOW_DESIRED_PROPERTY);
|
||||
@@ -292,7 +431,7 @@ public class IoTDeviceShadowService {
|
||||
ioTDeviceShadow.setMetadata(JSONUtil.toJsonStr(shadow));
|
||||
ioTDeviceShadowMapper.insertSelective(ioTDeviceShadow);
|
||||
// 代理调用内部方法,清除缓存
|
||||
iotCacheRemoveService.removeDevInstanceBOCache();
|
||||
// iotCacheRemoveService.removeDevInstanceBOCache();
|
||||
} else {
|
||||
// 更新最后通信时间
|
||||
IoTDeviceShadow ioTDeviceShadow =
|
||||
@@ -398,7 +537,6 @@ public class IoTDeviceShadowService {
|
||||
metadata.getReported() != null ? metadata.getReported() : new JSONObject();
|
||||
JSONObject metadataDesired =
|
||||
metadata.getReported() != null ? metadata.getDesired() : new JSONObject();
|
||||
Timestamp timestamp = new Timestamp(DateUtil.currentSeconds());
|
||||
|
||||
data.forEach(
|
||||
(key, value) -> {
|
||||
@@ -407,12 +545,12 @@ public class IoTDeviceShadowService {
|
||||
// 删除当前reply回复的期望
|
||||
stateDesired.remove(key);
|
||||
metadataDesired.remove(key);
|
||||
metadataReported.set(key, timestamp);
|
||||
metadataReported.set(key, new Timestamp(DateUtil.currentSeconds()));
|
||||
}
|
||||
// 回复内有数据时以回复为准
|
||||
if (!"".equals(value)) {
|
||||
stateReported.set(key, value);
|
||||
metadataReported.set(key, timestamp);
|
||||
metadataReported.set(key, new Timestamp(DateUtil.currentSeconds()));
|
||||
}
|
||||
});
|
||||
state.setReported(stateReported);
|
||||
@@ -441,20 +579,71 @@ public class IoTDeviceShadowService {
|
||||
|
||||
/** 优化的设备影子处理方法 - 直接调用优化类 */
|
||||
public void doShadow(UPRequest upRequest, IoTDeviceDTO ioTDeviceDTO) {
|
||||
if (shadowCacheEnabled) {
|
||||
doShadowCache(upRequest, ioTDeviceDTO);
|
||||
return;
|
||||
}
|
||||
doShadowOriginal(upRequest, ioTDeviceDTO);
|
||||
// try {
|
||||
// // 直接调用优化类的doShadow方法
|
||||
// if (ioTDeviceShadowServiceOptimized != null) {
|
||||
// ioTDeviceShadowServiceOptimized.doShadow(upRequest, ioTDeviceDTO);
|
||||
// } else {
|
||||
// log.warn("优化版服务未注入,使用原方法");
|
||||
// doShadowOriginal(upRequest, ioTDeviceDTO);
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error("优化方法处理失败,降级到原方法: iotId={}, error={}", upRequest.getIotId(), e.getMessage(),
|
||||
// e);
|
||||
// // 降级处理:使用原来的同步方式
|
||||
// doShadowOriginal(upRequest, ioTDeviceDTO);
|
||||
// }
|
||||
}
|
||||
|
||||
/** 新增:仅缓存与标记刷盘的影子处理,不改动原有 doShadowOriginal */
|
||||
public void doShadowCache(UPRequest upRequest, IoTDeviceDTO ioTDeviceDTO) {
|
||||
if (!shadowCacheEnabled) {
|
||||
// 回退到原始逻辑
|
||||
doShadowOriginal(upRequest, ioTDeviceDTO);
|
||||
return;
|
||||
}
|
||||
String iotId = ioTDeviceDTO.getIotId();
|
||||
Shadow shadow = readShadowFromCache(iotId);
|
||||
if (shadow == null) {
|
||||
// fallback DB
|
||||
IoTDeviceShadow fromDb =
|
||||
ioTDeviceShadowMapper.selectOne(IoTDeviceShadow.builder().iotId(iotId).build());
|
||||
if (fromDb != null && StrUtil.isNotBlank(fromDb.getMetadata())) {
|
||||
shadow = JSONUtil.toBean(fromDb.getMetadata(), Shadow.class);
|
||||
} else {
|
||||
shadow = Shadow.builder().timestamp(DateUtil.currentSeconds()).version(1L).build();
|
||||
}
|
||||
}
|
||||
// 合并期望/上报并自增版本
|
||||
doDesired(shadow, ioTDeviceDTO, upRequest);
|
||||
// 写缓存
|
||||
writeShadowToCache(iotId, shadow);
|
||||
// 标记刷盘
|
||||
markShadowDirtyForFlush(iotId);
|
||||
// 清除相关业务缓存
|
||||
// iotCacheRemoveService.removeDevInstanceBOCache();
|
||||
}
|
||||
|
||||
/** 检查版本号是否超过阈值,需要强制刷盘 */
|
||||
private boolean checkVersionThreshold(String iotId) {
|
||||
try {
|
||||
String cacheJson = stringRedisTemplate.opsForValue().get(buildShadowKey(iotId));
|
||||
if (StrUtil.isBlank(cacheJson)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSONObject shadow = JSONUtil.parseObj(cacheJson);
|
||||
Long currentVersion = shadow.getLong("version", 1L);
|
||||
|
||||
IoTDeviceShadow dbShadow =
|
||||
ioTDeviceShadowMapper.selectOne(IoTDeviceShadow.builder().iotId(iotId).build());
|
||||
if (dbShadow != null && StrUtil.isNotBlank(dbShadow.getMetadata())) {
|
||||
// 数据库记录存在:正常比较
|
||||
JSONObject dbShadowObj = JSONUtil.parseObj(dbShadow.getMetadata());
|
||||
Long dbVersion = dbShadowObj.getLong("version", 1L);
|
||||
|
||||
return currentVersion != null
|
||||
&& dbVersion != null
|
||||
&& (currentVersion - dbVersion) >= VERSION_THRESHOLD;
|
||||
} else {
|
||||
// 数据库记录不存在:立即触发刷盘(创建记录)
|
||||
log.info("[ShadowFlush] 数据库记录不存在,立即刷盘: iotId={}, currentVersion={}", iotId, currentVersion);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[ShadowFlush] 版本号检查失败: iotId={}, error={}", iotId, e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+17
-6
@@ -343,12 +343,7 @@ public class IoTProductDeviceService {
|
||||
return productsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网络类型
|
||||
*
|
||||
* @param productKey 产品key
|
||||
* @return int 总数
|
||||
*/
|
||||
/** 查询网络类型 */
|
||||
@Cacheable(cacheNames = "selectNetworkUnionId", key = "#productKey", unless = "#result == null")
|
||||
public String selectNetworkUnionId(String productKey) {
|
||||
if (StrUtil.isBlank(productKey)) {
|
||||
@@ -356,4 +351,20 @@ public class IoTProductDeviceService {
|
||||
}
|
||||
return ioTProductMapper.selectNetworkUnionId(productKey);
|
||||
}
|
||||
|
||||
/** 查询产品配置信息 */
|
||||
@Cacheable(
|
||||
cacheNames = "getProductConfiguration",
|
||||
key = "#productKey",
|
||||
unless = "#result == null")
|
||||
public JSONObject getProductConfiguration(String productKey) {
|
||||
// status-0 正常
|
||||
IoTProduct product =
|
||||
IoTProduct.builder().productKey(productKey).state(IoTConstant.NORMAL.byteValue()).build();
|
||||
product = ioTProductMapper.selectOne(product);
|
||||
if (product == null || StrUtil.isBlank(product.getConfiguration())) {
|
||||
return null;
|
||||
}
|
||||
return JSONUtil.parseObj(product.getConfiguration());
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -12,7 +12,6 @@
|
||||
|
||||
package cn.universal.dm.device.service.log;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
@@ -21,10 +20,10 @@ import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.common.constant.IoTConstant.MessageType;
|
||||
import cn.universal.core.message.UPRequest;
|
||||
import cn.universal.core.metadata.DeviceMetadata;
|
||||
import cn.universal.dm.device.constant.DeviceManagerConstant;
|
||||
import cn.universal.dm.device.service.impl.IoTProductDeviceService;
|
||||
import cn.universal.persistence.base.BaseUPRequest;
|
||||
import cn.universal.persistence.dto.IoTDeviceDTO;
|
||||
import cn.universal.persistence.dto.LogStorePolicyDTO;
|
||||
import cn.universal.persistence.entity.IoTDeviceEvents;
|
||||
import cn.universal.persistence.entity.IoTDeviceLog;
|
||||
import cn.universal.persistence.entity.IoTDeviceLogMetadata;
|
||||
@@ -32,6 +31,7 @@ import cn.universal.persistence.entity.IoTDeviceLogMetadata.IoTDeviceLogMetadata
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.mapper.IoTProductMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -56,7 +56,7 @@ public abstract class AbstractIoTDeviceLogService implements IIoTDeviceLogServic
|
||||
IoTDeviceLogMetadataBuilder builder(UPRequest up) {
|
||||
final IoTDeviceLogMetadataBuilder ioTDeviceLogMetadataBuilder =
|
||||
IoTDeviceLogMetadata.builder()
|
||||
.createTime(Integer.parseInt(DateUtil.currentSeconds() + ""))
|
||||
.createTime(LocalDateTime.now())
|
||||
.deviceId(up.getDeviceId())
|
||||
.deviceName(StrUtil.sub(up.getDeviceName(), 0, 30))
|
||||
.ext1(JSONUtil.toJsonStr(up.getData()))
|
||||
@@ -71,20 +71,20 @@ public abstract class AbstractIoTDeviceLogService implements IIoTDeviceLogServic
|
||||
IoTDeviceLog.builder()
|
||||
.content(peopertiesOrEventData(upRequest))
|
||||
.deviceId(ioTDeviceDTO.getDeviceId())
|
||||
// .extDeviceId(ioTDeviceDTO.getExtDeviceId())
|
||||
.iotId(ioTDeviceDTO.getIotId())
|
||||
.productKey(ioTDeviceDTO.getProductKey())
|
||||
.messageType(upRequest.getMessageType().name())
|
||||
.event(functionOrEvent(upRequest))
|
||||
.commandId(upRequest.getCommandId())
|
||||
.commandStatus(upRequest.getCommandStatus())
|
||||
.createTime(
|
||||
upRequest.getTime() == null
|
||||
? System.currentTimeMillis() / 1000
|
||||
: upRequest.getTime() / 1000)
|
||||
.createTime(LocalDateTime.now())
|
||||
.deviceName(StrUtil.sub(ioTDeviceDTO.getDeviceName(), 0, 30))
|
||||
.point(ioTDeviceDTO.getCoordinate())
|
||||
.build();
|
||||
if (upRequest.getProperties() != null
|
||||
&& upRequest.getProperties().containsKey(DeviceManagerConstant.COORDINATE)) {
|
||||
log.setPoint(ioTDeviceDTO.getCoordinate());
|
||||
}
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,6 @@ public abstract class AbstractIoTDeviceLogService implements IIoTDeviceLogServic
|
||||
List<IoTDeviceEvents> ioTDeviceEventsList = new ArrayList<>();
|
||||
JSONArray properties = metadata.getJSONArray("events");
|
||||
if (properties != null) {
|
||||
LogStorePolicyDTO storePolicy = iotProductDeviceService.getProductLogStorePolicy(productKey);
|
||||
for (Object object : properties) {
|
||||
JSONObject jsonObject = JSONUtil.parseObj(object);
|
||||
JSONObject expands = JSONUtil.parseObj(jsonObject.getStr("expands"));
|
||||
|
||||
+1
-1
@@ -239,7 +239,7 @@ public class ClickHouseIoTDeviceLogService extends AbstractIoTDeviceLogService {
|
||||
}
|
||||
if (MapUtil.isNotEmpty(logQuery.getParams())
|
||||
&& ObjectUtil.isNotNull(logQuery.getParams().get("endCreateTime"))) {
|
||||
builder1.append(" AND create_time <= " + logQuery.getParams().get("beginCreateTime"));
|
||||
builder1.append(" AND create_time <= " + logQuery.getParams().get("endCreateTime"));
|
||||
}
|
||||
builder1.append(" ORDER BY create_time DESC ");
|
||||
try {
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ public class MysqlIoTDeviceLogService extends AbstractIoTDeviceLogService {
|
||||
/** 产品数据存储策略,不为空则保存日志 */
|
||||
if (StrUtil.isNotBlank(ioTProduct.getStorePolicy())) {
|
||||
try {
|
||||
ioTDeviceLog.setPoint(ioTDeviceDTO.getCoordinate());
|
||||
// ioTDeviceLog.setPoint(ioTDeviceDTO.getCoordinate());
|
||||
// ioTDeviceLogMapper.insertSelective(ioTDeviceLog);
|
||||
// 日志分表 暂时双写单读
|
||||
if (enable) {
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.dm.device.service.protocol;
|
||||
|
||||
/**
|
||||
* 协议集群服务接口
|
||||
*
|
||||
* <p>用于管理协议服务器的集群操作,包括启动、停止、重启等集群化操作
|
||||
*
|
||||
* @author Aleo
|
||||
* @version 1.0
|
||||
* @since 2025/1/9
|
||||
*/
|
||||
public interface ProtocolClusterService {
|
||||
|
||||
/**
|
||||
* 集群启动(本地启动 + 广播)
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否启动成功
|
||||
*/
|
||||
boolean start(String productKey);
|
||||
|
||||
/**
|
||||
* 集群停止(本地停止 + 广播)
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否停止成功
|
||||
*/
|
||||
boolean stop(String productKey);
|
||||
|
||||
/**
|
||||
* 集群重启(本地重启 + 广播)
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否重启成功
|
||||
*/
|
||||
boolean restart(String productKey);
|
||||
|
||||
/**
|
||||
* 本地启动(不广播)
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否启动成功
|
||||
*/
|
||||
boolean startLocal(String productKey);
|
||||
|
||||
/**
|
||||
* 本地停止(不广播)
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否停止成功
|
||||
*/
|
||||
boolean stopLocal(String productKey);
|
||||
|
||||
/**
|
||||
* 本地重启(不广播)
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否重启成功
|
||||
*/
|
||||
boolean restartLocal(String productKey);
|
||||
|
||||
/**
|
||||
* 检查产品服务器是否存活
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否存活
|
||||
*/
|
||||
default boolean isProductServerAlive(String productKey) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.dm.device.service.protocol;
|
||||
|
||||
/**
|
||||
* 协议服务器管理器接口
|
||||
*
|
||||
* <p>用于管理协议服务器的生命周期,包括启动、停止、重启等操作
|
||||
*
|
||||
* @author Aleo
|
||||
* @version 1.0
|
||||
* @since 2025/1/9
|
||||
*/
|
||||
public interface ProtocolServerManager {
|
||||
|
||||
/**
|
||||
* 获取服务器实例
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 服务器实例,如果不存在则返回null
|
||||
*/
|
||||
Object getServerInstance(String productKey);
|
||||
|
||||
/**
|
||||
* 检查服务器是否存活
|
||||
*
|
||||
* @param serverInstance 服务器实例
|
||||
* @return 是否存活
|
||||
*/
|
||||
boolean isAlive(Object serverInstance);
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.dm.device.service.protocol;
|
||||
|
||||
/**
|
||||
* UDP服务器启动器接口
|
||||
*
|
||||
* <p>用于管理UDP服务器的启动、停止、重启等操作
|
||||
*
|
||||
* @author Aleo
|
||||
* @version 1.0
|
||||
* @since 2025/1/9
|
||||
*/
|
||||
public interface UdpServerBootstrap {
|
||||
|
||||
/**
|
||||
* 启动产品UDP服务器
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否启动成功
|
||||
*/
|
||||
boolean startProductUdpServer(String productKey);
|
||||
|
||||
/**
|
||||
* 停止产品UDP服务器
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否停止成功
|
||||
*/
|
||||
boolean stopProductUdpServer(String productKey);
|
||||
|
||||
/**
|
||||
* 重启产品UDP服务器
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否重启成功
|
||||
*/
|
||||
boolean restartProductUdpServer(String productKey);
|
||||
|
||||
/**
|
||||
* 检查产品UDP服务器是否存活
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 是否存活
|
||||
*/
|
||||
boolean isProductUdpServerAlive(String productKey);
|
||||
}
|
||||
+10
-7
@@ -104,6 +104,10 @@ public class HttpPushStrategy implements PushStrategy {
|
||||
// 添加时间戳和签名
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
httpRequest.header("X-Timestamp", timestamp);
|
||||
if (StrUtil.isNotBlank(httpConfig.getHeader())
|
||||
&& StrUtil.isNotBlank(httpConfig.getSecret())) {
|
||||
httpRequest.header(httpConfig.getHeader(), httpConfig.getSecret());
|
||||
}
|
||||
httpRequest.header(Header.CONTENT_TYPE, "application/json");
|
||||
|
||||
// 添加自定义请求头(如果有配置)
|
||||
@@ -121,11 +125,7 @@ public class HttpPushStrategy implements PushStrategy {
|
||||
// 执行请求
|
||||
HttpResponse response = httpRequest.execute();
|
||||
String result = response.body();
|
||||
log.warn(
|
||||
"[HTTP推送] 推送, url={}, status={}, messageJson={}",
|
||||
httpConfig.getUrl(),
|
||||
response != null ? response.getStatus() : "null",
|
||||
result);
|
||||
|
||||
// 检查响应状态
|
||||
if (response == null || response.getStatus() != 200) {
|
||||
log.warn(
|
||||
@@ -143,8 +143,11 @@ public class HttpPushStrategy implements PushStrategy {
|
||||
"HTTP状态码错误: " + (response != null ? response.getStatus() : "null"),
|
||||
"HTTP_ERROR");
|
||||
}
|
||||
|
||||
log.info("[HTTP推送] 推送成功, url={}, deviceId={}", httpConfig.getUrl(), request.getIotId());
|
||||
log.info(
|
||||
"[HTTP推送] 推送成功, url={}, status={}, messageJson={}",
|
||||
httpConfig.getUrl(),
|
||||
response != null ? response.getStatus() : "null",
|
||||
result);
|
||||
removeSuccess(httpConfig.getUrl());
|
||||
|
||||
return IoTPushResult.success(
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 AleoXin 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: AleoXin
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
package cn.universal.dm.device.service.push;
|
||||
|
||||
/**
|
||||
* mqtt消息推送接口
|
||||
*
|
||||
* @version 1.0 @Author Aleo
|
||||
* @since 2025/08/30
|
||||
*/
|
||||
public interface MQTTPushService {
|
||||
|
||||
/**
|
||||
* 消息推送
|
||||
*
|
||||
* @param topic 主题
|
||||
* @param payload 有效载荷
|
||||
* @param qos qos
|
||||
* @param retained 是否保存
|
||||
* @return
|
||||
*/
|
||||
boolean publishMessage(String topic, byte[] payload, int qos, boolean retained);
|
||||
}
|
||||
+21
-22
@@ -12,11 +12,13 @@
|
||||
|
||||
package cn.universal.dm.device.service.push;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.universal.dm.device.entity.IoTPushResult;
|
||||
import cn.universal.persistence.base.BaseUPRequest;
|
||||
import cn.universal.persistence.entity.bo.UPPushBO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -35,6 +37,13 @@ public class MqttPushStrategy implements PushStrategy {
|
||||
this.mqttConfig = mqttConfig;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Qualifier("sysMQTTManager")
|
||||
private MQTTPushService mqttPushService;
|
||||
|
||||
@Value("${mqtt.cfg.defined.thing:$thing}")
|
||||
private String thingPrefix;
|
||||
|
||||
@Override
|
||||
public IoTPushResult execute(BaseUPRequest request, String messageJson) {
|
||||
if (mqttConfig == null) {
|
||||
@@ -48,31 +57,21 @@ public class MqttPushStrategy implements PushStrategy {
|
||||
"配置为空",
|
||||
"CONFIG_NULL");
|
||||
}
|
||||
|
||||
try {
|
||||
if (StrUtil.isBlank(mqttConfig.getTopic())) {
|
||||
log.warn("[MQTT推送] Topic为空,跳过推送");
|
||||
return IoTPushResult.failed(
|
||||
request.getIoTDeviceDTO().getThirdPlatform(),
|
||||
request.getProductKey(),
|
||||
request.getIotId(),
|
||||
"MQTT",
|
||||
messageJson,
|
||||
"Topic为空",
|
||||
"TOPIC_EMPTY");
|
||||
}
|
||||
|
||||
// 构建完整的topic
|
||||
String fullTopic = mqttConfig.getTopic().replace("#", request.getIotId());
|
||||
// 构建完整的topic,不允许修改默认为
|
||||
// thingPrefix/${appid}/${productKey}/${deviceId}
|
||||
String fullTopic =
|
||||
thingPrefix
|
||||
+ "/"
|
||||
+ request.getIoTDeviceDTO().getAppId()
|
||||
+ "/"
|
||||
+ request.getProductKey()
|
||||
+ "/"
|
||||
+ request.getDeviceId();
|
||||
|
||||
log.info("[MQTT推送] 推送到Topic: {}, 消息: {}", fullTopic, request.getIotId());
|
||||
|
||||
// TODO: 实现具体的MQTT推送逻辑
|
||||
// 这里可以集成具体的MQTT客户端,比如:
|
||||
// - Eclipse Paho MQTT Client
|
||||
// - HiveMQ MQTT Client
|
||||
// - Spring Integration MQTT
|
||||
// mqttClient.publish(fullTopic, messageJson.getBytes(), 1, false);
|
||||
mqttPushService.publishMessage(fullTopic, messageJson.getBytes(), 1, false);
|
||||
|
||||
// 暂时返回成功(实际实现时需要根据MQTT推送结果返回)
|
||||
return IoTPushResult.success(
|
||||
|
||||
+10
-10
@@ -77,7 +77,7 @@ public class PushRetryProcessor implements UPProcessor<BaseUPRequest> {
|
||||
/** 获取线程池状态信息 */
|
||||
public String getThreadPoolStatus() {
|
||||
if (retryExecutor instanceof java.util.concurrent.ThreadPoolExecutor) {
|
||||
java.util.concurrent.ThreadPoolExecutor executor =
|
||||
java.util.concurrent.ThreadPoolExecutor executor =
|
||||
(java.util.concurrent.ThreadPoolExecutor) retryExecutor;
|
||||
return String.format(
|
||||
"PushRetry线程池状态: 核心线程数=%d, 最大线程数=%d, 当前线程数=%d, 活跃线程数=%d, 队列大小=%d, 已完成任务数=%d",
|
||||
@@ -162,10 +162,10 @@ public class PushRetryProcessor implements UPProcessor<BaseUPRequest> {
|
||||
// 添加到重试队列
|
||||
redisTemplate.opsForList().rightPush(retryKey, retryData);
|
||||
redisTemplate.expire(retryKey, java.time.Duration.ofDays(1));
|
||||
log.debug("[推送重试] Redis添加队列成功: deviceId={}, channel={}",
|
||||
log.debug("[推送重试] Redis添加队列成功: deviceId={}, channel={}",
|
||||
result.getDeviceId(), result.getChannel());
|
||||
} catch (Exception e) {
|
||||
log.error("[推送重试] Redis添加队列失败: deviceId={}, channel={}",
|
||||
log.error("[推送重试] Redis添加队列失败: deviceId={}, channel={}",
|
||||
result.getDeviceId(), result.getChannel(), e);
|
||||
}
|
||||
},
|
||||
@@ -178,10 +178,10 @@ public class PushRetryProcessor implements UPProcessor<BaseUPRequest> {
|
||||
// 增加重试次数
|
||||
redisTemplate.opsForValue().increment(countKey);
|
||||
redisTemplate.expire(countKey, java.time.Duration.ofDays(1));
|
||||
log.debug("[推送重试] Redis增加计数成功: deviceId={}, channel={}",
|
||||
log.debug("[推送重试] Redis增加计数成功: deviceId={}, channel={}",
|
||||
result.getDeviceId(), result.getChannel());
|
||||
} catch (Exception e) {
|
||||
log.error("[推送重试] Redis增加计数失败: deviceId={}, channel={}",
|
||||
log.error("[推送重试] Redis增加计数失败: deviceId={}, channel={}",
|
||||
result.getDeviceId(), result.getChannel(), e);
|
||||
}
|
||||
},
|
||||
@@ -570,10 +570,10 @@ public class PushRetryProcessor implements UPProcessor<BaseUPRequest> {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPush(failedKey, failedData);
|
||||
redisTemplate.expire(failedKey, java.time.Duration.ofDays(7)); // 失败记录保留7天
|
||||
log.debug("[推送重试] 记录失败推送成功: deviceId={}, channel={}",
|
||||
log.debug("[推送重试] 记录失败推送成功: deviceId={}, channel={}",
|
||||
result.getDeviceId(), result.getChannel());
|
||||
} catch (Exception e) {
|
||||
log.error("[推送重试] 记录失败推送Redis操作失败: deviceId={}, channel={}",
|
||||
log.error("[推送重试] 记录失败推送Redis操作失败: deviceId={}, channel={}",
|
||||
result.getDeviceId(), result.getChannel(), e);
|
||||
}
|
||||
},
|
||||
@@ -581,15 +581,15 @@ public class PushRetryProcessor implements UPProcessor<BaseUPRequest> {
|
||||
|
||||
// 异步处理结果,不阻塞当前线程
|
||||
future.thenRun(() -> {
|
||||
log.debug("[推送重试] 记录失败推送完成: deviceId={}, channel={}",
|
||||
log.debug("[推送重试] 记录失败推送完成: deviceId={}, channel={}",
|
||||
result.getDeviceId(), result.getChannel());
|
||||
}).exceptionally(throwable -> {
|
||||
log.error("[推送重试] 记录失败推送异常: deviceId={}, channel={}",
|
||||
log.error("[推送重试] 记录失败推送异常: deviceId={}, channel={}",
|
||||
result.getDeviceId(), result.getChannel(), throwable);
|
||||
return null;
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("[推送重试] 记录失败推送失败: deviceId={}, channel={}",
|
||||
log.error("[推送重试] 记录失败推送失败: deviceId={}, channel={}",
|
||||
result.getDeviceId(), result.getChannel(), e);
|
||||
}
|
||||
}
|
||||
|
||||
+532
@@ -0,0 +1,532 @@
|
||||
package cn.universal.dm.device.service.task;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.common.config.InstanceIdProvider;
|
||||
import cn.universal.dm.device.util.DistributedLockUtil;
|
||||
import cn.universal.persistence.entity.IoTDevice;
|
||||
import cn.universal.persistence.entity.IoTDeviceShadow;
|
||||
import cn.universal.persistence.mapper.IoTDeviceMapper;
|
||||
import cn.universal.persistence.mapper.IoTDeviceShadowMapper;
|
||||
import cn.universal.persistence.query.IoTDeviceQuery;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备影子定时缓存
|
||||
*
|
||||
* <p>Periodically flush device shadows from Redis to DB. Uses Redis ZSET (score = nextFlushAtMs)
|
||||
* and Redisson distributed lock.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ShadowFlushScheduler {
|
||||
|
||||
@Resource private StringRedisTemplate stringRedisTemplate;
|
||||
@Resource private IoTDeviceShadowMapper ioTDeviceShadowMapper;
|
||||
@Resource private IoTDeviceMapper ioTDeviceMapper;
|
||||
@Resource private DistributedLockUtil distributedLockUtil;
|
||||
|
||||
@Value("${shadow.cache.enabled:true}")
|
||||
private boolean shadowCacheEnabled;
|
||||
|
||||
@Value("${shadow.cache.key-prefix:shadow}")
|
||||
private String shadowKeyPrefix;
|
||||
|
||||
@Value("${shadow.flush.enabled:true}")
|
||||
private boolean flushEnabled;
|
||||
|
||||
@Value("${shadow.flush.zset-key:shadow:flush}")
|
||||
private String shadowFlushZsetKey;
|
||||
|
||||
@Value("${shadow.flush.scan-interval-ms:300000}") // 5 minutes
|
||||
private long scanIntervalMs;
|
||||
|
||||
@Value("${shadow.flush.batch-size:1000}")
|
||||
private int batchSize;
|
||||
|
||||
@Value("${shadow.flush.lock-key:shadow:flush:lock}")
|
||||
private String lockKey;
|
||||
|
||||
@Value("${shadow.flush.lock-wait-time:10}")
|
||||
private long lockWaitTime;
|
||||
|
||||
@Value("${shadow.flush.lock-lease-time:60}")
|
||||
private long lockLeaseTime;
|
||||
|
||||
@Value("${shadow.flush.max-retries:3}")
|
||||
private int maxRetries;
|
||||
|
||||
@Resource private InstanceIdProvider instanceIdProvider;
|
||||
|
||||
@Scheduled(fixedDelayString = "${shadow.flush.scan-interval-ms:300000}")
|
||||
public void flushDueShadows() {
|
||||
if (!shadowCacheEnabled || !flushEnabled) {
|
||||
log.debug(
|
||||
"[ShadowFlush] 影子刷盘已禁用: shadowCacheEnabled={}, flushEnabled={}",
|
||||
shadowCacheEnabled,
|
||||
flushEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
String instanceId = instanceIdProvider.getInstanceId();
|
||||
log.info("[ShadowFlush] 开始设备影子扫描: scanTime={}, instanceId={}", now, instanceId);
|
||||
|
||||
// 使用分布式锁工具类执行任务,确保集群环境下只有一个实例执行
|
||||
Integer result =
|
||||
distributedLockUtil.tryLockAndExecute(
|
||||
lockKey, lockWaitTime, lockLeaseTime, TimeUnit.SECONDS, this::executeFlushTask);
|
||||
|
||||
if (result != null) {
|
||||
log.info(
|
||||
"[ShadowFlush] 影子扫描完成: processed={}, cost={}ms, instanceId={}",
|
||||
result,
|
||||
(System.currentTimeMillis() - now),
|
||||
instanceId);
|
||||
} else {
|
||||
log.debug("[ShadowFlush] 获取分布式锁失败,跳过本次扫描: instanceId={}", instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildShadowKey(String iotId) {
|
||||
return shadowKeyPrefix + ":" + iotId;
|
||||
}
|
||||
|
||||
/** 获取缓存数据 */
|
||||
private Map<String, String> getCacheData(Set<String> iotIds) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
for (String iotId : iotIds) {
|
||||
try {
|
||||
String cacheJson = stringRedisTemplate.opsForValue().get(buildShadowKey(iotId));
|
||||
if (StrUtil.isNotBlank(cacheJson)) {
|
||||
result.put(iotId, cacheJson);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[ShadowFlush] get cache error iotId={}, err={}", iotId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 处理单个设备影子 - 带重试机制 */
|
||||
private boolean processShadowWithRetry(String iotId, String cacheJson) {
|
||||
for (int retry = 0; retry < maxRetries; retry++) {
|
||||
try {
|
||||
return processShadow(iotId, cacheJson);
|
||||
} catch (Exception e) {
|
||||
if (retry == maxRetries - 1) {
|
||||
log.error("[ShadowFlush] final retry failed iotId={}, err={}", iotId, e.getMessage());
|
||||
return false;
|
||||
} else {
|
||||
log.warn("[ShadowFlush] retry {} failed iotId={}, err={}", retry, iotId, e.getMessage());
|
||||
try {
|
||||
Thread.sleep(1000); // 短暂延迟后重试
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 处理单个设备影子 */
|
||||
private boolean processShadow(String iotId, String cacheJson) {
|
||||
IoTDeviceShadow existed =
|
||||
ioTDeviceShadowMapper.selectOne(IoTDeviceShadow.builder().iotId(iotId).build());
|
||||
|
||||
if (existed == null) {
|
||||
IoTDeviceShadow entity = new IoTDeviceShadow();
|
||||
entity.setIotId(iotId);
|
||||
entity.setMetadata(cacheJson);
|
||||
entity.setUpdateDate(new Date());
|
||||
entity.setLastTime(new Date());
|
||||
entity.setActiveTime(new Date());
|
||||
ioTDeviceShadowMapper.insertSelective(entity);
|
||||
} else {
|
||||
String merged = mergeShadowJson(existed.getMetadata(), cacheJson);
|
||||
existed.setMetadata(merged);
|
||||
existed.setUpdateDate(new Date());
|
||||
existed.setLastTime(new Date());
|
||||
ioTDeviceShadowMapper.updateByPrimaryKeySelective(existed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 批量处理设备影子 - 优化数据库操作 */
|
||||
private int processBatchShadows(List<String> iotIds, Map<String, String> cacheData) {
|
||||
if (CollUtil.isEmpty(iotIds)) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
// 批量查询已存在的记录
|
||||
List<IoTDeviceShadow> existingShadows = ioTDeviceShadowMapper.selectByIotIds(iotIds);
|
||||
Map<String, IoTDeviceShadow> existingMap =
|
||||
existingShadows.stream()
|
||||
.collect(Collectors.toMap(IoTDeviceShadow::getIotId, Function.identity()));
|
||||
|
||||
// 分离需要插入和更新的记录
|
||||
List<IoTDeviceShadow> toInsert = new ArrayList<>();
|
||||
List<IoTDeviceShadow> toUpdate = new ArrayList<>();
|
||||
Date now = new Date();
|
||||
|
||||
for (String iotId : iotIds) {
|
||||
String cacheJson = cacheData.get(iotId);
|
||||
if (StrUtil.isBlank(cacheJson)) {
|
||||
continue;
|
||||
}
|
||||
if (existingMap.containsKey(iotId)) {
|
||||
IoTDeviceShadow updated = buildUpdatedShadow(existingMap.get(iotId), cacheJson, now);
|
||||
toUpdate.add(updated);
|
||||
} else {
|
||||
IoTDeviceShadow created = buildNewShadow(iotId, cacheJson, now);
|
||||
if (created != null) {
|
||||
toInsert.add(created);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 批量插入
|
||||
if (!toInsert.isEmpty()) {
|
||||
ioTDeviceShadowMapper.batchInsert(toInsert);
|
||||
}
|
||||
|
||||
// 批量更新
|
||||
if (!toUpdate.isEmpty()) {
|
||||
ioTDeviceShadowMapper.batchUpdate(toUpdate);
|
||||
}
|
||||
|
||||
return toInsert.size() + toUpdate.size();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[ShadowFlush] batch process error: {}", e.getMessage(), e);
|
||||
// 回退到单个处理
|
||||
int processed = 0;
|
||||
for (String iotId : iotIds) {
|
||||
String cacheJson = cacheData.get(iotId);
|
||||
if (StrUtil.isNotBlank(cacheJson) && processShadowWithRetry(iotId, cacheJson)) {
|
||||
processed++;
|
||||
}
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
}
|
||||
|
||||
private IoTDeviceShadow buildUpdatedShadow(IoTDeviceShadow existed, String cacheJson, Date now) {
|
||||
if (existed == null) {
|
||||
return null;
|
||||
}
|
||||
String merged = mergeShadowJson(existed.getMetadata(), cacheJson);
|
||||
existed.setMetadata(merged);
|
||||
existed.setUpdateDate(now);
|
||||
existed.setLastTime(now);
|
||||
return existed;
|
||||
}
|
||||
|
||||
private IoTDeviceShadow buildNewShadow(String iotId, String cacheJson, Date now) {
|
||||
IoTDeviceShadow entity = new IoTDeviceShadow();
|
||||
entity.setIotId(iotId);
|
||||
entity.setMetadata(cacheJson);
|
||||
entity.setUpdateDate(now);
|
||||
entity.setLastTime(now);
|
||||
entity.setActiveTime(now);
|
||||
try {
|
||||
IoTDeviceQuery query = new IoTDeviceQuery();
|
||||
query.setIotId(iotId);
|
||||
IoTDevice dev = ioTDeviceMapper.getOneByIotId(query);
|
||||
if (dev != null) {
|
||||
entity.setProductKey(dev.getProductKey());
|
||||
entity.setDeviceId(dev.getDeviceId());
|
||||
entity.setExtDeviceId(dev.getExtDeviceId());
|
||||
entity.setInstance(dev.getApplication());
|
||||
if (dev.getOnlineTime() != null) {
|
||||
entity.setOnlineTime(DateUtil.date(dev.getOnlineTime() * 1000));
|
||||
}
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库中的影子 JSON 与缓存中的影子 JSON 进行深度合并,避免覆盖丢失。 规则: - 对象与对象递归合并; - 非对象类型以 incoming 覆盖 existing; -
|
||||
* 数组整体覆盖; - 针对顶层的 timestamp/version,取二者较大值。
|
||||
*/
|
||||
private String mergeShadowJson(String existingJson, String incomingJson) {
|
||||
if (StrUtil.isBlank(incomingJson)) {
|
||||
return StrUtil.blankToDefault(existingJson, "{}");
|
||||
}
|
||||
if (StrUtil.isBlank(existingJson)) {
|
||||
return incomingJson;
|
||||
}
|
||||
JSONObject existing;
|
||||
JSONObject incoming;
|
||||
try {
|
||||
existing = JSONUtil.parseObj(existingJson);
|
||||
} catch (Exception e) {
|
||||
existing = new JSONObject();
|
||||
}
|
||||
try {
|
||||
incoming = JSONUtil.parseObj(incomingJson);
|
||||
} catch (Exception e) {
|
||||
// 如果新数据都无法解析,直接返回旧数据
|
||||
log.warn("[ShadowFlush]新数据都无法解析,直接返回旧数据 error: {}", incomingJson, e);
|
||||
return existingJson;
|
||||
}
|
||||
|
||||
JSONObject merged = deepMergeObject(existing, incoming);
|
||||
|
||||
// 顶层特殊字段处理
|
||||
try {
|
||||
Long tsExisting = merged.getLong("timestamp");
|
||||
Long tsIncoming = incoming.getLong("timestamp");
|
||||
if (tsExisting != null || tsIncoming != null) {
|
||||
long maxTs =
|
||||
Math.max(tsExisting == null ? 0L : tsExisting, tsIncoming == null ? 0L : tsIncoming);
|
||||
merged.set("timestamp", maxTs);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
log.warn("[ShadowFlush顶层特殊字段处理] merge shadow json error: {}", incomingJson, ignore);
|
||||
}
|
||||
try {
|
||||
Long verExisting = merged.getLong("version");
|
||||
Long verIncoming = incoming.getLong("version");
|
||||
if (verExisting != null || verIncoming != null) {
|
||||
long maxVer =
|
||||
Math.max(
|
||||
verExisting == null ? 0L : verExisting, verIncoming == null ? 0L : verIncoming);
|
||||
merged.set("version", maxVer);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
log.warn("[ShadowFlush] merge shadow json error: {}", incomingJson);
|
||||
}
|
||||
|
||||
return JSONUtil.toJsonStr(merged);
|
||||
}
|
||||
|
||||
/** 对 JSONObject 进行递归深度合并 */
|
||||
private JSONObject deepMergeObject(JSONObject base, JSONObject incoming) {
|
||||
if (base == null) {
|
||||
return incoming == null ? new JSONObject() : incoming;
|
||||
}
|
||||
if (incoming == null) {
|
||||
return base;
|
||||
}
|
||||
JSONObject result = JSONUtil.parseObj(base.toString());
|
||||
for (String key : incoming.keySet()) {
|
||||
Object inVal = incoming.get(key);
|
||||
Object baseVal = result.get(key);
|
||||
|
||||
if (inVal instanceof JSONObject && baseVal instanceof JSONObject) {
|
||||
result.set(key, deepMergeObject((JSONObject) baseVal, (JSONObject) inVal));
|
||||
} else {
|
||||
// 数组或标量:直接覆盖
|
||||
result.set(key, inVal);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 执行影子刷新任务 */
|
||||
private Integer executeFlushTask() {
|
||||
long start = System.currentTimeMillis();
|
||||
int processed = 0;
|
||||
String instanceId = instanceIdProvider.getInstanceId();
|
||||
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// 检查ZSet中总共有多少待刷盘的设备
|
||||
Long totalCount =
|
||||
stringRedisTemplate
|
||||
.opsForZSet()
|
||||
.count(shadowFlushZsetKey, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
|
||||
log.debug("[ShadowFlush] ZSet中待刷盘设备总数: {}, instanceId={}", totalCount, instanceId);
|
||||
|
||||
// fetch due iotIds by score <= now
|
||||
Set<String> dueIds =
|
||||
stringRedisTemplate
|
||||
.opsForZSet()
|
||||
.rangeByScore(
|
||||
shadowFlushZsetKey, Double.NEGATIVE_INFINITY, (double) now, 0, batchSize);
|
||||
|
||||
if (dueIds == null || dueIds.isEmpty()) {
|
||||
log.debug("[ShadowFlush] 没有到期的设备需要刷盘: now={}, instanceId={}", now, instanceId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
log.info("[ShadowFlush] 发现{}个到期设备需要刷盘: {}, instanceId={}", dueIds.size(), dueIds, instanceId);
|
||||
|
||||
// 获取缓存数据
|
||||
Map<String, String> cacheData = getCacheData(dueIds);
|
||||
if (cacheData.isEmpty()) {
|
||||
log.warn("[ShadowFlush] 缓存数据为空,跳过刷盘: dueIds={}, instanceId={}", dueIds, instanceId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 使用原子操作从ZSet中移除已获取的设备ID,防止集群环境下的重复处理
|
||||
List<String> validIotIds = new ArrayList<>(cacheData.keySet());
|
||||
Long removedCount =
|
||||
stringRedisTemplate.opsForZSet().remove(shadowFlushZsetKey, validIotIds.toArray());
|
||||
|
||||
if (removedCount == null || removedCount == 0L) {
|
||||
log.warn(
|
||||
"[ShadowFlush] 从ZSet中移除设备失败,可能被其他实例处理: validIotIds={}, instanceId={}",
|
||||
validIotIds,
|
||||
instanceId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
log.info(
|
||||
"[ShadowFlush] 从ZSet中移除{}个设备: {}, instanceId={}", removedCount, validIotIds, instanceId);
|
||||
|
||||
// 批量处理设备影子
|
||||
processed = processBatchShadows(validIotIds, cacheData);
|
||||
|
||||
log.info(
|
||||
"[ShadowFlush] 刷盘任务完成: processed={}, cost={}ms, validIotIds={}, instanceId={}",
|
||||
processed,
|
||||
(System.currentTimeMillis() - start),
|
||||
validIotIds,
|
||||
instanceId);
|
||||
|
||||
return processed;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[ShadowFlush] 刷盘任务异常: instanceId={}, error={}", instanceId, e.getMessage(), e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** 测试Redisson分布式锁是否正常工作 可以通过API调用此方法进行测试 */
|
||||
public String testDistributedLock() {
|
||||
String testLockKey = "test:shadow:flush:lock";
|
||||
|
||||
return distributedLockUtil.tryLockAndExecute(
|
||||
testLockKey,
|
||||
5, // 等待5秒
|
||||
10, // 持有10秒
|
||||
TimeUnit.SECONDS,
|
||||
() -> {
|
||||
log.info("[ShadowFlush] 测试锁获取成功,当前时间: {}", DateUtil.now());
|
||||
try {
|
||||
Thread.sleep(2000); // 模拟处理时间
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return "锁测试成功,处理完成";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查强制刷盘状态 - 用于调试
|
||||
*
|
||||
* @param iotId 设备ID
|
||||
* @return 强制刷盘状态信息
|
||||
*/
|
||||
public String checkForceFlushStatus(String iotId) {
|
||||
try {
|
||||
// 检查ZSet中的刷盘时间
|
||||
Double score = stringRedisTemplate.opsForZSet().score(shadowFlushZsetKey, iotId);
|
||||
if (score == null) {
|
||||
return "设备" + iotId + "不在刷盘队列中";
|
||||
}
|
||||
|
||||
long flushTime = score.longValue();
|
||||
long now = System.currentTimeMillis();
|
||||
long timeUntilFlush = flushTime - now;
|
||||
|
||||
// 检查缓存数据
|
||||
String cacheKey = buildShadowKey(iotId);
|
||||
String cacheData = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
boolean hasCacheData = StrUtil.isNotBlank(cacheData);
|
||||
|
||||
return String.format(
|
||||
"设备%s刷盘状态: 刷盘时间=%d, 当前时间=%d, 距离刷盘=%dms, 有缓存数据=%s",
|
||||
iotId, flushTime, now, timeUntilFlush, hasCacheData);
|
||||
|
||||
} catch (Exception e) {
|
||||
return "检查强制刷盘状态失败: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查集群状态 - 用于调试和监控
|
||||
*
|
||||
* @return 集群状态信息
|
||||
*/
|
||||
public String checkClusterStatus() {
|
||||
try {
|
||||
String instanceId = instanceIdProvider.getInstanceId();
|
||||
|
||||
// 检查分布式锁状态
|
||||
boolean isLocked = distributedLockUtil.isLocked(lockKey);
|
||||
boolean isHeldByCurrentThread = distributedLockUtil.isHeldByCurrentThread(lockKey);
|
||||
|
||||
// 检查ZSet状态
|
||||
Long totalCount =
|
||||
stringRedisTemplate
|
||||
.opsForZSet()
|
||||
.count(shadowFlushZsetKey, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
|
||||
Long dueCount =
|
||||
stringRedisTemplate
|
||||
.opsForZSet()
|
||||
.count(
|
||||
shadowFlushZsetKey,
|
||||
Double.NEGATIVE_INFINITY,
|
||||
(double) System.currentTimeMillis());
|
||||
|
||||
return String.format(
|
||||
"集群状态: 当前实例=%s, 锁状态=%s, 当前实例持有锁=%s, 待刷盘总数=%d, 到期数量=%d",
|
||||
instanceId, isLocked, isHeldByCurrentThread, totalCount, dueCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
return "检查集群状态失败: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发刷盘 - 用于测试和紧急情况
|
||||
*
|
||||
* @return 处理结果
|
||||
*/
|
||||
public String manualFlush() {
|
||||
try {
|
||||
String instanceId = instanceIdProvider.getInstanceId();
|
||||
log.info("[ShadowFlush] 手动触发刷盘: instanceId={}", instanceId);
|
||||
|
||||
Integer result =
|
||||
distributedLockUtil.tryLockAndExecute(
|
||||
lockKey + ":manual", 10, 120, TimeUnit.SECONDS, this::executeFlushTask);
|
||||
|
||||
if (result != null) {
|
||||
return String.format("手动刷盘完成: processed=%d, instanceId=%s", result, instanceId);
|
||||
} else {
|
||||
return String.format("手动刷盘失败: 获取锁失败, instanceId=%s", instanceId);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
return "手动刷盘异常: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, IoT-Universal. All Rights Reserved.
|
||||
*
|
||||
* @Description: 本文件由 Aleo 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
|
||||
* @Author: Aleo
|
||||
* @Email: wo8335224@gmail.com
|
||||
* @Wechat: outlookFil
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package cn.universal.dm.device.service.task;
|
||||
|
||||
import cn.universal.common.constant.IoTConstant;
|
||||
import cn.universal.common.utils.DingTalkUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class TcpErrorNoticeTask {
|
||||
|
||||
@Resource private StringRedisTemplate stringRedisTemplate;
|
||||
private final String KEY = "tcpErrorNoticeTask";
|
||||
|
||||
@Scheduled(cron = "0 55 8 * * ? ")
|
||||
public void doTcpErrorNotice() {
|
||||
// 分布式锁,只执行1次,必须设置失效时间
|
||||
boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(KEY, "0", 5, TimeUnit.MINUTES);
|
||||
if (!flag) {
|
||||
return;
|
||||
}
|
||||
log.info("开始执行tcp连接错误次数通知");
|
||||
Map<Object, Object> entries =
|
||||
stringRedisTemplate.opsForHash().entries(IoTConstant.TCP_ERROR_MONITOR);
|
||||
String msg = "tcp异常连接数统计:\n";
|
||||
Iterator<Entry<Object, Object>> iterable = entries.entrySet().iterator();
|
||||
if (!iterable.hasNext()) {
|
||||
log.info("结束tcp连接错误次数通知");
|
||||
return;
|
||||
}
|
||||
while (iterable.hasNext()) {
|
||||
Map.Entry<Object, Object> entry = iterable.next();
|
||||
String host = entry.getKey().toString();
|
||||
Integer num = Integer.parseInt(entry.getValue().toString());
|
||||
msg = msg + String.format("ip=[ %s ]进入黑名单后重复连接[ %d ]次", host, num) + "\n";
|
||||
}
|
||||
DingTalkUtil.send(msg);
|
||||
stringRedisTemplate.delete(IoTConstant.TCP_ERROR_MONITOR);
|
||||
log.info("结束tcp连接错误次数通知");
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -53,8 +53,9 @@ public class IoTDeviceAutoInsertIntercept implements IoTDownWrapper {
|
||||
public R beforeDownAction(IoTProduct product, Object data, DownRequest downRequest) {
|
||||
// 产品为空或者没有开启自动注册直接略过
|
||||
if (ObjectUtil.isEmpty(product)
|
||||
|| !JSONUtil.parseObj(product.getConfiguration())
|
||||
.getBool(IoTConstant.ALLOW_INSERT, false)) {
|
||||
|| (JSONUtil.isTypeJSON(product.getConfiguration())
|
||||
&& !JSONUtil.parseObj(product.getConfiguration())
|
||||
.getBool(IoTConstant.ALLOW_INSERT, false))) {
|
||||
return null;
|
||||
}
|
||||
R r = null;
|
||||
|
||||
+4
-3
@@ -59,12 +59,12 @@ public class IoTDeviceDownFunctionWrapper implements IoTDownWrapper {
|
||||
if (function.startsWith("set") && ObjectUtil.isNotEmpty(data)) {
|
||||
// TODO: 判断物模型属性是否有可读写的
|
||||
|
||||
doShadow(product, ioTDevice, data);
|
||||
doShadow(ioTDevice, data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void doShadow(IoTProduct product, IoTDevice ioTDevice, JSONObject data) {
|
||||
private void doShadow(IoTDevice ioTDevice, JSONObject data) {
|
||||
IoTDeviceShadow ioTDeviceShadow =
|
||||
IoTDeviceShadow.builder()
|
||||
.deviceId(ioTDevice.getDeviceId())
|
||||
@@ -77,10 +77,11 @@ public class IoTDeviceDownFunctionWrapper implements IoTDownWrapper {
|
||||
stringRedisTemplate
|
||||
.opsForValue()
|
||||
.setIfAbsent("doCreateShadow:" + ioTDevice.getIotId(), "1", 10, TimeUnit.MINUTES);
|
||||
if (ObjectUtil.isNull(ioTDeviceShadow) && Boolean.TRUE.equals(flag)) {
|
||||
if (ObjectUtil.isNull(ioTDeviceShadow) && flag) {
|
||||
ioTDeviceShadow =
|
||||
IoTDeviceShadow.builder()
|
||||
.iotId(ioTDevice.getIotId())
|
||||
.productKey(ioTDevice.getProductKey())
|
||||
.extDeviceId(ioTDevice.getExtDeviceId())
|
||||
.deviceId(ioTDevice.getDeviceId())
|
||||
.activeTime(new Date())
|
||||
|
||||
Reference in New Issue
Block a user