Zimo 1 天之前
父节点
当前提交
3720a38220
共有 100 个文件被更改,包括 5186 次插入0 次删除
  1. 142 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
  2. 104 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertConfigController.java
  3. 58 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertRecordController.java
  4. 26 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigPageReqVO.java
  5. 43 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java
  6. 47 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java
  7. 35 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordPageReqVO.java
  8. 19 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordProcessReqVO.java
  9. 43 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordRespVO.java
  10. 101 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.http
  11. 92 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.java
  12. 55 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusConfigController.java
  13. 73 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusPointController.java
  14. 24 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceAuthInfoRespVO.java
  15. 22 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceBindGatewayReqVO.java
  16. 22 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceByProductKeyAndNamesReqVO.java
  17. 22 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUnbindGatewayReqVO.java
  18. 42 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessagePageReqVO.java
  19. 16 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespPairVO.java
  20. 56 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespVO.java
  21. 27 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageSendReqVO.java
  22. 48 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigRespVO.java
  23. 47 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigSaveReqVO.java
  24. 30 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointPageReqVO.java
  25. 55 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointRespVO.java
  26. 54 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointSaveReqVO.java
  27. 25 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyDetailRespVO.java
  28. 31 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyHistoryListReqVO.java
  29. 19 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyRespVO.java
  30. 65 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskController.java
  31. 99 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java
  32. 37 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskCreateReqVO.java
  33. 17 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskPageReqVO.java
  34. 40 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskRespVO.java
  35. 20 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordPageReqVO.java
  36. 48 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordRespVO.java
  37. 5 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.http
  38. 73 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataRuleController.java
  39. 84 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataSinkController.java
  40. 93 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotSceneRuleController.java
  41. 1 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/package-info.java
  42. 26 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRulePageReqVO.java
  43. 35 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleRespVO.java
  44. 40 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleSaveReqVO.java
  45. 34 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkPageReqVO.java
  46. 34 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkRespVO.java
  47. 41 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkSaveReqVO.java
  48. 36 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRulePageReqVO.java
  49. 35 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java
  50. 40 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleSaveReqVO.java
  51. 23 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleUpdateStatusReqVO.java
  52. 11 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.http
  53. 27 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageReqVO.java
  54. 19 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageSummaryByDateRespVO.java
  55. 30 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelTSLRespVO.java
  56. 84 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alert/IotAlertConfigDO.java
  57. 89 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alert/IotAlertRecordDO.java
  58. 109 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java
  59. 82 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusConfigDO.java
  60. 100 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java
  61. 70 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskDO.java
  62. 82 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java
  63. 109 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataRuleDO.java
  64. 62 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataSinkDO.java
  65. 251 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java
  66. 38 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java
  67. 42 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkDatabaseConfig.java
  68. 36 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkHttpConfig.java
  69. 35 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkKafkaConfig.java
  70. 34 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkMqttConfig.java
  71. 46 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRabbitMQConfig.java
  72. 64 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java
  73. 39 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRocketMQConfig.java
  74. 92 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java
  75. 132 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkWebSocketConfig.java
  76. 55 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelEvent.java
  77. 63 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelParam.java
  78. 63 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelProperty.java
  79. 62 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelService.java
  80. 37 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelArrayDataSpecs.java
  81. 30 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelBoolOrEnumDataSpecs.java
  82. 35 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelDataSpecs.java
  83. 34 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelDateOrTextDataSpecs.java
  84. 57 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelNumericDataSpec.java
  85. 57 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelStructDataSpecs.java
  86. 39 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java
  87. 46 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertRecordMapper.java
  88. 30 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusConfigMapper.java
  89. 47 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusPointMapper.java
  90. 32 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java
  91. 80 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java
  92. 42 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java
  93. 37 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataSinkMapper.java
  94. 33 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java
  95. 31 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceServerIdRedisDAO.java
  96. 79 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMessageMapper.java
  97. 25 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
  98. 105 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
  99. 46 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/alert/IotAlertReceiveTypeEnum.java
  100. 34 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskDeviceScopeEnum.java

+ 142 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java

@@ -0,0 +1,142 @@
+package cn.iocoder.yudao.module.iot.api.device;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.RpcConstants;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
+import cn.iocoder.yudao.module.iot.core.biz.dto.*;
+import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
+import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
+import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusConfigService;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.context.annotation.Primary;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+/**
+ * IoT 设备 API 实现类
+ *
+ * @author haohao
+ */
+@RestController
+@Validated
+@Primary // 保证优先匹配,因为 yudao-iot-gateway 也有 IotDeviceCommonApi 的实现,并且也可能会被 biz 引入
+public class IoTDeviceApiImpl implements IotDeviceCommonApi {
+
+    @Resource
+    private IotDeviceService deviceService;
+    @Resource
+    private IotProductService productService;
+    @Resource
+    @Lazy // 延迟加载,解决循环依赖
+    private IotDeviceModbusConfigService modbusConfigService;
+    @Resource
+    @Lazy // 延迟加载,解决循环依赖
+    private IotDeviceModbusPointService modbusPointService;
+
+    @Override
+    @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
+    @PermitAll
+    public CommonResult<Boolean> authDevice(@RequestBody IotDeviceAuthReqDTO authReqDTO) {
+        return success(deviceService.authDevice(authReqDTO));
+    }
+
+    @Override
+    @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/get") // 特殊:方便调用,暂时使用 POST,实际更推荐 GET
+    @PermitAll
+    public CommonResult<IotDeviceRespDTO> getDevice(@RequestBody IotDeviceGetReqDTO getReqDTO) {
+        NewIotDeviceDO device = getReqDTO.getId() != null ? deviceService.getDeviceFromCache(getReqDTO.getId())
+                : deviceService.getDeviceFromCache(getReqDTO.getProductKey(), getReqDTO.getDeviceName());
+        return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
+            IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
+            if (product != null) {
+                deviceDTO.setProtocolType(product.getProtocolType()).setSerializeType(product.getSerializeType());
+            }
+        }));
+    }
+
+    @Override
+    @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/modbus/config-list")
+    @PermitAll
+    @TenantIgnore
+    public CommonResult<List<IotModbusDeviceConfigRespDTO>> getModbusDeviceConfigList(
+            @RequestBody IotModbusDeviceConfigListReqDTO listReqDTO) {
+        // 1. 获取 Modbus 连接配置
+        List<IotDeviceModbusConfigDO> configList = modbusConfigService.getDeviceModbusConfigList(listReqDTO);
+        if (CollUtil.isEmpty(configList)) {
+            return success(new ArrayList<>());
+        }
+
+        // 2. 组装返回结果
+        Set<Long> deviceIds = convertSet(configList, IotDeviceModbusConfigDO::getDeviceId);
+        Map<Long, NewIotDeviceDO> deviceMap = deviceService.getDeviceMap(deviceIds);
+        Map<Long, List<IotDeviceModbusPointDO>> pointMap = modbusPointService.getEnabledDeviceModbusPointMapByDeviceIds(deviceIds);
+        Map<Long, IotProductDO> productMap = productService.getProductMap(convertSet(deviceMap.values(), NewIotDeviceDO::getProductId));
+        List<IotModbusDeviceConfigRespDTO> result = new ArrayList<>(configList.size());
+        for (IotDeviceModbusConfigDO config : configList) {
+            // 3.1 获取设备信息
+            NewIotDeviceDO device = deviceMap.get(config.getDeviceId());
+            if (device == null) {
+                continue;
+            }
+            // 3.2 按 protocolType 筛选(如果非空)
+            if (StrUtil.isNotEmpty(listReqDTO.getProtocolType())) {
+                IotProductDO product = productMap.get(device.getProductId());
+                if (product == null || ObjUtil.notEqual(listReqDTO.getProtocolType(), product.getProtocolType())) {
+                    continue;
+                }
+            }
+            // 3.3 获取启用的点位列表
+            List<IotDeviceModbusPointDO> pointList = pointMap.get(config.getDeviceId());
+            if (CollUtil.isEmpty(pointList)) {
+                continue;
+            }
+
+            // 3.4 构建 IotModbusDeviceConfigRespDTO 对象
+            IotModbusDeviceConfigRespDTO configDTO = BeanUtils.toBean(config, IotModbusDeviceConfigRespDTO.class, o ->
+                    o.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())
+                            .setPoints(BeanUtils.toBean(pointList, IotModbusPointRespDTO.class)));
+            result.add(configDTO);
+        }
+        return success(result);
+    }
+
+    @Override
+    @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/register")
+    @PermitAll
+    public CommonResult<IotDeviceRegisterRespDTO> registerDevice(@RequestBody IotDeviceRegisterReqDTO reqDTO) {
+        return success(deviceService.registerDevice(reqDTO));
+    }
+
+    @Override
+    @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/register-sub")
+    @PermitAll
+    public CommonResult<List<IotSubDeviceRegisterRespDTO>> registerSubDevices(@RequestBody IotSubDeviceRegisterFullReqDTO reqDTO) {
+        return success(deviceService.registerSubDevices(reqDTO));
+    }
+
+}

+ 104 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertConfigController.java

@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.iot.controller.admin.alert;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.alert.vo.config.IotAlertConfigPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
+import cn.iocoder.yudao.module.iot.service.alert.IotAlertConfigService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
+
+@Tag(name = "管理后台 - IoT 告警配置")
+@RestController
+@RequestMapping("/iot/alert-config")
+@Validated
+public class IotAlertConfigController {
+
+    @Resource
+    private IotAlertConfigService alertConfigService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建告警配置")
+    @PreAuthorize("@ss.hasPermission('iot:alert-config:create')")
+    public CommonResult<Long> createAlertConfig(@Valid @RequestBody IotAlertConfigSaveReqVO createReqVO) {
+        return success(alertConfigService.createAlertConfig(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新告警配置")
+    @PreAuthorize("@ss.hasPermission('iot:alert-config:update')")
+    public CommonResult<Boolean> updateAlertConfig(@Valid @RequestBody IotAlertConfigSaveReqVO updateReqVO) {
+        alertConfigService.updateAlertConfig(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除告警配置")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('iot:alert-config:delete')")
+    public CommonResult<Boolean> deleteAlertConfig(@RequestParam("id") Long id) {
+        alertConfigService.deleteAlertConfig(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得告警配置")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('iot:alert-config:query')")
+    public CommonResult<IotAlertConfigRespVO> getAlertConfig(@RequestParam("id") Long id) {
+        IotAlertConfigDO alertConfig = alertConfigService.getAlertConfig(id);
+        return success(BeanUtils.toBean(alertConfig, IotAlertConfigRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得告警配置分页")
+    @PreAuthorize("@ss.hasPermission('iot:alert-config:query')")
+    public CommonResult<PageResult<IotAlertConfigRespVO>> getAlertConfigPage(@Valid IotAlertConfigPageReqVO pageReqVO) {
+        PageResult<IotAlertConfigDO> pageResult = alertConfigService.getAlertConfigPage(pageReqVO);
+
+        // 转换返回
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSetByFlatMap(pageResult.getList(), config -> config.getReceiveUserIds().stream()));
+        return success(BeanUtils.toBean(pageResult, IotAlertConfigRespVO.class, vo -> {
+            vo.setReceiveUserNames(vo.getReceiveUserIds().stream()
+                    .map(userMap::get)
+                    .filter(Objects::nonNull)
+                    .map(AdminUserRespDTO::getNickname)
+                    .collect(Collectors.toList()));
+        }));
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "获得告警配置简单列表", description = "只包含被开启的告警配置,主要用于前端的下拉选项")
+    @PreAuthorize("@ss.hasPermission('iot:alert-config:query')")
+    public CommonResult<List<IotAlertConfigRespVO>> getAlertConfigSimpleList() {
+        List<IotAlertConfigDO> list = alertConfigService.getAlertConfigListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        return success(convertList(list, config -> // 只返回 id、name 字段
+                new IotAlertConfigRespVO().setId(config.getId()).setName(config.getName())));
+    }
+
+}

+ 58 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertRecordController.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.iot.controller.admin.alert;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.alert.vo.recrod.IotAlertRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordProcessReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
+import cn.iocoder.yudao.module.iot.service.alert.IotAlertRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static java.util.Collections.singleton;
+
+@Tag(name = "管理后台 - IoT 告警记录")
+@RestController
+@RequestMapping("/iot/alert-record")
+@Validated
+public class IotAlertRecordController {
+
+    @Resource
+    private IotAlertRecordService alertRecordService;
+
+    @GetMapping("/get")
+    @Operation(summary = "获得告警记录")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('iot:alert-record:query')")
+    public CommonResult<IotAlertRecordRespVO> getAlertRecord(@RequestParam("id") Long id) {
+        IotAlertRecordDO alertRecord = alertRecordService.getAlertRecord(id);
+        return success(BeanUtils.toBean(alertRecord, IotAlertRecordRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得告警记录分页")
+    @PreAuthorize("@ss.hasPermission('iot:alert-record:query')")
+    public CommonResult<PageResult<IotAlertRecordRespVO>> getAlertRecordPage(@Valid IotAlertRecordPageReqVO pageReqVO) {
+        PageResult<IotAlertRecordDO> pageResult = alertRecordService.getAlertRecordPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotAlertRecordRespVO.class));
+    }
+
+    @PutMapping("/process")
+    @Operation(summary = "处理告警记录")
+    @PreAuthorize("@ss.hasPermission('iot:alert-record:process')")
+    public CommonResult<Boolean> processAlertRecord(@Valid @RequestBody IotAlertRecordProcessReqVO processReqVO) {
+        alertRecordService.processAlertRecordList(singleton(processReqVO.getId()), processReqVO.getProcessRemark());
+        return success(true);
+    }
+
+}

+ 26 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigPageReqVO.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 告警配置分页 Request VO")
+@Data
+public class IotAlertConfigPageReqVO extends PageParam {
+
+    @Schema(description = "配置名称", example = "赵六")
+    private String name;
+
+    @Schema(description = "配置状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 43 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - IoT 告警配置 Response VO")
+@Data
+public class IotAlertConfigRespVO {
+
+    @Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3566")
+    private Long id;
+
+    @Schema(description = "配置名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    private String name;
+
+    @Schema(description = "配置描述", example = "你猜")
+    private String description;
+
+    @Schema(description = "告警级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer level;
+
+    @Schema(description = "配置状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "关联的场景联动规则编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
+    private List<Long> sceneRuleIds;
+
+    @Schema(description = "接收的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "100,200")
+    private List<Long> receiveUserIds;
+
+    @Schema(description = "接收的用户名称数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三,李四")
+    private List<String> receiveUserNames;
+
+    @Schema(description = "接收的类型数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
+    private List<Integer> receiveTypes;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 47 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - IoT 告警配置新增/修改 Request VO")
+@Data
+public class IotAlertConfigSaveReqVO {
+
+    @Schema(description = "配置编号", example = "3566")
+    private Long id;
+
+    @Schema(description = "配置名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @NotEmpty(message = "配置名称不能为空")
+    private String name;
+
+    @Schema(description = "配置描述", example = "你猜")
+    private String description;
+
+    @Schema(description = "告警级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "告警级别不能为空")
+    private Integer level;
+
+    @Schema(description = "配置状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "配置状态不能为空")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "关联的场景联动规则编号数组")
+    @NotEmpty(message = "关联的场景联动规则编号数组不能为空")
+    private List<Long> sceneRuleIds;
+
+    @Schema(description = "接收的用户编号数组")
+    @NotEmpty(message = "接收的用户编号数组不能为空")
+    private List<Long> receiveUserIds;
+
+    @Schema(description = "接收的类型数组")
+    @NotEmpty(message = "接收的类型数组不能为空")
+    private List<Integer> receiveTypes;
+
+}

+ 35 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordPageReqVO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 告警记录分页 Request VO")
+@Data
+public class IotAlertRecordPageReqVO extends PageParam {
+
+    @Schema(description = "告警配置编号", example = "29320")
+    private Long configId;
+
+    @Schema(description = "告警级别", example = "1")
+    private Integer level;
+
+    @Schema(description = "产品编号", example = "2050")
+    private Long productId;
+
+    @Schema(description = "设备编号", example = "21727")
+    private String deviceId;
+
+    @Schema(description = "是否处理", example = "true")
+    private Boolean processStatus;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 19 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordProcessReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - IoT 告警记录处理 Request VO")
+@Data
+public class IotAlertRecordProcessReqVO {
+
+    @Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "记录编号不能为空")
+    private Long id;
+
+    @Schema(description = "处理结果(备注)", requiredMode = Schema.RequiredMode.REQUIRED, example = "已处理告警,问题已解决")
+    private String processRemark;
+
+}

+ 43 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordRespVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod;
+
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IoT 告警记录 Response VO")
+@Data
+public class IotAlertRecordRespVO {
+
+    @Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19904")
+    private Long id;
+
+    @Schema(description = "告警配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29320")
+    private Long configId;
+
+    @Schema(description = "告警名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+    private String configName;
+
+    @Schema(description = "告警级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer configLevel;
+
+    @Schema(description = "产品编号", example = "2050")
+    private Long productId;
+
+    @Schema(description = "设备编号", example = "21727")
+    private Long deviceId;
+
+    @Schema(description = "触发的设备消息")
+    private IotDeviceMessage deviceMessage;
+
+    @Schema(description = "是否处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Boolean processStatus;
+
+    @Schema(description = "处理结果(备注)", example = "你说的对")
+    private String processRemark;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 101 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.http

@@ -0,0 +1,101 @@
+### 请求 /iot/device/message/send 接口(属性上报)=> 成功
+POST {{baseUrl}}/iot/device/message/send
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+Authorization: Bearer {{token}}
+
+{
+  "deviceId": 25,
+  "method": "thing.property.post",
+  "params": {
+    "width": 1,
+    "height": "2",
+    "oneThree": "3"
+  }
+}
+
+### 请求 /iot/device/downstream 接口(服务调用)=> 成功 TODO 芋艿:未更新为最新
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+Authorization: Bearer {{token}}
+
+{
+  "id": 25,
+  "type": "service",
+  "identifier": "temperature",
+  "data": {
+    "xx": "yy"
+  }
+}
+
+### 请求 /iot/device/downstream 接口(属性设置)=> 成功 TODO 芋艿:未更新为最新
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+Authorization: Bearer {{token}}
+
+{
+  "id": 25,
+  "type": "property",
+  "identifier": "set",
+  "data": {
+    "xx": "yy"
+  }
+}
+
+### 请求 /iot/device/downstream 接口(属性获取)=> 成功 TODO 芋艿:未更新为最新
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+Authorization: Bearer {{token}}
+
+{
+  "id": 25,
+  "type": "property",
+  "identifier": "get",
+  "data": ["xx", "yy"]
+}
+
+### 请求 /iot/device/downstream 接口(配置设置)=> 成功 TODO 芋艿:未更新为最新
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+Authorization: Bearer {{token}}
+
+{
+  "id": 25,
+  "type": "config",
+  "identifier": "set"
+}
+
+### 请求 /iot/device/downstream 接口(OTA 升级)=> 成功 TODO 芋艿:未更新为最新
+POST {{baseUrl}}/iot/device/downstream
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+Authorization: Bearer {{token}}
+
+{
+  "id": 25,
+  "type": "ota",
+  "identifier": "upgrade",
+  "data": {
+    "firmwareId": 1,
+    "version": "1.0.0",
+    "signMethod": "MD5",
+    "fileSign": "d41d8cd98f00b204e9800998ecf8427e",
+    "fileSize": 1024,
+    "fileUrl": "http://example.com/firmware.bin",
+    "information": "{\"desc\":\"升级到最新版本\"}"
+  }
+}
+
+### 查询设备消息对分页 - 基础查询(设备编号25)
+GET {{baseUrl}}/iot/device/message/pair-page?deviceId=25&pageNo=1&pageSize=10
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenantId}}
+
+### 查询设备消息对分页 - 按标识符过滤(identifier=eat)
+GET {{baseUrl}}/iot/device/message/pair-page?deviceId=25&identifier=eat&pageNo=1&pageSize=10
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenantId}}

+ 92 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.java

@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.message.IotDeviceMessagePageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageRespPairVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessageSendReqVO;
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
+import cn.iocoder.yudao.module.iot.dal.tdengine.IotDeviceMessageMapper;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
+import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
+import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+@Tag(name = "管理后台 - IoT 设备消息")
+@RestController
+@RequestMapping("/iot/device/message")
+@Validated
+public class IotDeviceMessageController {
+
+    @Resource
+    private IotDeviceMessageService deviceMessageService;
+    @Resource
+    private IotDeviceService deviceService;
+    @Resource
+    private IotThingModelService thingModelService;
+    @Resource
+    private IotDeviceMessageMapper deviceMessageMapper;
+
+    @GetMapping("/page")
+    @Operation(summary = "获得设备消息分页")
+    @PreAuthorize("@ss.hasPermission('iot:device:message-query')")
+    public CommonResult<PageResult<IotDeviceMessageRespVO>> getDeviceMessagePage(
+            @Valid IotDeviceMessagePageReqVO pageReqVO) {
+        PageResult<IotDeviceMessageDO> pageResult = deviceMessageService.getDeviceMessagePage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotDeviceMessageRespVO.class));
+    }
+
+    @GetMapping("/pair-page")
+    @Operation(summary = "获得设备消息对分页")
+    @PreAuthorize("@ss.hasPermission('iot:device:message-query')")
+    public CommonResult<PageResult<IotDeviceMessageRespPairVO>> getDeviceMessagePairPage(
+            @Valid IotDeviceMessagePageReqVO pageReqVO) {
+        // 1.1 先按照条件,查询 request 的消息(非 reply)
+        pageReqVO.setReply(false);
+        PageResult<IotDeviceMessageDO> requestMessagePageResult = deviceMessageService.getDeviceMessagePage(pageReqVO);
+        if (CollUtil.isEmpty(requestMessagePageResult.getList())) {
+            return success(PageResult.empty());
+        }
+        // 1.2 接着按照 requestIds,批量查询 reply 消息
+        List<String> requestIds = convertList(requestMessagePageResult.getList(), IotDeviceMessageDO::getRequestId);
+        List<IotDeviceMessageDO> replyMessageList = deviceMessageService.getDeviceMessageListByRequestIdsAndReply(
+                pageReqVO.getDeviceId(), requestIds, true);
+        Map<String, IotDeviceMessageDO> replyMessages = convertMap(replyMessageList, IotDeviceMessageDO::getRequestId);
+
+        // 2. 组装结果
+        List<IotDeviceMessageRespPairVO> pairMessages = convertList(requestMessagePageResult.getList(),
+                requestMessage -> {
+            IotDeviceMessageDO replyMessage = replyMessages.get(requestMessage.getRequestId());
+            return new IotDeviceMessageRespPairVO()
+                    .setRequest(BeanUtils.toBean(requestMessage, IotDeviceMessageRespVO.class))
+                    .setReply(BeanUtils.toBean(replyMessage, IotDeviceMessageRespVO.class));
+        });
+        return success(new PageResult<>(pairMessages, requestMessagePageResult.getTotal()));
+    }
+
+    @PostMapping("/send")
+    @Operation(summary = "发送消息", description = "可用于设备模拟")
+    @PreAuthorize("@ss.hasPermission('iot:device:message-end')")
+    public CommonResult<Boolean> sendDeviceMessage(@Valid @RequestBody IotDeviceMessageSendReqVO sendReqVO) {
+        deviceMessageService.sendDeviceMessage(BeanUtils.toBean(sendReqVO, IotDeviceMessage.class));
+        return success(true);
+    }
+
+}

+ 55 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusConfigController.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IoT 设备 Modbus 连接配置")
+@RestController
+@RequestMapping("/iot/device-modbus-config")
+@Validated
+public class IotDeviceModbusConfigController {
+
+    @Resource
+    private IotDeviceModbusConfigService modbusConfigService;
+
+    @PostMapping("/save")
+    @Operation(summary = "保存设备 Modbus 连接配置")
+    @PreAuthorize("@ss.hasPermission('iot:device:update')")
+    public CommonResult<Boolean> saveDeviceModbusConfig(@Valid @RequestBody IotDeviceModbusConfigSaveReqVO saveReqVO) {
+        modbusConfigService.saveDeviceModbusConfig(saveReqVO);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得设备 Modbus 连接配置")
+    @Parameter(name = "id", description = "编号", example = "1024")
+    @Parameter(name = "deviceId", description = "设备编号", example = "2048")
+    @PreAuthorize("@ss.hasPermission('iot:device:query')")
+    public CommonResult<IotDeviceModbusConfigRespVO> getDeviceModbusConfig(
+            @RequestParam(value = "id", required = false) Long id,
+            @RequestParam(value = "deviceId", required = false) Long deviceId) {
+        IotDeviceModbusConfigDO modbusConfig = null;
+        if (id != null) {
+            modbusConfig = modbusConfigService.getDeviceModbusConfig(id);
+        } else if (deviceId != null) {
+            modbusConfig = modbusConfigService.getDeviceModbusConfigByDeviceId(deviceId);
+        }
+        return success(BeanUtils.toBean(modbusConfig, IotDeviceModbusConfigRespVO.class));
+    }
+
+}

+ 73 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusPointController.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.modbus.IotDeviceModbusPointPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IoT 设备 Modbus 点位配置")
+@RestController
+@RequestMapping("/iot/device-modbus-point")
+@Validated
+public class IotDeviceModbusPointController {
+
+    @Resource
+    private IotDeviceModbusPointService modbusPointService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建设备 Modbus 点位配置")
+    @PreAuthorize("@ss.hasPermission('iot:device:update')")
+    public CommonResult<Long> createDeviceModbusPoint(@Valid @RequestBody IotDeviceModbusPointSaveReqVO createReqVO) {
+        return success(modbusPointService.createDeviceModbusPoint(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新设备 Modbus 点位配置")
+    @PreAuthorize("@ss.hasPermission('iot:device:update')")
+    public CommonResult<Boolean> updateDeviceModbusPoint(@Valid @RequestBody IotDeviceModbusPointSaveReqVO updateReqVO) {
+        modbusPointService.updateDeviceModbusPoint(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除设备 Modbus 点位配置")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('iot:device:update')")
+    public CommonResult<Boolean> deleteDeviceModbusPoint(@RequestParam("id") Long id) {
+        modbusPointService.deleteDeviceModbusPoint(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得设备 Modbus 点位配置")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('iot:device:query')")
+    public CommonResult<IotDeviceModbusPointRespVO> getDeviceModbusPoint(@RequestParam("id") Long id) {
+        IotDeviceModbusPointDO modbusPoint = modbusPointService.getDeviceModbusPoint(id);
+        return success(BeanUtils.toBean(modbusPoint, IotDeviceModbusPointRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得设备 Modbus 点位配置分页")
+    @PreAuthorize("@ss.hasPermission('iot:device:query')")
+    public CommonResult<PageResult<IotDeviceModbusPointRespVO>> getDeviceModbusPointPage(@Valid IotDeviceModbusPointPageReqVO pageReqVO) {
+        PageResult<IotDeviceModbusPointDO> pageResult = modbusPointService.getDeviceModbusPointPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotDeviceModbusPointRespVO.class));
+    }
+
+}

+ 24 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceAuthInfoRespVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Schema(description = "管理后台 - IoT 设备认证信息 Response VO")
+@Data
+public class IotDeviceAuthInfoRespVO {
+
+    @Schema(description = "客户端 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123.device001")
+    @NotBlank(message = "客户端 ID 不能为空")
+    private String clientId;
+
+    @Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "device001&product123")
+    @NotBlank(message = "用户名不能为空")
+    private String username;
+
+    @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1a2b3c4d5e6f7890abcdef1234567890")
+    @NotBlank(message = "密码不能为空")
+    private String password;
+
+}

+ 22 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceBindGatewayReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Set;
+
+@Schema(description = "管理后台 - IoT 设备绑定网关 Request VO")
+@Data
+public class IotDeviceBindGatewayReqVO {
+
+    @Schema(description = "子设备编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
+    @NotEmpty(message = "子设备编号列表不能为空")
+    private Set<Long> subIds;
+
+    @Schema(description = "网关设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @NotNull(message = "网关设备编号不能为空")
+    private Long gatewayId;
+
+}

+ 22 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceByProductKeyAndNamesReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+@Schema(description = "管理后台 - 通过产品标识和设备名称列表获取设备 Request VO")
+@Data
+public class IotDeviceByProductKeyAndNamesReqVO {
+
+    @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "1de24640dfe")
+    @NotBlank(message = "产品标识不能为空")
+    private String productKey;
+
+    @Schema(description = "设备名称列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "device001,device002")
+    @NotEmpty(message = "设备名称列表不能为空")
+    private List<String> deviceNames;
+
+}

+ 22 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUnbindGatewayReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Set;
+
+@Schema(description = "管理后台 - IoT 设备解绑网关 Request VO")
+@Data
+public class IotDeviceUnbindGatewayReqVO {
+
+    @Schema(description = "子设备编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
+    @NotEmpty(message = "子设备编号列表不能为空")
+    private Set<Long> subIds;
+
+    @Schema(description = "网关设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "网关设备编号不能为空")
+    private Long gatewayId;
+
+}

+ 42 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessagePageReqVO.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 设备消息分页查询 Request VO")
+@Data
+public class IotDeviceMessagePageReqVO extends PageParam {
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "设备编号不能为空")
+    private Long deviceId;
+
+    @Schema(description = "消息类型", example = "property")
+    @InEnum(IotDeviceMessageMethodEnum.class)
+    private String method;
+
+    @Schema(description = "是否上行", example = "true")
+    private Boolean upstream;
+
+    @Schema(description = "是否回复", example = "true")
+    private Boolean reply;
+
+    @Schema(description = "标识符", example = "temperature")
+    private String identifier;
+
+    @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @Size(min = 2, max = 2, message = "请选择时间范围")
+    private LocalDateTime[] times;
+
+}

+ 16 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespPairVO.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备消息对 Response VO")
+@Data
+public class IotDeviceMessageRespPairVO {
+
+    @Schema(description = "请求消息", requiredMode = Schema.RequiredMode.REQUIRED)
+    private IotDeviceMessageRespVO request;
+
+    @Schema(description = "响应消息")
+    private IotDeviceMessageRespVO reply; // 通过 requestId 配对
+
+}

+ 56 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespVO.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IoT 设备消息 Response VO")
+@Data
+public class IotDeviceMessageRespVO {
+
+    @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private String id;
+
+    @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime reportTime;
+
+    @Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime ts;
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
+    private Long deviceId;
+
+    @Schema(description = "服务编号", example = "server_123")
+    private String serverId;
+
+    @Schema(description = "是否上行消息", example = "true", examples = "false")
+    private Boolean upstream;
+
+    @Schema(description = "是否回复消息", example = "false", examples = "true")
+    private Boolean reply;
+
+    @Schema(description = "标识符", example = "temperature")
+    private String identifier;
+
+    // ========== codec(编解码)字段 ==========
+
+    @Schema(description = "请求编号", example = "req_123")
+    private String requestId;
+
+    @Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "thing.property.post")
+    private String method;
+
+    @Schema(description = "请求参数")
+    private Object params;
+
+    @Schema(description = "响应结果")
+    private Object data;
+
+    @Schema(description = "响应错误码", example = "200")
+    private Integer code;
+
+    @Schema(description = "响应提示", example = "操作成功")
+    private String msg;
+
+}

+ 27 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageSendReqVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.message;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - IoT 设备消息发送 Request VO") // 属性上报、事件上报、状态变更等
+@Data
+public class IotDeviceMessageSendReqVO {
+
+    @Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "report")
+    @NotEmpty(message = "请求方法不能为空")
+    @InEnum(IotDeviceMessageMethodEnum.class)
+    private String method;
+
+    @Schema(description = "请求参数")
+    private Object params; // 例如说:属性上报的 properties、事件上报的 params
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
+    @NotNull(message = "设备编号不能为空")
+    private Long deviceId;
+
+}

+ 48 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigRespVO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置 Response VO")
+@Data
+public class IotDeviceModbusConfigRespVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long deviceId;
+
+    @Schema(description = "设备名称", example = "温湿度传感器")
+    private String deviceName;
+
+    @Schema(description = "Modbus 服务器 IP 地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100")
+    private String ip;
+
+    @Schema(description = "Modbus 端口", requiredMode = Schema.RequiredMode.REQUIRED, example = "502")
+    private Integer port;
+
+    @Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer slaveId;
+
+    @Schema(description = "连接超时时间(毫秒)", example = "3000")
+    private Integer timeout;
+
+    @Schema(description = "重试间隔(毫秒)", example = "1000")
+    private Integer retryInterval;
+
+    @Schema(description = "工作模式", example = "1")
+    private Integer mode;
+
+    @Schema(description = "数据帧格式", example = "1")
+    private Integer frameFormat;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    private Integer status;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 47 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigSaveReqVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusModeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置新增/修改 Request VO")
+@Data
+public class IotDeviceModbusConfigSaveReqVO {
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "设备编号不能为空")
+    private Long deviceId;
+
+    @Schema(description = "Modbus 服务器 IP 地址", example = "192.168.1.100")
+    private String ip;
+
+    @Schema(description = "Modbus 端口", example = "502")
+    private Integer port;
+
+    @Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "从站地址不能为空")
+    private Integer slaveId;
+
+    @Schema(description = "连接超时时间(毫秒)", example = "3000")
+    private Integer timeout;
+
+    @Schema(description = "重试间隔(毫秒)", example = "1000")
+    private Integer retryInterval;
+
+    @Schema(description = "工作模式", example = "1")
+    @InEnum(IotModbusModeEnum.class)
+    private Integer mode;
+
+    @Schema(description = "数据帧格式", example = "1")
+    @InEnum(IotModbusFrameFormatEnum.class)
+    private Integer frameFormat;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+}

+ 30 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointPageReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotDeviceModbusPointPageReqVO extends PageParam {
+
+    @Schema(description = "设备编号", example = "1024")
+    private Long deviceId;
+
+    @Schema(description = "属性标识符", example = "temperature")
+    private String identifier;
+
+    @Schema(description = "属性名称", example = "温度")
+    private String name;
+
+    @Schema(description = "Modbus 功能码", example = "3")
+    private Integer functionCode;
+
+    @Schema(description = "状态", example = "0")
+    private Integer status;
+
+}

+ 55 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointRespVO.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置 Response VO")
+@Data
+public class IotDeviceModbusPointRespVO {
+
+    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long deviceId;
+
+    @Schema(description = "物模型属性编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    private Long thingModelId;
+
+    @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature")
+    private String identifier;
+
+    @Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "温度")
+    private String name;
+
+    @Schema(description = "Modbus 功能码", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+    private Integer functionCode;
+
+    @Schema(description = "寄存器起始地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    private Integer registerAddress;
+
+    @Schema(description = "寄存器数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer registerCount;
+
+    @Schema(description = "字节序", requiredMode = Schema.RequiredMode.REQUIRED, example = "AB")
+    private String byteOrder;
+
+    @Schema(description = "原始数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "INT16")
+    private String rawDataType;
+
+    @Schema(description = "缩放因子", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
+    private BigDecimal scale;
+
+    @Schema(description = "轮询间隔(毫秒)", requiredMode = Schema.RequiredMode.REQUIRED, example = "5000")
+    private Integer pollInterval;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    private Integer status;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 54 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointSaveReqVO.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置新增/修改 Request VO")
+@Data
+public class IotDeviceModbusPointSaveReqVO {
+
+    @Schema(description = "主键", example = "1")
+    private Long id;
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "设备编号不能为空")
+    private Long deviceId;
+
+    @Schema(description = "物模型属性编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    @NotNull(message = "物模型属性编号不能为空")
+    private Long thingModelId;
+
+    @Schema(description = "Modbus 功能码", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+    @NotNull(message = "Modbus 功能码不能为空")
+    private Integer functionCode;
+
+    @Schema(description = "寄存器起始地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "寄存器起始地址不能为空")
+    private Integer registerAddress;
+
+    @Schema(description = "寄存器数量", example = "1")
+    private Integer registerCount;
+
+    @Schema(description = "字节序", requiredMode = Schema.RequiredMode.REQUIRED, example = "AB")
+    @NotEmpty(message = "字节序不能为空")
+    private String byteOrder;
+
+    @Schema(description = "原始数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "INT16")
+    @NotEmpty(message = "原始数据类型不能为空")
+    private String rawDataType;
+
+    @Schema(description = "缩放因子", example = "1.0")
+    private BigDecimal scale;
+
+    @Schema(description = "轮询间隔(毫秒)", example = "5000")
+    private Integer pollInterval;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+}

+ 25 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyDetailRespVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.property;
+
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - IoT 设备属性详细 Response VO") // 额外增加 来自 ThingModelProperty 的变量 属性
+@Data
+public class IotDevicePropertyDetailRespVO extends IotDevicePropertyRespVO {
+
+    @Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String name;
+
+    @Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "int")
+    private String dataType;
+
+    @Schema(description = "数据定义")
+    private ThingModelDataSpecs dataSpecs;
+
+    @Schema(description = "数据定义列表")
+    private List<ThingModelDataSpecs> dataSpecsList;
+
+}

+ 31 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyHistoryListReqVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.property;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 设备属性历史列表 Request VO")
+@Data
+public class IotDevicePropertyHistoryListReqVO {
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
+    @NotNull(message = "设备编号不能为空")
+    private Long deviceId;
+
+    @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "属性标识符不能为空")
+    private String identifier;
+
+    @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @Size(min = 2, max = 2, message = "请选择时间范围")
+    private LocalDateTime[] times;
+
+}

+ 19 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.property;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备属性 Response VO")
+@Data
+public class IotDevicePropertyRespVO {
+
+    @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String identifier;
+
+    @Schema(description = "属性值", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Object value;
+
+    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long updateTime; // 由于从 TDengine 查询出来的是 Long 类型,所以这里也使用 Long 类型
+
+}

+ 65 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskController.java

@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.iot.controller.admin.ota;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.ota.vo.task.IotOtaTaskCreateReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
+import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IoT OTA 升级任务")
+@RestController
+@RequestMapping("/iot/ota/task")
+@Validated
+public class IotOtaTaskController {
+
+    @Resource
+    private IotOtaTaskService otaTaskService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建 OTA 升级任务")
+    @PreAuthorize(value = "@ss.hasPermission('iot:ota-task:create')")
+    public CommonResult<Long> createOtaTask(@Valid @RequestBody IotOtaTaskCreateReqVO createReqVO) {
+        return success(otaTaskService.createOtaTask(createReqVO));
+    }
+
+    @PostMapping("/cancel")
+    @Operation(summary = "取消 OTA 升级任务")
+    @Parameter(name = "id", description = "升级任务编号", required = true)
+    @PreAuthorize(value = "@ss.hasPermission('iot:ota-task:cancel')")
+    public CommonResult<Boolean> cancelOtaTask(@RequestParam("id") Long id) {
+        otaTaskService.cancelOtaTask(id);
+        return success(true);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得 OTA 升级任务分页")
+    @PreAuthorize(value = "@ss.hasPermission('iot:ota-task:query')")
+    public CommonResult<PageResult<IotOtaTaskRespVO>> getOtaTaskPage(@Valid IotOtaTaskPageReqVO pageReqVO) {
+        PageResult<IotOtaTaskDO> pageResult = otaTaskService.getOtaTaskPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotOtaTaskRespVO.class));
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得 OTA 升级任务")
+    @Parameter(name = "id", description = "升级任务编号", required = true, example = "1024")
+    @PreAuthorize(value = "@ss.hasPermission('iot:ota-task:query')")
+    public CommonResult<IotOtaTaskRespVO> getOtaTask(@RequestParam("id") Long id) {
+        IotOtaTaskDO upgradeTask = otaTaskService.getOtaTask(id);
+        return success(BeanUtils.toBean(upgradeTask, IotOtaTaskRespVO.class));
+    }
+
+}

+ 99 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java

@@ -0,0 +1,99 @@
+package cn.iocoder.yudao.module.iot.controller.admin.ota;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
+import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
+import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "管理后台 - IoT OTA 升级任务记录")
+@RestController
+@RequestMapping("/iot/ota/task/record")
+@Validated
+public class IotOtaTaskRecordController {
+
+    @Resource
+    private IotOtaTaskRecordService otaTaskRecordService;
+    @Resource
+    private IotDeviceService deviceService;
+    @Resource
+    private IotOtaFirmwareService otaFirmwareService;
+
+    @GetMapping("/get-status-statistics")
+    @Operation(summary = "获得 OTA 升级记录状态统计")
+    @Parameters({
+        @Parameter(name = "firmwareId", description = "固件编号", example = "1024"),
+        @Parameter(name = "taskId", description = "升级任务编号", example = "2048")
+    })
+    @PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
+    public CommonResult<Map<Integer, Long>> getOtaTaskRecordStatusStatistics(
+            @RequestParam(value = "firmwareId", required = false) Long firmwareId,
+            @RequestParam(value = "taskId", required = false) Long taskId) {
+        return success(otaTaskRecordService.getOtaTaskRecordStatusStatistics(firmwareId, taskId));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得 OTA 升级记录分页")
+    @PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
+    public CommonResult<PageResult<IotOtaTaskRecordRespVO>> getOtaTaskRecordPage(
+            @Valid IotOtaTaskRecordPageReqVO pageReqVO) {
+        PageResult<IotOtaTaskRecordDO> pageResult = otaTaskRecordService.getOtaTaskRecordPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty());
+        }
+
+         // 批量查询固件信息
+         Map<Long, IotOtaFirmwareDO> firmwareMap = otaFirmwareService.getOtaFirmwareMap(
+            convertSet(pageResult.getList(), IotOtaTaskRecordDO::getFromFirmwareId));
+        Map<Long, NewIotDeviceDO> deviceMap = deviceService.getDeviceMap(
+            convertSet(pageResult.getList(), IotOtaTaskRecordDO::getDeviceId));
+        // 转换为响应 VO
+        return success(BeanUtils.toBean(pageResult, IotOtaTaskRecordRespVO.class, (vo) -> {
+            MapUtils.findAndThen(firmwareMap, vo.getFromFirmwareId(), firmware ->
+                vo.setFromFirmwareVersion(firmware.getVersion()));
+            MapUtils.findAndThen(deviceMap, vo.getDeviceId(), device ->
+                vo.setDeviceName(device.getDeviceName()));
+        }));
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得 OTA 升级记录")
+    @PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
+    @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
+    public CommonResult<IotOtaTaskRecordRespVO> getOtaTaskRecord(@RequestParam("id") Long id) {
+        IotOtaTaskRecordDO upgradeRecord = otaTaskRecordService.getOtaTaskRecord(id);
+        return success(BeanUtils.toBean(upgradeRecord, IotOtaTaskRecordRespVO.class));
+    }
+
+    @PutMapping("/cancel")
+    @Operation(summary = "取消 OTA 升级记录")
+    @PreAuthorize("@ss.hasPermission('iot:ota-task-record:cancel')")
+    @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
+    public CommonResult<Boolean> cancelOtaTaskRecord(@RequestParam("id") Long id) {
+        otaTaskRecordService.cancelOtaTaskRecord(id);
+        return success(true);
+    }
+
+}

+ 37 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskCreateReqVO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskDeviceScopeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - IoT OTA 升级任务创建 Request VO")
+@Data
+public class IotOtaTaskCreateReqVO {
+
+    @NotEmpty(message = "任务名称不能为空")
+    @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "升级任务")
+    private String name;
+
+    @Schema(description = "任务描述", example = "升级任务")
+    private String description;
+
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "固件编号不能为空")
+    private Long firmwareId;
+
+    @Schema(description = "升级范围", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "升级范围不能为空")
+    @InEnum(value = IotOtaTaskDeviceScopeEnum.class)
+    private Integer deviceScope;
+
+    @Schema(description = "选中的设备编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
+    private List<Long> deviceIds;
+
+    // TODO @li:如果 deviceScope 等于 2 时,deviceIds 校验非空;
+
+}

+ 17 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskPageReqVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT OTA 升级任务分页 Request VO")
+@Data
+public class IotOtaTaskPageReqVO extends PageParam {
+
+    @Schema(description = "任务名称", example = "升级任务")
+    private String name;
+
+    @Schema(description = "固件编号", example = "1024")
+    private Long firmwareId;
+
+}

+ 40 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskRespVO.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task;
+
+import com.fhs.core.trans.vo.VO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IoT OTA 升级任务 Response VO")
+@Data
+public class IotOtaTaskRespVO implements VO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "升级任务")
+    private String name;
+
+    @Schema(description = "任务描述", example = "升级任务")
+    private String description;
+
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long firmwareId;
+
+    @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer status;
+
+    @Schema(description = "升级范围", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer deviceScope;
+
+    @Schema(description = "设备总共数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Integer deviceTotalCount;
+
+    @Schema(description = "设备成功数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "66")
+    private Integer deviceSuccessCount;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-08 07:30:00")
+    private LocalDateTime createTime;
+
+}

+ 20 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordPageReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT OTA 升级记录分页 Request VO")
+@Data
+public class IotOtaTaskRecordPageReqVO extends PageParam {
+
+    @Schema(description = "升级任务编号", example = "1024")
+    private Long taskId;
+
+    @Schema(description = "升级记录状态", example = "5")
+    @InEnum(IotOtaTaskRecordStatusEnum.class)
+    private Integer status;
+
+}

+ 48 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/record/IotOtaTaskRecordRespVO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IoT OTA 升级任务记录 Response VO")
+@Data
+public class IotOtaTaskRecordRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long firmwareId;
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    private Long taskId;
+
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long deviceId;
+
+    @Schema(description = "设备名称", example = "智能开关")
+    private String deviceName;
+
+    @Schema(description = "来源的固件编号", example = "1023")
+    private Long fromFirmwareId;
+
+    @Schema(description = "来源固件版本", example = "1.0.0")
+    private String fromFirmwareVersion;
+
+    @Schema(description = "升级状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "升级进度,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
+    private Integer progress;
+
+    @Schema(description = "升级进度描述", example = "正在下载固件...")
+    private String description;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime updateTime;
+
+}

+ 5 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.http

@@ -0,0 +1,5 @@
+### 请求 /iot/product/sync-property-table 接口 => 成功
+POST {{baseUrl}}/iot/product/sync-property-table
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+Authorization: Bearer {{token}}

+ 73 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataRuleController.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.rule.vo.data.rule.IotDataRulePageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
+import cn.iocoder.yudao.module.iot.service.rule.data.IotDataRuleService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - IoT 数据流转规则")
+@RestController
+@RequestMapping("/iot/data-rule")
+@Validated
+public class IotDataRuleController {
+
+    @Resource
+    private IotDataRuleService dataRuleService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建数据流转规则")
+    @PreAuthorize("@ss.hasPermission('iot:data-rule:create')")
+    public CommonResult<Long> createDataRule(@Valid @RequestBody IotDataRuleSaveReqVO createReqVO) {
+        return success(dataRuleService.createDataRule(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新数据流转规则")
+    @PreAuthorize("@ss.hasPermission('iot:data-rule:update')")
+    public CommonResult<Boolean> updateDataRule(@Valid @RequestBody IotDataRuleSaveReqVO updateReqVO) {
+        dataRuleService.updateDataRule(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除数据流转规则")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('iot:data-rule:delete')")
+    public CommonResult<Boolean> deleteDataRule(@RequestParam("id") Long id) {
+        dataRuleService.deleteDataRule(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得数据流转规则")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('iot:data-rule:query')")
+    public CommonResult<IotDataRuleRespVO> getDataRule(@RequestParam("id") Long id) {
+        IotDataRuleDO dataRule = dataRuleService.getDataRule(id);
+        return success(BeanUtils.toBean(dataRule, IotDataRuleRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得数据流转规则分页")
+    @PreAuthorize("@ss.hasPermission('iot:data-rule:query')")
+    public CommonResult<PageResult<IotDataRuleRespVO>> getDataRulePage(@Valid IotDataRulePageReqVO pageReqVO) {
+        PageResult<IotDataRuleDO> pageResult = dataRuleService.getDataRulePage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotDataRuleRespVO.class));
+    }
+
+}

+ 84 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotDataSinkController.java

@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.rule.vo.data.sink.IotDataSinkPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
+import cn.iocoder.yudao.module.iot.service.rule.data.IotDataSinkService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - IoT 数据流转目的")
+@RestController
+@RequestMapping("/iot/data-sink")
+@Validated
+public class IotDataSinkController {
+
+    @Resource
+    private IotDataSinkService dataSinkService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建数据目的")
+    @PreAuthorize("@ss.hasPermission('iot:data-sink:create')")
+    public CommonResult<Long> createDataSink(@Valid @RequestBody IotDataSinkSaveReqVO createReqVO) {
+        return success(dataSinkService.createDataSink(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新数据目的")
+    @PreAuthorize("@ss.hasPermission('iot:data-sink:update')")
+    public CommonResult<Boolean> updateDataSink(@Valid @RequestBody IotDataSinkSaveReqVO updateReqVO) {
+        dataSinkService.updateDataSink(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除数据目的")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('iot:data-sink:delete')")
+    public CommonResult<Boolean> deleteDataSink(@RequestParam("id") Long id) {
+        dataSinkService.deleteDataSink(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得数据目的")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('iot:data-sink:query')")
+    public CommonResult<IotDataSinkRespVO> getDataSink(@RequestParam("id") Long id) {
+        IotDataSinkDO sink = dataSinkService.getDataSink(id);
+        return success(BeanUtils.toBean(sink, IotDataSinkRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得数据目的分页")
+    @PreAuthorize("@ss.hasPermission('iot:data-sink:query')")
+    public CommonResult<PageResult<IotDataSinkRespVO>> getDataSinkPage(@Valid IotDataSinkPageReqVO pageReqVO) {
+        PageResult<IotDataSinkDO> pageResult = dataSinkService.getDataSinkPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotDataSinkRespVO.class));
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "获取数据目的的精简信息列表", description = "主要用于前端的下拉选项")
+    public CommonResult<List<IotDataSinkRespVO>> getDataSinkSimpleList() {
+        List<IotDataSinkDO> list = dataSinkService.getDataSinkListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        return success(convertList(list, sink -> // 只返回 id、name 字段
+                new IotDataSinkRespVO().setId(sink.getId()).setName(sink.getName())));
+    }
+
+}

+ 93 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/IotSceneRuleController.java

@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+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.rule.vo.scene.IotSceneRulePageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleUpdateStatusReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
+import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - IoT 场景联动")
+@RestController
+@RequestMapping("/iot/scene-rule")
+@Validated
+public class IotSceneRuleController {
+
+    @Resource
+    private IotSceneRuleService sceneRuleService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建场景联动")
+    @PreAuthorize("@ss.hasPermission('iot:scene-rule:create')")
+    public CommonResult<Long> createSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO createReqVO) {
+        return success(sceneRuleService.createSceneRule(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新场景联动")
+    @PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
+    public CommonResult<Boolean> updateSceneRule(@Valid @RequestBody IotSceneRuleSaveReqVO updateReqVO) {
+        sceneRuleService.updateSceneRule(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/update-status")
+    @Operation(summary = "更新场景联动状态")
+    @PreAuthorize("@ss.hasPermission('iot:scene-rule:update')")
+    public CommonResult<Boolean> updateSceneRuleStatus(@Valid @RequestBody IotSceneRuleUpdateStatusReqVO updateReqVO) {
+        sceneRuleService.updateSceneRuleStatus(updateReqVO.getId(), updateReqVO.getStatus());
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除场景联动")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('iot:scene-rule:delete')")
+    public CommonResult<Boolean> deleteSceneRule(@RequestParam("id") Long id) {
+        sceneRuleService.deleteSceneRule(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得场景联动")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
+    public CommonResult<IotSceneRuleRespVO> getSceneRule(@RequestParam("id") Long id) {
+        IotSceneRuleDO sceneRule = sceneRuleService.getSceneRule(id);
+        return success(BeanUtils.toBean(sceneRule, IotSceneRuleRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得场景联动分页")
+    @PreAuthorize("@ss.hasPermission('iot:scene-rule:query')")
+    public CommonResult<PageResult<IotSceneRuleRespVO>> getSceneRulePage(@Valid IotSceneRulePageReqVO pageReqVO) {
+        PageResult<IotSceneRuleDO> pageResult = sceneRuleService.getSceneRulePage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, IotSceneRuleRespVO.class));
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "获取场景联动的精简信息列表", description = "主要用于前端的下拉选项")
+    public CommonResult<List<IotSceneRuleRespVO>> getSceneRuleSimpleList() {
+        List<IotSceneRuleDO> list = sceneRuleService.getSceneRuleListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        return success(convertList(list, scene -> // 只返回 id、name 字段
+                new IotSceneRuleRespVO().setId(scene.getId()).setName(scene.getName())));
+    }
+
+}

+ 1 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data;

+ 26 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRulePageReqVO.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 数据流转规则分页 Request VO")
+@Data
+public class IotDataRulePageReqVO extends PageParam {
+
+    @Schema(description = "数据流转规则名称", example = "芋艿")
+    private String name;
+
+    @Schema(description = "数据流转规则状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 35 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleRespVO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule;
+
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - IoT 数据流转规则 Response VO")
+@Data
+public class IotDataRuleRespVO {
+
+    @Schema(description = "数据流转规则编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8540")
+    private Long id;
+
+    @Schema(description = "数据流转规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    private String name;
+
+    @Schema(description = "数据流转规则描述", example = "你猜")
+    private String description;
+
+    @Schema(description = "数据流转规则状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "数据源配置数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<IotDataRuleDO.SourceConfig> sourceConfigs;
+
+    @Schema(description = "数据目的编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<Long> sinkIds;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 40 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/rule/IotDataRuleSaveReqVO.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - IoT 数据流转规则新增/修改 Request VO")
+@Data
+public class IotDataRuleSaveReqVO {
+
+    @Schema(description = "数据流转规则编号", example = "8540")
+    private Long id;
+
+    @Schema(description = "数据流转规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    @NotEmpty(message = "数据流转规则名称不能为空")
+    private String name;
+
+    @Schema(description = "数据流转规则描述", example = "你猜")
+    private String description;
+
+    @Schema(description = "数据流转规则状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "数据流转规则状态不能为空")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "数据源配置数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "数据源配置数组不能为空")
+    private List<IotDataRuleDO.SourceConfig> sourceConfigs;
+
+    @Schema(description = "数据目的编号数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "数据目的编号数组不能为空")
+    private List<Long> sinkIds;
+
+}

+ 34 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkPageReqVO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 数据流转目的分页 Request VO")
+@Data
+public class IotDataSinkPageReqVO extends PageParam {
+
+    @Schema(description = "数据目的名称", example = "赵六")
+    private String name;
+
+    @Schema(description = "数据目的状态", example = "2")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "数据目的类型", example = "1")
+    @InEnum(IotDataSinkTypeEnum.class)
+    private Integer type;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 34 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkRespVO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink;
+
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotAbstractDataSinkConfig;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - IoT 数据流转目的 Response VO")
+@Data
+public class IotDataSinkRespVO {
+
+    @Schema(description = "数据目的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18564")
+    private Long id;
+
+    @Schema(description = "数据目的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    private String name;
+
+    @Schema(description = "数据目的描述", example = "随便")
+    private String description;
+
+    @Schema(description = "数据目的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "数据目的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer type;
+
+    @Schema(description = "数据目的配置")
+    private IotAbstractDataSinkConfig config;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 41 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/data/sink/IotDataSinkSaveReqVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotAbstractDataSinkConfig;
+import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - IoT 数据流转目的新增/修改 Request VO")
+@Data
+public class IotDataSinkSaveReqVO {
+
+    @Schema(description = "数据目的编号", example = "18564")
+    private Long id;
+
+    @Schema(description = "数据目的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @NotEmpty(message = "数据目的名称不能为空")
+    private String name;
+
+    @Schema(description = "数据目的描述", example = "随便")
+    private String description;
+
+    @Schema(description = "数据目的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "数据目的状态不能为空")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "数据目的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "数据目的类型不能为空")
+    @InEnum(IotDataSinkTypeEnum.class)
+    private Integer type;
+
+    @Schema(description = "数据目的配置")
+    @NotNull(message = "数据目的配置不能为空")
+    private IotAbstractDataSinkConfig config;
+
+}

+ 36 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRulePageReqVO.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 场景联动分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotSceneRulePageReqVO extends PageParam {
+
+    @Schema(description = "场景名称", example = "赵六")
+    private String name;
+
+    @Schema(description = "场景描述", example = "你猜")
+    private String description;
+
+    @Schema(description = "场景状态", example = "1")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 35 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
+
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - IoT 场景联动 Response VO")
+@Data
+public class IotSceneRuleRespVO {
+
+    @Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
+    private Long id;
+
+    @Schema(description = "场景名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    private String name;
+
+    @Schema(description = "场景描述", example = "你猜")
+    private String description;
+
+    @Schema(description = "场景状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "触发器数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<IotSceneRuleDO.Trigger> triggers;
+
+    @Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<IotSceneRuleDO.Action> actions;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 40 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleSaveReqVO.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - IoT 场景联动新增/修改 Request VO")
+@Data
+public class IotSceneRuleSaveReqVO {
+
+    @Schema(description = "场景编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15865")
+    private Long id;
+
+    @Schema(description = "场景名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @NotEmpty(message = "场景名称不能为空")
+    private String name;
+
+    @Schema(description = "场景描述", example = "你猜")
+    private String description;
+
+    @Schema(description = "场景状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "场景状态不能为空")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "触发器数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "触发器数组不能为空")
+    private List<IotSceneRuleDO.Trigger> triggers;
+
+    @Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "执行器数组不能为空")
+    private List<IotSceneRuleDO.Action> actions;
+
+}

+ 23 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleUpdateStatusReqVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - IoT 场景联动更新状态 Request VO")
+@Data
+public class IotSceneRuleUpdateStatusReqVO {
+
+    @Schema(description = "场景联动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "场景联动编号不能为空")
+    private Long id;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "状态不能为空")
+    @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
+    private Integer status;
+
+}

+ 11 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.http

@@ -0,0 +1,11 @@
+### 请求 /iot/statistics/get-device-message-summary-by-date 接口(小时)
+GET {{baseUrl}}/iot/statistics/get-device-message-summary-by-date?interval=0&times[0]=2025-06-13 00:00:00&times[1]=2025-06-14 23:59:59
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+Authorization: Bearer {{token}}
+
+### 请求 /iot/statistics/get-device-message-summary-by-date 接口(天)
+GET {{baseUrl}}/iot/statistics/get-device-message-summary-by-date?interval=1&times[0]=2025-06-13 00:00:00&times[1]=2025-06-14 23:59:59
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+Authorization: Bearer {{token}}

+ 27 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageReqVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.iot.controller.admin.statistics.vo;
+
+import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.Size;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - IoT 设备消息数量统计 Response VO")
+@Data
+public class IotStatisticsDeviceMessageReqVO {
+
+    @Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
+    private Integer interval;
+
+    @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @Size(min = 2, max = 2, message = "请选择时间范围")
+    private LocalDateTime[] times;
+
+}

+ 19 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/vo/IotStatisticsDeviceMessageSummaryByDateRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.iot.controller.admin.statistics.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - IoT 设备消息数量统计 Response VO")
+@Data
+public class IotStatisticsDeviceMessageSummaryByDateRespVO {
+
+    @Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
+    private String time;
+
+    @Schema(description = "上行消息数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer upstreamCount;
+
+    @Schema(description = "上行消息数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+    private Integer downstreamCount;
+
+}

+ 30 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelTSLRespVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.controller.admin.thingmodel.vo;
+
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelEvent;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelService;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - IoT 产品物模型 TSL Response VO")
+@Data
+public class IotThingModelTSLRespVO {
+
+    @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long productId;
+
+    @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature_sensor")
+    private String productKey;
+
+    @Schema(description = "属性列表", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<ThingModelProperty> properties;
+
+    @Schema(description = "服务列表", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<ThingModelEvent> events;
+
+    @Schema(description = "事件列表", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<ThingModelService> services;
+
+}

+ 84 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alert/IotAlertConfigDO.java

@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.alert;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
+import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
+import cn.iocoder.yudao.module.iot.enums.DictTypeConstants;
+import cn.iocoder.yudao.module.iot.enums.alert.IotAlertReceiveTypeEnum;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * IoT 告警配置 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_alert_config", autoResultMap = true)
+@KeySequence("iot_alert_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotAlertConfigDO extends BaseDO {
+
+    /**
+     * 配置编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 配置名称
+     */
+    private String name;
+    /**
+     * 配置描述
+     */
+    private String description;
+    /**
+     * 配置状态
+     *
+     * 字典 {@link DictTypeConstants#ALERT_LEVEL}
+     */
+    private Integer level;
+    /**
+     * 配置状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * 关联的场景联动规则编号数组
+     *
+     * 关联 {@link IotSceneRuleDO#getId()}
+     */
+    @TableField(typeHandler = LongListTypeHandler.class)
+    private List<Long> sceneRuleIds;
+
+    /**
+     * 接收的用户编号数组
+     *
+     * 关联 {@link AdminUserRespDTO#getId()}
+     */
+    @TableField(typeHandler = LongListTypeHandler.class)
+    private List<Long> receiveUserIds;
+    /**
+     * 接收的类型数组
+     *
+     * 枚举 {@link IotAlertReceiveTypeEnum}
+     */
+    @TableField(typeHandler = IntegerListTypeHandler.class)
+    private List<Integer> receiveTypes;
+
+}

+ 89 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alert/IotAlertRecordDO.java

@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.alert;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT 告警记录 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_alert_record", autoResultMap = true)
+@KeySequence("iot_alert_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotAlertRecordDO extends BaseDO {
+
+    /**
+     * 记录编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 告警名称
+     *
+     * 冗余 {@link IotAlertConfigDO#getId()}
+     */
+    private Long configId;
+    /**
+     * 告警名称
+     *
+     * 冗余 {@link IotAlertConfigDO#getName()}
+     */
+    private String configName;
+    /**
+     * 告警级别
+     *
+     * 冗余 {@link IotAlertConfigDO#getLevel()}
+     * 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#ALERT_LEVEL}
+     */
+    private Integer configLevel;
+    /**
+     * 场景规则编号
+     *
+     * 关联 {@link IotSceneRuleDO#getId()}
+     */
+    private Long sceneRuleId;
+
+    /**
+     * 产品编号
+     *
+     * 关联 {@link IotProductDO#getId()}
+     */
+    private Long productId;
+    /**
+     * 设备编号
+     *
+     * 关联 {@link NewIotDeviceDO#getId()}
+     */
+    private Long deviceId;
+    /**
+     * 触发的设备消息
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private IotDeviceMessage deviceMessage;
+
+    /**
+     * 是否处理
+     */
+    private Boolean processStatus;
+    /**
+     * 处理结果(备注)
+     */
+    private String processRemark;
+
+}

+ 109 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java

@@ -0,0 +1,109 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.device;
+
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT 设备消息数据 DO
+ *
+ * 目前使用 TDengine 存储
+ *
+ * @author alwayssuper
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceMessageDO {
+
+    /**
+     * 消息编号
+     */
+    private String id;
+    /**
+     * 上报时间戳
+     */
+    private Long reportTime;
+    /**
+     * 存储时间戳
+     */
+    private Long ts;
+
+    /**
+     * 设备编号
+     *
+     * 关联 {@link NewIotDeviceDO#getId()}
+     */
+    private Long deviceId;
+    /**
+     * 租户编号
+     */
+    private Long tenantId;
+
+    /**
+     * 服务编号,该消息由哪个 server 发送
+     */
+    private String serverId;
+
+    /**
+     * 是否上行消息
+     *
+     * 由 {@link cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils#isUpstreamMessage(IotDeviceMessage)} 计算。
+     * 计算并存储的目的:方便计算多少条上行、多少条下行
+     */
+    private Boolean upstream;
+    /**
+     * 是否回复消息
+     *
+     * 由 {@link cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils#isReplyMessage(IotDeviceMessage)} 计算。
+     * 计算并存储的目的:方便计算多少条请求、多少条回复
+     */
+    private Boolean reply;
+    /**
+     * 标识符
+     *
+     * 例如说:{@link IotThingModelDO#getIdentifier()}
+     * 目前,只有事件上报、服务调用才有!!!
+     */
+    private String identifier;
+
+    // ========== codec(编解码)字段 ==========
+
+    /**
+     * 请求编号
+     *
+     * 由设备生成,对应阿里云 IoT 的 Alink 协议中的 id、华为云 IoTDA 协议的 request_id
+     */
+    private String requestId;
+    /**
+     * 请求方法
+     *
+     * 枚举 {@link IotDeviceMessageMethodEnum}
+     * 例如说:thing.property.post 属性上报
+     */
+    private String method;
+    /**
+     * 请求参数
+     *
+     * 例如说:属性上报的 properties、事件上报的 params
+     */
+    private Object params;
+    /**
+     * 响应结果
+     */
+    private Object data;
+    /**
+     * 响应错误码
+     */
+    private Integer code;
+    /**
+     * 响应提示
+     */
+    private String msg;
+
+}

+ 82 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusConfigDO.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.device;
+
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusModeEnum;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * IoT 设备 Modbus 连接配置 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("iot_device_modbus_config")
+@KeySequence("iot_device_modbus_config_seq")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceModbusConfigDO extends TenantBaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 产品编号
+     *
+     * 关联 {@link IotProductDO#getId()}
+     */
+    private Long productId;
+    /**
+     * 设备编号
+     *
+     * 关联 {@link NewIotDeviceDO#getId()}
+     */
+    private Long deviceId;
+
+    /**
+     * Modbus 服务器 IP 地址
+     */
+    private String ip;
+    /**
+     * Modbus 服务器端口
+     */
+    private Integer port;
+    /**
+     * 从站地址
+     */
+    private Integer slaveId;
+    /**
+     * 连接超时时间,单位:毫秒
+     */
+    private Integer timeout;
+    /**
+     * 重试间隔,单位:毫秒
+     */
+    private Integer retryInterval;
+    /**
+     * 模式
+     *
+     * @see IotModbusModeEnum
+     */
+    private Integer mode;
+    /**
+     * 数据帧格式
+     *
+     * @see IotModbusFrameFormatEnum
+     */
+    private Integer frameFormat;
+    /**
+     * 状态
+     *
+     * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+     */
+    private Integer status;
+
+}

+ 100 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java

@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.device;
+
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusByteOrderEnum;
+import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusRawDataTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * IoT 设备 Modbus 点位配置 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("iot_device_modbus_point")
+@KeySequence("iot_device_modbus_point_seq")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDeviceModbusPointDO extends TenantBaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 设备编号
+     *
+     * 关联 {@link NewIotDeviceDO#getId()}
+     */
+    private Long deviceId;
+    /**
+     * 物模型属性编号
+     *
+     * 关联 {@link IotThingModelDO#getId()}
+     */
+    private Long thingModelId;
+    /**
+     * 属性标识符
+     *
+     * 冗余 {@link IotThingModelDO#getIdentifier()}
+     */
+    private String identifier;
+    /**
+     * 属性名称
+     *
+     * 冗余 {@link IotThingModelDO#getName()}
+     */
+    private String name;
+
+    // ========== Modbus 协议配置 ==========
+
+    /**
+     * Modbus 功能码
+     *
+     * 取值范围:FC01-04(读线圈、读离散输入、读保持寄存器、读输入寄存器)
+     */
+    private Integer functionCode;
+    /**
+     * 寄存器起始地址
+     */
+    private Integer registerAddress;
+    /**
+     * 寄存器数量
+     */
+    private Integer registerCount;
+    /**
+     * 字节序
+     *
+     * 枚举 {@link IotModbusByteOrderEnum}
+     */
+    private String byteOrder;
+    /**
+     * 原始数据类型
+     *
+     * 枚举 {@link IotModbusRawDataTypeEnum}
+     */
+    private String rawDataType;
+    /**
+     * 缩放因子
+     */
+    private BigDecimal scale;
+    /**
+     * 轮询间隔(毫秒)
+     */
+    private Integer pollInterval;
+    /**
+     * 状态
+     *
+     * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+     */
+    private Integer status;
+
+}

+ 70 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskDO.java

@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskDeviceScopeEnum;
+import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT OTA 升级任务 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_ota_task", autoResultMap = true)
+@KeySequence("iot_ota_task_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotOtaTaskDO extends BaseDO {
+
+    /**
+     * 任务编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 任务名称
+     */
+    private String name;
+    /**
+     * 任务描述
+     */
+    private String description;
+
+    /**
+     * 固件编号
+     * <p>
+     * 关联 {@link IotOtaFirmwareDO#getId()}
+     */
+    private Long firmwareId;
+
+    /**
+     * 任务状态
+     * <p>
+     * 关联 {@link IotOtaTaskStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * 设备升级范围
+     * <p>
+     * 关联 {@link IotOtaTaskDeviceScopeEnum}
+     */
+    private Integer deviceScope;
+    /**
+     * 设备总数数量
+     */
+    private Integer deviceTotalCount;
+    /**
+     * 设备成功数量
+     */
+    private Integer deviceSuccessCount;
+
+}

+ 82 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
+import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT OTA 升级任务记录 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_ota_task_record", autoResultMap = true)
+@KeySequence("iot_ota_task_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotOtaTaskRecordDO extends BaseDO {
+
+    public static final String DESCRIPTION_CANCEL_BY_TASK = "管理员手动取消升级任务(批量)";
+
+    public static final String DESCRIPTION_CANCEL_BY_RECORD = "管理员手动取消升级记录(单个)";
+
+    /**
+     * 升级记录编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 固件编号
+     *
+     * 关联 {@link IotOtaFirmwareDO#getId()}
+     */
+    private Long firmwareId;
+    /**
+     * 任务编号
+     *
+     * 关联 {@link IotOtaTaskDO#getId()}
+     */
+    private Long taskId;
+
+    /**
+     * 设备编号
+     *
+     * 关联 {@link NewIotDeviceDO#getId()}
+     */
+    private Long deviceId;
+    /**
+     * 来源的固件编号
+     *
+     * 关联 {@link NewIotDeviceDO#getFirmwareId()}
+     */
+    private Long fromFirmwareId;
+
+    /**
+     * 升级状态
+     *
+     * 关联 {@link IotOtaTaskRecordStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 升级进度,百分比
+     */
+    private Integer progress;
+    /**
+     * 升级进度描述
+     *
+     * 注意,只记录设备最后一次的升级进度描述
+     * 如果想看历史记录,可以查看 {@link IotDeviceMessageDO} 设备日志
+     */
+    private String description;
+
+}

+ 109 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataRuleDO.java

@@ -0,0 +1,109 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
+import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * IoT 数据流转规则 DO
+ *
+ * 监听 {@link SourceConfig} 数据源,转发到 {@link IotDataSinkDO} 数据目的
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_data_rule", autoResultMap = true)
+@KeySequence("iot_data_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDataRuleDO extends BaseDO {
+
+    /**
+     * 数据流转规格编号
+     */
+    private Long id;
+    /**
+     * 数据流转规格名称
+     */
+    private String name;
+    /**
+     * 数据流转规格描述
+     */
+    private String description;
+    /**
+     * 数据流转规格状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * 数据源配置数组
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<SourceConfig> sourceConfigs;
+    /**
+     * 数据目的编号数组
+     *
+     * 关联 {@link IotDataSinkDO#getId()}
+     */
+    @TableField(typeHandler = LongListTypeHandler.class)
+    private List<Long> sinkIds;
+
+    // TODO @芋艿:未来考虑使用 groovy;支持数据处理;
+
+    /**
+     * 数据源配置
+     */
+    @Data
+    public static class SourceConfig {
+
+        /**
+         * 消息方法
+         *
+         * 枚举 {@link IotDeviceMessageMethodEnum} 中的 upstream 上行部分
+         */
+        @NotEmpty(message = "消息方法不能为空")
+        private String method;
+
+        /**
+         * 产品编号
+         *
+         * 关联 {@link IotProductDO#getId()}
+         */
+        private Long productId;
+        /**
+         * 设备编号
+         *
+         * 关联 {@link NewIotDeviceDO#getId()}
+         * 特殊:如果为 {@link NewIotDeviceDO#DEVICE_ID_ALL} 时,则是全部设备
+         */
+        @NotEmpty(message = "设备编号不能为空")
+        private Long deviceId;
+
+        /**
+         * 标识符
+         *
+         * 1. 物模型时,对应:{@link IotThingModelDO#getIdentifier()}
+         */
+        private String identifier;
+
+    }
+
+}

+ 62 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataSinkDO.java

@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotAbstractDataSinkConfig;
+import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * IoT 数据流转目的 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_data_sink", autoResultMap = true)
+@KeySequence("iot_data_bridge_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotDataSinkDO extends BaseDO {
+
+    /**
+     * 数据流转目的编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 数据流转目的名称
+     */
+    private String name;
+    /**
+     * 数据流转目的描述
+     */
+    private String description;
+    /**
+     * 数据流转目的状态
+     *
+     *  枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * 数据流转目的类型
+     *
+     * 枚举 {@link IotDataSinkTypeEnum}
+     */
+    private Integer type;
+    /**
+     * 数据流转目的配置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private IotAbstractDataSinkConfig config;
+
+}

+ 251 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotSceneRuleDO.java

@@ -0,0 +1,251 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * IoT 场景联动规则 DO
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "iot_scene_rule", autoResultMap = true)
+@KeySequence("iot_scene_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotSceneRuleDO extends TenantBaseDO {
+
+    /**
+     * 场景联动编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 场景联动名称
+     */
+    private String name;
+    /**
+     * 场景联动描述
+     */
+    private String description;
+    /**
+     * 场景联动状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+
+    /**
+     * 最后触发时间
+     */
+    private LocalDateTime lastTriggerTime;
+
+    /**
+     * 场景定义配置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<Trigger> triggers;
+
+    /**
+     * 场景动作配置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<Action> actions;
+
+    /**
+     * 场景定义配置
+     */
+    @Data
+    public static class Trigger {
+
+        // ========== 事件部分 ==========
+
+        /**
+         * 场景事件类型
+         *
+         * 枚举 {@link IotSceneRuleTriggerTypeEnum}
+         * 1. {@link IotSceneRuleTriggerTypeEnum#DEVICE_STATE_UPDATE} 时,operator 非空,并且 value 为在线状态
+         * 2. {@link IotSceneRuleTriggerTypeEnum#DEVICE_PROPERTY_POST}
+         *    {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST} 时,identifier、operator 非空,并且 value 为属性值
+         * 3. {@link IotSceneRuleTriggerTypeEnum#DEVICE_EVENT_POST}
+         *    {@link IotSceneRuleTriggerTypeEnum#DEVICE_SERVICE_INVOKE} 时,identifier 非空,但是 operator、value 为空
+         * 4. {@link IotSceneRuleTriggerTypeEnum#TIMER} 时,conditions 非空,并且设备无关(无需 productId、deviceId 字段)
+         */
+        private Integer type;
+
+        /**
+         * 产品编号
+         *
+         * 关联 {@link IotProductDO#getId()}
+         */
+        private Long productId;
+        /**
+         * 设备编号
+         *
+         * 关联 {@link NewIotDeviceDO#getId()}
+         * 特殊:如果为 {@link NewIotDeviceDO#DEVICE_ID_ALL} 时,则是全部设备
+         */
+        private Long deviceId;
+        /**
+         * 物模型标识符
+         *
+         * 对应:{@link IotThingModelDO#getIdentifier()}
+         */
+        private String identifier;
+        /**
+         * 操作符
+         *
+         * 枚举 {@link IotSceneRuleConditionOperatorEnum}
+         */
+        private String operator;
+        /**
+         * 参数(属性值、在线状态)
+         * <p>
+         * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
+         * 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
+         */
+        private String value;
+
+        /**
+         * CRON 表达式
+         */
+        private String cronExpression;
+
+        // ========== 条件部分 ==========
+
+        /**
+         * 触发条件分组(状态条件分组)的数组
+         * <p>
+         * 第一层 List:分组与分组之间,是“或”的关系
+         * 第二层 List:条件与条件之间,是“且”的关系
+         */
+        private List<List<TriggerCondition>> conditionGroups;
+
+    }
+
+    /**
+     * 触发条件(状态条件)
+     */
+    @Data
+    public static class TriggerCondition {
+
+        /**
+         * 触发条件类型
+         *
+         * 枚举 {@link IotSceneRuleConditionTypeEnum}
+         * 1. {@link IotSceneRuleConditionTypeEnum#DEVICE_STATE} 时,operator 非空,并且 value 为在线状态
+         * 2. {@link IotSceneRuleConditionTypeEnum#DEVICE_PROPERTY} 时,identifier、operator 非空,并且 value 为属性值
+         * 3. {@link IotSceneRuleConditionTypeEnum#CURRENT_TIME} 时,operator 非空(使用 DATE_TIME_ 和 TIME_ 部分),并且 value 非空
+         */
+        private Integer type;
+
+        /**
+         * 产品编号
+         *
+         * 关联 {@link IotProductDO#getId()}
+         */
+        private Long productId;
+        /**
+         * 设备编号
+         *
+         * 关联 {@link NewIotDeviceDO#getId()}
+         */
+        private Long deviceId;
+        /**
+         * 标识符(属性)
+         *
+         * 关联 {@link IotThingModelDO#getIdentifier()}
+         */
+        private String identifier;
+        /**
+         * 操作符
+         *
+         * 枚举 {@link IotSceneRuleConditionOperatorEnum}
+         */
+        private String operator;
+        /**
+         * 参数
+         *
+         * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"。
+         * 例如说,{@link IotSceneRuleConditionOperatorEnum#IN}、{@link IotSceneRuleConditionOperatorEnum#BETWEEN}
+         */
+        private String param;
+
+    }
+
+    /**
+     * 场景动作配置
+     */
+    @Data
+    public static class Action {
+
+        /**
+         * 执行类型
+         *
+         * 枚举 {@link IotSceneRuleActionTypeEnum}
+         * 1. {@link IotSceneRuleActionTypeEnum#DEVICE_PROPERTY_SET} 时,params 非空
+         *    {@link IotSceneRuleActionTypeEnum#DEVICE_SERVICE_INVOKE} 时,params 非空
+         * 2. {@link IotSceneRuleActionTypeEnum#ALERT_TRIGGER} 时,alertConfigId 为空,因为是 {@link IotAlertConfigDO} 里面关联它
+         * 3. {@link IotSceneRuleActionTypeEnum#ALERT_RECOVER} 时,alertConfigId 非空
+         */
+        private Integer type;
+
+        /**
+         * 产品编号
+         *
+         * 关联 {@link IotProductDO#getId()}
+         */
+        private Long productId;
+        /**
+         * 设备编号
+         *
+         * 关联 {@link NewIotDeviceDO#getId()}
+         */
+        private Long deviceId;
+
+        /**
+         * 标识符(服务)
+         * <p>
+         * 关联 {@link IotThingModelDO#getIdentifier()}
+         */
+        private String identifier;
+
+        /**
+         * 请求参数
+         *
+         * 一般来说,对应 {@link IotDeviceMessage#getParams()} 请求参数
+         */
+        private String params;
+
+        /**
+         * 告警配置编号
+         *
+         * 关联 {@link IotAlertConfigDO#getId()}
+         */
+        private Long alertConfigId;
+
+    }
+
+}

+ 38 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import lombok.Data;
+
+/**
+ * IoT IotDataBridgeConfig 抽象类
+ *
+ * 用于表示数据目的配置数据的通用类型,根据具体的 "type" 字段动态映射到对应的子类
+ * 提供多态支持,适用于不同类型的数据结构序列化和反序列化场景。
+ *
+ * @author HUIHUI
+ */
+@Data
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
+@JsonSubTypes({
+        @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"),
+        @JsonSubTypes.Type(value = IotDataSinkTcpConfig.class, name = "2"),
+        @JsonSubTypes.Type(value = IotDataSinkWebSocketConfig.class, name = "3"),
+        @JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"),
+        @JsonSubTypes.Type(value = IotDataSinkDatabaseConfig.class, name = "20"),
+        @JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"),
+        @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
+        @JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"),
+        @JsonSubTypes.Type(value = IotDataSinkKafkaConfig.class, name = "32"),
+})
+public abstract class IotAbstractDataSinkConfig {
+
+    /**
+     * 配置类型
+     *
+     * 枚举 {@link IotDataSinkTypeEnum#getType()}
+     */
+    private String type;
+
+}

+ 42 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkDatabaseConfig.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT Database 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * 通过 JDBC 连接数据库,将设备消息写入指定表。
+ * 支持 MySQL、PostgreSQL、Oracle、SQL Server、DM 达梦等数据库,
+ * HikariCP 会根据 JDBC URL 自动加载对应的驱动。
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkDatabaseConfig extends IotAbstractDataSinkConfig {
+
+    /**
+     * JDBC 连接地址
+     *
+     * 例如:jdbc:mysql://localhost:3306/iot_data
+     * 例如:jdbc:postgresql://localhost:5432/iot_data
+     * 例如:jdbc:dm://localhost:5236/iot_data
+     *
+     * HikariCP 会根据 URL 自动检测并加载对应的 JDBC 驱动
+     */
+    private String jdbcUrl;
+    /**
+     * 数据库用户名
+     */
+    private String username;
+    /**
+     * 数据库密码
+     */
+    private String password;
+    /**
+     * 目标表名
+     *
+     * 设备消息将以固定结构写入该表
+     */
+    private String tableName;
+
+}

+ 36 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkHttpConfig.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * IoT HTTP 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkHttpConfig extends IotAbstractDataSinkConfig {
+
+    /**
+     * 请求 URL
+     */
+    private String url;
+    /**
+     * 请求方法
+     */
+    private String method;
+    /**
+     * 请求头
+     */
+    private Map<String, String> headers;
+    /**
+     * 请求参数
+     */
+    private Map<String, String> query;
+    /**
+     * 请求体
+     */
+    private String body;
+
+}

+ 35 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkKafkaConfig.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT Kafka 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkKafkaConfig extends IotAbstractDataSinkConfig {
+
+    /**
+     * Kafka 服务器地址
+     */
+    private String bootstrapServers;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 密码
+     */
+    private String password;
+    /**
+     * 是否启用 SSL
+     */
+    private Boolean ssl;
+
+    /**
+     * 主题
+     */
+    private String topic;
+
+}

+ 34 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkMqttConfig.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT MQTT 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkMqttConfig extends IotAbstractDataSinkConfig {
+
+    /**
+     * MQTT 服务器地址
+     */
+    private String url;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 密码
+     */
+    private String password;
+    /**
+     * 客户端编号
+     */
+    private String clientId;
+    /**
+     * 主题
+     */
+    private String topic;
+
+}

+ 46 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRabbitMQConfig.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT RabbitMQ 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkRabbitMQConfig extends IotAbstractDataSinkConfig {
+
+    /**
+     * RabbitMQ 服务器地址
+     */
+    private String host;
+    /**
+     * 端口
+     */
+    private Integer port;
+    /**
+     * 虚拟主机
+     */
+    private String virtualHost;
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 交换机名称
+     */
+    private String exchange;
+    /**
+     * 路由键
+     */
+    private String routingKey;
+    /**
+     * 队列名称
+     */
+    private String queue;
+}

+ 64 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
+import lombok.Data;
+
+/**
+ * IoT Redis 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkRedisConfig extends IotAbstractDataSinkConfig {
+
+    /**
+     * Redis 服务器地址
+     */
+    private String host;
+    /**
+     * 端口
+     */
+    private Integer port;
+    /**
+     * 密码
+     */
+    private String password;
+    /**
+     * 数据库索引
+     */
+    private Integer database;
+
+    /**
+     * Redis 数据结构类型
+     * <p>
+     * 枚举 {@link IotRedisDataStructureEnum}
+     */
+    @InEnum(IotRedisDataStructureEnum.class)
+    private Integer dataStructure;
+
+    /**
+     * 主题/键名
+     * <p>
+     * 对于不同的数据结构:
+     * - Stream: 流的键名
+     * - Hash: Hash 的键名
+     * - List: 列表的键名
+     * - Set: 集合的键名
+     * - ZSet: 有序集合的键名
+     * - String: 字符串的键名
+     */
+    private String topic;
+
+    /**
+     * Hash 字段名(仅当 dataStructure 为 HASH 时使用)
+     */
+    private String hashField;
+
+    /**
+     * ZSet 分数字段(仅当 dataStructure 为 ZSET 时使用)
+     * 指定消息中哪个字段作为分数,如果不指定则使用当前时间戳
+     */
+    private String scoreField;
+
+}

+ 39 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRocketMQConfig.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT RocketMQ 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkRocketMQConfig extends IotAbstractDataSinkConfig {
+
+    /**
+     * RocketMQ 名称服务器地址
+     */
+    private String nameServer;
+    /**
+     * 访问密钥
+     */
+    private String accessKey;
+    /**
+     * 秘密钥匙
+     */
+    private String secretKey;
+
+    /**
+     * 生产者组
+     */
+    private String group;
+    /**
+     * 主题
+     */
+    private String topic;
+    /**
+     * 标签
+     */
+    private String tags;
+
+}

+ 92 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkTcpConfig.java

@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT TCP 配置 {@link IotAbstractDataSinkConfig} 实现类
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkTcpConfig extends IotAbstractDataSinkConfig {
+
+    /**
+     * 默认连接超时时间(毫秒)
+     */
+    public static final int DEFAULT_CONNECT_TIMEOUT_MS = 5000;
+    /**
+     * 默认读取超时时间(毫秒)
+     */
+    public static final int DEFAULT_READ_TIMEOUT_MS = 10000;
+    /**
+     * 默认是否启用 SSL
+     */
+    public static final boolean DEFAULT_SSL = false;
+    /**
+     * 默认数据格式
+     */
+    public static final String DEFAULT_DATA_FORMAT = "JSON";
+    /**
+     * 默认心跳间隔时间(毫秒)
+     */
+    public static final long DEFAULT_HEARTBEAT_INTERVAL_MS = 30000L;
+    /**
+     * 默认重连间隔时间(毫秒)
+     */
+    public static final long DEFAULT_RECONNECT_INTERVAL_MS = 5000L;
+    /**
+     * 默认最大重连次数
+     */
+    public static final int DEFAULT_MAX_RECONNECT_ATTEMPTS = 3;
+
+    /**
+     * TCP 服务器地址
+     */
+    private String host;
+
+    /**
+     * TCP 服务器端口
+     */
+    private Integer port;
+
+    /**
+     * 连接超时时间(毫秒)
+     */
+    private Integer connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS;
+
+    /**
+     * 读取超时时间(毫秒)
+     */
+    private Integer readTimeoutMs = DEFAULT_READ_TIMEOUT_MS;
+
+    /**
+     * 是否启用 SSL
+     */
+    private Boolean ssl = DEFAULT_SSL;
+
+    /**
+     * SSL 证书路径(当 ssl=true 时需要)
+     */
+    private String sslCertPath;
+
+    /**
+     * 数据格式:JSON 或 BINARY
+     */
+    private String dataFormat = DEFAULT_DATA_FORMAT;
+
+    /**
+     * 心跳间隔时间(毫秒),0 表示不启用心跳
+     */
+    private Long heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS;
+
+    /**
+     * 重连间隔时间(毫秒)
+     */
+    private Long reconnectIntervalMs = DEFAULT_RECONNECT_INTERVAL_MS;
+
+    /**
+     * 最大重连次数
+     */
+    private Integer maxReconnectAttempts = DEFAULT_MAX_RECONNECT_ATTEMPTS;
+
+}

+ 132 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkWebSocketConfig.java

@@ -0,0 +1,132 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
+
+import lombok.Data;
+
+/**
+ * IoT WebSocket 配置 {@link IotAbstractDataSinkConfig} 实现类
+ * <p>
+ * 配置设备消息通过 WebSocket 协议发送到外部 WebSocket 服务器
+ * 支持 WebSocket (ws://) 和 WebSocket Secure (wss://) 连接
+ *
+ * @author HUIHUI
+ */
+@Data
+public class IotDataSinkWebSocketConfig extends IotAbstractDataSinkConfig {
+
+    /**
+     * 默认连接超时时间(毫秒)
+     */
+    public static final int DEFAULT_CONNECT_TIMEOUT_MS = 5000;
+    /**
+     * 默认发送超时时间(毫秒)
+     */
+    public static final int DEFAULT_SEND_TIMEOUT_MS = 10000;
+    /**
+     * 默认心跳间隔时间(毫秒)
+     */
+    public static final long DEFAULT_HEARTBEAT_INTERVAL_MS = 30000L;
+    /**
+     * 默认心跳消息内容
+     */
+    public static final String DEFAULT_HEARTBEAT_MESSAGE = "{\"type\":\"heartbeat\"}";
+    /**
+     * 默认是否启用 SSL 证书验证
+     */
+    public static final boolean DEFAULT_VERIFY_SSL_CERT = true;
+    /**
+     * 默认数据格式
+     */
+    public static final String DEFAULT_DATA_FORMAT = "JSON";
+    /**
+     * 默认重连间隔时间(毫秒)
+     */
+    public static final long DEFAULT_RECONNECT_INTERVAL_MS = 5000L;
+    /**
+     * 默认最大重连次数
+     */
+    public static final int DEFAULT_MAX_RECONNECT_ATTEMPTS = 3;
+    /**
+     * 默认是否启用压缩
+     */
+    public static final boolean DEFAULT_ENABLE_COMPRESSION = false;
+    /**
+     * 默认消息发送重试次数
+     */
+    public static final int DEFAULT_SEND_RETRY_COUNT = 1;
+    /**
+     * 默认消息发送重试间隔(毫秒)
+     */
+    public static final long DEFAULT_SEND_RETRY_INTERVAL_MS = 1000L;
+
+    /**
+     * WebSocket 服务器地址
+     * 例如:ws://localhost:8080/ws 或 wss://example.com/ws
+     */
+    private String serverUrl;
+
+    /**
+     * 连接超时时间(毫秒)
+     */
+    private Integer connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS;
+
+    /**
+     * 发送超时时间(毫秒)
+     */
+    private Integer sendTimeoutMs = DEFAULT_SEND_TIMEOUT_MS;
+
+    /**
+     * 心跳间隔时间(毫秒),0 表示不启用心跳
+     */
+    private Long heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS;
+
+    /**
+     * 心跳消息内容(JSON 格式)
+     */
+    private String heartbeatMessage = DEFAULT_HEARTBEAT_MESSAGE;
+
+    /**
+     * 子协议列表(逗号分隔)
+     */
+    private String subprotocols;
+
+    /**
+     * 自定义请求头(JSON 格式)
+     */
+    private String customHeaders;
+
+    /**
+     * 是否启用 SSL 证书验证(仅对 wss:// 生效)
+     */
+    private Boolean verifySslCert = DEFAULT_VERIFY_SSL_CERT;
+
+    /**
+     * 数据格式:JSON 或 TEXT
+     */
+    private String dataFormat = DEFAULT_DATA_FORMAT;
+
+    /**
+     * 重连间隔时间(毫秒)
+     */
+    private Long reconnectIntervalMs = DEFAULT_RECONNECT_INTERVAL_MS;
+
+    /**
+     * 最大重连次数
+     */
+    private Integer maxReconnectAttempts = DEFAULT_MAX_RECONNECT_ATTEMPTS;
+
+    /**
+     * 是否启用压缩
+     */
+    private Boolean enableCompression = DEFAULT_ENABLE_COMPRESSION;
+
+    /**
+     * 消息发送重试次数
+     */
+    private Integer sendRetryCount = DEFAULT_SEND_RETRY_COUNT;
+
+    /**
+     * 消息发送重试间隔(毫秒)
+     */
+    private Long sendRetryIntervalMs = DEFAULT_SEND_RETRY_INTERVAL_MS;
+
+}

+ 55 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelEvent.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceEventTypeEnum;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+import java.util.List;
+
+/**
+ * IoT 物模型中的事件
+ *
+ * @author HUIHUI
+ */
+@Data
+public class ThingModelEvent {
+
+    /**
+     * 事件标识符
+     */
+    @NotEmpty(message = "事件标识符不能为空")
+//    @Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]*$", message = "属性标识符只能由字母、数字和下划线组成,必须以字母开头")
+    private String identifier;
+    /**
+     * 事件名称
+     */
+    @NotEmpty(message = "事件名称不能为空")
+    private String name;
+    /**
+     * 是否是标准品类的必选事件
+     */
+    private Boolean required;
+    /**
+     * 事件类型
+     *
+     * 枚举 {@link IotThingModelServiceEventTypeEnum}
+     */
+    @NotEmpty(message = "事件类型不能为空")
+    @InEnum(IotThingModelServiceEventTypeEnum.class)
+    private String type;
+    /**
+     * 事件的输出参数
+     *
+     * 输出参数定义事件调用后返回的结果或反馈信息,用于确认操作结果或提供额外的信息。
+     */
+    @Valid
+    private List<ThingModelParam> outputParams;
+    /**
+     * 标识设备需要执行的具体操作
+     */
+    private String method;
+
+}

+ 63 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelParam.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelParamDirectionEnum;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+import java.util.List;
+
+/**
+ * IoT 产品物模型中的参数
+ *
+ * @author HUIHUI
+ */
+@Data
+public class ThingModelParam {
+
+    /**
+     * 参数标识符
+     */
+    @NotEmpty(message = "参数标识符不能为空")
+//    @Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]*$", message = "属性标识符只能由字母、数字和下划线组成,必须以字母开头")
+    private String identifier;
+    /**
+     * 参数名称
+     */
+    @NotEmpty(message = "参数名称不能为空")
+    private String name;
+    /**
+     * 用于区分输入或输出参数
+     *
+     * 枚举 {@link IotThingModelParamDirectionEnum}
+     */
+    @NotEmpty(message = "参数方向不能为空")
+    @InEnum(IotThingModelParamDirectionEnum.class)
+    private String direction;
+    /**
+     * 参数的序号。从 0 开始排序,且不能重复。
+     *
+     * TODO 考虑要不要序号,感觉是要的, 先留一手看看
+     */
+    private Integer paraOrder;
+    /**
+     * 参数值的数据类型,与 dataSpecs 的 dataType 保持一致
+     *
+     * 枚举 {@link IotDataSpecsDataTypeEnum}
+     */
+    @NotEmpty(message = "数据类型不能为空")
+    @InEnum(IotDataSpecsDataTypeEnum.class)
+    private String dataType;
+    /**
+     * 参数值的数据类型(dataType)为非列表型(int、float、double、text、date、array)的数据规范存储在 dataSpecs 中
+     */
+    private ThingModelDataSpecs dataSpecs;
+    /**
+     * 参数值的数据类型(dataType)为列表型(enum、bool、struct)的数据规范存储在 dataSpecsList 中
+     */
+    private List<ThingModelDataSpecs> dataSpecsList;
+
+}

+ 63 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelProperty.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType.ThingModelDataSpecs;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+import java.util.List;
+
+/**
+ * IoT 物模型中的属性
+ *
+ * dataSpecs 和 dataSpecsList 之中必须传入且只能传入一个
+ *
+ * @author HUIHUI
+ */
+@Data
+public class ThingModelProperty {
+
+    /**
+     * 属性标识符
+     */
+    @NotEmpty(message = "属性标识符不能为空")
+//    @Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]*$", message = "属性标识符只能由字母、数字和下划线组成,必须以字母开头")
+    private String identifier;
+    /**
+     * 属性名称
+     */
+    @NotEmpty(message = "属性名称不能为空")
+    private String name;
+    /**
+     * 云端可以对该属性进行的操作类型
+     *
+     * 枚举 {@link IotThingModelAccessModeEnum}
+     */
+    @NotEmpty(message = "操作类型不能为空")
+    @InEnum(IotThingModelAccessModeEnum.class)
+    private String accessMode;
+    /**
+     * 是否是标准品类的必选服务
+     */
+    private Boolean required;
+    /**
+     * 参数值的数据类型,与 dataSpecs 的 dataType 保持一致
+     *
+     * 枚举 {@link IotDataSpecsDataTypeEnum}
+     */
+    @NotEmpty(message = "数据类型不能为空")
+    @InEnum(IotDataSpecsDataTypeEnum.class)
+    private String dataType;
+    /**
+     * 数据类型(dataType)为非列表型(int、float、double、text、date、array)的数据规范存储在 dataSpecs 中
+     */
+    private ThingModelDataSpecs dataSpecs;
+    /**
+     * 数据类型(dataType)为列表型(enum、bool、struct)的数据规范存储在 dataSpecsList 中
+     */
+    private List<ThingModelDataSpecs> dataSpecsList;
+
+}

+ 62 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/ThingModelService.java

@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelServiceCallTypeEnum;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+import java.util.List;
+
+/**
+ * IoT 物模型中的服务
+ *
+ * @author HUIHUI
+ */
+@Data
+public class ThingModelService {
+
+    /**
+     * 服务标识符
+     */
+    @NotEmpty(message = "服务标识符不能为空")
+    //@Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]*$", message = "属性标识符只能由字母、数字和下划线组成,必须以字母开头")
+    private String identifier;
+    /**
+     * 服务名称
+     */
+    @NotEmpty(message = "服务名称不能为空")
+    private String name;
+    /**
+     * 是否是标准品类的必选服务
+     */
+    private Boolean required;
+    /**
+     * 调用类型
+     *
+     * 枚举 {@link IotThingModelServiceCallTypeEnum}
+     */
+    @NotEmpty(message = "调用类型不能为空")
+    @InEnum(IotThingModelServiceCallTypeEnum.class)
+    private String callType;
+    /**
+     * 服务的输入参数
+     *
+     * 输入参数定义服务调用时所需提供的信息,用于控制设备行为或执行特定任务
+     */
+    @Valid
+    private List<ThingModelParam> inputParams;
+    /**
+     * 服务的输出参数
+     *
+     * 输出参数定义服务调用后返回的结果或反馈信息,用于确认操作结果或提供额外的信息。
+     */
+    @Valid
+    private List<ThingModelParam> outputParams;
+    /**
+     * 标识设备需要执行的具体操作
+     */
+    private String method;
+
+}

+ 37 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelArrayDataSpecs.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import java.util.List;
+
+/**
+ * IoT 物模型数据类型为数组的 DataSpec 定义
+ *
+ * @author HUIHUI
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段,从而避免重复
+public class ThingModelArrayDataSpecs extends ThingModelDataSpecs {
+
+    @NotNull(message = "数组元素个数不能为空")
+    private Integer size;
+
+    @NotEmpty(message = "数组元素的数据类型不能为空")
+    @Pattern(regexp = "^(struct|int|float|double|text)$", message = "数组元素的数据类型必须为:struct、int、float、double 或 text")
+    private String childDataType;
+    /**
+     * 数据类型(childDataType)为列表型 struct 的数据规范存储在 dataSpecsList 中
+     * 此时 struct 取值范围为:int、float、double、text、date、enum、bool
+     */
+    @Valid
+    private List<ThingModelDataSpecs> dataSpecsList;
+
+}
+

+ 30 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelBoolOrEnumDataSpecs.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+/**
+ * IoT 物模型数据类型为布尔型或枚举型的 DataSpec 定义
+ *
+ * 数据类型,取值为 bool 或 enum
+ *
+ * @author HUIHUI
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段,从而避免重复
+public class ThingModelBoolOrEnumDataSpecs extends ThingModelDataSpecs {
+
+    @NotEmpty(message = "枚举项的名称不能为空")
+    @Pattern(regexp = "^[\\u4e00-\\u9fa5a-zA-Z0-9][\\u4e00-\\u9fa5a-zA-Z0-9_-]{0,19}$", message = "枚举项的名称只能包含中文、大小写英文字母、数字、下划线和短划线,必须以中文、英文字母或数字开头,长度不超过 20 个字符")
+    private String name;
+
+    @NotNull(message = "枚举值不能为空")
+    private Integer value;
+
+}

+ 35 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelDataSpecs.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import lombok.Data;
+
+/**
+ * IoT ThingModelDataSpecs 抽象类
+ *
+ * 用于表示物模型数据的通用类型,根据具体的 "dataType" 字段动态映射到对应的子类
+ * 提供多态支持,适用于不同类型的数据结构序列化和反序列化场景
+ *
+ * @author HUIHUI
+ */
+@Data
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "dataType", visible = true)
+@JsonSubTypes({
+        @JsonSubTypes.Type(value = ThingModelNumericDataSpec.class, name = "int"),
+        @JsonSubTypes.Type(value = ThingModelNumericDataSpec.class, name = "float"),
+        @JsonSubTypes.Type(value = ThingModelNumericDataSpec.class, name = "double"),
+        @JsonSubTypes.Type(value = ThingModelDateOrTextDataSpecs.class, name = "text"),
+        @JsonSubTypes.Type(value = ThingModelDateOrTextDataSpecs.class, name = "date"),
+        @JsonSubTypes.Type(value = ThingModelBoolOrEnumDataSpecs.class, name = "bool"),
+        @JsonSubTypes.Type(value = ThingModelBoolOrEnumDataSpecs.class, name = "enum"),
+        @JsonSubTypes.Type(value = ThingModelArrayDataSpecs.class, name = "array"),
+        @JsonSubTypes.Type(value = ThingModelStructDataSpecs.class, name = "struct")
+})
+public abstract class ThingModelDataSpecs {
+
+    /**
+     * 数据类型
+     */
+    private String dataType;
+
+}

+ 34 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelDateOrTextDataSpecs.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.Max;
+
+/**
+ * IoT 物模型数据类型为时间型或文本型的 DataSpec 定义
+ *
+ * 数据类型,取值为 date 或 text
+ *
+ * @author HUIHUI
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段,从而避免重复
+public class ThingModelDateOrTextDataSpecs extends ThingModelDataSpecs {
+
+    /**
+     * 数据长度,单位为字节。取值不能超过 2048
+     *
+     * 当 dataType 为 text 时,需传入该参数
+     */
+    @Max(value = 2048, message = "数据长度不能超过 2048")
+    private Integer length;
+    /**
+     * 默认值,可选参数,用于存储默认值
+     */
+    private String defaultValue;
+
+}
+

+ 57 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelNumericDataSpec.java

@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+
+/**
+ * IoT 物模型数据类型为数值的 DataSpec 定义
+ *
+ * 数据类型,取值为 int、float 或 double
+ *
+ * @author HUIHUI
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段,从而避免重复
+public class ThingModelNumericDataSpec extends ThingModelDataSpecs {
+
+    /**
+     * 最大值,需转为字符串类型。值必须与 dataType 类型一致
+     */
+    @NotEmpty(message = "最大值不能为空")
+    @Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "最大值必须为数值类型")
+    private String max;
+    /**
+     * 最小值,需转为字符串类型。值必须与 dataType 类型一致
+     */
+    @NotEmpty(message = "最小值不能为空")
+    @Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "最小值必须为数值类型")
+    private String min;
+    /**
+     * 步长,需转为字符串类型。值必须与 dataType 类型一致
+     */
+    @NotEmpty(message = "步长不能为空")
+    @Pattern(regexp = "^-?\\d+(\\.\\d+)?$", message = "步长必须为数值类型")
+    private String step;
+    /**
+     * 精度。当 dataType 为 float 或 double 时可选传入
+     */
+    private String precise;
+    /**
+     * 默认值,可传入用于存储的默认值
+     */
+    private String defaultValue;
+    /**
+     * 单位的符号
+     */
+    private String unit;
+    /**
+     * 单位的名称
+     */
+    private String unitName;
+
+}

+ 57 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/model/dataType/ThingModelStructDataSpecs.java

@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.dataType;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelAccessModeEnum;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+import java.util.List;
+
+/**
+ * IoT 物模型数据类型为 struct 的 DataSpec 定义
+ *
+ * @author HUIHUI
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@JsonIgnoreProperties({"dataType"}) // 忽略子类中的 dataType 字段,从而避免重复
+public class ThingModelStructDataSpecs extends ThingModelDataSpecs {
+
+    @NotEmpty(message = "属性标识符不能为空")
+//    @Pattern(regexp = "^[a-zA-Z][a-zA-Z0-9_]*$", message = "属性标识符只能由字母、数字和下划线组成,必须以字母开头")
+    private String identifier;
+
+    @NotEmpty(message = "属性名称不能为空")
+    private String name;
+
+    @NotEmpty(message = "操作类型不能为空")
+    @InEnum(IotThingModelAccessModeEnum.class)
+    private String accessMode;
+
+    /**
+     * 是否是标准品类的必选服务
+     */
+    private Boolean required;
+
+    @NotEmpty(message = "数据类型不能为空")
+    @Pattern(regexp = "^(int|float|double|text|date|enum|bool)$", message = "数据类型必须为:int、float、double、text、date、enum、bool")
+    private String childDataType;
+
+    /**
+     * 数据类型(dataType)为非列表型(int、float、double、text、date、array)的数据规范存储在 dataSpecs 中
+     */
+    @Valid
+    private ThingModelDataSpecs dataSpecs;
+
+    /**
+     * 数据类型(dataType)为列表型(enum、bool、struct)的数据规范存储在 dataSpecsList 中
+     */
+    @Valid
+    private List<ThingModelDataSpecs> dataSpecsList;
+
+}
+

+ 39 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.alert;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 告警配置 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotAlertConfigMapper extends BaseMapperX<IotAlertConfigDO> {
+
+    default PageResult<IotAlertConfigDO> selectPage(IotAlertConfigPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<IotAlertConfigDO>()
+                .likeIfPresent(IotAlertConfigDO::getName, reqVO.getName())
+                .eqIfPresent(IotAlertConfigDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(IotAlertConfigDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(IotAlertConfigDO::getId));
+    }
+
+    default List<IotAlertConfigDO> selectListByStatus(Integer status) {
+        return selectList(IotAlertConfigDO::getStatus, status);
+    }
+
+    default List<IotAlertConfigDO> selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) {
+        return selectList(new LambdaQueryWrapperX<IotAlertConfigDO>()
+                .eq(IotAlertConfigDO::getStatus, status)
+                .apply(MyBatisUtils.findInSet("scene_rule_ids", sceneRuleId)));
+    }
+
+}

+ 46 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertRecordMapper.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.alert;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.recrod.IotAlertRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertRecordDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * IoT 告警记录 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotAlertRecordMapper extends BaseMapperX<IotAlertRecordDO> {
+
+    default PageResult<IotAlertRecordDO> selectPage(IotAlertRecordPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<IotAlertRecordDO>()
+                .eqIfPresent(IotAlertRecordDO::getConfigId, reqVO.getConfigId())
+                .eqIfPresent(IotAlertRecordDO::getConfigLevel, reqVO.getLevel())
+                .eqIfPresent(IotAlertRecordDO::getProductId, reqVO.getProductId())
+                .eqIfPresent(IotAlertRecordDO::getDeviceId, reqVO.getDeviceId())
+                .eqIfPresent(IotAlertRecordDO::getProcessStatus, reqVO.getProcessStatus())
+                .betweenIfPresent(IotAlertRecordDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(IotAlertRecordDO::getId));
+    }
+
+    default List<IotAlertRecordDO> selectListBySceneRuleId(Long sceneRuleId, Long deviceId, Boolean processStatus) {
+        return selectList(new LambdaQueryWrapperX<IotAlertRecordDO>()
+                .eq(IotAlertRecordDO::getSceneRuleId, sceneRuleId)
+                .eqIfPresent(IotAlertRecordDO::getDeviceId, deviceId)
+                .eqIfPresent(IotAlertRecordDO::getProcessStatus, processStatus)
+                .orderByDesc(IotAlertRecordDO::getId));
+    }
+
+    default int updateList(Collection<Long> ids, IotAlertRecordDO updateObj) {
+        return update(updateObj, new LambdaUpdateWrapper<IotAlertRecordDO>()
+                .in(IotAlertRecordDO::getId, ids));
+    }
+
+}

+ 30 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusConfigMapper.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.device;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 设备 Modbus 连接配置 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotDeviceModbusConfigMapper extends BaseMapperX<IotDeviceModbusConfigDO> {
+
+    default IotDeviceModbusConfigDO selectByDeviceId(Long deviceId) {
+        return selectOne(IotDeviceModbusConfigDO::getDeviceId, deviceId);
+    }
+
+    default List<IotDeviceModbusConfigDO> selectList(IotModbusDeviceConfigListReqDTO reqDTO) {
+        return selectList(new LambdaQueryWrapperX<IotDeviceModbusConfigDO>()
+                .eqIfPresent(IotDeviceModbusConfigDO::getStatus, reqDTO.getStatus())
+                .eqIfPresent(IotDeviceModbusConfigDO::getMode, reqDTO.getMode())
+                .inIfPresent(IotDeviceModbusConfigDO::getDeviceId, reqDTO.getDeviceIds()));
+    }
+
+}

+ 47 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusPointMapper.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.device;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * IoT 设备 Modbus 点位配置 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotDeviceModbusPointMapper extends BaseMapperX<IotDeviceModbusPointDO> {
+
+    default PageResult<IotDeviceModbusPointDO> selectPage(IotDeviceModbusPointPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceModbusPointDO>()
+                .eqIfPresent(IotDeviceModbusPointDO::getDeviceId, reqVO.getDeviceId())
+                .likeIfPresent(IotDeviceModbusPointDO::getIdentifier, reqVO.getIdentifier())
+                .likeIfPresent(IotDeviceModbusPointDO::getName, reqVO.getName())
+                .eqIfPresent(IotDeviceModbusPointDO::getFunctionCode, reqVO.getFunctionCode())
+                .eqIfPresent(IotDeviceModbusPointDO::getStatus, reqVO.getStatus())
+                .orderByDesc(IotDeviceModbusPointDO::getId));
+    }
+
+    default List<IotDeviceModbusPointDO> selectListByDeviceIdsAndStatus(Collection<Long> deviceIds, Integer status) {
+        return selectList(new LambdaQueryWrapperX<IotDeviceModbusPointDO>()
+                .in(IotDeviceModbusPointDO::getDeviceId, deviceIds)
+                .eq(IotDeviceModbusPointDO::getStatus, status));
+    }
+
+    default IotDeviceModbusPointDO selectByDeviceIdAndIdentifier(Long deviceId, String identifier) {
+        return selectOne(IotDeviceModbusPointDO::getDeviceId, deviceId,
+                IotDeviceModbusPointDO::getIdentifier, identifier);
+    }
+
+    default void updateByThingModelId(Long thingModelId, IotDeviceModbusPointDO updateObj) {
+        update(updateObj, new LambdaQueryWrapperX<IotDeviceModbusPointDO>()
+                .eq(IotDeviceModbusPointDO::getThingModelId, thingModelId));
+    }
+
+}

+ 32 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.ota;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface IotOtaTaskMapper extends BaseMapperX<IotOtaTaskDO> {
+
+    default IotOtaTaskDO selectByFirmwareIdAndName(Long firmwareId, String name) {
+        return selectOne(IotOtaTaskDO::getFirmwareId, firmwareId,
+                IotOtaTaskDO::getName, name);
+    }
+
+    default PageResult<IotOtaTaskDO> selectPage(IotOtaTaskPageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskDO>()
+                .eqIfPresent(IotOtaTaskDO::getFirmwareId, pageReqVO.getFirmwareId())
+                .likeIfPresent(IotOtaTaskDO::getName, pageReqVO.getName())
+                .orderByDesc(IotOtaTaskDO::getId));
+    }
+
+    default int updateByIdAndStatus(Long id, Integer whereStatus, IotOtaTaskDO updateObj) {
+        return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskDO>()
+                .eq(IotOtaTaskDO::getId, id)
+                .eq(IotOtaTaskDO::getStatus, whereStatus));
+    }
+
+}

+ 80 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java

@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.ota;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+@Mapper
+public interface IotOtaTaskRecordMapper extends BaseMapperX<IotOtaTaskRecordDO> {
+
+    default List<IotOtaTaskRecordDO> selectListByFirmwareIdAndTaskId(Long firmwareId, Long taskId) {
+        return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
+                .eqIfPresent(IotOtaTaskRecordDO::getFirmwareId, firmwareId)
+                .eqIfPresent(IotOtaTaskRecordDO::getTaskId, taskId)
+                .select(IotOtaTaskRecordDO::getDeviceId, IotOtaTaskRecordDO::getStatus));
+    }
+
+    default PageResult<IotOtaTaskRecordDO> selectPage(IotOtaTaskRecordPageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
+                .eqIfPresent(IotOtaTaskRecordDO::getTaskId, pageReqVO.getTaskId())
+                .eqIfPresent(IotOtaTaskRecordDO::getStatus, pageReqVO.getStatus()));
+    }
+
+    default List<IotOtaTaskRecordDO> selectListByTaskIdAndStatus(Long taskId, Collection<Integer> statuses) {
+        return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
+               .eq(IotOtaTaskRecordDO::getTaskId, taskId)
+               .in(IotOtaTaskRecordDO::getStatus, statuses));
+    }
+
+    default Long selectCountByTaskIdAndStatus(Long taskId, Collection<Integer> statuses) {
+        return selectCount(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
+                .eq(IotOtaTaskRecordDO::getTaskId, taskId)
+                .in(IotOtaTaskRecordDO::getStatus, statuses));
+    }
+
+    default int updateByIdAndStatus(Long id, Integer status,
+                                    IotOtaTaskRecordDO updateObj) {
+        return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
+                .eq(IotOtaTaskRecordDO::getId, id)
+                .eq(IotOtaTaskRecordDO::getStatus, status));
+    }
+
+    default int updateByIdAndStatus(Long id, Collection<Integer> whereStatuses,
+                                    IotOtaTaskRecordDO updateObj) {
+        return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
+                .eq(IotOtaTaskRecordDO::getId, id)
+                .in(IotOtaTaskRecordDO::getStatus, whereStatuses));
+    }
+
+    default void updateListByIdAndStatus(Collection<Long> ids, Collection<Integer> whereStatuses,
+                                         IotOtaTaskRecordDO updateObj) {
+        update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
+                .in(IotOtaTaskRecordDO::getId, ids)
+                .in(IotOtaTaskRecordDO::getStatus, whereStatuses));
+    }
+
+    default List<IotOtaTaskRecordDO> selectListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses) {
+        return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
+                .inIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceIds)
+                .inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
+    }
+
+    default List<IotOtaTaskRecordDO> selectListByDeviceIdAndStatus(Long deviceId, Set<Integer> statuses) {
+        return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
+                .eqIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceId)
+                .inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
+    }
+
+    default List<IotOtaTaskRecordDO> selectListByStatus(Integer status) {
+        return selectList(IotOtaTaskRecordDO::getStatus, status);
+    }
+
+}

+ 42 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.rule;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 数据流转规则 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface IotDataRuleMapper extends BaseMapperX<IotDataRuleDO> {
+
+    default PageResult<IotDataRuleDO> selectPage(IotDataRulePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<IotDataRuleDO>()
+                .likeIfPresent(IotDataRuleDO::getName, reqVO.getName())
+                .eqIfPresent(IotDataRuleDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(IotDataRuleDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(IotDataRuleDO::getId));
+    }
+
+    default List<IotDataRuleDO> selectListBySinkId(Long sinkId) {
+        return selectList(new LambdaQueryWrapperX<IotDataRuleDO>()
+                .apply(MyBatisUtils.findInSet("sink_ids", sinkId)));
+    }
+
+    default List<IotDataRuleDO> selectListByStatus(Integer status) {
+        return selectList(IotDataRuleDO::getStatus, status);
+    }
+
+    default IotDataRuleDO selectByName(String name) {
+        return selectOne(IotDataRuleDO::getName, name);
+    }
+
+}

+ 37 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataSinkMapper.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.rule;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 数据流转目的 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface IotDataSinkMapper extends BaseMapperX<IotDataSinkDO> {
+
+    default PageResult<IotDataSinkDO> selectPage(IotDataSinkPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<IotDataSinkDO>()
+                .likeIfPresent(IotDataSinkDO::getName, reqVO.getName())
+                .eqIfPresent(IotDataSinkDO::getStatus, reqVO.getStatus())
+                .eqIfPresent(IotDataSinkDO::getType, reqVO.getType())
+                .betweenIfPresent(IotDataSinkDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(IotDataSinkDO::getId));
+    }
+
+    default List<IotDataSinkDO> selectListByStatus(Integer status) {
+        return selectList(IotDataSinkDO::getStatus, status);
+    }
+
+    default IotDataSinkDO selectByName(String name) {
+        return selectOne(IotDataSinkDO::getName, name);
+    }
+
+}

+ 33 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotSceneRuleMapper.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.iot.dal.mysql.rule;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * IoT 场景联动 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface IotSceneRuleMapper extends BaseMapperX<IotSceneRuleDO> {
+
+    default PageResult<IotSceneRuleDO> selectPage(IotSceneRulePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<IotSceneRuleDO>()
+                .likeIfPresent(IotSceneRuleDO::getName, reqVO.getName())
+                .likeIfPresent(IotSceneRuleDO::getDescription, reqVO.getDescription())
+                .eqIfPresent(IotSceneRuleDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(IotSceneRuleDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(IotSceneRuleDO::getId));
+    }
+
+    default List<IotSceneRuleDO> selectListByStatus(Integer status) {
+        return selectList(IotSceneRuleDO::getStatus, status);
+    }
+
+}

+ 31 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DeviceServerIdRedisDAO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.iot.dal.redis.device;
+
+import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+
+/**
+ * 设备关联的网关 serverId 的 Redis DAO
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class DeviceServerIdRedisDAO {
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    public void update(Long deviceId, String serverId) {
+        stringRedisTemplate.opsForHash().put(RedisKeyConstants.DEVICE_SERVER_ID,
+                String.valueOf(deviceId), serverId);
+    }
+
+    public String get(Long deviceId) {
+        Object value = stringRedisTemplate.opsForHash().get(RedisKeyConstants.DEVICE_SERVER_ID,
+                String.valueOf(deviceId));
+        return value != null ? (String) value : null;
+    }
+
+}

+ 79 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMessageMapper.java

@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.iot.dal.tdengine;
+
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.message.IotDeviceMessagePageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceMessageDO;
+import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 设备消息 {@link IotDeviceMessageDO} Mapper 接口
+ */
+@Mapper
+@TDengineDS
+@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
+public interface IotDeviceMessageMapper {
+
+    /**
+     * 创建设备消息超级表
+     */
+    void createSTable();
+
+    /**
+     * 查询设备消息表是否存在
+     *
+     * @return 存在则返回表名;不存在则返回 null
+     */
+    String showSTable();
+
+    /**
+     * 插入设备消息数据
+     *
+     * 如果子表不存在,会自动创建子表
+     *
+     * @param message 设备消息数据
+     */
+    void insert(IotDeviceMessageDO message);
+
+    /**
+     * 获得设备消息分页
+     *
+     * @param reqVO 分页查询条件
+     * @return 设备消息列表
+     */
+    IPage<IotDeviceMessageDO> selectPage(IPage<IotDeviceMessageDO> page,
+                                         @Param("reqVO") IotDeviceMessagePageReqVO reqVO);
+
+    /**
+     * 统计设备消息数量
+     *
+     * @param createTime 创建时间,如果为空,则统计所有消息数量
+     * @return 消息数量
+     */
+    Long selectCountByCreateTime(@Param("createTime") Long createTime);
+
+    /**
+     * 按照 requestIds 批量查询消息
+     *
+     * @param deviceId 设备编号
+     * @param requestIds 请求编号集合
+     * @param reply 是否回复消息
+     * @return 消息列表
+     */
+    List<IotDeviceMessageDO> selectListByRequestIdsAndReply(@Param("deviceId") Long deviceId,
+                                                            @Param("requestIds") Collection<String> requestIds,
+                                                            @Param("reply") Boolean reply);
+
+    /**
+     * 按照时间范围(小时),统计设备的消息数量
+     */
+    List<Map<String, Object>> selectDeviceMessageCountGroupByDate(@Param("startTime") Long startTime,
+                                                                  @Param("endTime") Long endTime);
+
+}

+ 25 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.enums;
+
+/**
+ * IoT 字典类型的枚举类
+ *
+ * @author 芋道源码
+ */
+public class DictTypeConstants {
+
+    public static final String NET_TYPE = "iot_net_type";
+    public static final String PROTOCOL_TYPE = "iot_protocol_type";
+    public static final String SERIALIZE_TYPE = "iot_serialize_type";
+
+    public static final String PRODUCT_STATUS = "iot_product_status";
+    public static final String PRODUCT_DEVICE_TYPE = "iot_product_device_type";
+
+    public static final String DEVICE_STATE = "iot_device_state";
+
+    public static final String ALERT_LEVEL = "iot_alert_level";
+
+    public static final String OTA_TASK_DEVICE_SCOPE = "iot_ota_task_device_scope";
+    public static final String OTA_TASK_STATUS = "iot_ota_task_status";
+    public static final String OTA_TASK_RECORD_STATUS = "iot_ota_task_record_status";
+
+}

+ 105 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java

@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.iot.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * iot 错误码枚举类
+ * <p>
+ * iot 系统,使用 1-050-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+    // ========== 产品相关 1-050-001-000 ============
+    ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在");
+    ErrorCode PRODUCT_KEY_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
+    ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
+    ErrorCode PRODUCT_STATUS_NOT_ALLOW_THING_MODEL = new ErrorCode(1_050_001_003, "产品状是发布状态,不允许操作物模型");
+    ErrorCode PRODUCT_DELETE_FAIL_HAS_DEVICE = new ErrorCode(1_050_001_004, "产品下存在设备,不允许删除");
+
+    // ========== 产品物模型 1-050-002-000 ============
+    ErrorCode THING_MODEL_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
+    ErrorCode THING_MODEL_EXISTS_BY_PRODUCT_KEY = new ErrorCode(1_050_002_001, "ProductKey 对应的产品物模型已存在");
+    ErrorCode THING_MODEL_IDENTIFIER_EXISTS = new ErrorCode(1_050_002_002, "存在重复的功能标识符。");
+    ErrorCode THING_MODEL_NAME_EXISTS = new ErrorCode(1_050_002_003, "存在重复的功能名称。");
+    ErrorCode THING_MODEL_IDENTIFIER_INVALID = new ErrorCode(1_050_002_003, "产品物模型标识无效");
+
+    // ========== 设备 1-050-003-000 ============
+    ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
+    ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
+    ErrorCode DEVICE_GATEWAY_HAS_SUB = new ErrorCode(1_050_003_002, "网关设备存在已绑定的子设备,不允许删除");
+    ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
+    ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
+    ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
+    ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
+    ErrorCode DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL = new ErrorCode(1_050_003_007, "下行设备消息失败,原因:设备未连接网关");
+    ErrorCode DEVICE_SERIAL_NUMBER_EXISTS = new ErrorCode(1_050_003_008, "设备序列号已存在,序列号必须全局唯一");
+    ErrorCode DEVICE_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_009, "设备【{}/{}】不是网关子设备类型,无法绑定到网关");
+    ErrorCode DEVICE_GATEWAY_BINDTO_EXISTS = new ErrorCode(1_050_003_010, "设备【{}/{}】已绑定到其他网关,请先解绑");
+    // 拓扑管理相关错误码 1-050-003-100
+    ErrorCode DEVICE_TOPO_PARAMS_INVALID = new ErrorCode(1_050_003_100, "拓扑管理参数无效");
+    ErrorCode DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID = new ErrorCode(1_050_003_101, "子设备用户名格式无效");
+    ErrorCode DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED = new ErrorCode(1_050_003_102, "子设备认证失败");
+    ErrorCode DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY = new ErrorCode(1_050_003_103, "子设备【{}/{}】未绑定到该网关");
+    // 设备注册相关错误码 1-050-003-200
+    ErrorCode DEVICE_SUB_REGISTER_PARAMS_INVALID = new ErrorCode(1_050_003_200, "子设备注册参数无效");
+    ErrorCode DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_201, "产品【{}】不是网关子设备类型");
+    ErrorCode DEVICE_REGISTER_DISABLED = new ErrorCode(1_050_003_210, "该产品未开启动态注册功能");
+    ErrorCode DEVICE_REGISTER_SECRET_INVALID = new ErrorCode(1_050_003_211, "产品密钥验证失败");
+    ErrorCode DEVICE_REGISTER_ALREADY_EXISTS = new ErrorCode(1_050_003_212, "设备已存在,不允许重复注册");
+
+    // ========== 产品分类 1-050-004-000 ==========
+    ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
+
+    // ========== 设备分组 1-050-005-000 ==========
+    ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
+    ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
+
+    // ========== 设备 Modbus 配置 1-050-006-000 ==========
+    ErrorCode DEVICE_MODBUS_CONFIG_NOT_EXISTS = new ErrorCode(1_050_006_000, "设备 Modbus 连接配置不存在");
+    ErrorCode DEVICE_MODBUS_CONFIG_EXISTS = new ErrorCode(1_050_006_001, "设备 Modbus 连接配置已存在");
+
+    // ========== 设备 Modbus 点位 1-050-007-000 ==========
+    ErrorCode DEVICE_MODBUS_POINT_NOT_EXISTS = new ErrorCode(1_050_007_000, "设备 Modbus 点位配置不存在");
+    ErrorCode DEVICE_MODBUS_POINT_EXISTS = new ErrorCode(1_050_007_001, "设备 Modbus 点位配置已存在");
+
+    // ========== OTA 固件相关 1-050-008-000 ==========
+
+    ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
+    ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
+
+    // ========== OTA 升级任务相关 1-050-008-100 ==========
+
+    ErrorCode OTA_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在");
+    ErrorCode OTA_TASK_CREATE_FAIL_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "创建 OTA 任务失败,原因:任务名称重复");
+    ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_FIRMWARE_EXISTS = new ErrorCode(1_050_008_102,
+            "创建 OTA 任务失败,原因:设备({})已经是该固件版本");
+    ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_OTA_IN_PROCESS = new ErrorCode(1_050_008_102,
+            "创建 OTA 任务失败,原因:设备({})已经在升级中...");
+    ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_EMPTY = new ErrorCode(1_050_008_103, "创建 OTA 任务失败,原因:没有可升级的设备");
+    ErrorCode OTA_TASK_CANCEL_FAIL_STATUS_END = new ErrorCode(1_050_008_104, "取消 OTA 任务失败,原因:任务状态不是进行中");
+
+    // ========== OTA 升级任务记录相关 1-050-008-200 ==========
+
+    ErrorCode OTA_TASK_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
+    ErrorCode OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR = new ErrorCode(1_050_008_201, "取消 OTA 升级记录失败,原因:记录状态不是进行中");
+    ErrorCode OTA_TASK_RECORD_UPDATE_PROGRESS_FAIL_NO_EXISTS = new ErrorCode(1_050_008_202, "更新 OTA 升级记录进度失败,原因:该设备没有进行中的升级记录");
+
+    // ========== IoT 数据流转规则 1-050-010-000 ==========
+    ErrorCode DATA_RULE_NOT_EXISTS = new ErrorCode(1_050_010_000, "数据流转规则不存在");
+    ErrorCode DATA_RULE_NAME_EXISTS = new ErrorCode(1_050_010_001, "数据流转规则名称已存在");
+
+    // ========== IoT 数据流转目的 1-050-011-000 ==========
+    ErrorCode DATA_SINK_NOT_EXISTS = new ErrorCode(1_050_011_000, "数据桥梁不存在");
+    ErrorCode DATA_SINK_DELETE_FAIL_USED_BY_RULE = new ErrorCode(1_050_011_001, "数据流转目的正在被数据流转规则使用,无法删除");
+    ErrorCode DATA_SINK_NAME_EXISTS = new ErrorCode(1_050_011_002, "数据流转目的名称已存在");
+
+    // ========== IoT 场景联动 1-050-012-000 ==========
+    ErrorCode RULE_SCENE_NOT_EXISTS = new ErrorCode(1_050_012_000, "场景联动不存在");
+
+    // ========== IoT 告警配置 1-050-013-000 ==========
+    ErrorCode ALERT_CONFIG_NOT_EXISTS = new ErrorCode(1_050_013_000, "IoT 告警配置不存在");
+
+    // ========== IoT 告警记录 1-050-014-000 ==========
+    ErrorCode ALERT_RECORD_NOT_EXISTS = new ErrorCode(1_050_014_000, "IoT 告警记录不存在");
+
+}

+ 46 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/alert/IotAlertReceiveTypeEnum.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.iot.enums.alert;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT 告警的接收方式枚举
+ *
+ * @author 芋道源码
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotAlertReceiveTypeEnum implements ArrayValuable<Integer> {
+
+    SMS(1, "iot_alert_sms"), // 短信
+    MAIL(2, "iot_alert_mail"), // 邮箱
+    NOTIFY(3, "iot_alert_notify"); // 站内信
+    // TODO 待实现(欢迎 pull request):webhook 4
+
+    /**
+     * 接收方式
+     */
+    private final Integer type;
+    /**
+     * 模板编号
+     *
+     * 关联 SmsTemplateDO / MailTemplateDO / NotifyTemplateDO 的 code 属性
+     */
+    private final String templateCode;
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotAlertReceiveTypeEnum::getType).toArray(Integer[]::new);
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+    public static IotAlertReceiveTypeEnum of(Integer type) {
+        return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
+    }
+
+}

+ 34 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskDeviceScopeEnum.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.iot.enums.ota;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * IoT OTA 升级任务的设备范围枚举
+ *
+ * @author haohao
+ */
+@RequiredArgsConstructor
+@Getter
+public enum IotOtaTaskDeviceScopeEnum implements ArrayValuable<Integer> {
+
+    ALL(1), // 全部设备:只包括当前产品下的设备,不包括未来创建的设备
+    SELECT(2); // 指定设备
+
+    public static final Integer[] ARRAYS = Arrays.stream(values())
+            .map(IotOtaTaskDeviceScopeEnum::getScope).toArray(Integer[]::new);
+
+    /**
+     * 范围
+     */
+    private final Integer scope;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}

部分文件因为文件数量过多而无法显示