Pārlūkot izejas kodu

Merge remote-tracking branch 'origin/master'

zhangcl 14 stundas atpakaļ
vecāks
revīzija
ec2327803f
100 mainītis faili ar 2137 papildinājumiem un 1985 dzēšanām
  1. 1 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java
  2. 44 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/MapUtils.java
  3. 70 7
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
  4. 90 1
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java
  5. 15 5
      yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java
  6. 6 7
      yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java
  7. 72 0
      yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java
  8. 6 0
      yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/stream/AbstractRedisStreamMessageListener.java
  9. 4 5
      yudao-module-iot/pom.xml
  10. 0 53
      yudao-module-iot/yudao-module-iot-api/pom.xml
  11. 0 94
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java
  12. 0 22
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceConfigSetReqDTO.java
  13. 0 66
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceOtaUpgradeReqDTO.java
  14. 0 24
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDevicePropertyGetReqDTO.java
  15. 0 22
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDevicePropertySetReqDTO.java
  16. 0 26
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceServiceInvokeReqDTO.java
  17. 0 26
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceEventReportReqDTO.java
  18. 0 35
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceOtaProgressReqDTO.java
  19. 0 21
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceOtaPullReqDTO.java
  20. 0 21
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceOtaReportReqDTO.java
  21. 0 22
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDevicePropertyReportReqDTO.java
  22. 0 12
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterReqDTO.java
  23. 0 43
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterSubReqDTO.java
  24. 0 24
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceStateUpdateReqDTO.java
  25. 0 44
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceTopologyAddReqDTO.java
  26. 0 45
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java
  27. 0 45
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotPluginInstanceHeartbeatReqDTO.java
  28. 0 4
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/package-info.java
  29. 0 6
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
  30. 0 16
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ApiConstants.java
  31. 0 75
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
  32. 0 44
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java
  33. 0 37
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java
  34. 0 35
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeTaskStatusEnum.java
  35. 0 37
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java
  36. 0 37
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java
  37. 0 37
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java
  38. 0 38
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotDataFormatEnum.java
  39. 0 40
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProtocolTypeEnum.java
  40. 0 37
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotValidateTypeEnum.java
  41. 0 31
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java
  42. 0 30
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerTypeEnum.java
  43. 28 47
      yudao-module-iot/yudao-module-iot-biz/pom.xml
  44. 0 61
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java
  45. 142 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
  46. 0 78
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
  47. 1 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
  48. 104 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertConfigController.java
  49. 58 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/IotAlertRecordController.java
  50. 26 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigPageReqVO.java
  51. 43 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigRespVO.java
  52. 47 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/config/IotAlertConfigSaveReqVO.java
  53. 35 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordPageReqVO.java
  54. 19 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordProcessReqVO.java
  55. 43 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/recrod/IotAlertRecordRespVO.java
  56. 0 75
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.http
  57. 110 44
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
  58. 0 40
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceLogController.java
  59. 101 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.http
  60. 92 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceMessageController.java
  61. 55 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusConfigController.java
  62. 73 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusPointController.java
  63. 37 43
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDevicePropertyController.java
  64. 7 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/NewIotDeviceGroupController.java
  65. 0 31
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/control/IotDeviceDownstreamReqVO.java
  66. 0 31
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/control/IotDeviceUpstreamReqVO.java
  67. 0 23
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogPageReqVO.java
  68. 0 36
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogRespVO.java
  69. 24 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceAuthInfoRespVO.java
  70. 22 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceBindGatewayReqVO.java
  71. 22 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceByProductKeyAndNamesReqVO.java
  72. 1 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java
  73. 0 25
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceMqttConnectionParamsRespVO.java
  74. 4 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java
  75. 12 10
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java
  76. 13 5
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java
  77. 22 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUnbindGatewayReqVO.java
  78. 42 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessagePageReqVO.java
  79. 16 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespPairVO.java
  80. 56 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespVO.java
  81. 27 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageSendReqVO.java
  82. 48 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigRespVO.java
  83. 47 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigSaveReqVO.java
  84. 30 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointPageReqVO.java
  85. 55 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointRespVO.java
  86. 54 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointSaveReqVO.java
  87. 25 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyDetailRespVO.java
  88. 3 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyHistoryListReqVO.java
  89. 4 5
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyRespVO.java
  90. 15 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaFirmwareController.java
  91. 65 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskController.java
  92. 99 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java
  93. 0 75
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeRecordController.java
  94. 0 65
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeTaskController.java
  95. 7 14
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java
  96. 10 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwarePageReqVO.java
  97. 29 65
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareRespVO.java
  98. 2 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java
  99. 37 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskCreateReqVO.java
  100. 17 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/task/IotOtaTaskPageReqVO.java

+ 1 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/DateIntervalEnum.java

@@ -16,6 +16,7 @@ import java.util.Arrays;
 @AllArgsConstructor
 public enum DateIntervalEnum implements ArrayValuable<Integer> {
 
+    HOUR(0, "小时"), // 特殊:字典里,暂时不会有这个枚举!!!因为大多数情况下,用不到这个间隔
     DAY(1, "天"),
     WEEK(2, "周"),
     MONTH(3, "月"),

+ 44 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/MapUtils.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -65,4 +66,47 @@ public class MapUtils {
         return map;
     }
 
+    /**
+     * 从 Map 中获取 BigDecimal 值
+     *
+     * @param map Map 数据源
+     * @param key 键名
+     * @return BigDecimal 值,解析失败或值为 null 时返回 null
+     */
+    public static BigDecimal getBigDecimal(Map<String, ?> map, String key) {
+        return getBigDecimal(map, key, null);
+    }
+
+    /**
+     * 从 Map 中获取 BigDecimal 值
+     *
+     * @param map          Map 数据源
+     * @param key          键名
+     * @param defaultValue 默认值
+     * @return BigDecimal 值,解析失败或值为 null 时返回默认值
+     */
+    public static BigDecimal getBigDecimal(Map<String, ?> map, String key, BigDecimal defaultValue) {
+        if (map == null) {
+            return defaultValue;
+        }
+        Object value = map.get(key);
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof BigDecimal) {
+            return (BigDecimal) value;
+        }
+        if (value instanceof Number) {
+            return BigDecimal.valueOf(((Number) value).doubleValue());
+        }
+        if (value instanceof String) {
+            try {
+                return new BigDecimal((String) value);
+            } catch (NumberFormatException e) {
+                return defaultValue;
+            }
+        }
+        return defaultValue;
+    }
+
 }

+ 70 - 7
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java

@@ -3,19 +3,25 @@ package cn.iocoder.yudao.framework.common.util.date;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DatePattern;
 import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.date.TemporalAccessorUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
 
+import java.sql.Timestamp;
 import java.time.*;
+import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAdjusters;
 import java.util.ArrayList;
 import java.util.List;
 
+import static cn.hutool.core.date.DatePattern.UTC_MS_WITH_XXX_OFFSET_PATTERN;
+import static cn.hutool.core.date.DatePattern.createFormatter;
+
 /**
- * 时间工具类,用于 {@link java.time.LocalDateTime}
+ * 时间工具类,用于 {@link LocalDateTime}
  *
  * @author 芋道源码
  */
@@ -26,6 +32,8 @@ public class LocalDateTimeUtils {
      */
     public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
 
+    public static DateTimeFormatter UTC_MS_WITH_XXX_OFFSET_FORMATTER = createFormatter(UTC_MS_WITH_XXX_OFFSET_PATTERN);
+
     /**
      * 解析时间
      *
@@ -62,17 +70,32 @@ public class LocalDateTimeUtils {
      * 创建指定时间
      *
      * @param year  年
-     * @param mouth 月
+     * @param month 月
      * @param day   日
      * @return 指定时间
      */
-    public static LocalDateTime buildTime(int year, int mouth, int day) {
-        return LocalDateTime.of(year, mouth, day, 0, 0, 0);
+    public static LocalDateTime buildTime(int year, int month, int day) {
+        return LocalDateTime.of(year, month, day, 0, 0, 0);
     }
 
-    public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,
-                                                   int year2, int mouth2, int day2) {
-        return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
+    public static LocalDateTime[] buildBetweenTime(int year1, int month1, int day1,
+                                                   int year2, int month2, int day2) {
+        return new LocalDateTime[]{buildTime(year1, month1, day1), buildTime(year2, month2, day2)};
+    }
+
+    /**
+     * 判指定断时间,是否在该时间范围内
+     *
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @param time 指定时间
+     * @return 是否
+     */
+    public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, Timestamp time) {
+        if (startTime == null || endTime == null || time == null) {
+            return false;
+        }
+        return LocalDateTimeUtil.isIn(LocalDateTimeUtil.of(time), startTime, endTime);
     }
 
     /**
@@ -227,6 +250,11 @@ public class LocalDateTimeUtils {
         // 2. 循环,生成时间范围
         List<LocalDateTime[]> timeRanges = new ArrayList<>();
         switch (intervalEnum) {
+            case HOUR:
+                while (startTime.isBefore(endTime)) {
+                    timeRanges.add(new LocalDateTime[]{startTime, startTime.plusHours(1).minusNanos(1)});
+                    startTime = startTime.plusHours(1);
+                }
             case DAY:
                 while (startTime.isBefore(endTime)) {
                     timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});
@@ -290,6 +318,8 @@ public class LocalDateTimeUtils {
 
         // 2. 循环,生成时间范围
         switch (intervalEnum) {
+            case HOUR:
+                return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATETIME_MINUTE_PATTERN);
             case DAY:
                 return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);
             case WEEK:
@@ -306,4 +336,37 @@ public class LocalDateTimeUtils {
         }
     }
 
+    /**
+     * 获取指定日期所在季度的第一天
+     *
+     * @param date 日期
+     * @return 所在季度的第一天
+     */
+    public static LocalDate getQuarterStart(LocalDate date) {
+        Month firstMonthOfQuarter = date.getMonth().firstMonthOfQuarter();
+        return LocalDate.of(date.getYear(), firstMonthOfQuarter, 1);
+    }
+
+    /**
+     * 获取指定日期所在周的第一天(周一)
+     *
+     * @param date 日期
+     * @return 所在周的周一
+     */
+    public static LocalDate getWeekStart(LocalDate date) {
+        return date.with(DayOfWeek.MONDAY);
+    }
+
+    /**
+     * 将给定的 {@link LocalDateTime} 转换为自 Unix 纪元时间(1970-01-01T00:00:00Z)以来的秒数。
+     *
+     * @param sourceDateTime 需要转换的本地日期时间,不能为空
+     * @return 自 1970-01-01T00:00:00Z 起的秒数(epoch second)
+     * @throws NullPointerException 如果 {@code sourceDateTime} 为 {@code null}
+     * @throws DateTimeException 如果转换过程中发生时间超出范围或其他时间处理异常
+     */
+    public static Long toEpochSecond(LocalDateTime sourceDateTime) {
+        return TemporalAccessorUtil.toInstant(sourceDateTime).getEpochSecond();
+    }
+
 }

+ 90 - 1
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java

@@ -3,18 +3,23 @@ package cn.iocoder.yudao.framework.common.util.json;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
+import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import lombok.Getter;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 
 import java.io.IOException;
 import java.lang.reflect.Type;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -26,13 +31,18 @@ import java.util.List;
 @Slf4j
 public class JsonUtils {
 
+    @Getter
     private static ObjectMapper objectMapper = new ObjectMapper();
 
     static {
         objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
         objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
         objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
-        objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
+        // 解决 LocalDateTime 的序列化
+        SimpleModule simpleModule = new JavaTimeModule()
+                .addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
+                .addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
+        objectMapper.registerModules(simpleModule);
     }
 
     /**
@@ -99,6 +109,18 @@ public class JsonUtils {
         }
     }
 
+    public static <T> T parseObject(byte[] text, Type type) {
+        if (ArrayUtil.isEmpty(text)) {
+            return null;
+        }
+        try {
+            return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
+        } catch (IOException e) {
+            log.error("json parse err,json:{}", text, e);
+            throw new RuntimeException(e);
+        }
+    }
+
     /**
      * 将字符串解析成指定类型的对象
      * 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
@@ -151,6 +173,24 @@ public class JsonUtils {
         }
     }
 
+    /**
+     * 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null
+     *
+     * @param text 字符串
+     * @param clazz 类型
+     * @return 指定类型的对象
+     */
+    public static <T> T parseObjectQuietly(String text, Class<T> clazz) {
+        if (StrUtil.isEmpty(text)) {
+            return null;
+        }
+        try {
+            return objectMapper.readValue(text, clazz);
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
     public static <T> List<T> parseArray(String text, Class<T> clazz) {
         if (StrUtil.isEmpty(text)) {
             return new ArrayList<>();
@@ -207,4 +247,53 @@ public class JsonUtils {
         return JSONUtil.isTypeJSONObject(str);
     }
 
+    /**
+     * 将 Object 转换为目标类型
+     * <p>
+     * 避免先转 jsonString 再 parseObject 的性能损耗
+     *
+     * @param obj   源对象(可以是 Map、POJO 等)
+     * @param clazz 目标类型
+     * @return 转换后的对象
+     */
+    public static <T> T convertObject(Object obj, Class<T> clazz) {
+        if (obj == null) {
+            return null;
+        }
+        if (clazz.isInstance(obj)) {
+            return clazz.cast(obj);
+        }
+        return objectMapper.convertValue(obj, clazz);
+    }
+
+    /**
+     * 将 Object 转换为目标类型(支持泛型)
+     *
+     * @param obj           源对象
+     * @param typeReference 目标类型引用
+     * @return 转换后的对象
+     */
+    public static <T> T convertObject(Object obj, TypeReference<T> typeReference) {
+        if (obj == null) {
+            return null;
+        }
+        return objectMapper.convertValue(obj, typeReference);
+    }
+
+    /**
+     * 将 Object 转换为 List 类型
+     * <p>
+     * 避免先转 jsonString 再 parseArray 的性能损耗
+     *
+     * @param obj   源对象(可以是 List、数组等)
+     * @param clazz 目标元素类型
+     * @return 转换后的 List
+     */
+    public static <T> List<T> convertList(Object obj, Class<T> clazz) {
+        if (obj == null) {
+            return new ArrayList<>();
+        }
+        return objectMapper.convertValue(obj, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
+    }
+
 }

+ 15 - 5
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java

@@ -6,12 +6,12 @@ import cn.hutool.system.SystemUtil;
 import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
 import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
 import cn.iocoder.yudao.framework.mq.redis.core.job.RedisPendingMessageResendJob;
+import cn.iocoder.yudao.framework.mq.redis.core.job.RedisStreamMessageCleanupJob;
 import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;
 import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
 import lombok.extern.slf4j.Slf4j;
 import org.redisson.api.RedissonClient;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.context.annotation.Bean;
@@ -68,9 +68,19 @@ public class YudaoRedisMQConsumerAutoConfiguration {
     @ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
     public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractRedisStreamMessageListener<?>> listeners,
                                                                      RedisMQTemplate redisTemplate,
-                                                                     @Value("${spring.application.name}") String groupName,
                                                                      RedissonClient redissonClient) {
-        return new RedisPendingMessageResendJob(listeners, redisTemplate, groupName, redissonClient);
+        return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient);
+    }
+
+    /**
+     * 创建 Redis Stream 消息清理任务
+     */
+    @Bean
+    @ConditionalOnBean(AbstractRedisStreamMessageListener.class)
+    public RedisStreamMessageCleanupJob redisStreamMessageCleanupJob(List<AbstractRedisStreamMessageListener<?>> listeners,
+                                                                     RedisMQTemplate redisTemplate,
+                                                                     RedissonClient redissonClient) {
+        return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient);
     }
 
     /**
@@ -129,14 +139,14 @@ public class YudaoRedisMQConsumerAutoConfiguration {
      *
      * @return 消费者名字
      */
-    private static String buildConsumerName() {
+    public static String buildConsumerName() {
         return String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());
     }
 
     /**
      * 校验 Redis 版本号,是否满足最低的版本号要求!
      */
-    private static void checkRedisVersion(RedisTemplate<String, ?> redisTemplate) {
+    public static void checkRedisVersion(RedisTemplate<String, ?> redisTemplate) {
         // 获得 Redis 版本
         Properties info = redisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);
         String version = MapUtil.getStr(info, "redis_version");

+ 6 - 7
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java

@@ -23,23 +23,22 @@ import java.util.Objects;
 @AllArgsConstructor
 public class RedisPendingMessageResendJob {
 
-    private static final String LOCK_KEY = "redis:pending:msg:lock";
+    private static final String LOCK_KEY = "redis:stream:pending-message-resend:lock";
 
     /**
      * 消息超时时间,默认 5 分钟
      *
      * 1. 超时的消息才会被重新投递
-     * 2. 由于定时任务 1 分钟一次,消息超时后不会被立即重投,极端情况下消息5分钟过期后,再等 1 分钟才会被扫瞄到
+     * 2. 由于定时任务 1 分钟一次,消息超时后不会被立即重投,极端情况下消息 5 分钟过期后,再等 1 分钟才会被扫瞄到
      */
     private static final int EXPIRE_TIME = 5 * 60;
 
     private final List<AbstractRedisStreamMessageListener<?>> listeners;
     private final RedisMQTemplate redisTemplate;
-    private final String groupName;
     private final RedissonClient redissonClient;
 
     /**
-     * 一分钟执行一次,这里选择每分钟的35秒执行,是为了避免整点任务过多的问题
+     * 一分钟执行一次,这里选择每分钟的 35 秒执行,是为了避免整点任务过多的问题
      */
     @Scheduled(cron = "35 * * * * ?")
     public void messageResend() {
@@ -64,13 +63,13 @@ public class RedisPendingMessageResendJob {
     private void execute() {
         StreamOperations<String, Object, Object> ops = redisTemplate.getRedisTemplate().opsForStream();
         listeners.forEach(listener -> {
-            PendingMessagesSummary pendingMessagesSummary = Objects.requireNonNull(ops.pending(listener.getStreamKey(), groupName));
+            PendingMessagesSummary pendingMessagesSummary = Objects.requireNonNull(ops.pending(listener.getStreamKey(), listener.getGroup()));
             // 每个消费者的 pending 队列消息数量
             Map<String, Long> pendingMessagesPerConsumer = pendingMessagesSummary.getPendingMessagesPerConsumer();
             pendingMessagesPerConsumer.forEach((consumerName, pendingMessageCount) -> {
                 log.info("[processPendingMessage][消费者({}) 消息数量({})]", consumerName, pendingMessageCount);
                 // 每个消费者的 pending消息的详情信息
-                PendingMessages pendingMessages = ops.pending(listener.getStreamKey(), Consumer.from(groupName, consumerName), Range.unbounded(), pendingMessageCount);
+                PendingMessages pendingMessages = ops.pending(listener.getStreamKey(), Consumer.from(listener.getGroup(), consumerName), Range.unbounded(), pendingMessageCount);
                 if (pendingMessages.isEmpty()) {
                     return;
                 }
@@ -91,7 +90,7 @@ public class RedisPendingMessageResendJob {
                             .ofObject(records.get(0).getValue()) // 设置内容
                             .withStreamKey(listener.getStreamKey()));
                     // ack 消息消费完成
-                    redisTemplate.getRedisTemplate().opsForStream().acknowledge(groupName, records.get(0));
+                    redisTemplate.getRedisTemplate().opsForStream().acknowledge(listener.getGroup(), records.get(0));
                     log.info("[processPendingMessage][消息({})重新投递成功]", records.get(0).getId());
                 });
             });

+ 72 - 0
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java

@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.framework.mq.redis.core.job;
+
+import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
+import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.data.redis.core.StreamOperations;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import java.util.List;
+
+/**
+ * Redis Stream 消息清理任务
+ * 用于定期清理已消费的消息,防止内存占用过大
+ *
+ * @see <a href="https://www.cnblogs.com/nanxiang/p/16179519.html">记一次 redis stream 数据类型内存不释放问题</a>
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+@AllArgsConstructor
+public class RedisStreamMessageCleanupJob {
+
+    private static final String LOCK_KEY = "redis:stream:message-cleanup:lock";
+
+    /**
+     * 保留的消息数量,默认保留最近 10000 条消息
+     */
+    private static final long MAX_COUNT = 10000;
+
+    private final List<AbstractRedisStreamMessageListener<?>> listeners;
+    private final RedisMQTemplate redisTemplate;
+    private final RedissonClient redissonClient;
+
+    /**
+     * 每小时执行一次清理任务
+     */
+    @Scheduled(cron = "0 0 * * * ?")
+    public void cleanup() {
+        RLock lock = redissonClient.getLock(LOCK_KEY);
+        // 尝试加锁
+        if (lock.tryLock()) {
+            try {
+                execute();
+            } catch (Exception ex) {
+                log.error("[cleanup][执行异常]", ex);
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    /**
+     * 执行清理逻辑
+     */
+    private void execute() {
+        StreamOperations<String, Object, Object> ops = redisTemplate.getRedisTemplate().opsForStream();
+        listeners.forEach(listener -> {
+            try {
+                // 使用 XTRIM 命令清理消息,只保留最近的 MAX_LEN 条消息
+                Long trimCount = ops.trim(listener.getStreamKey(), MAX_COUNT, true);
+                if (trimCount != null && trimCount > 0) {
+                    log.info("[execute][Stream({}) 清理消息数量({})]", listener.getStreamKey(), trimCount);
+                }
+            } catch (Exception ex) {
+                log.error("[execute][Stream({}) 清理异常]", listener.getStreamKey(), ex);
+            }
+        });
+    }
+}

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/stream/AbstractRedisStreamMessageListener.java

@@ -53,6 +53,12 @@ public abstract class AbstractRedisStreamMessageListener<T extends AbstractRedis
         this.streamKey = messageType.getDeclaredConstructor().newInstance().getStreamKey();
     }
 
+    protected AbstractRedisStreamMessageListener(String streamKey, String group) {
+        this.messageType = null;
+        this.streamKey = streamKey;
+        this.group = group;
+    }
+
     @Override
     public void onMessage(ObjectRecord<String, String> message) {
         // 消费消息

+ 4 - 5
yudao-module-iot/pom.xml

@@ -1,16 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xmlns="http://maven.apache.org/POM/4.0.0"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
         <artifactId>yudao</artifactId>
         <groupId>cn.iocoder.boot</groupId>
         <version>${revision}</version>
     </parent>
     <modules>
-        <module>yudao-module-iot-api</module>
         <module>yudao-module-iot-biz</module>
-        <module>yudao-module-iot-plugins</module>
+        <module>yudao-module-iot-core</module>
+        <module>yudao-module-iot-gateway</module>
     </modules>
     <modelVersion>4.0.0</modelVersion>
 
@@ -20,7 +19,7 @@
     <name>${project.artifactId}</name>
     <description>
         物联网模块
-        <!-- TODO 芋艿:需要补充下说明! -->
+                <!-- TODO 芋艿:需要补充下说明! -->
     </description>
 
 </project>

+ 0 - 53
yudao-module-iot/yudao-module-iot-api/pom.xml

@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>yudao-module-iot</artifactId>
-        <groupId>cn.iocoder.boot</groupId>
-        <version>${revision}</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>yudao-module-iot-api</artifactId>
-    <packaging>jar</packaging>
-
-    <name>${project.artifactId}</name>
-    <!-- TODO 芋艿:需要在整理下,特别是 PF4J -->
-    <description>
-        物联网 模块 API,暴露给其它模块调用
-    </description>
-
-    <dependencies>
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-common</artifactId>
-        </dependency>
-
-        <!-- Web 相关 -->
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-web</artifactId>
-            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
-        </dependency>
-
-        <!-- 工具类相关 -->
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-databind</artifactId>
-            <scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
-        </dependency>
-
-        <dependency>
-            <groupId>org.pf4j</groupId> <!-- PF4J:内置插件机制 -->
-            <artifactId>pf4j-spring</artifactId>
-        </dependency>
-
-        <!-- 参数校验 -->
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-validation</artifactId>
-            <optional>true</optional>
-        </dependency>
-    </dependencies>
-
-</project>

+ 0 - 94
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java

@@ -1,94 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
-import cn.iocoder.yudao.module.iot.enums.ApiConstants;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-
-import javax.validation.Valid;
-
-/**
- * 设备数据 Upstream 上行 API
- *
- * 目的:设备 -> 插件 -> 服务端
- *
- * @author haohao
- */
-public interface IotDeviceUpstreamApi {
-
-    String PREFIX = ApiConstants.PREFIX + "/device/upstream";
-
-    // ========== 设备相关 ==========
-
-    /**
-     * 更新设备状态
-     *
-     * @param updateReqDTO 更新设备状态 DTO
-     */
-    @PostMapping(PREFIX + "/update-state")
-    CommonResult<Boolean> updateDeviceState(@Valid @RequestBody IotDeviceStateUpdateReqDTO updateReqDTO);
-
-    /**
-     * 上报设备属性数据
-     *
-     * @param reportReqDTO 上报设备属性数据 DTO
-     */
-    @PostMapping(PREFIX + "/report-property")
-    CommonResult<Boolean> reportDeviceProperty(@Valid @RequestBody IotDevicePropertyReportReqDTO reportReqDTO);
-
-    /**
-     * 上报设备事件数据
-     *
-     * @param reportReqDTO 设备事件
-     */
-    @PostMapping(PREFIX + "/report-event")
-    CommonResult<Boolean> reportDeviceEvent(@Valid @RequestBody IotDeviceEventReportReqDTO reportReqDTO);
-
-    // TODO @芋艿:这个需要 plugins 接入下
-    /**
-     * 注册设备
-     *
-     * @param registerReqDTO 注册设备 DTO
-     */
-    @PostMapping(PREFIX + "/register")
-    CommonResult<Boolean> registerDevice(@Valid @RequestBody IotDeviceRegisterReqDTO registerReqDTO);
-
-    // TODO @芋艿:这个需要 plugins 接入下
-    /**
-     * 注册子设备
-     *
-     * @param registerReqDTO 注册子设备 DTO
-     */
-    @PostMapping(PREFIX + "/register-sub")
-    CommonResult<Boolean> registerSubDevice(@Valid @RequestBody IotDeviceRegisterSubReqDTO registerReqDTO);
-
-    // TODO @芋艿:这个需要 plugins 接入下
-    /**
-     * 注册设备拓扑
-     *
-     * @param addReqDTO 注册设备拓扑 DTO
-     */
-    @PostMapping(PREFIX + "/add-topology")
-    CommonResult<Boolean> addDeviceTopology(@Valid @RequestBody IotDeviceTopologyAddReqDTO addReqDTO);
-
-    // TODO @芋艿:考虑 http 认证
-    /**
-     * 认证 Emqx 连接
-     *
-     * @param authReqDTO 认证 Emqx 连接 DTO
-     */
-    @PostMapping(PREFIX + "/authenticate-emqx-connection")
-    CommonResult<Boolean> authenticateEmqxConnection(@Valid @RequestBody IotDeviceEmqxAuthReqDTO authReqDTO);
-
-    // ========== 插件相关 ==========
-
-    /**
-     * 心跳插件实例
-     *
-     * @param heartbeatReqDTO 心跳插件实例 DTO
-     */
-    @PostMapping(PREFIX + "/heartbeat-plugin-instance")
-    CommonResult<Boolean> heartbeatPluginInstance(@Valid @RequestBody IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO);
-
-}

+ 0 - 22
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceConfigSetReqDTO.java

@@ -1,22 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotNull;
-import java.util.Map;
-
-/**
- * IoT 设备【配置】设置 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotDeviceConfigSetReqDTO extends IotDeviceDownstreamAbstractReqDTO {
-
-    /**
-     * 配置
-     */
-    @NotNull(message = "配置不能为空")
-    private Map<String, Object> config;
-
-}

+ 0 - 66
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceOtaUpgradeReqDTO.java

@@ -1,66 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
-
-import cn.hutool.core.map.MapUtil;
-import lombok.Data;
-
-import java.util.Map;
-
-/**
- * IoT 设备【OTA】升级下发 Request DTO(更新固件消息)
- *
- * @author 芋道源码
- */
-@Data
-public class IotDeviceOtaUpgradeReqDTO extends IotDeviceDownstreamAbstractReqDTO {
-
-    /**
-     * 固件编号
-     */
-    private Long firmwareId;
-    /**
-     * 固件版本
-     */
-    private String version;
-
-    /**
-     * 签名方式
-     *
-     * 例如说:MD5、SHA256
-     */
-    private String signMethod;
-    /**
-     * 固件文件签名
-     */
-    private String fileSign;
-    /**
-     * 固件文件大小
-     */
-    private Long fileSize;
-    /**
-     * 固件文件 URL
-     */
-    private String fileUrl;
-
-    /**
-     * 自定义信息,建议使用 JSON 格式
-     */
-    private String information;
-
-    public static IotDeviceOtaUpgradeReqDTO build(Map<?, ?> map) {
-        return new IotDeviceOtaUpgradeReqDTO()
-                .setFirmwareId(MapUtil.getLong(map, "firmwareId")).setVersion((String) map.get("version"))
-                .setSignMethod((String) map.get("signMethod")).setFileSign((String) map.get("fileSign"))
-                .setFileSize(MapUtil.getLong(map, "fileSize")).setFileUrl((String) map.get("fileUrl"))
-                .setInformation((String) map.get("information"));
-    }
-
-    public static Map<?, ?> build(IotDeviceOtaUpgradeReqDTO dto) {
-        return MapUtil.builder()
-                .put("firmwareId", dto.getFirmwareId()).put("version", dto.getVersion())
-                .put("signMethod", dto.getSignMethod()).put("fileSign", dto.getFileSign())
-                .put("fileSize", dto.getFileSize()).put("fileUrl", dto.getFileUrl())
-                .put("information", dto.getInformation())
-                .build();
-    }
-
-}

+ 0 - 24
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDevicePropertyGetReqDTO.java

@@ -1,24 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import java.util.List;
-
-// TODO @芋艿:从 server => plugin => device 是否有必要?从阿里云 iot 来看,没有这个功能?!
-// TODO @芋艿:是不是改成 read 更好?在看看阿里云的 topic 设计
-/**
- * IoT 设备【属性】获取 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotDevicePropertyGetReqDTO extends IotDeviceDownstreamAbstractReqDTO {
-
-    /**
-     * 属性标识数组
-     */
-    @NotEmpty(message = "属性标识数组不能为空")
-    private List<String> identifiers;
-
-}

+ 0 - 22
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDevicePropertySetReqDTO.java

@@ -1,22 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import java.util.Map;
-
-/**
- * IoT 设备【属性】设置 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotDevicePropertySetReqDTO extends IotDeviceDownstreamAbstractReqDTO {
-
-    /**
-     * 属性参数
-     */
-    @NotEmpty(message = "属性参数不能为空")
-    private Map<String, Object> properties;
-
-}

+ 0 - 26
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/downstream/IotDeviceServiceInvokeReqDTO.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.downstream;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import java.util.Map;
-
-/**
- * IoT 设备【服务】调用 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotDeviceServiceInvokeReqDTO extends IotDeviceDownstreamAbstractReqDTO {
-
-    /**
-     * 服务标识
-     */
-    @NotEmpty(message = "服务标识不能为空")
-    private String identifier;
-    /**
-     * 调用参数
-     */
-    private Map<String, Object> params;
-
-}

+ 0 - 26
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceEventReportReqDTO.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import java.util.Map;
-
-/**
- * IoT 设备【事件】上报 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotDeviceEventReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {
-
-    /**
-     * 事件标识
-     */
-    @NotEmpty(message = "事件标识不能为空")
-    private String identifier;
-    /**
-     * 事件参数
-     */
-    private Map<String, Object> params;
-
-}

+ 0 - 35
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceOtaProgressReqDTO.java

@@ -1,35 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-import lombok.Data;
-
-// TODO @芋艿:待实现:/ota/${productKey}/${deviceName}/progress
-/**
- * IoT 设备【OTA】升级进度 Request DTO(上报更新固件进度)
- *
- * @author 芋道源码
- */
-@Data
-public class IotDeviceOtaProgressReqDTO extends IotDeviceUpstreamAbstractReqDTO {
-
-    /**
-     * 固件编号
-     */
-    private Long firmwareId;
-
-    /**
-     * 升级状态
-     *
-     * 枚举 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum}
-     */
-    private Integer status;
-    /**
-     * 升级进度,百分比
-     */
-    private Integer progress;
-
-    /**
-     * 升级进度描述
-     */
-    private String description;
-
-}

+ 0 - 21
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceOtaPullReqDTO.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-// TODO @芋艿:待实现:/ota/${productKey}/${deviceName}/pull
-/**
- * IoT 设备【OTA】升级下拉 Request DTO(拉取固件更新)
- *
- * @author 芋道源码
- */
-public class IotDeviceOtaPullReqDTO {
-
-    /**
-     * 固件编号
-     */
-    private Long firmwareId;
-
-    /**
-     * 固件版本
-     */
-    private String version;
-
-}

+ 0 - 21
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceOtaReportReqDTO.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-// TODO @芋艿:待实现:/ota/${productKey}/${deviceName}/report
-/**
- * IoT 设备【OTA】上报 Request DTO(上报固件版本)
- *
- * @author 芋道源码
- */
-public class IotDeviceOtaReportReqDTO {
-
-    /**
-     * 固件编号
-     */
-    private Long firmwareId;
-
-    /**
-     * 固件版本
-     */
-    private String version;
-
-}

+ 0 - 22
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDevicePropertyReportReqDTO.java

@@ -1,22 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import java.util.Map;
-
-/**
- * IoT 设备【属性】上报 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotDevicePropertyReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {
-
-    /**
-     * 属性参数
-     */
-    @NotEmpty(message = "属性参数不能为空")
-    private Map<String, Object> properties;
-
-}

+ 0 - 12
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterReqDTO.java

@@ -1,12 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-import lombok.Data;
-
-/**
- * IoT 设备【注册】自己 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotDeviceRegisterReqDTO extends IotDeviceUpstreamAbstractReqDTO {
-}

+ 0 - 43
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterSubReqDTO.java

@@ -1,43 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import java.util.List;
-
-/**
- * IoT 设备【注册】子设备 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotDeviceRegisterSubReqDTO extends IotDeviceUpstreamAbstractReqDTO {
-
-    // TODO @芋艿:看看要不要优化命名
-    /**
-     * 子设备数组
-     */
-    @NotEmpty(message = "子设备不能为空")
-    private List<Device> params;
-
-    /**
-     * 设备信息
-     */
-    @Data
-    public static class Device {
-
-        /**
-         * 产品标识
-         */
-        @NotEmpty(message = "产品标识不能为空")
-        private String productKey;
-
-        /**
-         * 设备名称
-         */
-        @NotEmpty(message = "设备名称不能为空")
-        private String deviceName;
-
-    }
-
-}

+ 0 - 24
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceStateUpdateReqDTO.java

@@ -1,24 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
-import lombok.Data;
-
-import javax.validation.constraints.NotNull;
-
-/**
- * IoT 设备【状态】更新 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotDeviceStateUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {
-
-    /**
-     * 设备状态
-     */
-    @NotNull(message = "设备状态不能为空")
-    @InEnum(IotDeviceStateEnum.class) // 只使用:在线、离线
-    private Integer state;
-
-}

+ 0 - 44
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceTopologyAddReqDTO.java

@@ -1,44 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import java.util.List;
-
-// TODO @芋艿:要写清楚,是来自设备网关,还是设备。
-/**
- * IoT 设备【拓扑】添加 Request DTO
- */
-@Data
-public class IotDeviceTopologyAddReqDTO extends IotDeviceUpstreamAbstractReqDTO {
-
-    // TODO @芋艿:看看要不要优化命名
-    /**
-     * 子设备数组
-     */
-    @NotEmpty(message = "子设备不能为空")
-    private List<IotDeviceRegisterSubReqDTO.Device> params;
-
-    /**
-     * 设备信息
-     */
-    @Data
-    public static class Device {
-
-        /**
-         * 产品标识
-         */
-        @NotEmpty(message = "产品标识不能为空")
-        private String productKey;
-
-        /**
-         * 设备名称
-         */
-        @NotEmpty(message = "设备名称不能为空")
-        private String deviceName;
-
-        // TODO @芋艿:阿里云还有 sign 签名
-
-    }
-
-}

+ 0 - 45
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import java.time.LocalDateTime;
-
-/**
- * IoT 设备上行的抽象 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public abstract class IotDeviceUpstreamAbstractReqDTO {
-
-    /**
-     * 请求编号
-     */
-    private String requestId;
-
-    /**
-     * 插件实例的进程编号
-     */
-    private String processId;
-
-    /**
-     * 产品标识
-     */
-    @NotEmpty(message = "产品标识不能为空")
-    private String productKey;
-    /**
-     * 设备名称
-     */
-    @NotEmpty(message = "设备名称不能为空")
-    private String deviceName;
-
-    /**
-     * 上报时间
-     */
-    @JsonSerialize(using = TimestampLocalDateTimeSerializer.class) // 解决 iot plugins 序列化 LocalDateTime 是数组,导致无法解析的问题
-    private LocalDateTime reportTime;
-
-}

+ 0 - 45
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotPluginInstanceHeartbeatReqDTO.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
-
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-
-/**
- * IoT 插件实例心跳 Request DTO
- *
- * @author 芋道源码
- */
-@Data
-public class IotPluginInstanceHeartbeatReqDTO {
-
-    /**
-     * 请求编号
-     */
-    @NotEmpty(message = "请求编号不能为空")
-    private String processId;
-
-    /**
-     * 插件包标识符
-     */
-    @NotEmpty(message = "插件包标识符不能为空")
-    private String pluginKey;
-
-    /**
-     * 插件实例所在 IP
-     */
-    @NotEmpty(message = "插件实例所在 IP 不能为空")
-    private String hostIp;
-    /**
-     * 插件实例的进程编号
-     */
-    @NotNull(message = "插件实例的进程编号不能为空")
-    private Integer downstreamPort;
-
-    /**
-     * 是否在线
-     */
-    @NotNull(message = "是否在线不能为空")
-    private Boolean online;
-
-}

+ 0 - 4
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/package-info.java

@@ -1,4 +0,0 @@
-/**
- * TODO 芋艿:占位
- */
-package cn.iocoder.yudao.module.iot.api.device.dto;

+ 0 - 6
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java

@@ -1,6 +0,0 @@
-/**
- * 占位
- *
- * TODO 芋艿:后续删除
- */
-package cn.iocoder.yudao.module.iot.api;

+ 0 - 16
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ApiConstants.java

@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums;
-
-import cn.iocoder.yudao.framework.common.enums.RpcConstants;
-
-/**
- * API 相关的枚举
- *
- * @author 芋道源码
- */
-public class ApiConstants {
-
-    public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/iot";
-
-    public static final String VERSION = "1.0.0";
-
-}

+ 0 - 75
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java

@@ -1,75 +0,0 @@
-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, "产品状是发布状态,不允许操作物模型");
-
-    // ========== 产品物模型 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_HAS_CHILDREN = 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 = new ErrorCode(1_050_003_007, "执行失败,原因:{}");
-
-    // ========== 产品分类 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, "设备分组下存在设备,不允许删除");
-
-    // ========== 插件配置 1-050-006-000 ==========
-    ErrorCode PLUGIN_CONFIG_NOT_EXISTS = new ErrorCode(1_050_006_000, "插件配置不存在");
-    ErrorCode PLUGIN_INSTALL_FAILED = new ErrorCode(1_050_006_001, "插件安装失败");
-    ErrorCode PLUGIN_INSTALL_FAILED_FILE_NAME_NOT_MATCH = new ErrorCode(1_050_006_002, "插件安装失败,文件名与原插件id不匹配");
-    ErrorCode PLUGIN_CONFIG_DELETE_FAILED_RUNNING = new ErrorCode(1_050_006_003, "请先停止插件");
-    ErrorCode PLUGIN_STATUS_INVALID = new ErrorCode(1_050_006_004, "插件状态无效");
-    ErrorCode PLUGIN_CONFIG_KEY_DUPLICATE = new ErrorCode(1_050_006_005, "插件标识已存在");
-    ErrorCode PLUGIN_START_FAILED = new ErrorCode(1_050_006_006, "插件启动失败");
-    ErrorCode PLUGIN_STOP_FAILED = new ErrorCode(1_050_006_007, "插件停止失败");
-
-    // ========== 插件实例 1-050-007-000 ==========
-
-    // ========== 固件相关 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, "产品版本号重复");
-
-    ErrorCode OTA_UPGRADE_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在");
-    ErrorCode OTA_UPGRADE_TASK_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "升级任务名称重复");
-    ErrorCode OTA_UPGRADE_TASK_DEVICE_IDS_EMPTY = new ErrorCode(1_050_008_102, "设备编号列表不能为空");
-    ErrorCode OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY = new ErrorCode(1_050_008_103, "设备列表不能为空");
-    ErrorCode OTA_UPGRADE_TASK_CANNOT_CANCEL = new ErrorCode(1_050_008_104, "升级任务不能取消");
-
-    ErrorCode OTA_UPGRADE_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
-    ErrorCode OTA_UPGRADE_RECORD_DUPLICATE = new ErrorCode(1_050_008_201, "升级记录重复");
-    ErrorCode OTA_UPGRADE_RECORD_CANNOT_RETRY = new ErrorCode(1_050_008_202, "升级记录不能重试");
-
-    // ========== MQTT 通信相关 1-050-009-000 ==========
-    ErrorCode MQTT_TOPIC_ILLEGAL = new ErrorCode(1_050_009_000, "topic illegal");
-
-    // ========== IoT 数据桥梁 1-050-010-000 ==========
-    ErrorCode DATA_BRIDGE_NOT_EXISTS = new ErrorCode(1_050_010_000, "IoT 数据桥梁不存在");
-
-}

+ 0 - 44
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java

@@ -1,44 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.device;
-
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-// TODO @芋艿:需要添加对应的 DTO,以及上下行的链路,网关、网关服务、设备等
-/**
- * IoT 设备消息标识符枚举
- */
-@Getter
-@RequiredArgsConstructor
-public enum IotDeviceMessageIdentifierEnum {
-
-    PROPERTY_GET("get"), // 下行 TODO 芋艿:【讨论】貌似这个“上行”更合理?device 主动拉取配置。和 IotDevicePropertyGetReqDTO 一样的配置
-    PROPERTY_SET("set"), // 下行
-    PROPERTY_REPORT("report"), // 上行
-
-    STATE_ONLINE("online"), // 上行
-    STATE_OFFLINE("offline"), // 上行
-
-    CONFIG_GET("get"), // 上行 TODO 芋艿:【讨论】暂时没有上行的场景
-    CONFIG_SET("set"), // 下行
-
-    SERVICE_INVOKE("${identifier}"), // 下行
-    SERVICE_REPLY_SUFFIX("_reply"), // 芋艿:TODO 芋艿:【讨论】上行 or 下行
-
-    OTA_UPGRADE("upgrade"), // 下行
-    OTA_PULL("pull"), // 上行
-    OTA_PROGRESS("progress"), // 上行
-    OTA_REPORT("report"), // 上行
-
-    REGISTER_REGISTER("register"), // 上行
-    REGISTER_REGISTER_SUB("register_sub"), // 上行
-    REGISTER_UNREGISTER_SUB("unregister_sub"), // 下行
-
-    TOPOLOGY_ADD("topology_add"), // 下行;
-    ;
-
-    /**
-     * 标志符
-     */
-    private final String identifier;
-
-}

+ 0 - 37
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java

@@ -1,37 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.device;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-import java.util.Arrays;
-
-/**
- * IoT 设备消息类型枚举
- */
-@Getter
-@RequiredArgsConstructor
-public enum IotDeviceMessageTypeEnum implements ArrayValuable<String> {
-
-    STATE("state"), // 设备状态
-    PROPERTY("property"), // 设备属性:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
-    EVENT("event"), // 设备事件:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
-    SERVICE("service"), // 设备服务:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
-    CONFIG("config"), // 设备配置:可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置
-    OTA("ota"), // 设备 OTA:可参考 https://help.aliyun.com/zh/iot/user-guide/ota-update OTA 升级
-    REGISTER("register"), // 设备注册:可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册
-    TOPOLOGY("topology"),; // 设备拓扑:可参考 https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships 设备拓扑
-
-    public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new);
-
-    /**
-     * 属性
-     */
-    private final String type;
-
-    @Override
-    public String[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 35
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaUpgradeTaskStatusEnum.java

@@ -1,35 +0,0 @@
-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 IotOtaUpgradeTaskStatusEnum implements ArrayValuable<Integer> {
-
-    IN_PROGRESS(10), // 进行中:升级中
-    COMPLETED(20), // 已完成:已结束,全部升级完成
-    INCOMPLETE(21), // 未完成:已结束,部分升级完成
-    CANCELED(30),; // 已取消:一般是主动取消任务
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotOtaUpgradeTaskStatusEnum::getStatus).toArray(Integer[]::new);
-
-    /**
-     * 范围
-     */
-    private final Integer status;
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 37
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java

@@ -1,37 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.plugin;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-import java.util.Arrays;
-
-/**
- * IoT 部署方式枚举
- *
- * @author haohao
- */
-@RequiredArgsConstructor
-@Getter
-public enum IotPluginDeployTypeEnum implements ArrayValuable<Integer> {
-
-    JAR(0, "JAR 部署"),
-    STANDALONE(1, "独立部署");
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginDeployTypeEnum::getDeployType).toArray(Integer[]::new);
-
-    /**
-     * 部署方式
-     */
-    private final Integer deployType;
-    /**
-     * 部署方式名
-     */
-    private final String name;
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 37
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java

@@ -1,37 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.plugin;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-import java.util.Arrays;
-
-/**
- * IoT 插件状态枚举
- *
- * @author haohao
- */
-@RequiredArgsConstructor
-@Getter
-public enum IotPluginStatusEnum implements ArrayValuable<Integer> {
-
-    STOPPED(0, "停止"),
-    RUNNING(1, "运行");
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginStatusEnum::getStatus).toArray(Integer[]::new);
-
-    /**
-     * 状态
-     */
-    private final Integer status;
-    /**
-     * 状态名
-     */
-    private final String name;
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 37
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java

@@ -1,37 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.plugin;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-
-/**
- * IoT 插件类型枚举
- *
- * @author haohao
- */
-@AllArgsConstructor
-@Getter
-public enum IotPluginTypeEnum implements ArrayValuable<Integer> {
-
-    NORMAL(0, "普通插件"),
-    DEVICE(1, "设备插件");
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotPluginTypeEnum::getType).toArray(Integer[]::new);
-
-    /**
-     * 类型
-     */
-    private final Integer type;
-    /**
-     * 类型名
-     */
-    private final String name;
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 38
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotDataFormatEnum.java

@@ -1,38 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.product;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-
-/**
- * 产品数据格式枚举类
- *
- * @author ahh
- * @see <a href="https://help.aliyun.com/zh/iot/user-guide/message-parsing">阿里云 - 什么是消息解析</a>
- */
-@AllArgsConstructor
-@Getter
-public enum IotDataFormatEnum implements ArrayValuable<Integer> {
-
-    JSON(0, "标准数据格式(JSON)"),
-    CUSTOMIZE(1, "透传/自定义");
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotDataFormatEnum::getType).toArray(Integer[]::new);
-
-    /**
-     * 类型
-     */
-    private final Integer type;
-    /**
-     * 描述
-     */
-    private final String description;
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 40
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProtocolTypeEnum.java

@@ -1,40 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.product;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-
-/**
- * IoT 接入网关协议枚举类
- *
- * @author ahh
- */
-@AllArgsConstructor
-@Getter
-public enum IotProtocolTypeEnum implements ArrayValuable<Integer> {
-
-    CUSTOM(0, "自定义"),
-    MODBUS(1, "Modbus"),
-    OPC_UA(2, "OPC UA"),
-    ZIGBEE(3, "ZigBee"),
-    BLE(4, "BLE");
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotProtocolTypeEnum::getType).toArray(Integer[]::new);
-
-    /**
-     * 类型
-     */
-    private final Integer type;
-    /**
-     * 描述
-     */
-    private final String description;
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 37
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotValidateTypeEnum.java

@@ -1,37 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.product;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-
-/**
- * IoT 数据校验级别枚举类
- *
- * @author ahh
- */
-@AllArgsConstructor
-@Getter
-public enum IotValidateTypeEnum implements ArrayValuable<Integer> {
-
-    WEAK(0, "弱校验"),
-    NONE(1, "免校验");
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotValidateTypeEnum::getType).toArray(Integer[]::new);
-
-    /**
-     * 类型
-     */
-    private final Integer type;
-    /**
-     * 描述
-     */
-    private final String description;
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 31
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java

@@ -1,31 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.rule;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-import java.util.Arrays;
-
-/**
- * IoT 规则场景的触发类型枚举
- *
- * 设备触发,定时触发
- */
-@RequiredArgsConstructor
-@Getter
-public enum IotRuleSceneActionTypeEnum implements ArrayValuable<Integer> {
-
-    DEVICE_CONTROL(1), // 设备执行
-    ALERT(2), // 告警执行
-    DATA_BRIDGE(3); // 桥接执行
-
-    private final Integer type;
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneActionTypeEnum::getType).toArray(Integer[]::new);
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}

+ 0 - 30
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerTypeEnum.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.iot.enums.rule;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-import java.util.Arrays;
-
-/**
- * IoT 场景流转的触发类型枚举
- *
- * @author 芋道源码
- */
-@RequiredArgsConstructor
-@Getter
-public enum IotRuleSceneTriggerTypeEnum implements ArrayValuable<Integer> {
-
-    DEVICE(1), // 设备触发
-    TIMER(2); // 定时触发
-
-    private final Integer type;
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneTriggerTypeEnum::getType).toArray(Integer[]::new);
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}

+ 28 - 47
yudao-module-iot/yudao-module-iot-biz/pom.xml

@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
         <artifactId>yudao-module-iot</artifactId>
         <groupId>cn.iocoder.boot</groupId>
@@ -15,13 +14,18 @@
     <name>${project.artifactId}</name>
     <description>
         物联网 模块,主要实现 产品管理、设备管理、协议管理等功能。
-        <!-- TODO 芋艿:后续补充下 -->
+                <!-- TODO 芋艿:后续补充下 -->
     </description>
 
     <dependencies>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-iot-api</artifactId>
+            <artifactId>yudao-module-system</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-iot-core</artifactId>
             <version>${revision}</version>
         </dependency>
 
@@ -69,11 +73,30 @@
             <artifactId>yudao-spring-boot-starter-excel</artifactId>
         </dependency>
 
+        <!-- OkHttp -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>mockwebserver</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- MQTT -->
+        <dependency>
+            <groupId>org.eclipse.paho</groupId>
+            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
+            <optional>true</optional>
+        </dependency>
+
         <!-- 消息队列相关 -->
+        <!-- TODO @芋艿:临时打开 -->
         <dependency>
             <groupId>org.apache.rocketmq</groupId>
             <artifactId>rocketmq-spring-boot-starter</artifactId>
-            <optional>true</optional>
+<!--            <optional>true</optional>-->
         </dependency>
         <dependency>
             <groupId>org.springframework.kafka</groupId>
@@ -85,48 +108,6 @@
             <artifactId>spring-boot-starter-amqp</artifactId>
             <optional>true</optional>
         </dependency>
-
-        <dependency>
-            <groupId>org.pf4j</groupId>  <!-- PF4J:内置插件机制 -->
-            <artifactId>pf4j-spring</artifactId>
-        </dependency>
-
-        <!-- TODO @芋艿:bom 管理 -->
-        <dependency>
-            <groupId>org.apache.groovy</groupId>
-            <artifactId>groovy-all</artifactId>
-            <version>4.0.25</version>
-            <type>pom</type>
-        </dependency>
-
-        <!-- TODO @芋艿:bom 管理 TODO @芋艿:不支持 jdk8 -->
-<!--        <dependency>-->
-<!--            <groupId>org.graalvm.js</groupId>-->
-<!--            <artifactId>js</artifactId>-->
-<!--            <version>24.1.2</version>-->
-<!--            <type>pom</type>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>org.graalvm.js</groupId>-->
-<!--            <artifactId>js-scriptengine</artifactId>-->
-<!--            <version>24.1.2</version>-->
-<!--        </dependency>-->
-
-        <!-- TODO @芋艿:合理注释 -->
-        <!-- IoT 数据桥梁的执行器所需消息队列。如果您只需要使用 rocketmq 那么则注释掉其它消息队列即可 -->
-        <!--        <dependency>-->
-        <!--            <groupId>org.apache.rocketmq</groupId>-->
-        <!--            <artifactId>rocketmq-spring-boot-starter</artifactId>-->
-        <!--        </dependency>-->
-        <!--        <dependency>-->
-        <!--            <groupId>org.springframework.kafka</groupId>-->
-        <!--            <artifactId>spring-kafka</artifactId>-->
-        <!--        </dependency>-->
-        <!--        <dependency>-->
-        <!--            <groupId>org.springframework.boot</groupId>-->
-        <!--            <artifactId>spring-boot-starter-amqp</artifactId>-->
-        <!--        </dependency>-->
-
     </dependencies>
 
 </project>

+ 0 - 61
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java

@@ -1,61 +0,0 @@
-package cn.iocoder.yudao.module.iot;
-
-import cn.hutool.script.ScriptUtil;
-import javax.script.Bindings;
-import javax.script.ScriptEngine;
-import javax.script.ScriptException;
-
-/**
- * TODO 芋艿:测试脚本的接入
- */
-public class ScriptTest {
-
-    public static void main2(String[] args) {
-        // 创建一个 Groovy 脚本引擎
-        ScriptEngine engine = ScriptUtil.createGroovyEngine();
-
-        // 创建绑定参数
-        Bindings bindings = engine.createBindings();
-        bindings.put("name", "Alice");
-        bindings.put("age", 30);
-
-        // 定义一个稍微复杂的 Groovy 脚本
-        String script = "def greeting = 'Hello, ' + name + '!';\n" +
-                "def ageInFiveYears = age + 5;\n" +
-                "def message = greeting + ' In five years, you will be ' + ageInFiveYears + ' years old.';\n" +
-                "return message.toUpperCase();\n";
-
-        try {
-            // 执行脚本并获取结果
-            Object result = engine.eval(script, bindings);
-            System.out.println(result); // 输出: HELLO, ALICE! IN FIVE YEARS, YOU WILL BE 35 YEARS OLD.
-        } catch (ScriptException e) {
-            e.printStackTrace();
-        }
-    }
-
-    public static void main(String[] args) {
-        // 创建一个 JavaScript 脚本引擎
-        ScriptEngine jsEngine = ScriptUtil.createJsEngine();
-
-        // 创建绑定参数
-        Bindings jsBindings = jsEngine.createBindings();
-        jsBindings.put("name", "Bob");
-        jsBindings.put("age", 25);
-
-        // 定义一个简单的 JavaScript 脚本
-        String jsScript = "var greeting = 'Hello, ' + name + '!';\n" +
-                "var ageInTenYears = age + 10;\n" +
-                "var message = greeting + ' In ten years, you will be ' + ageInTenYears + ' years old.';\n" +
-                "message.toUpperCase();\n";
-
-        try {
-            // 执行脚本并获取结果
-            Object jsResult = jsEngine.eval(jsScript, jsBindings);
-            System.out.println(jsResult); // 输出: HELLO, BOB! IN TEN YEARS, YOU WILL BE 35 YEARS OLD.
-        } catch (ScriptException e) {
-            e.printStackTrace();
-        }
-    }
-
-}

+ 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));
+    }
+
+}

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

@@ -1,78 +0,0 @@
-package cn.iocoder.yudao.module.iot.api.device;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
-import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
-import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-/**
- * * 设备数据 Upstream 上行 API 实现类
- */
-@RestController
-@Validated
-public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
-
-    @Resource
-    private IotDeviceUpstreamService deviceUpstreamService;
-    @Resource
-    private IotPluginInstanceService pluginInstanceService;
-
-    // ========== 设备相关 ==========
-
-    @Override
-    public CommonResult<Boolean> updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
-        deviceUpstreamService.updateDeviceState(updateReqDTO);
-        return success(true);
-    }
-
-    @Override
-    public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
-        deviceUpstreamService.reportDeviceProperty(reportReqDTO);
-        return success(true);
-    }
-
-    @Override
-    public CommonResult<Boolean> reportDeviceEvent(IotDeviceEventReportReqDTO reportReqDTO) {
-        deviceUpstreamService.reportDeviceEvent(reportReqDTO);
-        return success(true);
-    }
-
-    @Override
-    public CommonResult<Boolean> registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
-        deviceUpstreamService.registerDevice(registerReqDTO);
-        return success(true);
-    }
-
-    @Override
-    public CommonResult<Boolean> registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
-        deviceUpstreamService.registerSubDevice(registerReqDTO);
-        return success(true);
-    }
-
-    @Override
-    public CommonResult<Boolean> addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
-        deviceUpstreamService.addDeviceTopology(addReqDTO);
-        return success(true);
-    }
-
-    @Override
-    public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
-        boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO);
-        return success(result);
-    }
-
-    // ========== 插件相关 ==========
-
-    @Override
-    public CommonResult<Boolean> heartbeatPluginInstance(IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO) {
-        pluginInstanceService.heartbeatPluginInstance(heartbeatReqDTO);
-        return success(true);
-    }
-
-}

+ 1 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java

@@ -1,6 +1,4 @@
 /**
- * 占位
- *
- * TODO 芋艿:后续删除
+ * iot API 包,定义并实现提供给其它模块的 API
  */
 package cn.iocoder.yudao.module.iot.api;

+ 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;
+
+}

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

@@ -1,75 +0,0 @@
-### 请求 /iot/device/downstream 接口(服务调用) => 成功
-POST {{baseUrl}}/iot/device/downstream
-Content-Type: application/json
-tenant-id: {{adminTenentId}}
-Authorization: Bearer {{token}}
-
-{
-  "id": 25,
-  "type": "service",
-  "identifier": "temperature",
-  "data": {
-    "xx": "yy"
-  }
-}
-
-### 请求 /iot/device/downstream 接口(属性设置) => 成功
-POST {{baseUrl}}/iot/device/downstream
-Content-Type: application/json
-tenant-id: {{adminTenentId}}
-Authorization: Bearer {{token}}
-
-{
-  "id": 25,
-  "type": "property",
-  "identifier": "set",
-  "data": {
-    "xx": "yy"
-  }
-}
-
-### 请求 /iot/device/downstream 接口(属性获取) => 成功
-POST {{baseUrl}}/iot/device/downstream
-Content-Type: application/json
-tenant-id: {{adminTenentId}}
-Authorization: Bearer {{token}}
-
-{
-  "id": 25,
-  "type": "property",
-  "identifier": "get",
-  "data": ["xx", "yy"]
-}
-
-### 请求 /iot/device/downstream 接口(配置设置) => 成功
-POST {{baseUrl}}/iot/device/downstream
-Content-Type: application/json
-tenant-id: {{adminTenentId}}
-Authorization: Bearer {{token}}
-
-{
-  "id": 25,
-  "type": "config",
-  "identifier": "set"
-}
-
-### 请求 /iot/device/downstream 接口(OTA 升级) => 成功
-POST {{baseUrl}}/iot/device/downstream
-Content-Type: application/json
-tenant-id: {{adminTenentId}}
-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\":\"升级到最新版本\"}"
-  }
-}

+ 110 - 44
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java

@@ -1,20 +1,21 @@
 package cn.iocoder.yudao.module.iot.controller.admin.device;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 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.framework.excel.core.util.ExcelUtils;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
-import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+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.service.device.IotDeviceService;
-import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceDownstreamService;
-import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 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;
@@ -25,13 +26,12 @@ import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
 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
@@ -42,9 +42,7 @@ public class IotDeviceController {
     @Resource
     private IotDeviceService deviceService;
     @Resource
-    private IotDeviceUpstreamService deviceUpstreamService;
-    @Resource
-    private IotDeviceDownstreamService deviceDownstreamService;
+    private IotProductService productService;
 
     @PostMapping("/create")
     @Operation(summary = "创建设备")
@@ -53,6 +51,7 @@ public class IotDeviceController {
         return success(deviceService.createDevice(createReqVO));
     }
 
+
     @PutMapping("/update")
     @Operation(summary = "更新设备")
     @PreAuthorize("@ss.hasPermission('iot:device:update')")
@@ -61,7 +60,57 @@ public class IotDeviceController {
         return success(true);
     }
 
-    // TODO @芋艿:参考阿里云:1)绑定网关;2)解绑网关
+    @PutMapping("/bind-gateway")
+    @Operation(summary = "绑定子设备到网关")
+    @PreAuthorize("@ss.hasPermission('iot:device:update')")
+    public CommonResult<Boolean> bindDeviceGateway(@Valid @RequestBody IotDeviceBindGatewayReqVO reqVO) {
+        deviceService.bindDeviceGateway(reqVO.getSubIds(), reqVO.getGatewayId());
+        return success(true);
+    }
+
+    @PutMapping("/unbind-gateway")
+    @Operation(summary = "解绑子设备与网关")
+    @PreAuthorize("@ss.hasPermission('iot:device:update')")
+    public CommonResult<Boolean> unbindDeviceGateway(@Valid @RequestBody IotDeviceUnbindGatewayReqVO reqVO) {
+        deviceService.unbindDeviceGateway(reqVO.getSubIds(), reqVO.getGatewayId());
+        return success(true);
+    }
+
+    @GetMapping("/sub-device-list")
+    @Operation(summary = "获取网关的子设备列表")
+    @Parameter(name = "gatewayId", description = "网关设备编号", required = true, example = "1")
+    @PreAuthorize("@ss.hasPermission('iot:device:query')")
+    public CommonResult<List<IotDeviceRespVO>> getSubDeviceList(@RequestParam("gatewayId") Long gatewayId) {
+        List<NewIotDeviceDO> list = deviceService.getDeviceListByGatewayId(gatewayId);
+        if (CollUtil.isEmpty(list)) {
+            return success(Collections.emptyList());
+        }
+
+        // 补充产品名称
+        Map<Long, IotProductDO> productMap = convertMap(productService.getProductList(), IotProductDO::getId);
+        return success(convertList(list, device -> {
+            IotDeviceRespVO respVO = BeanUtils.toBean(device, IotDeviceRespVO.class);
+            MapUtils.findAndThen(productMap, device.getProductId(),
+                    product -> respVO.setProductName(product.getName()));
+            return respVO;
+        }));
+    }
+
+    @GetMapping("/unbound-sub-device-page")
+    @Operation(summary = "获取未绑定网关的子设备分页")
+    @PreAuthorize("@ss.hasPermission('iot:device:query')")
+    public CommonResult<PageResult<IotDeviceRespVO>> getUnboundSubDevicePage(@Valid IotDevicePageReqVO pageReqVO) {
+        PageResult<NewIotDeviceDO> pageResult = deviceService.getUnboundSubDevicePage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty());
+        }
+
+        // 补充产品名称
+        Map<Long, IotProductDO> productMap = convertMap(productService.getProductList(), IotProductDO::getId);
+        PageResult<IotDeviceRespVO> result = BeanUtils.toBean(pageResult, IotDeviceRespVO.class, device ->
+                MapUtils.findAndThen(productMap, device.getProductId(), product -> device.setProductName(product.getName())));
+        return success(result);
+    }
 
     @PutMapping("/update-group")
     @Operation(summary = "更新设备分组")
@@ -94,7 +143,7 @@ public class IotDeviceController {
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('iot:device:query')")
     public CommonResult<IotDeviceRespVO> getDevice(@RequestParam("id") Long id) {
-        IotDeviceDO device = deviceService.getDevice(id);
+        NewIotDeviceDO device = deviceService.getDevice(id);
         return success(BeanUtils.toBean(device, IotDeviceRespVO.class));
     }
 
@@ -102,7 +151,7 @@ public class IotDeviceController {
     @Operation(summary = "获得设备分页")
     @PreAuthorize("@ss.hasPermission('iot:device:query')")
     public CommonResult<PageResult<IotDeviceRespVO>> getDevicePage(@Valid IotDevicePageReqVO pageReqVO) {
-        PageResult<IotDeviceDO> pageResult = deviceService.getDevicePage(pageReqVO);
+        PageResult<NewIotDeviceDO> pageResult = deviceService.getDevicePage(pageReqVO);
         return success(BeanUtils.toBean(pageResult, IotDeviceRespVO.class));
     }
 
@@ -111,7 +160,7 @@ public class IotDeviceController {
     @PreAuthorize("@ss.hasPermission('iot:device:export')")
     @ApiAccessLog(operateType = EXPORT)
     public void exportDeviceExcel(@Valid IotDevicePageReqVO exportReqVO,
-            HttpServletResponse response) throws IOException {
+                                  HttpServletResponse response) throws IOException {
         exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
         CommonResult<PageResult<IotDeviceRespVO>> result = getDevicePage(exportReqVO);
         // 导出 Excel
@@ -129,12 +178,37 @@ public class IotDeviceController {
 
     @GetMapping("/simple-list")
     @Operation(summary = "获取设备的精简信息列表", description = "主要用于前端的下拉选项")
-    @Parameter(name = "deviceType", description = "设备类型", example = "1")
-    public CommonResult<List<IotDeviceRespVO>> getSimpleDeviceList(
-            @RequestParam(value = "deviceType", required = false) Integer deviceType) {
-        List<IotDeviceDO> list = deviceService.getDeviceListByDeviceType(deviceType);
-        return success(convertList(list, device ->  // 只返回 id、name 字段
-                new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())));
+    @Parameters({
+            @Parameter(name = "deviceType", description = "设备类型", example = "1"),
+            @Parameter(name = "productId", description = "产品编号", example = "1024")
+    })
+    public CommonResult<List<IotDeviceRespVO>> getDeviceSimpleList(
+            @RequestParam(value = "deviceType", required = false) Integer deviceType,
+            @RequestParam(value = "productId", required = false) Long productId) {
+        List<NewIotDeviceDO> list = deviceService.getDeviceListByCondition(deviceType, productId);
+        return success(convertList(list, device ->  // 只返回 id、name、productId 字段
+                new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())
+                        .setProductId(device.getProductId()).setState(device.getState())));
+    }
+
+    @GetMapping("/location-list")
+    @Operation(summary = "获取设备位置列表", description = "获取有经纬度信息的设备列表,用于地图展示")
+    @PreAuthorize("@ss.hasPermission('iot:device:query')")
+    public CommonResult<List<IotDeviceRespVO>> getDeviceLocationList() {
+        // 1. 获取有位置信息的设备列表
+        List<NewIotDeviceDO> devices = deviceService.getDeviceListByHasLocation();
+        if (CollUtil.isEmpty(devices)) {
+            return success(Collections.emptyList());
+        }
+
+        // 2. 转换并返回
+        Map<Long, IotProductDO> productMap = convertMap(productService.getProductList(), IotProductDO::getId);
+        return success(convertList(devices, device -> {
+            IotDeviceRespVO respVO = BeanUtils.toBean(device, IotDeviceRespVO.class);
+            MapUtils.findAndThen(productMap, device.getProductId(),
+                    product -> respVO.setProductName(product.getName()));
+            return respVO;
+        }));
     }
 
     @PostMapping("/import")
@@ -155,34 +229,26 @@ public class IotDeviceController {
         List<IotDeviceImportExcelVO> list = Arrays.asList(
                 IotDeviceImportExcelVO.builder().deviceName("温度传感器001").parentDeviceName("gateway110")
                         .productKey("1de24640dfe").groupNames("灰度分组,生产分组").build(),
-                IotDeviceImportExcelVO.builder().deviceName("biubiu")
-                        .productKey("YzvHxd4r67sT4s2B").groupNames("").build());
+                IotDeviceImportExcelVO.builder().deviceName("biubiu").productKey("YzvHxd4r67sT4s2B")
+                        .groupNames("").build());
         // 输出
         ExcelUtils.write(response, "设备导入模板.xls", "数据", IotDeviceImportExcelVO.class, list);
     }
 
-    @PostMapping("/upstream")
-    @Operation(summary = "设备上行", description = "可用于设备模拟")
-    @PreAuthorize("@ss.hasPermission('iot:device:upstream')")
-    public CommonResult<Boolean> upstreamDevice(@Valid @RequestBody IotDeviceUpstreamReqVO upstreamReqVO) {
-        deviceUpstreamService.upstreamDevice(upstreamReqVO);
-        return success(true);
+    @GetMapping("/get-auth-info")
+    @Operation(summary = "获得设备连接信息")
+    @PreAuthorize("@ss.hasPermission('iot:device:auth-info')")
+    public CommonResult<IotDeviceAuthInfoRespVO> getDeviceAuthInfo(@RequestParam("id") Long id) {
+        return success(deviceService.getDeviceAuthInfo(id));
     }
 
-    @PostMapping("/downstream")
-    @Operation(summary = "设备下行", description = "可用于设备模拟")
-    @PreAuthorize("@ss.hasPermission('iot:device:downstream')")
-    public CommonResult<Boolean> downstreamDevice(@Valid @RequestBody IotDeviceDownstreamReqVO downstreamReqVO) {
-        deviceDownstreamService.downstreamDevice(downstreamReqVO);
-        return success(true);
-    }
-
-    // TODO @haohao:是不是默认详情接口,不返回 secret,然后这个接口,用于统一返回。然后接口名可以更通用一点。
-    @GetMapping("/mqtt-connection-params")
-    @Operation(summary = "获取 MQTT 连接参数")
-    @PreAuthorize("@ss.hasPermission('iot:device:mqtt-connection-params')")
-    public CommonResult<IotDeviceMqttConnectionParamsRespVO> getMqttConnectionParams(@RequestParam("deviceId") Long deviceId) {
-        return success(deviceService.getMqttConnectionParams(deviceId));
+    // TODO @haohao:可以使用 @RequestParam("productKey") String productKey, @RequestParam("deviceNames") List<String> deviceNames 来接收哇?
+    @GetMapping("/list-by-product-key-and-names")
+    @Operation(summary = "通过产品标识和设备名称列表获取设备")
+    @PreAuthorize("@ss.hasPermission('iot:device:query')")
+    public CommonResult<List<IotDeviceRespVO>> getDevicesByProductKeyAndNames(@Valid IotDeviceByProductKeyAndNamesReqVO reqVO) {
+        List<NewIotDeviceDO> devices = deviceService.getDeviceListByProductKeyAndNames(reqVO.getProductKey(), reqVO.getDeviceNames());
+        return success(BeanUtils.toBean(devices, IotDeviceRespVO.class));
     }
 
-}
+}

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

@@ -1,40 +0,0 @@
-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.data.IotDeviceLogPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDeviceLogRespVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO;
-import cn.iocoder.yudao.module.iot.service.device.data.IotDeviceLogService;
-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.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import javax.validation.Valid;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@Tag(name = "管理后台 - IoT 设备日志")
-@RestController
-@RequestMapping("/iot/device/log")
-@Validated
-public class IotDeviceLogController {
-
-    @Resource
-    private IotDeviceLogService deviceLogService;
-
-    @GetMapping("/page")
-    @Operation(summary = "获得设备日志分页")
-    @PreAuthorize("@ss.hasPermission('iot:device:log-query')")
-    public CommonResult<PageResult<IotDeviceLogRespVO>> getDeviceLogPage(@Valid IotDeviceLogPageReqVO pageReqVO) {
-        PageResult<IotDeviceLogDO> pageResult = deviceLogService.getDeviceLogPage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, IotDeviceLogRespVO.class));
-    }
-
-}

+ 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));
+    }
+
+}

+ 37 - 43
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDevicePropertyController.java

@@ -1,22 +1,21 @@
 package cn.iocoder.yudao.module.iot.controller.admin.device;
 
-import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDevicePropertyHistoryPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.data.IotDevicePropertyRespVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyDetailRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyHistoryListReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.property.IotDevicePropertyRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.model.ThingModelProperty;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
+import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
-import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
+import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
 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;
@@ -46,50 +45,45 @@ public class IotDevicePropertyController {
     @Resource
     private IotDeviceService deviceService;
 
-    @GetMapping("/latest")
+    @GetMapping("/get-latest")
     @Operation(summary = "获取设备属性最新属性")
-    @Parameters({
-            @Parameter(name = "deviceId", description = "设备编号", required = true),
-            @Parameter(name = "identifier", description = "标识符"),
-            @Parameter(name = "name", description = "名称")
-    })
+    @Parameter(name = "deviceId", description = "设备编号", required = true)
     @PreAuthorize("@ss.hasPermission('iot:device:property-query')")
-    public CommonResult<List<IotDevicePropertyRespVO>> getLatestDeviceProperties(
-            @RequestParam("deviceId") Long deviceId,
-            @RequestParam(value = "identifier", required = false) String identifier,
-            @RequestParam(value = "name", required = false) String name) {
+    public CommonResult<List<IotDevicePropertyDetailRespVO>> getLatestDeviceProperties(
+            @RequestParam("deviceId") Long deviceId) {
+        // 1.1 获取设备信息
+        NewIotDeviceDO device = deviceService.getDevice(deviceId);
+        Assert.notNull(device, "设备不存在");
+        // 1.2 获取设备最新属性
         Map<String, IotDevicePropertyDO> properties = devicePropertyService.getLatestDeviceProperties(deviceId);
+        // 1.3 根据 productId + type 查询属性类型的物模型
+        List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductIdAndType(
+                device.getProductId(), IotThingModelTypeEnum.PROPERTY.getType());
 
-        // 拼接数据
-        IotDeviceDO device = deviceService.getDevice(deviceId);
-        Assert.notNull(device, "设备不存在");
-        List<IotThingModelDO> thingModels = thingModelService.getThingModelListByProductId(device.getProductId());
-        return success(convertList(properties.entrySet(), entry -> {
-            IotThingModelDO thingModel = CollUtil.findOne(thingModels,
-                    item -> item.getIdentifier().equals(entry.getKey()));
-            if (thingModel == null || thingModel.getProperty() == null) {
-                return null;
-            }
-            if (StrUtil.isNotEmpty(identifier) && !StrUtil.contains(thingModel.getIdentifier(), identifier)) {
-                return null;
-            }
-            if (StrUtil.isNotEmpty(name) && !StrUtil.contains(thingModel.getName(), name)) {
-                return null;
+        // 2. 基于 thingModels 遍历,拼接 properties
+        return success(convertList(thingModels, thingModel -> {
+            ThingModelProperty thingModelProperty = thingModel.getProperty();
+            Assert.notNull(thingModelProperty, "属性不能为空");
+            IotDevicePropertyDetailRespVO result = new IotDevicePropertyDetailRespVO()
+                    .setName(thingModel.getName()).setDataType(thingModelProperty.getDataType())
+                    .setDataSpecs(thingModelProperty.getDataSpecs())
+                    .setDataSpecsList(thingModelProperty.getDataSpecsList());
+            result.setIdentifier(thingModel.getIdentifier());
+            IotDevicePropertyDO property = properties.get(thingModel.getIdentifier());
+            if (property != null) {
+                result.setValue(property.getValue())
+                        .setUpdateTime(LocalDateTimeUtil.toEpochMilli(property.getUpdateTime()));
             }
-            // 构建对象
-            IotDevicePropertyDO property = entry.getValue();
-            return new IotDevicePropertyRespVO().setProperty(thingModel.getProperty())
-                    .setValue(property.getValue()).setUpdateTime(LocalDateTimeUtil.toEpochMilli(property.getUpdateTime()));
+            return result;
         }));
     }
 
-    @GetMapping("/history-page")
-    @Operation(summary = "获取设备属性历史数据")
+    @GetMapping("/history-list")
+    @Operation(summary = "获取设备属性历史数据列表")
     @PreAuthorize("@ss.hasPermission('iot:device:property-query')")
-    public CommonResult<PageResult<IotDevicePropertyRespVO>> getHistoryDevicePropertyPage(
-            @Valid IotDevicePropertyHistoryPageReqVO pageReqVO) {
-        Assert.notEmpty(pageReqVO.getIdentifier(), "标识符不能为空");
-        return success(devicePropertyService.getHistoryDevicePropertyPage(pageReqVO));
+    public CommonResult<List<IotDevicePropertyRespVO>> getHistoryDevicePropertyList(
+            @Valid IotDevicePropertyHistoryListReqVO listReqVO) {
+        return success(devicePropertyService.getHistoryDevicePropertyList(listReqVO));
     }
 
 }

+ 7 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceGroupController.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/NewIotDeviceGroupController.java

@@ -7,8 +7,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupPageReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupRespVO;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupSaveReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
-import cn.iocoder.yudao.module.iot.service.device.IotDeviceGroupService;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceGroupDO;
+import cn.iocoder.yudao.module.iot.service.device.NewIotDeviceGroupService;
 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -28,10 +28,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
 @RestController
 @RequestMapping("/iot/device-group")
 @Validated
-public class IotDeviceGroupController {
+public class NewIotDeviceGroupController {
 
     @Resource
-    private IotDeviceGroupService deviceGroupService;
+    private NewIotDeviceGroupService deviceGroupService;
     @Resource
     private IotDeviceService deviceService;
 
@@ -64,7 +64,7 @@ public class IotDeviceGroupController {
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('iot:device-group:query')")
     public CommonResult<IotDeviceGroupRespVO> getDeviceGroup(@RequestParam("id") Long id) {
-        IotDeviceGroupDO deviceGroup = deviceGroupService.getDeviceGroup(id);
+        NewIotDeviceGroupDO deviceGroup = deviceGroupService.getDeviceGroup(id);
         return success(BeanUtils.toBean(deviceGroup, IotDeviceGroupRespVO.class));
     }
 
@@ -72,7 +72,7 @@ public class IotDeviceGroupController {
     @Operation(summary = "获得设备分组分页")
     @PreAuthorize("@ss.hasPermission('iot:device-group:query')")
     public CommonResult<PageResult<IotDeviceGroupRespVO>> getDeviceGroupPage(@Valid IotDeviceGroupPageReqVO pageReqVO) {
-        PageResult<IotDeviceGroupDO> pageResult = deviceGroupService.getDeviceGroupPage(pageReqVO);
+        PageResult<NewIotDeviceGroupDO> pageResult = deviceGroupService.getDeviceGroupPage(pageReqVO);
         return success(BeanUtils.toBean(pageResult, IotDeviceGroupRespVO.class,
                 group -> group.setDeviceCount(deviceService.getDeviceCountByGroupId(group.getId()))));
     }
@@ -80,7 +80,7 @@ public class IotDeviceGroupController {
     @GetMapping("/simple-list")
     @Operation(summary = "获取设备分组的精简信息列表", description = "只包含被开启的分组,主要用于前端的下拉选项")
     public CommonResult<List<IotDeviceGroupRespVO>> getSimpleDeviceGroupList() {
-        List<IotDeviceGroupDO> list = deviceGroupService.getDeviceGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        List<NewIotDeviceGroupDO> list = deviceGroupService.getDeviceGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
         return success(convertList(list, group -> // 只返回 id、name 字段
                 new IotDeviceGroupRespVO().setId(group.getId()).setName(group.getName())));
     }

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

@@ -1,31 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.device.vo.control;
-
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
-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 IotDeviceDownstreamReqVO {
-
-    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
-    @NotNull(message = "设备编号不能为空")
-    private Long id;
-
-    @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
-    @NotEmpty(message = "消息类型不能为空")
-    @InEnum(IotDeviceMessageTypeEnum.class)
-    private String type;
-
-    @Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "report")
-    @NotEmpty(message = "标识符不能为空")
-    private String identifier; // 参见 IotDeviceMessageIdentifierEnum 枚举类
-
-    @Schema(description = "请求参数", requiredMode = Schema.RequiredMode.REQUIRED)
-    private Object data; // 例如说:服务调用的 params、属性设置的 properties
-
-}

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

@@ -1,31 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.device.vo.control;
-
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
-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 IotDeviceUpstreamReqVO {
-
-    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
-    @NotNull(message = "设备编号不能为空")
-    private Long id;
-
-    @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
-    @NotEmpty(message = "消息类型不能为空")
-    @InEnum(IotDeviceMessageTypeEnum.class)
-    private String type;
-
-    @Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "report")
-    @NotEmpty(message = "标识符不能为空")
-    private String identifier; // 参见 IotDeviceMessageIdentifierEnum 枚举类
-
-    @Schema(description = "请求参数", requiredMode = Schema.RequiredMode.REQUIRED)
-    private Object data; // 例如说:属性上报的 properties、事件上报的 params
-
-}

+ 0 - 23
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogPageReqVO.java

@@ -1,23 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import javax.validation.constraints.NotEmpty;
-
-@Schema(description = "管理后台 - IoT 设备日志分页查询 Request VO")
-@Data
-public class IotDeviceLogPageReqVO extends PageParam {
-
-    @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123")
-    @NotEmpty(message = "设备标识不能为空")
-    private String deviceKey;
-
-    @Schema(description = "消息类型", example = "property")
-    private String type; // 参见 IotDeviceMessageTypeEnum 枚举,精准匹配
-
-    @Schema(description = "标识符", example = "temperature")
-    private String identifier; // 参见 IotDeviceMessageIdentifierEnum 枚举,模糊匹配
-
-}

+ 0 - 36
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDeviceLogRespVO.java

@@ -1,36 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-@Schema(description = "管理后台 - IoT 设备日志 Response VO")
-@Data
-public class IotDeviceLogRespVO {
-
-    @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    private String id;
-
-    @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "product123")
-    private String productKey;
-
-    @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "device123")
-    private String deviceKey;
-
-    @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "property")
-    private String type;
-
-    @Schema(description = "标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature")
-    private String identifier;
-
-    @Schema(description = "日志内容", requiredMode = Schema.RequiredMode.REQUIRED)
-    private String content;
-
-    @Schema(description = "上报时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime reportTime;
-
-    @Schema(description = "记录时间戳", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime ts;
-
-}

+ 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;
+
+}

+ 1 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java

@@ -6,7 +6,6 @@ import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
 
 import javax.validation.constraints.NotEmpty;
 
@@ -17,7 +16,6 @@ import javax.validation.constraints.NotEmpty;
 @Builder
 @AllArgsConstructor
 @NoArgsConstructor
-@Accessors(chain = false) // 设置 chain = false,避免设备导入有问题
 public class IotDeviceImportExcelVO {
 
     @ExcelProperty("设备名称")
@@ -35,4 +33,4 @@ public class IotDeviceImportExcelVO {
     @ExcelProperty("设备分组")
     private String groupNames;
 
-}
+}

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

@@ -1,25 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
-
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Schema(description = "管理后台 - IoT 设备 MQTT 连接参数 Response VO")
-@Data
-@ExcelIgnoreUnannotated
-public class IotDeviceMqttConnectionParamsRespVO {
-
-    @Schema(description = "MQTT 客户端 ID", example = "24602")
-    @ExcelProperty("MQTT 客户端 ID")
-    private String mqttClientId;
-
-    @Schema(description = "MQTT 用户名", example = "芋艿")
-    @ExcelProperty("MQTT 用户名")
-    private String mqttUsername;
-
-    @Schema(description = "MQTT 密码")
-    @ExcelProperty("MQTT 密码")
-    private String mqttPassword;
-
-}

+ 4 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java

@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -31,4 +31,7 @@ public class IotDevicePageReqVO extends PageParam {
     @Schema(description = "设备分组编号", example = "1024")
     private Long groupId;
 
+    @Schema(description = "网关设备 ID", example = "16380")
+    private Long gatewayId;
+
 }

+ 12 - 10
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java

@@ -1,12 +1,13 @@
 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
 
-import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
-import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Set;
 
@@ -20,10 +21,6 @@ public class IotDeviceRespVO {
     @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
     private Long id;
 
-    @Schema(description = "设备唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED)
-    @ExcelProperty("设备唯一标识符")
-    private String deviceKey;
-
     @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
     @ExcelProperty("设备名称")
     private String deviceName;
@@ -47,6 +44,9 @@ public class IotDeviceRespVO {
     @ExcelProperty("产品编号")
     private Long productId;
 
+    @Schema(description = "产品名称", example = "温湿度传感器")
+    private String productName; // 只有部分接口返回,例如 getDeviceLocationList
+
     @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("产品 Key")
     private String productKey;
@@ -79,13 +79,15 @@ public class IotDeviceRespVO {
     @ExcelProperty("设备密钥")
     private String deviceSecret;
 
-    @Schema(description = "认证类型(如一机一密、动态注册)", example = "2")
-    @ExcelProperty("认证类型(如一机一密、动态注册)")
-    private String authType;
-
     @Schema(description = "设备配置", example = "{\"abc\": \"efg\"}")
     private String config;
 
+    @Schema(description = "设备位置的纬度", example = "45.000000")
+    private BigDecimal latitude;
+
+    @Schema(description = "设备位置的经度", example = "45.000000")
+    private BigDecimal longitude;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;

+ 13 - 5
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java

@@ -3,7 +3,9 @@ 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.Size;
+import javax.validation.constraints.DecimalMax;
+import javax.validation.constraints.DecimalMin;
+import java.math.BigDecimal;
 import java.util.Set;
 
 @Schema(description = "管理后台 - IoT 设备新增/修改 Request VO")
@@ -13,10 +15,6 @@ public class IotDeviceSaveReqVO {
     @Schema(description = "设备编号", example = "177")
     private Long id;
 
-    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177")
-    @Size(max = 50, message = "设备编号长度不能超过 50 个字符")
-    private String deviceKey;
-
     @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.AUTO, example = "王五")
     private String deviceName;
 
@@ -41,4 +39,14 @@ public class IotDeviceSaveReqVO {
     @Schema(description = "设备配置", example = "{\"abc\": \"efg\"}")
     private String config;
 
+    @Schema(description = "设备位置的纬度", example = "39.915")
+    @DecimalMin(value = "-90", message = "纬度范围为 -90 到 90")
+    @DecimalMax(value = "90", message = "纬度范围为 -90 到 90")
+    private BigDecimal latitude;
+
+    @Schema(description = "设备位置的经度", example = "116.404")
+    @DecimalMin(value = "-180", message = "经度范围为 -180 到 180")
+    @DecimalMax(value = "180", message = "经度范围为 -180 到 180")
+    private BigDecimal longitude;
+
 }

+ 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;
+
+}

+ 3 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDevicePropertyHistoryPageReqVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyHistoryListReqVO.java

@@ -1,6 +1,5 @@
-package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.property;
 
-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;
@@ -12,17 +11,14 @@ 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")
+@Schema(description = "管理后台 - IoT 设备属性历史列表 Request VO")
 @Data
-public class IotDevicePropertyHistoryPageReqVO extends PageParam {
+public class IotDevicePropertyHistoryListReqVO {
 
     @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
     @NotNull(message = "设备编号不能为空")
     private Long deviceId;
 
-    @Schema(description = "设备 Key", hidden = true)
-    private String deviceKey; // 非前端传递,后端自己查询设置
-
     @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotEmpty(message = "属性标识符不能为空")
     private String identifier;

+ 4 - 5
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/data/IotDevicePropertyRespVO.java → yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/property/IotDevicePropertyRespVO.java

@@ -1,6 +1,5 @@
-package cn.iocoder.yudao.module.iot.controller.admin.device.vo.data;
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo.property;
 
-import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -8,10 +7,10 @@ import lombok.Data;
 @Data
 public class IotDevicePropertyRespVO {
 
-    @Schema(description = "属性定义", requiredMode = Schema.RequiredMode.REQUIRED)
-    private ThingModelProperty property;
+    @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String identifier;
 
-    @Schema(description = "最新值", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "属性值", requiredMode = Schema.RequiredMode.REQUIRED)
     private Object value;
 
     @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)

+ 15 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaFirmwareController.java

@@ -8,7 +8,9 @@ import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwa
 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareRespVO;
 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareUpdateReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
 import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -22,12 +24,14 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
 @Tag(name = "管理后台 - IoT OTA 固件")
 @RestController
-@RequestMapping("/iot/ota-firmware")
+@RequestMapping("/iot/ota/firmware")
 @Validated
 public class IotOtaFirmwareController {
 
     @Resource
     private IotOtaFirmwareService otaFirmwareService;
+    @Resource
+    private IotProductService productService;
 
     @PostMapping("/create")
     @Operation(summary = "创建 OTA 固件")
@@ -48,8 +52,16 @@ public class IotOtaFirmwareController {
     @Operation(summary = "获得 OTA 固件")
     @PreAuthorize("@ss.hasPermission('iot:ota-firmware:query')")
     public CommonResult<IotOtaFirmwareRespVO> getOtaFirmware(@RequestParam("id") Long id) {
-        IotOtaFirmwareDO otaFirmware = otaFirmwareService.getOtaFirmware(id);
-        return success(BeanUtils.toBean(otaFirmware, IotOtaFirmwareRespVO.class));
+        IotOtaFirmwareDO firmware = otaFirmwareService.getOtaFirmware(id);
+        if (firmware == null) {
+            return success(null);
+        }
+        return success(BeanUtils.toBean(firmware, IotOtaFirmwareRespVO.class, o -> {
+            IotProductDO product = productService.getProduct(firmware.getProductId());
+            if (product != null) {
+                o.setProductName(product.getName());
+            }
+        }));
     }
 
     @GetMapping("/page")

+ 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);
+    }
+
+}

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

@@ -1,75 +0,0 @@
-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.upgrade.record.IotOtaUpgradeRecordPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordRespVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
-import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeRecordService;
-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.Map;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@Tag(name = "管理后台 - IoT OTA 升级记录")
-@RestController
-@RequestMapping("/iot/ota-upgrade-record")
-@Validated
-public class IotOtaUpgradeRecordController {
-
-    @Resource
-    private IotOtaUpgradeRecordService upgradeRecordService;
-
-    @GetMapping("/get-statistics")
-    @Operation(summary = "固件升级设备统计")
-    @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')")
-    @Parameter(name = "firmwareId", description = "固件编号", required = true, example = "1024")
-    public CommonResult<Map<Integer, Long>> getOtaUpgradeRecordStatistics(@RequestParam(value = "firmwareId") Long firmwareId) {
-        return success(upgradeRecordService.getOtaUpgradeRecordStatistics(firmwareId));
-    }
-
-    @GetMapping("/get-count")
-    @Operation(summary = "获得升级记录分页 tab 数量")
-    @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')")
-    public CommonResult<Map<Integer, Long>> getOtaUpgradeRecordCount(
-            @Valid IotOtaUpgradeRecordPageReqVO pageReqVO) {
-        return success(upgradeRecordService.getOtaUpgradeRecordCount(pageReqVO));
-    }
-
-    @GetMapping("/page")
-    @Operation(summary = "获得升级记录分页")
-    @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')")
-    public CommonResult<PageResult<IotOtaUpgradeRecordRespVO>> getUpgradeRecordPage(
-            @Valid IotOtaUpgradeRecordPageReqVO pageReqVO) {
-        PageResult<IotOtaUpgradeRecordDO> pageResult = upgradeRecordService.getUpgradeRecordPage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, IotOtaUpgradeRecordRespVO.class));
-    }
-
-    @GetMapping("/get")
-    @Operation(summary = "获得升级记录")
-    @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')")
-    @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
-    public CommonResult<IotOtaUpgradeRecordRespVO> getUpgradeRecord(@RequestParam("id") Long id) {
-        IotOtaUpgradeRecordDO upgradeRecord = upgradeRecordService.getUpgradeRecord(id);
-        return success(BeanUtils.toBean(upgradeRecord, IotOtaUpgradeRecordRespVO.class));
-    }
-
-    @PutMapping("/retry")
-    @Operation(summary = "重试升级记录")
-    @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:retry')")
-    @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
-    public CommonResult<Boolean> retryUpgradeRecord(@RequestParam("id") Long id) {
-        upgradeRecordService.retryUpgradeRecord(id);
-        return success(true);
-    }
-
-}

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

@@ -1,65 +0,0 @@
-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.upgrade.task.IotOtaUpgradeTaskPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskRespVO;
-import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskSaveReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
-import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeTaskService;
-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-upgrade-task")
-@Validated
-public class IotOtaUpgradeTaskController {
-
-    @Resource
-    private IotOtaUpgradeTaskService upgradeTaskService;
-
-    @PostMapping("/create")
-    @Operation(summary = "创建升级任务")
-    @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:create')")
-    public CommonResult<Long> createUpgradeTask(@Valid @RequestBody IotOtaUpgradeTaskSaveReqVO createReqVO) {
-        return success(upgradeTaskService.createUpgradeTask(createReqVO));
-    }
-
-    @PostMapping("/cancel")
-    @Operation(summary = "取消升级任务")
-    @Parameter(name = "id", description = "升级任务编号", required = true)
-    @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:cancel')")
-    public CommonResult<Boolean> cancelUpgradeTask(@RequestParam("id") Long id) {
-        upgradeTaskService.cancelUpgradeTask(id);
-        return success(true);
-    }
-
-    @GetMapping("/page")
-    @Operation(summary = "获得升级任务分页")
-    @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:query')")
-    public CommonResult<PageResult<IotOtaUpgradeTaskRespVO>> getUpgradeTaskPage(@Valid IotOtaUpgradeTaskPageReqVO pageReqVO) {
-        PageResult<IotOtaUpgradeTaskDO> pageResult = upgradeTaskService.getUpgradeTaskPage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, IotOtaUpgradeTaskRespVO.class));
-    }
-
-    @GetMapping("/get")
-    @Operation(summary = "获得升级任务")
-    @Parameter(name = "id", description = "升级任务编号", required = true, example = "1024")
-    @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:query')")
-    public CommonResult<IotOtaUpgradeTaskRespVO> getUpgradeTask(@RequestParam("id") Long id) {
-        IotOtaUpgradeTaskDO upgradeTask = upgradeTaskService.getUpgradeTask(id);
-        return success(BeanUtils.toBean(upgradeTask, IotOtaUpgradeTaskRespVO.class));
-    }
-
-}

+ 7 - 14
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java

@@ -2,40 +2,33 @@ package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
+import org.hibernate.validator.constraints.URL;
 
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Schema(description = "管理后台 - IoT OTA 固件创建 Request VO")
 @Data
 public class IotOtaFirmwareCreateReqVO {
 
-    @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
+    @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能开关固件")
     @NotEmpty(message = "固件名称不能为空")
     private String name;
 
     @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
     private String description;
 
-    @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0")
+    @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0.0")
     @NotEmpty(message = "版本号不能为空")
     private String version;
 
-    @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "产品编号不能为空")
-    private String productId;
-
-    @Schema(description = "签名方式", example = "MD5")
-    // TODO @li:是不是必传哈
-    private String signMethod;
+    private Long productId;
 
-    @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip")
+    @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.zip")
     @NotEmpty(message = "固件文件 URL 不能为空")
+    @URL(message = "固件文件 URL 格式错误")
     private String fileUrl;
 
-    @Schema(description = "自定义信息,建议使用 JSON 格式", example = "{\"key1\":\"value1\",\"key2\":\"value2\"}")
-    private String information;
-
 }

+ 10 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwarePageReqVO.java

@@ -3,21 +3,24 @@ package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
 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;
 
-@Data
 @Schema(description = "管理后台 - IoT OTA 固件分页 Request VO")
+@Data
 public class IotOtaFirmwarePageReqVO extends PageParam {
 
-    /**
-     * 固件名称
-     */
     @Schema(description = "固件名称", example = "智能开关固件")
     private String name;
 
-    /**
-     * 产品标识
-     */
     @Schema(description = "产品标识", example = "1024")
     private String productId;
 
+    @Schema(description = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
 }

+ 29 - 65
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareRespVO.java

@@ -1,85 +1,49 @@
 package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
 
-import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
-import com.fhs.core.trans.anno.Trans;
-import com.fhs.core.trans.constant.TransType;
 import com.fhs.core.trans.vo.VO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
+import java.time.LocalDateTime;
 
-@Data
 @Schema(description = "管理后台 - IoT OTA 固件 Response VO")
+@Data
 public class IotOtaFirmwareRespVO implements VO {
 
-    /**
-     * 固件编号
-     */
-    @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
-    /**
-     * 固件名称
-     */
-    @Schema(description = "固件名称", requiredMode = REQUIRED, example = "OTA固件")
+
+    @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA 固件")
     private String name;
-    /**
-     * 固件描述
-     */
+
     @Schema(description = "固件描述")
     private String description;
-    /**
-     * 版本号
-     */
-    @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0")
+
+    @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0.0")
     private String version;
 
-    /**
-     * 产品编号
-     * <p>
-     * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
-     */
-    @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024")
-    @Trans(type = TransType.SIMPLE, target = IotProductDO.class, fields = {"name"}, refs = {"productName"})
-    private String productId;
-    /**
-     * 产品标识
-     * <p>
-     * 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
-     */
-    @Schema(description = "产品标识", requiredMode = REQUIRED, example = "iot-product-key")
-    private String productKey;
-    /**
-     * 产品名称
-     */
-    @Schema(description = "产品名称", requiredMode = REQUIRED, example = "OTA产品")
+    @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long productId;
+
+    @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能设备")
     private String productName;
-    /**
-     * 签名方式
-     * <p>
-     * 例如说:MD5、SHA256
-     */
-    @Schema(description = "签名方式", example = "MD5")
-    private String signMethod;
-    /**
-     * 固件文件签名
-     */
-    @Schema(description = "固件文件签名", example = "1024")
-    private String fileSign;
-    /**
-     * 固件文件大小
-     */
-    @Schema(description = "固件文件大小", requiredMode = REQUIRED, example = "1024")
-    private Long fileSize;
-    /**
-     * 固件文件 URL
-     */
-    @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn")
+
+    @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/firmware.bin")
     private String fileUrl;
-    /**
-     * 自定义信息,建议使用 JSON 格式
-     */
-    @Schema(description = "自定义信息,建议使用 JSON 格式")
-    private String information;
+
+    @Schema(description = "固件文件大小", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long fileSize;
+
+    @Schema(description = "固件文件签名算法", example = "MD5")
+    private String fileDigestAlgorithm;
+
+    @Schema(description = "固件文件签名结果", example = "d41d8cd98f00b204e9800998ecf8427e")
+    private String fileDigestValue;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime updateTime;
 
 }

+ 2 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java

@@ -3,22 +3,17 @@ package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
 @Schema(description = "管理后台 - IoT OTA 固件更新 Request VO")
 @Data
 public class IotOtaFirmwareUpdateReqVO {
 
-    @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
+    @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "固件编号不能为空")
     private Long id;
 
-    // TODO @li:name 是不是可以飞必传哈
-    @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
-    @NotEmpty(message = "固件名称不能为空")
+    @Schema(description = "固件名称", example = "智能开关固件")
     private String name;
 
     @Schema(description = "固件描述", example = "某品牌型号固件,测试用")

+ 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;
+
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels