From 8c84ac9d8a88dd73d03746274f4f6e2de7004668 Mon Sep 17 00:00:00 2001
From: 安浩浩 <1036606149@qq.com>
Date: Sun, 27 Oct 2024 21:26:35 +0800
Subject: [PATCH] 【新增功能】 mqtt 数据接收
---
yudao-module-iot/yudao-module-iot-biz/pom.xml | 5 +++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessage.java | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java | 14 ++++++++++++--
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java | 227 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java | 26 ++++++++++++++++++++++++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java | 39 +++++++++++++++++++++++++++++++++++++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java | 9 +++++++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataServiceImpl.java | 16 ++++++++--------
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotTdEngineService.java | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotTdEngineServiceImpl.java | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageService.java | 16 ++++++++++++++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java | 13 +++++++++++++
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineService.java | 144 ------------------------------------------------------------------------------------------------------------------------------------------------
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineServiceImpl.java | 107 -----------------------------------------------------------------------------------------------------------
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdEngineMapper.xml | 18 ++++++++++++------
16 files changed, 695 insertions(+), 494 deletions(-)
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessage.java
delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotTdEngineService.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotTdEngineServiceImpl.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageService.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java
delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineService.java
delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineServiceImpl.java
diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml
index cb2fd81..013287d 100644
--- a/yudao-module-iot/yudao-module-iot-biz/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -25,6 +25,11 @@
${revision}
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-biz-tenant
+
+
cn.iocoder.boot
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessage.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessage.java
new file mode 100644
index 0000000..d5009dc
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/ThingModelMessage.java
@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 物模型消息
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class ThingModelMessage {
+
+ /**
+ * 消息ID
+ */
+ private String id;
+
+ /**
+ * 扩展功能的参数
+ */
+ private Object sys;
+
+ /**
+ * 请求方法 例如:thing.event.property.post
+ */
+ private String method;
+
+ /**
+ * 请求参数
+ */
+ private Object params;
+
+ /**
+ * 属性上报时间戳
+ */
+ private Long time;
+
+ /**
+ * 设备信息
+ */
+ private String productKey;
+
+ /**
+ * 设备名称
+ */
+ private String deviceName;
+
+ /**
+ * 设备 key
+ */
+ private String deviceKey;
+
+ /**
+ * 转换为 Map 类型
+ */
+ public Map dataToMap() {
+ Map mapData = new HashMap<>();
+ if (params instanceof Map) {
+ ((Map, ?>) params).forEach((key, value) -> mapData.put(key.toString(), value));
+ }
+ return mapData;
+ }
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java
index 0c1a87f..c82d0d3 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java
@@ -1,5 +1,8 @@
package cn.iocoder.yudao.module.iot.emq.service;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
+import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
@@ -16,13 +19,20 @@ import org.springframework.stereotype.Service;
@Service
public class EmqxServiceImpl implements EmqxService {
+ @Resource
+ private IotDeviceDataService iotDeviceDataService;
+
// TODO 多线程处理消息
@Override
public void subscribeCallback(String topic, MqttMessage mqttMessage) {
log.info("收到消息,主题: {}, 内容: {}", topic, new String(mqttMessage.getPayload()));
// 根据不同的主题,处理不同的业务逻辑
if (topic.contains("/property/post")) {
- // 设备上报数据
+ // 设备上报数据 topic /sys/f13f57c63e9/dianbiao1/thing/event/property/post
+ String productKey = topic.split("/")[2];
+ String deviceName = topic.split("/")[3];
+ String message = new String(mqttMessage.getPayload());
+ iotDeviceDataService.saveDeviceData(productKey, deviceName, message);
}
}
@@ -30,7 +40,7 @@ public class EmqxServiceImpl implements EmqxService {
public void subscribe(MqttClient client) {
try {
// 订阅默认主题,可以根据需要修改
-// client.subscribe("$share/yudao/+/+/#", 1);
+ client.subscribe("/sys/+/+/#", 1);
log.info("订阅默认主题成功");
} catch (Exception e) {
log.error("订阅默认主题失败", e);
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java
deleted file mode 100644
index 2ae08bb..0000000
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.device;
-
-import cn.hutool.core.util.IdUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
-import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
-import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
-import cn.iocoder.yudao.module.iot.service.product.IotProductService;
-import jakarta.annotation.Resource;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.validation.annotation.Validated;
-
-import java.time.LocalDateTime;
-import java.util.UUID;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
-
-/**
- * IoT 设备 Service 实现类
- *
- * @author 芋道源码
- */
-@Service
-@Validated
-@Slf4j
-public class DeviceServiceImpl implements IotDeviceService {
-
- @Resource
- private IotDeviceMapper deviceMapper;
- @Resource
- private IotProductService productService;
-
- /**
- * 创建 IoT 设备
- *
- * @param createReqVO 创建请求 VO
- * @return 设备 ID
- */
- @Override
- @Transactional(rollbackFor = Exception.class)
- public Long createDevice(IotDeviceSaveReqVO createReqVO) {
- // 1.1 校验产品是否存在
- IotProductDO product = productService.getProduct(createReqVO.getProductId());
- if (product == null) {
- throw exception(PRODUCT_NOT_EXISTS);
- }
- // 1.2 校验设备名称在同一产品下是否唯一
- if (StrUtil.isBlank(createReqVO.getDeviceName())) {
- createReqVO.setDeviceName(generateUniqueDeviceName(product.getProductKey()));
- } else {
- validateDeviceNameUnique(product.getProductKey(), createReqVO.getDeviceName());
- }
-
- // 2.1 转换 VO 为 DO
- IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class)
- .setProductKey(product.getProductKey())
- .setDeviceType(product.getDeviceType());
- // 2.2 生成并设置必要的字段
- device.setDeviceKey(generateUniqueDeviceKey());
- device.setDeviceSecret(generateDeviceSecret());
- device.setMqttClientId(generateMqttClientId());
- device.setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey()));
- device.setMqttPassword(generateMqttPassword());
- // 2.3 设置设备状态为未激活
- device.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus());
- device.setStatusLastUpdateTime(LocalDateTime.now());
- // 2.4 插入到数据库
- deviceMapper.insert(device);
- return device.getId();
- }
-
- /**
- * 校验设备名称在同一产品下是否唯一
- *
- * @param productKey 产品 Key
- * @param deviceName 设备名称
- */
- private void validateDeviceNameUnique(String productKey, String deviceName) {
- IotDeviceDO existingDevice = deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
- if (existingDevice != null) {
- throw exception(DEVICE_NAME_EXISTS);
- }
- }
-
- /**
- * 生成唯一的 deviceKey
- *
- * @return 生成的 deviceKey
- */
- private String generateUniqueDeviceKey() {
- return UUID.randomUUID().toString();
- }
-
- /**
- * 生成 deviceSecret
- *
- * @return 生成的 deviceSecret
- */
- private String generateDeviceSecret() {
- return IdUtil.fastSimpleUUID();
- }
-
- /**
- * 生成 MQTT Client ID
- *
- * @return 生成的 MQTT Client ID
- */
- private String generateMqttClientId() {
- return UUID.randomUUID().toString();
- }
-
- /**
- * 生成 MQTT Username
- *
- * @param deviceName 设备名称
- * @param productKey 产品 Key
- * @return 生成的 MQTT Username
- */
- private String generateMqttUsername(String deviceName, String productKey) {
- return deviceName + "&" + productKey;
- }
-
- /**
- * 生成 MQTT Password
- *
- * @return 生成的 MQTT Password
- */
- private String generateMqttPassword() {
- // TODO @haohao:【后续优化】在实际应用中,建议使用更安全的方法生成 MQTT Password,如加密或哈希
- return UUID.randomUUID().toString();
- }
-
- /**
- * 生成唯一的 DeviceName
- *
- * @param productKey 产品标识
- * @return 生成的唯一 DeviceName
- */
- private String generateUniqueDeviceName(String productKey) {
- for (int i = 0; i < Short.MAX_VALUE; i++) {
- String deviceName = IdUtil.fastSimpleUUID().substring(0, 20);
- if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
- return deviceName;
- }
- }
- throw new IllegalArgumentException("生成 DeviceName 失败");
- }
-
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void updateDevice(IotDeviceSaveReqVO updateReqVO) {
- // 1. 校验存在
- validateDeviceExists(updateReqVO.getId());
-
- // 2. 更新到数据库
- IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class)
- .setDeviceName(null).setProductId(null); // 设备名称 和 产品 ID 不能修改
- deviceMapper.updateById(updateObj);
- }
-
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void deleteDevice(Long id) {
- // 1.1 校验存在
- IotDeviceDO device = validateDeviceExists(id);
- // 1.2 如果是网关设备,检查是否有子设备
- if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(id) > 0) {
- throw exception(DEVICE_HAS_CHILDREN);
- }
-
- // 2. 删除设备
- deviceMapper.deleteById(id);
- }
-
- /**
- * 校验设备是否存在
- *
- * @param id 设备 ID
- * @return 设备对象
- */
- private IotDeviceDO validateDeviceExists(Long id) {
- IotDeviceDO device = deviceMapper.selectById(id);
- if (device == null) {
- throw exception(DEVICE_NOT_EXISTS);
- }
- return device;
- }
-
- @Override
- public IotDeviceDO getDevice(Long id) {
- IotDeviceDO device = deviceMapper.selectById(id);
- if (device == null) {
- throw exception(DEVICE_NOT_EXISTS);
- }
- return device;
- }
-
- @Override
- public PageResult getDevicePage(IotDevicePageReqVO pageReqVO) {
- return deviceMapper.selectPage(pageReqVO);
- }
-
- @Override
- public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
- // 校验存在
- validateDeviceExists(updateReqVO.getId());
-
- // 更新状态和更新时间
- IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
- deviceMapper.updateById(updateObj);
- }
-
- @Override
- public Long getDeviceCountByProductId(Long productId) {
- return deviceMapper.selectCountByProductId(productId);
- }
-
-}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java
new file mode 100644
index 0000000..4f3ef37
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.iot.service.device;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import jakarta.validation.Valid;
+
+/**
+ * IoT 设备数据 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface IotDeviceDataService {
+
+
+ /**
+ * 保存设备数据
+ *
+ * @param productKey 产品 key
+ * @param deviceName 设备名称
+ * @param message 消息
+ */
+ void saveDeviceData(String productKey, String deviceName, String message);
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java
new file mode 100644
index 0000000..8f0c859
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.iot.service.device;
+
+import cn.hutool.json.JSONObject;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
+import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class IotDeviceDataServiceImpl implements IotDeviceDataService {
+
+ @Resource
+ private IotDeviceService deviceService;
+ @Resource
+ private IotThingModelMessageService thingModelMessageService;
+
+ @Override
+ public void saveDeviceData(String productKey, String deviceName, String message) {
+ // 1. 根据产品 key 和设备名称,获得设备信息
+ IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(productKey, deviceName);
+ // 2. 解析消息,保存数据
+ JSONObject jsonObject = new JSONObject(message);
+ log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", productKey, deviceName, jsonObject);
+ ThingModelMessage thingModelMessage = ThingModelMessage.builder()
+ .id(jsonObject.getStr("id"))
+ .sys(jsonObject.get("sys"))
+ .method(jsonObject.getStr("method"))
+ .params(jsonObject.get("params"))
+ .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time"))
+ .productKey(productKey)
+ .deviceName(deviceName)
+ .deviceKey(device.getDeviceKey())
+ .build();
+ thingModelMessageService.saveThingModelMessage(thingModelMessage);
+ }
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
index 032a747..c967a95 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
@@ -64,4 +64,13 @@ public interface IotDeviceService {
* @return 设备数量
*/
Long getDeviceCountByProductId(Long productId);
+
+ /**
+ * 根据产品 key 和设备名称,获得设备信息
+ *
+ * @param productKey 产品 key
+ * @param deviceName 设备名称
+ * @return 设备信息
+ */
+ IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName);
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
new file mode 100644
index 0000000..d24975b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
@@ -0,0 +1,234 @@
+package cn.iocoder.yudao.module.iot.service.device;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDevicePageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceSaveReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.IotDeviceStatusUpdateReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
+import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
+
+/**
+ * IoT 设备 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+@Slf4j
+public class IotDeviceServiceImpl implements IotDeviceService {
+
+ @Resource
+ private IotDeviceMapper deviceMapper;
+ @Resource
+ private IotProductService productService;
+
+ /**
+ * 创建 IoT 设备
+ *
+ * @param createReqVO 创建请求 VO
+ * @return 设备 ID
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createDevice(IotDeviceSaveReqVO createReqVO) {
+ // 1.1 校验产品是否存在
+ IotProductDO product = productService.getProduct(createReqVO.getProductId());
+ if (product == null) {
+ throw exception(PRODUCT_NOT_EXISTS);
+ }
+ // 1.2 校验设备名称在同一产品下是否唯一
+ if (StrUtil.isBlank(createReqVO.getDeviceName())) {
+ createReqVO.setDeviceName(generateUniqueDeviceName(product.getProductKey()));
+ } else {
+ validateDeviceNameUnique(product.getProductKey(), createReqVO.getDeviceName());
+ }
+
+ // 2.1 转换 VO 为 DO
+ IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class)
+ .setProductKey(product.getProductKey())
+ .setDeviceType(product.getDeviceType());
+ // 2.2 生成并设置必要的字段
+ device.setDeviceKey(generateUniqueDeviceKey());
+ device.setDeviceSecret(generateDeviceSecret());
+ device.setMqttClientId(generateMqttClientId());
+ device.setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey()));
+ device.setMqttPassword(generateMqttPassword());
+ // 2.3 设置设备状态为未激活
+ device.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus());
+ device.setStatusLastUpdateTime(LocalDateTime.now());
+ // 2.4 插入到数据库
+ deviceMapper.insert(device);
+ return device.getId();
+ }
+
+ /**
+ * 校验设备名称在同一产品下是否唯一
+ *
+ * @param productKey 产品 Key
+ * @param deviceName 设备名称
+ */
+ private void validateDeviceNameUnique(String productKey, String deviceName) {
+ IotDeviceDO existingDevice = deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
+ if (existingDevice != null) {
+ throw exception(DEVICE_NAME_EXISTS);
+ }
+ }
+
+ /**
+ * 生成唯一的 deviceKey
+ *
+ * @return 生成的 deviceKey
+ */
+ private String generateUniqueDeviceKey() {
+ return UUID.randomUUID().toString();
+ }
+
+ /**
+ * 生成 deviceSecret
+ *
+ * @return 生成的 deviceSecret
+ */
+ private String generateDeviceSecret() {
+ return IdUtil.fastSimpleUUID();
+ }
+
+ /**
+ * 生成 MQTT Client ID
+ *
+ * @return 生成的 MQTT Client ID
+ */
+ private String generateMqttClientId() {
+ return UUID.randomUUID().toString();
+ }
+
+ /**
+ * 生成 MQTT Username
+ *
+ * @param deviceName 设备名称
+ * @param productKey 产品 Key
+ * @return 生成的 MQTT Username
+ */
+ private String generateMqttUsername(String deviceName, String productKey) {
+ return deviceName + "&" + productKey;
+ }
+
+ /**
+ * 生成 MQTT Password
+ *
+ * @return 生成的 MQTT Password
+ */
+ private String generateMqttPassword() {
+ // TODO @haohao:【后续优化】在实际应用中,建议使用更安全的方法生成 MQTT Password,如加密或哈希
+ return UUID.randomUUID().toString();
+ }
+
+ /**
+ * 生成唯一的 DeviceName
+ *
+ * @param productKey 产品标识
+ * @return 生成的唯一 DeviceName
+ */
+ private String generateUniqueDeviceName(String productKey) {
+ for (int i = 0; i < Short.MAX_VALUE; i++) {
+ String deviceName = IdUtil.fastSimpleUUID().substring(0, 20);
+ if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
+ return deviceName;
+ }
+ }
+ throw new IllegalArgumentException("生成 DeviceName 失败");
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateDevice(IotDeviceSaveReqVO updateReqVO) {
+ // 1. 校验存在
+ validateDeviceExists(updateReqVO.getId());
+
+ // 2. 更新到数据库
+ IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class)
+ .setDeviceName(null).setProductId(null); // 设备名称 和 产品 ID 不能修改
+ deviceMapper.updateById(updateObj);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteDevice(Long id) {
+ // 1.1 校验存在
+ IotDeviceDO device = validateDeviceExists(id);
+ // 1.2 如果是网关设备,检查是否有子设备
+ if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(id) > 0) {
+ throw exception(DEVICE_HAS_CHILDREN);
+ }
+
+ // 2. 删除设备
+ deviceMapper.deleteById(id);
+ }
+
+ /**
+ * 校验设备是否存在
+ *
+ * @param id 设备 ID
+ * @return 设备对象
+ */
+ private IotDeviceDO validateDeviceExists(Long id) {
+ IotDeviceDO device = deviceMapper.selectById(id);
+ if (device == null) {
+ throw exception(DEVICE_NOT_EXISTS);
+ }
+ return device;
+ }
+
+ @Override
+ public IotDeviceDO getDevice(Long id) {
+ IotDeviceDO device = deviceMapper.selectById(id);
+ if (device == null) {
+ throw exception(DEVICE_NOT_EXISTS);
+ }
+ return device;
+ }
+
+ @Override
+ public PageResult getDevicePage(IotDevicePageReqVO pageReqVO) {
+ return deviceMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
+ // 校验存在
+ validateDeviceExists(updateReqVO.getId());
+
+ // 更新状态和更新时间
+ IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
+ deviceMapper.updateById(updateObj);
+ }
+
+ @Override
+ public Long getDeviceCountByProductId(Long productId) {
+ return deviceMapper.selectCountByProductId(productId);
+ }
+
+ @Override
+ @TenantIgnore
+ public IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName) {
+ return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataServiceImpl.java
index d42fd72..0506546 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataServiceImpl.java
@@ -25,7 +25,7 @@ import java.util.stream.Collectors;
public class IotDbStructureDataServiceImpl implements IotDbStructureDataService {
@Resource
- private TdEngineService tdEngineService;
+ private IotTdEngineService iotTdEngineService;
@Resource
private TdRestApi tdRestApi;
@@ -87,7 +87,7 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
// 5. 创建超级表
String dataBaseName = url.substring(url.lastIndexOf("/") + 1);
- tdEngineService.createSuperTable(schemaFields, tagsFields, dataBaseName, superTableName);
+ iotTdEngineService.createSuperTable(schemaFields, tagsFields, dataBaseName, superTableName);
}
@Override
@@ -107,7 +107,7 @@ public class IotDbStructureDataServiceImpl implements IotDbStructureDataService
private List getTableFields(String tableName) {
List fields = new ArrayList<>();
// 获取超级表的描述信息
- List