diff --git a/yudao-module-customer/src/main/java/cn/iocoder/yudao/module/cus/enums/ErrorCodeConstants.java b/yudao-module-customer/src/main/java/cn/iocoder/yudao/module/cus/enums/ErrorCodeConstants.java
index c9701cd..78bce04 100644
--- a/yudao-module-customer/src/main/java/cn/iocoder/yudao/module/cus/enums/ErrorCodeConstants.java
+++ b/yudao-module-customer/src/main/java/cn/iocoder/yudao/module/cus/enums/ErrorCodeConstants.java
@@ -11,6 +11,9 @@ public interface ErrorCodeConstants {
// ========== 管理客户页面 (1-030-100-000) ==========
ErrorCode MANAGEMENT_NOT_EXISTS = new ErrorCode(1_030_100_000, "客户不存在");
+ ErrorCode MANAGEMENT_CODE_NOT_EXISTS = new ErrorCode(1_030_100_000, "客户编码不能为空");
+ ErrorCode MANAGEMENT_CUSTOMER_CODE_DUPLICATE = new ErrorCode(1_002_000_001, "客户编码已存在,请勿重复");
+
}
diff --git a/yudao-module-customer/src/main/java/cn/iocoder/yudao/module/cus/service/management/ManagementServiceImpl.java b/yudao-module-customer/src/main/java/cn/iocoder/yudao/module/cus/service/management/ManagementServiceImpl.java
index a28d2a1..560c934 100644
--- a/yudao-module-customer/src/main/java/cn/iocoder/yudao/module/cus/service/management/ManagementServiceImpl.java
+++ b/yudao-module-customer/src/main/java/cn/iocoder/yudao/module/cus/service/management/ManagementServiceImpl.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.cus.service.management;
+import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
@@ -12,8 +13,8 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.cus.dal.mysql.management.ManagementMapper;
-import static cn.iocoder.yudao.module.cus.enums.ErrorCodeConstants.MANAGEMENT_NOT_EXISTS;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.cus.enums.ErrorCodeConstants.*;
/**
* 客户管理 Service 实现类
@@ -29,6 +30,8 @@ public class ManagementServiceImpl implements ManagementService {
@Override
public Long createManagement(ManagementSaveReqVO createReqVO) {
+ //customerCode 唯一校验
+ validateCustomerCodeUnique(createReqVO.getCustomerCode(), null);
// 插入
ManagementDO management = BeanUtils.toBean(createReqVO, ManagementDO.class);
managementMapper.insert(management);
@@ -36,6 +39,23 @@ public class ManagementServiceImpl implements ManagementService {
// 返回
return management.getId();
}
+ private void validateCustomerCodeUnique(String customerCode, Long id) {
+ if (StrUtil.isBlank(customerCode)) {
+ throw exception(MANAGEMENT_CODE_NOT_EXISTS);
+ }
+
+ ManagementDO exist = managementMapper.selectOne(
+ ManagementDO::getCustomerCode, customerCode
+ );
+ if (exist == null) {
+ return;
+ }
+
+ // update 场景排除自己(create 时 id = null)
+ if (id == null || !Objects.equals(exist.getId(), id)) {
+ throw exception(MANAGEMENT_CUSTOMER_CODE_DUPLICATE);
+ }
+ }
@Override
public void updateManagement(ManagementSaveReqVO updateReqVO) {
diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml
index 02644cb..04f402f 100644
--- a/yudao-module-iot/yudao-module-iot-biz/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -101,6 +101,12 @@
spring-boot-starter-amqp
true
+
+ cn.iocoder.boot
+ yudao-module-customer
+ 2026.01-jdk8-SNAPSHOT
+ compile
+
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java
index c4e57f7..2269b99 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java
@@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.infra.service.job.JobService;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
//import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo.DeviceWarinningRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
//import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO;
@@ -120,10 +122,9 @@ public class DeviceController {
ExcelUtils.write(response, "物联设备.xls", "数据", DeviceRespVO.class,list);
}
@GetMapping("/deviceList")
-// @PreAuthorize("@ss.hasPermission('iot:device:query')")
+ @PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult> deviceList(@Valid DevicePageReqVO pageReqVO) {
- pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
- List list = deviceService.getDevicePage(pageReqVO).getList();
+ List list = deviceService.deviceList(pageReqVO);
return success(list);
}
@@ -240,11 +241,11 @@ public class DeviceController {
@GetMapping("/getDeviceOperationalStatus")
- @Operation(summary = "获取首页设备运行状态")
+ @Operation(summary = "获取设备运行状态")
// @PreAuthorize("@ss.hasPermission('iot:device:query')")
@Parameter(name = "orgId", description = "产线组织Id")
- public CommonResult getDeviceOperationalStatus(@RequestParam(name = "orgId",required = false) Long orgId) throws JsonProcessingException {
- DeviceOperationStatusRespVO deviceOperationalStatus=deviceService.getDeviceOperationalStatus();
+ public CommonResult getDeviceOperationalStatus(LocalDateTime startTime, LocalDateTime endTime) throws JsonProcessingException {
+ DeviceOperationStatusRespVO deviceOperationalStatus=deviceService.getDeviceOperationalStatus(startTime,endTime);
return success(deviceOperationalStatus);
}
@@ -350,4 +351,30 @@ public class DeviceController {
return success(true);
}
+
+ @GetMapping("/device-run-status-stats")
+ @Operation(summary = "查询设备总数及各运行状态数量")
+ @PreAuthorize("@ss.hasPermission('iot:org-node:query')")
+ public CommonResult getDeviceRunStatusStats(
+ @RequestParam(value = "customerId", required = false) Long customerId,
+ @RequestParam(value = "orgNodeId", required = false) Long orgNodeId) {
+ return success(deviceService.getDeviceRunStatusStats(customerId, orgNodeId));
+ }
+
+ @GetMapping("/run-status-stats-by-customer")
+ @Operation(summary = "按客户统计设备总数及运行状态数")
+ @Parameter(name = "customerId", description = "客户ID", required = true, example = "5")
+ @PreAuthorize("@ss.hasPermission('iot:device:query')")
+ public CommonResult getRunStatusStatsByCustomer(@RequestParam("customerId") Long customerId) {
+ return success(deviceService.getRunStatusStatsByCustomer(customerId));
+ }
+
+ @GetMapping("/status-count-by-customer")
+ @Operation(summary = "按客户统计设备状态数量")
+ @PreAuthorize("@ss.hasPermission('iot:device:query')")
+ public CommonResult> getStatusCountByCustomer() {
+ return success(deviceService.getStatusCountByCustomer());
+ }
+
+
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/dto/DeviceLatestRuleDTO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/dto/DeviceLatestRuleDTO.java
new file mode 100644
index 0000000..b227457
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/dto/DeviceLatestRuleDTO.java
@@ -0,0 +1,9 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.dto;
+
+import lombok.Data;
+
+@Data
+public class DeviceLatestRuleDTO {
+ private Long deviceId;
+ private String rule;
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/CustomerDeviceStatusStatsRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/CustomerDeviceStatusStatsRespVO.java
new file mode 100644
index 0000000..ad4bb77
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/CustomerDeviceStatusStatsRespVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class CustomerDeviceStatusStatsRespVO {
+ private Long customerId;
+ private String customerName;
+ private Long totalDeviceCount;
+ private Long offlineCount;
+ private Long runningCount;
+ private Long standbyCount;
+ private Long faultStandbyCount;
+ private Long alarmRunningCount;
+
+ /**
+ * 经度
+ */
+ private BigDecimal longitude;
+ /**
+ * 纬度
+ */
+ private BigDecimal latitude;
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceOperationStatusRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceOperationStatusRespVO.java
index da93533..a9798f9 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceOperationStatusRespVO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceOperationStatusRespVO.java
@@ -31,6 +31,9 @@ public class DeviceOperationStatusRespVO {
@Schema(description = "故障率")
private String faultRate;
+ @Schema(description = "报告统计数")
+ private Long warningRecordCount;
+
}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceRespVO.java
index 4af131c..1690925 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceRespVO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceRespVO.java
@@ -127,4 +127,10 @@ public class DeviceRespVO {
@Schema(description = "客户组织节点Id")
private Long orgNodeId;
+
+ @Schema(description = "客户名称")
+ private String customerName;
+
+ @Schema(description = "组织名称")
+ private String orgNodeName;
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceRunStatusStatsRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceRunStatusStatsRespVO.java
new file mode 100644
index 0000000..c91a49a
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceRunStatusStatsRespVO.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
+
+import lombok.Data;
+
+@Data
+public class DeviceRunStatusStatsRespVO {
+
+ private Long totalDeviceCount; // 设备总数
+
+ private Long offlineCount; // 0 离线
+ private Long runningCount; // 1 运行
+ private Long standbyCount; // 2 待机中
+ private Long faultStandbyCount; // 3 故障中
+ private Long alarmRunningCount; // 4 报警中
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceSimpleRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceSimpleRespVO.java
new file mode 100644
index 0000000..3945fa8
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/DeviceSimpleRespVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.iot.controller.admin.device.vo;
+
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 设备简要信息 Response VO")
+@Data
+public class DeviceSimpleRespVO {
+
+ @Schema(description = "设备ID", example = "170")
+ private Long id;
+
+ @Schema(description = "设备编号", example = "DEV-001")
+ private String deviceCode;
+
+ @Schema(description = "设备名称", example = "注塑机-01")
+ private String deviceName;
+
+ @Schema(description = "状态", example = "2")
+ private String status;
+
+ @Schema(description = "客户ID", example = "5")
+ private Long customerId;
+
+ @Schema(description = "组织节点ID", example = "12")
+ private Long orgNodeId;
+
+ @Schema(description = "通讯协议", example = "")
+ private String protocol;
+
+ @Schema(description = "运行状态")
+ private String operatingStatus;
+
+ @Schema(description = "是否启用")
+ private Boolean isEnable;
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/DeviceWarinningRecordController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/DeviceWarinningRecordController.java
index 6dc94c2..83f5c97 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/DeviceWarinningRecordController.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/DeviceWarinningRecordController.java
@@ -6,9 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.devicewarinningrecord.vo.DeviceWarinningRecordPageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo.DeviceWarinningRecordRespVO;
-import cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo.DeviceWarinningRecordSaveReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.devicewarinningrecord.DeviceWarinningRecordDO;
import cn.iocoder.yudao.module.iot.service.devicewarinningrecord.DeviceWarinningRecordService;
import io.swagger.v3.oas.annotations.Operation;
@@ -73,9 +71,9 @@ public class DeviceWarinningRecordController {
@GetMapping("/page")
@Operation(summary = "获得告警记录分页")
@PreAuthorize("@ss.hasPermission('iot:device-warinning-record:query')")
- public CommonResult> getDeviceWarinningRecordPage(@Valid DeviceWarinningRecordPageReqVO pageReqVO) {
- PageResult pageResult = deviceWarinningRecordService.getDeviceWarinningRecordPage(pageReqVO);
- return success(BeanUtils.toBean(pageResult, DeviceWarinningRecordRespVO.class));
+ public CommonResult> getDeviceWarinningRecordPage(@Valid DeviceWarinningRecordPageReqVO pageReqVO) {
+ PageResult pageResult = deviceWarinningRecordService.getDeviceWarinningRecordPage(pageReqVO);
+ return success(pageResult);
}
@GetMapping("/export-excel")
@@ -85,22 +83,18 @@ public class DeviceWarinningRecordController {
public void exportDeviceWarinningRecordExcel(@Valid DeviceWarinningRecordPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
- List list = deviceWarinningRecordService.getDeviceWarinningRecordPage(pageReqVO).getList();
+ List list = deviceWarinningRecordService.getDeviceWarinningRecordPage(pageReqVO).getList();
// 导出 Excel
- ExcelUtils.write(response, "告警记录.xls", "数据", DeviceWarinningRecordRespVO.class,
- BeanUtils.toBean(list, DeviceWarinningRecordRespVO.class));
+ ExcelUtils.write(response, "告警记录.xls", "数据", DeviceWarinningRecordPageRespVO.class,
+ list);
}
@GetMapping("/getList")
@Operation(summary = "获得告警记录列表")
- @Parameter(name = "deviceId", description = "设备Id", required = true, example = "1024")
- @Parameter(name = "orgId", description = "产线组织Id", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:device-warinning-record:query')")
- public CommonResult> getList(@RequestParam(name = "deviceId" ,required = false) Long id,
- @RequestParam(name = "orgId" ,required = false) Long orgId) {
- List deviceWarinningRecord = deviceWarinningRecordService.getList(id);
- return success(deviceWarinningRecord);
+ public CommonResult> getList(@Valid DeviceWarningListReqVO reqVO) {
+ return success(deviceWarinningRecordService.getList(reqVO));
}
@@ -113,4 +107,20 @@ public class DeviceWarinningRecordController {
return success(hourCounts);
}
+ @GetMapping("/count")
+ @Operation(summary = "告警数量统计")
+ @PreAuthorize("@ss.hasPermission('iot:device-warinning-record:query')")
+ public CommonResult count(@Valid DeviceWarningCountReqVO reqVO) {
+ return success(deviceWarinningRecordService.getWarningCount(reqVO));
+ }
+
+
+
+ @GetMapping("/trend")
+ @Operation(summary = "告警趋势(今日/本周/本月)")
+ @PreAuthorize("@ss.hasPermission('iot:device-warinning-record:query')")
+ public CommonResult trend(@Valid DeviceWarningTrendReqVO reqVO) {
+ return CommonResult.success(deviceWarinningRecordService.getWarningTrend(reqVO));
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarinningRecordPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarinningRecordPageReqVO.java
index 9f91761..2f3b184 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarinningRecordPageReqVO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarinningRecordPageReqVO.java
@@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
+import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -32,9 +34,11 @@ public class DeviceWarinningRecordPageReqVO extends PageParam {
@Schema(description = "地址值")
private String addressValue;
- @Schema(description = "创建时间")
- @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
- private LocalDateTime[] createTime;
+ @NotNull(message = "开始时间不能为空")
+ private String startTime;
+
+ @NotNull(message = "结束时间不能为空")
+ private String endTime;
@Schema(description = "点位规则Id", example = "25946")
private Long ruleId;
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarinningRecordPageRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarinningRecordPageRespVO.java
new file mode 100644
index 0000000..8dcd50b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarinningRecordPageRespVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo;
+
+
+import lombok.Data;
+
+@Data
+public class DeviceWarinningRecordPageRespVO {
+
+ private Long id;
+ private Long deviceId;
+ private String alarmLevel;
+ private String alarmContent;
+ private String createTime;
+ private Integer deleted;
+
+ private String deviceName;
+ private String deviceCode;
+ private String customerCode;
+ private String customerName;
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningCountReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningCountReqVO.java
new file mode 100644
index 0000000..ac5c444
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningCountReqVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+// DeviceWarningCountReqVO.java
+@Data
+public class DeviceWarningCountReqVO {
+
+ @NotNull(message = "开始时间不能为空")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime startTime;
+
+ @NotNull(message = "结束时间不能为空")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime endTime;
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningCountRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningCountRespVO.java
new file mode 100644
index 0000000..d259e7b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningCountRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo;
+
+import lombok.Data;
+
+// DeviceWarningCountRespVO.java
+@Data
+public class DeviceWarningCountRespVO {
+
+ private Long totalCount;
+
+ // alarmLevel = 1
+ private Long normalCount;
+
+ // alarmLevel = 2
+ private Long tipCount;
+
+ // alarmLevel = 0
+ private Long seriousCount;
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningListReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningListReqVO.java
new file mode 100644
index 0000000..f3cb848
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningListReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+@Data
+public class DeviceWarningListReqVO {
+
+ private Long deviceId; // 可选
+ private Long orgId; // 可选(先保留)
+
+ @NotNull(message = "开始时间不能为空")
+ private LocalDateTime startTime;
+
+ @NotNull(message = "结束时间不能为空")
+ private LocalDateTime endTime;
+
+ private String alarmLevel; // 可选
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningListRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningListRespVO.java
new file mode 100644
index 0000000..c4e3195
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningListRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class DeviceWarningListRespVO {
+ private Long id;
+ private Long deviceId;
+ private Long modelId;
+ private String rule;
+ private String alarmLevel;
+ private String addressValue;
+ private Long ruleId;
+ private String deviceName;
+ private String modelName;
+ private String ruleName;
+ private LocalDateTime createTime;
+
+ // 新增
+ private String customerName;
+}
+
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningTrendReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningTrendReqVO.java
new file mode 100644
index 0000000..0869ea1
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningTrendReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Data
+@Schema(description = "管理后台 - 设备告警趋势 Request VO")
+public class DeviceWarningTrendReqVO {
+
+ @NotNull(message = "开始时间不能为空")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-03-01 00:00:00")
+ private LocalDateTime startTime;
+
+ @NotNull(message = "结束时间不能为空")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-03-31 23:59:59")
+ private LocalDateTime endTime;
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningTrendRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningTrendRespVO.java
new file mode 100644
index 0000000..fc66312
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/devicewarinningrecord/vo/DeviceWarningTrendRespVO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class DeviceWarningTrendRespVO {
+
+ @Schema(description = "X轴时间点")
+ private List timePoints;
+
+ @Schema(description = "Y轴告警总数")
+ private List counts;
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/OrgNodeController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/OrgNodeController.java
index 2fee65c..e9329b7 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/OrgNodeController.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/OrgNodeController.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.controller.admin.orgnode;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceSimpleRespVO;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
@@ -107,4 +108,12 @@ public class OrgNodeController {
return CommonResult.success(orgNodeService.getOrgTree(customerId));
}
+ @GetMapping("/device-list-by-node")
+ @Operation(summary = "按树节点获取设备列表")
+ public CommonResult> getDeviceListByNode(@RequestParam("nodeId") Long nodeId,
+ @RequestParam("nodeType") Integer nodeType) {
+ return success(orgNodeService.getDeviceListByNode(nodeId, nodeType));
+ }
+
+
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/vo/OrgNodeSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/vo/OrgNodeSaveReqVO.java
index 5119b72..1086541 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/vo/OrgNodeSaveReqVO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/vo/OrgNodeSaveReqVO.java
@@ -29,7 +29,7 @@ public class OrgNodeSaveReqVO {
private String name;
@Schema(description = "排序字段,数值越小越靠前", requiredMode = Schema.RequiredMode.REQUIRED)
- @NotNull(message = "排序字段,数值越小越靠前不能为空")
+// @NotNull(message = "排序字段,数值越小越靠前不能为空")
private Integer sort;
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/vo/OrgNodeTreeRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/vo/OrgNodeTreeRespVO.java
index eb727d1..de30e5a 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/vo/OrgNodeTreeRespVO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/orgnode/vo/OrgNodeTreeRespVO.java
@@ -8,7 +8,14 @@ import java.util.List;
public class OrgNodeTreeRespVO {
private Long id;
private Long parentId;
- private Integer nodeType; // 1客户 2车间 3产线
+ private Integer nodeType; // 1客户 2车间 3产线 4设备
private String name;
private List children;
+ private String operateStatus;
+
+ // 全局唯一节点标识
+ private String nodeKey; // C_5 / O_12 / D_170
+ private String parentKey; // 0 / C_5 / O_12
+
+ private String deviceCode; //设备编码
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/devicewarinningrecord/WarningTrendPointRespDTO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/devicewarinningrecord/WarningTrendPointRespDTO.java
new file mode 100644
index 0000000..e83a2d5
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/devicewarinningrecord/WarningTrendPointRespDTO.java
@@ -0,0 +1,10 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.devicewarinningrecord;
+
+
+import lombok.Data;
+
+@Data
+public class WarningTrendPointRespDTO {
+ private String timeKey;
+ private Long count;
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceMapper.java
index f467c9f..6fe5f6e 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceMapper.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceMapper.java
@@ -127,4 +127,21 @@ public interface DeviceMapper extends BaseMapperX {
Integer getTotalDeviceCount();
List getAllDeviceIds();
+
+
+ List selectByTenantCustomerAndOrgNodeIds(@Param("tenantId") Long tenantId,
+ @Param("customerId") Long customerId,
+ @Param("orgNodeIds") List orgNodeIds);
+
+
+ List selectByCustomerAndOrgNodeIds(@Param("customerId") Long customerId,
+ @Param("orgNodeIds") List orgNodeIds);
+
+ List selectByOrgNodeIds(@Param("orgNodeIds") List orgNodeIds);
+
+ List selectByCustomerId(@Param("customerId") Long customerId);
+
+ boolean existsByOrgNodeId(@Param("orgNodeId") Long orgNodeId);
+
+
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicepointrules/DevicePointRulesMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicepointrules/DevicePointRulesMapper.java
index cf2086f..3e512d4 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicepointrules/DevicePointRulesMapper.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicepointrules/DevicePointRulesMapper.java
@@ -17,7 +17,7 @@ public interface DevicePointRulesMapper extends BaseMapperX
default PageResult selectPage(DevicePointRulesPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX()
- .eqIfPresent(DevicePointRulesDO::getIdentifier, reqVO.getIdentifier())
+ .likeIfPresent(DevicePointRulesDO::getIdentifier, reqVO.getIdentifier())
.likeIfPresent(DevicePointRulesDO::getFieldName, reqVO.getFieldName())
.eqIfPresent(DevicePointRulesDO::getFieldRule, reqVO.getFieldRule())
.eqIfPresent(DevicePointRulesDO::getDefaultValue, reqVO.getDefaultValue())
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicewarinningrecord/DeviceWarinningRecordMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicewarinningrecord/DeviceWarinningRecordMapper.java
index 8227665..49b46ad 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicewarinningrecord/DeviceWarinningRecordMapper.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/devicewarinningrecord/DeviceWarinningRecordMapper.java
@@ -25,7 +25,7 @@ public interface DeviceWarinningRecordMapper extends BaseMapperX {
List selectListByTenantAndCustomer(@Param("tenantId") Long tenantId,
@Param("customerId") Long customerId);
+
+ default List selectListByCustomer(Long customerId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(OrgNodeDO::getCustomerId, customerId)
+ .eq(OrgNodeDO::getDeleted, false)
+ .orderByAsc(OrgNodeDO::getSort)
+ .orderByAsc(OrgNodeDO::getId));
+ }
+
+ List selectListByCustomerByNodeId(@Param("nodeId") Long nodeId);
+
+ boolean existsByParentId(@Param("parentId") Long parentId);
+
+ boolean existsByOrgNodeId(@Param("orgNodeId") Long orgNodeId);
+
+
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMapper.java
new file mode 100644
index 0000000..3180fe5
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDeviceMapper.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.iot.dal.tdengine;
+
+import cn.iocoder.yudao.module.iot.controller.admin.device.dto.DeviceLatestRuleDTO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.devicewarinningrecord.WarningTrendPointRespDTO;
+import cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo.DeviceWarningCountRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.devicewarinningrecord.DeviceWarinningRecordDO;
+import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
+import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Mapper
+@TDengineDS
+@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
+public interface IotDeviceMapper {
+
+
+ void alterProductPropertySTableDropField(@Param("productId") Long productId,
+ @Param("field") TDengineTableField field);
+ Long countWarningRecordByTime(@Param("startTime") LocalDateTime startTime,
+ @Param("endTime") LocalDateTime endTime);
+
+ List selectWarningRecordList(@Param("deviceId") Long deviceId,
+ @Param("startTime") LocalDateTime startTime,
+ @Param("endTime") LocalDateTime endTime,
+ @Param("alarmLevel") String alarmLevel);
+ // IotDeviceMapper.java (TDengine mapper)
+ DeviceWarningCountRespVO selectWarningCount(@Param("startTime") String startTime,
+ @Param("endTime") String endTime);
+
+
+ List selectWarningTrendByHour(@Param("startTime") String startTime,
+ @Param("endTime") String endTime);
+
+ List selectWarningTrendByDay(@Param("startTime") String startTime,
+ @Param("endTime") String endTime);
+
+
+ List selectLatestRuleByDeviceIds(@Param("deviceIds") List deviceIds);
+
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDevicePropertyMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDevicePropertyMapper.java
deleted file mode 100644
index 484589d..0000000
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDevicePropertyMapper.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package cn.iocoder.yudao.module.iot.dal.tdengine;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
-
-import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
-import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
-import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
-import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Param;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-@Mapper
-@TDengineDS
-@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
-public interface IotDevicePropertyMapper {
-
- List getProductPropertySTableFieldList(@Param("productId") Long productId);
-
- void createProductPropertySTable(@Param("productId") Long productId,
- @Param("fields") List fields);
-
- @SuppressWarnings("SimplifyStreamApiCallChains") // 保持 JDK8 兼容性
- default void alterProductPropertySTable(Long productId,
- List oldFields,
- List newFields) {
- oldFields.removeIf(field -> StrUtil.equalsAny(field.getField(),
- TDengineTableField.FIELD_TS, "report_time", "device_id"));
- List addFields = newFields.stream().filter( // 新增的字段
- newField -> oldFields.stream().noneMatch(oldField -> oldField.getField().equals(newField.getField())))
- .collect(Collectors.toList());
- List dropFields = oldFields.stream().filter( // 删除的字段
- oldField -> newFields.stream().noneMatch(n -> n.getField().equals(oldField.getField())))
- .collect(Collectors.toList());
- List modifyTypeFields = new ArrayList<>(); // 变更类型的字段
- List modifyLengthFields = new ArrayList<>(); // 变更长度的字段
- newFields.forEach(newField -> {
- TDengineTableField oldField = CollUtil.findOne(oldFields, field -> field.getField().equals(newField.getField()));
- if (oldField == null) {
- return;
- }
- if (ObjectUtil.notEqual(oldField.getType(), newField.getType())) {
- modifyTypeFields.add(newField);
- return;
- }
- if (newField.getLength() != null) {
- if (newField.getLength() > oldField.getLength()) {
- modifyLengthFields.add(newField);
- } else if (newField.getLength() < oldField.getLength()) {
- // 特殊:TDengine 长度修改时,只允许变长,所以此时认为是修改类型
- modifyTypeFields.add(newField);
- }
- }
- });
-
- // 执行
- addFields.forEach(field -> alterProductPropertySTableAddField(productId, field));
- dropFields.forEach(field -> alterProductPropertySTableDropField(productId, field));
- modifyLengthFields.forEach(field -> alterProductPropertySTableModifyField(productId, field));
- modifyTypeFields.forEach(field -> {
- alterProductPropertySTableDropField(productId, field);
- alterProductPropertySTableAddField(productId, field);
- });
- }
-
- void alterProductPropertySTableAddField(@Param("productId") Long productId,
- @Param("field") TDengineTableField field);
-
- void alterProductPropertySTableModifyField(@Param("productId") Long productId,
- @Param("field") TDengineTableField field);
-
- void alterProductPropertySTableDropField(@Param("productId") Long productId,
- @Param("field") TDengineTableField field);
-
-
-}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
index 590b9c6..b2e16fc 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
@@ -9,34 +9,11 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
*/
public interface ErrorCodeConstants {
- // ========== 产品相关 1-050-001-000 ============
- ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_050_001_000, "产品不存在");
- ErrorCode PRODUCT_KEY_EXISTS = new ErrorCode(1_050_001_001, "产品标识已经存在");
- ErrorCode PRODUCT_STATUS_NOT_DELETE = new ErrorCode(1_050_001_002, "产品状是发布状态,不允许删除");
- ErrorCode PRODUCT_STATUS_NOT_ALLOW_THING_MODEL = new ErrorCode(1_050_001_003, "产品状是发布状态,不允许操作物模型");
- ErrorCode PRODUCT_DELETE_FAIL_HAS_DEVICE = new ErrorCode(1_050_001_004, "产品下存在设备,不允许删除");
-
- // ========== 产品物模型 1-050-002-000 ============
- ErrorCode THING_MODEL_NOT_EXISTS = new ErrorCode(1_050_002_000, "产品物模型不存在");
- ErrorCode THING_MODEL_EXISTS_BY_PRODUCT_KEY = new ErrorCode(1_050_002_001, "ProductKey 对应的产品物模型已存在");
- ErrorCode THING_MODEL_IDENTIFIER_EXISTS = new ErrorCode(1_050_002_002, "存在重复的功能标识符。");
- ErrorCode THING_MODEL_NAME_EXISTS = new ErrorCode(1_050_002_003, "存在重复的功能名称。");
- ErrorCode THING_MODEL_IDENTIFIER_INVALID = new ErrorCode(1_050_002_003, "产品物模型标识无效");
// ========== 设备 1-050-003-000 ============
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
- ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
- ErrorCode DEVICE_GATEWAY_HAS_SUB = new ErrorCode(1_050_003_002, "网关设备存在已绑定的子设备,不允许删除");
ErrorCode GATEWAY_NOT_EXISTS = new ErrorCode(1_003_000_000, "网关不存在");
- ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
- ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
- ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
- ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
- ErrorCode DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL = new ErrorCode(1_050_003_007, "下行设备消息失败,原因:设备未连接网关");
- ErrorCode DEVICE_SERIAL_NUMBER_EXISTS = new ErrorCode(1_050_003_008, "设备序列号已存在,序列号必须全局唯一");
- ErrorCode DEVICE_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_009, "设备【{}/{}】不是网关子设备类型,无法绑定到网关");
- ErrorCode DEVICE_GATEWAY_BINDTO_EXISTS = new ErrorCode(1_050_003_010, "设备【{}/{}】已绑定到其他网关,请先解绑");
- ErrorCode DEVICE_CONTACT_MODEL_NOT_EXISTS = new ErrorCode(1_050_003_011, "查询不到该点位");
+ ErrorCode DEVICE_CONTACT_MODEL_NOT_EXISTS = new ErrorCode(1_050_003_011, "查询不到该点位");
ErrorCode DEVICE_MODEL_POINT_CODE_EXISTS = new ErrorCode(1_003_000_005, "采集设备采集点位编码已存在");
ErrorCode DEVICE_MODEL_ATTRIBUTE_POTIN_CODE_EXISTS = new ErrorCode(1_003_000_005, "采集设备模型点位编码已存在");
ErrorCode DEVICE_MODEL_ATTRIBUTE_NOT_EXISTS = new ErrorCode(1_003_000_005, "采集设备模型点位不存在");
@@ -57,75 +34,14 @@ public interface ErrorCodeConstants {
ErrorCode DEVICE_ID_MODEL_NOT_EXISTS = new ErrorCode(1_003_000_003, "该设备模型ID不能为空");
ErrorCode DEVICE_DOES_NOT_EXIST= new ErrorCode(1_003_000_010, "该采集设备不存在");
ErrorCode DEVICE_MQTT_TOPIC_EXIST = new ErrorCode(1_003_000_000, "设备MQTT主题不存在。");
+ ErrorCode DEVICE_MODEL_CODE_EXISTS = new ErrorCode(1_003_000_002, "采集设备模型编码已存在");
+ ErrorCode DEVICE_WARNING_TIME_REQUIRED = new ErrorCode(1_003_000_002, "开始时间和结束时间不能为空");
+ ErrorCode ORG_NODE_CUSTOMER_ID_REQUIRED = new ErrorCode(1_003_000_002, "客户节点不能为空");
+ ErrorCode DEVICE_WARNING_TIME_RANGE_INVALID = new ErrorCode(1_003_000_003, "开始时间不能大于结束时间");
+ ErrorCode ORG_NODE_DELETE_HAS_CHILDREN = new ErrorCode(1_003_001_001, "当前节点下存在子节点,请先删除子节点");
+ ErrorCode ORG_NODE_DELETE_HAS_DEVICES = new ErrorCode(1_003_001_002, "当前节点下存在设备,请先解绑或删除设备");
- // 拓扑管理相关错误码 1-050-003-100
- ErrorCode DEVICE_TOPO_PARAMS_INVALID = new ErrorCode(1_050_003_100, "拓扑管理参数无效");
- ErrorCode DEVICE_TOPO_SUB_DEVICE_USERNAME_INVALID = new ErrorCode(1_050_003_101, "子设备用户名格式无效");
- ErrorCode DEVICE_TOPO_SUB_DEVICE_AUTH_FAILED = new ErrorCode(1_050_003_102, "子设备认证失败");
- ErrorCode DEVICE_TOPO_SUB_NOT_BINDTO_GATEWAY = new ErrorCode(1_050_003_103, "子设备【{}/{}】未绑定到该网关");
- // 设备注册相关错误码 1-050-003-200
- ErrorCode DEVICE_SUB_REGISTER_PARAMS_INVALID = new ErrorCode(1_050_003_200, "子设备注册参数无效");
- ErrorCode DEVICE_SUB_REGISTER_PRODUCT_NOT_GATEWAY_SUB = new ErrorCode(1_050_003_201, "产品【{}】不是网关子设备类型");
- ErrorCode DEVICE_REGISTER_DISABLED = new ErrorCode(1_050_003_210, "该产品未开启动态注册功能");
- ErrorCode DEVICE_REGISTER_SECRET_INVALID = new ErrorCode(1_050_003_211, "产品密钥验证失败");
- ErrorCode DEVICE_REGISTER_ALREADY_EXISTS = new ErrorCode(1_050_003_212, "设备已存在,不允许重复注册");
-
- // ========== 产品分类 1-050-004-000 ==========
- ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
-
- // ========== 设备分组 1-050-005-000 ==========
- ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
- ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
-
- // ========== 设备 Modbus 配置 1-050-006-000 ==========
- ErrorCode DEVICE_MODBUS_CONFIG_NOT_EXISTS = new ErrorCode(1_050_006_000, "设备 Modbus 连接配置不存在");
- ErrorCode DEVICE_MODBUS_CONFIG_EXISTS = new ErrorCode(1_050_006_001, "设备 Modbus 连接配置已存在");
-
- // ========== 设备 Modbus 点位 1-050-007-000 ==========
- ErrorCode DEVICE_MODBUS_POINT_NOT_EXISTS = new ErrorCode(1_050_007_000, "设备 Modbus 点位配置不存在");
- ErrorCode DEVICE_MODBUS_POINT_EXISTS = new ErrorCode(1_050_007_001, "设备 Modbus 点位配置已存在");
-
- // ========== OTA 固件相关 1-050-008-000 ==========
-
- ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
- ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
-
- // ========== OTA 升级任务相关 1-050-008-100 ==========
-
- ErrorCode OTA_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在");
- ErrorCode OTA_TASK_CREATE_FAIL_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "创建 OTA 任务失败,原因:任务名称重复");
- ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_FIRMWARE_EXISTS = new ErrorCode(1_050_008_102,
- "创建 OTA 任务失败,原因:设备({})已经是该固件版本");
- ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_OTA_IN_PROCESS = new ErrorCode(1_050_008_102,
- "创建 OTA 任务失败,原因:设备({})已经在升级中...");
- ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_EMPTY = new ErrorCode(1_050_008_103, "创建 OTA 任务失败,原因:没有可升级的设备");
- ErrorCode OTA_TASK_CANCEL_FAIL_STATUS_END = new ErrorCode(1_050_008_104, "取消 OTA 任务失败,原因:任务状态不是进行中");
-
- // ========== OTA 升级任务记录相关 1-050-008-200 ==========
-
- ErrorCode OTA_TASK_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
- ErrorCode OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR = new ErrorCode(1_050_008_201, "取消 OTA 升级记录失败,原因:记录状态不是进行中");
- ErrorCode OTA_TASK_RECORD_UPDATE_PROGRESS_FAIL_NO_EXISTS = new ErrorCode(1_050_008_202, "更新 OTA 升级记录进度失败,原因:该设备没有进行中的升级记录");
-
- // ========== IoT 数据流转规则 1-050-010-000 ==========
- ErrorCode DATA_RULE_NOT_EXISTS = new ErrorCode(1_050_010_000, "数据流转规则不存在");
- ErrorCode DATA_RULE_NAME_EXISTS = new ErrorCode(1_050_010_001, "数据流转规则名称已存在");
-
- // ========== IoT 数据流转目的 1-050-011-000 ==========
- ErrorCode DATA_SINK_NOT_EXISTS = new ErrorCode(1_050_011_000, "数据桥梁不存在");
- ErrorCode DATA_SINK_DELETE_FAIL_USED_BY_RULE = new ErrorCode(1_050_011_001, "数据流转目的正在被数据流转规则使用,无法删除");
- ErrorCode DATA_SINK_NAME_EXISTS = new ErrorCode(1_050_011_002, "数据流转目的名称已存在");
-
- // ========== IoT 场景联动 1-050-012-000 ==========
- ErrorCode RULE_SCENE_NOT_EXISTS = new ErrorCode(1_050_012_000, "场景联动不存在");
-
- // ========== IoT 告警配置 1-050-013-000 ==========
- ErrorCode ALERT_CONFIG_NOT_EXISTS = new ErrorCode(1_050_013_000, "IoT 告警配置不存在");
-
- // ========== IoT 告警记录 1-050-014-000 ==========
- ErrorCode ALERT_RECORD_NOT_EXISTS = new ErrorCode(1_050_014_000, "IoT 告警记录不存在");
-
// ======================================= Tdengine ============================================
ErrorCode TABLE_CREATION_FAILED = new ErrorCode(1_004_000_008, "TDengine 表创建失败");
ErrorCode COLOUMN_CREATION_FAILED = new ErrorCode(1_004_000_008, "TDengine 列创建失败");
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/DeviceJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/DeviceJob.java
new file mode 100644
index 0000000..c2caf6b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/DeviceJob.java
@@ -0,0 +1,411 @@
+package cn.iocoder.yudao.module.iot.job;
+
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.module.iot.controller.admin.device.enums.DeviceStatusEnum;
+import cn.iocoder.yudao.module.iot.controller.admin.devicemodelrules.vo.PointRulesRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.deviceoperationrecord.DeviceOperationRecordDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.devicepointrules.DevicePointRulesDO;
+import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceMapper;
+import cn.iocoder.yudao.module.iot.dal.mysql.devicecontactmodel.DeviceContactModelMapper;
+import cn.iocoder.yudao.module.iot.dal.mysql.deviceoperationrecord.DeviceOperationRecordMapper;
+import cn.iocoder.yudao.module.iot.dal.mysql.devicepointrules.DevicePointRulesMapper;
+import cn.iocoder.yudao.module.iot.service.device.TDengineService;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+public class DeviceJob implements JobHandler {
+
+ @Resource
+ private TDengineService tDengineService;
+
+ @Resource
+ private DeviceMapper deviceMapper;
+
+ @Resource
+ private DeviceContactModelMapper deviceContactModelMapper;
+
+ @Resource
+ private DeviceOperationRecordMapper deviceOperationRecordMapper;
+
+ @Resource
+ private DevicePointRulesMapper devicePointRulesMapper;
+
+
+ @Override
+ public String execute(String param) throws Exception {
+ // 设置租户上下文
+ TenantContextHolder.setTenantId(1L);
+
+ // 解析超时时间(默认60秒)
+ long timeoutSeconds = 60L;
+ if (StringUtils.isNotBlank(param)) {
+ try {
+ timeoutSeconds = Long.parseLong(param);
+ } catch (NumberFormatException e) {
+ log.warn("定时任务参数非法,使用默认60秒 param={}", param);
+ }
+ }
+
+ log.info("定时任务开始, timeoutSeconds={} 时间={}", timeoutSeconds, new Date());
+
+ // 查询采集设备列表
+ List deviceDOS =
+ deviceMapper.selectList(Wrappers.lambdaQuery().orderByDesc(DeviceDO::getId));
+
+ if (CollectionUtils.isEmpty(deviceDOS)) {
+ return param;
+ }
+
+ List deviceIds = deviceDOS.stream()
+ .map(DeviceDO::getId)
+ .collect(Collectors.toList());
+
+ // 获取设备的每条最新数据
+ Map> deviceRowMap =
+ tDengineService.queryDevicesLatestRow(deviceIds, null, null);
+
+ Instant now = Instant.now();
+
+ // 遍历设备
+ for (Long deviceId : deviceIds) {
+ Map row = deviceRowMap.get(deviceId);
+ boolean isTimeout = false;
+
+ if (row == null || row.get("ts") == null) {
+ isTimeout = true;
+ } else {
+ Instant ts = parseTs(row.get("ts"), deviceId);
+ if (ts == null || Duration.between(ts, now).getSeconds() > timeoutSeconds) {
+ isTimeout = true;
+ }
+ }
+
+ if (isTimeout) {
+ handleDeviceTimeout(deviceId);
+ } else {
+ handleDeviceOnline(deviceId, row);
+ }
+ }
+
+ return param;
+ }
+
+ /**
+ * 安全转换 ts 为 Instant
+ */
+ private Instant parseTs(Object tsObj, Long deviceId) {
+ if (tsObj == null) return null;
+
+ log.debug("设备 {} tsObj 类型: {}, 值: {}", deviceId, tsObj.getClass().getName(), tsObj);
+
+ if (tsObj instanceof Instant) {
+ return (Instant) tsObj;
+ } else if (tsObj instanceof Timestamp) {
+ return ((Timestamp) tsObj).toInstant();
+ } else if (tsObj instanceof Date) {
+ return ((Date) tsObj).toInstant();
+ } else if (tsObj instanceof LocalDateTime) {
+ return ((LocalDateTime) tsObj).atZone(ZoneId.systemDefault()).toInstant();
+ } else if (tsObj instanceof String) {
+ String tsStr = (String) tsObj;
+ try {
+ return Instant.parse(tsStr); // ISO 8601
+ } catch (Exception e1) {
+ try {
+ return Timestamp.valueOf(tsStr).toInstant(); // yyyy-MM-dd HH:mm:ss
+ } catch (Exception e2) {
+ log.warn("设备 {} ts 字符串解析失败: {}", deviceId, tsStr);
+ }
+ }
+ } else {
+ log.warn("设备 {} ts 类型未知: {}", deviceId, tsObj);
+ }
+ return null;
+ }
+
+ /**
+ * 设备在线处理
+ */
+ private void handleDeviceOnline(Long deviceId, Map row) {
+ if (row == null) return;
+
+ // 1. 查询设备规则
+ DevicePointRulesDO pointRulesDO = devicePointRulesMapper.selectOne(
+ Wrappers.lambdaQuery()
+ .eq(DevicePointRulesDO::getDeviceId, deviceId)
+ .eq(DevicePointRulesDO::getIdentifier, "RUNNING")
+ .orderByDesc(DevicePointRulesDO::getId)
+ .last("LIMIT 1")
+ );
+
+ if(pointRulesDO == null || StringUtils.isBlank(pointRulesDO.getFieldRule())){
+ //处理待机中
+ DeviceOperationRecordDO record = new DeviceOperationRecordDO();
+ record.setDeviceId(deviceId);
+ record.setRule(DeviceStatusEnum.STANDBY.getCode());
+ record.setCreator("1");
+ record.setUpdater("1");
+// deviceOperationRecordMapper.insert(record);
+ tDengineService.insertDeviceOperationRecord(record);
+
+ return;
+ }
+
+
+ // 解析规则列表
+ List pointRulesVOList = JSON.parseArray(
+ pointRulesDO.getFieldRule(), PointRulesRespVO.class
+ );
+ if (CollectionUtils.isEmpty(pointRulesVOList)) return;
+
+ // 2. 查询设备 contact model
+ List deviceContactModelDOS = deviceContactModelMapper.selectList(
+ Wrappers.lambdaQuery().eq(DeviceContactModelDO::getDeviceId, deviceId)
+ );
+ if (CollectionUtils.isEmpty(deviceContactModelDOS)) return;
+
+ // 3. 遍历规则,匹配成功则保存记录
+ for (PointRulesRespVO pointRule : pointRulesVOList) {
+ if (StringUtils.isBlank(pointRule.getCode())) continue;
+
+ String ruleCode = pointRule.getCode().toLowerCase();
+ String processedValue = row.get(ruleCode).toString();
+ boolean matched = matchRule(processedValue, pointRule);
+
+ if (!matched) {
+ log.debug("规则匹配失败: device={}, value={}, rule={}", deviceId, processedValue, JSON.toJSONString(pointRule));
+ continue;
+ }
+
+ log.info("规则匹配成功: device={}, value={}, rule={}", deviceId, processedValue, JSON.toJSONString(pointRule));
+
+ // 4. 遍历 contact model 查找对应 code
+ DeviceContactModelDO matchedContact = null;
+ for (DeviceContactModelDO contact : deviceContactModelDOS) {
+ if (ruleCode.equalsIgnoreCase(contact.getAttributeCode())) {
+ matchedContact = contact;
+ break;
+ }
+ }
+
+ if (matchedContact == null) {
+ log.warn("设备 {} 找不到 attributeCode={} 对应的 modelId,跳过", deviceId, pointRule.getCode());
+ continue;
+ }
+
+ // 5. 保存运行记录
+ DeviceOperationRecordDO record = new DeviceOperationRecordDO();
+ record.setDeviceId(deviceId);
+ record.setModelId(matchedContact.getId());
+ record.setRule(pointRule.getRule());
+ record.setAddressValue(processedValue);
+ record.setRuleId(pointRulesDO.getId());
+ record.setCreator("1");
+ record.setUpdater("1");
+
+// deviceOperationRecordMapper.insert(record);
+ tDengineService.insertDeviceOperationRecord(record);
+
+ break;
+ }
+ }
+
+
+ private void handleDeviceTimeout(Long deviceId) {
+ DeviceOperationRecordDO record = new DeviceOperationRecordDO();
+ record.setDeviceId(deviceId);
+ record.setRule(DeviceStatusEnum.OFFLINE.getCode());
+ record.setCreator("1");
+ record.setUpdater("1");
+// deviceOperationRecordMapper.insert(record);
+ tDengineService.insertDeviceOperationRecord(record);
+ }
+
+ /**
+ * 判断值是否符合规则
+ * 支持操作符: EQ(等于), NE(不等于), GT(大于), GE(大于等于),
+ * LT(小于), LE(小于等于), TRUE(为真), FALSE(为假)
+ */
+ private boolean matchRule(String value, PointRulesRespVO rule) {
+ if (StringUtils.isBlank(value) || rule == null ||
+ StringUtils.isBlank(rule.getOperator())) {
+ return false;
+ }
+
+ try {
+ String operator = rule.getOperator().toUpperCase();
+ String inputValue = value.trim().toLowerCase();
+ String ruleValue = StringUtils.trimToEmpty(rule.getOperatorRule());
+
+ // 1. 处理布尔值判断
+ if ("TRUE".equals(operator) || "FALSE".equals(operator)) {
+ return matchBooleanRule(inputValue, operator);
+ }
+
+ // 2. 如果operatorRule为空,且不是布尔操作符,则返回false
+ if (StringUtils.isBlank(ruleValue)) {
+ log.warn("规则比较值为空,但操作符不是布尔类型: operator={}", operator);
+ return false;
+ }
+
+ ruleValue = ruleValue.trim();
+
+ // 3. 尝试数值比较
+ if (isNumeric(inputValue) && isNumeric(ruleValue)) {
+ Double num1 = Double.parseDouble(inputValue);
+ Double num2 = Double.parseDouble(ruleValue);
+
+ return compareNumbers(num1, num2, operator);
+ }
+ // 4. 字符串比较
+ else {
+ return compareStrings(inputValue, ruleValue, operator);
+ }
+
+ } catch (Exception e) {
+ log.error("规则匹配异常: value={}, rule={}, error={}",
+ value, JSON.toJSONString(rule), e.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * 字符串比较
+ */
+ private boolean compareStrings(String value, String ruleValue, String operator) {
+ switch (operator) {
+ case "EQ":
+ return value.equals(ruleValue);
+ case "NE":
+ return !value.equals(ruleValue);
+ case "GT":
+ return value.compareTo(ruleValue) > 0;
+ case "GE":
+ return value.compareTo(ruleValue) >= 0;
+ case "LT":
+ return value.compareTo(ruleValue) < 0;
+ case "LE":
+ return value.compareTo(ruleValue) <= 0;
+ default:
+ log.warn("不支持的操作符: {}", operator);
+ return false;
+ }
+ }
+
+
+ /**
+ * 数值比较
+ */
+ private boolean compareNumbers(Double value, Double ruleValue, String operator) {
+ switch (operator) {
+ case "EQ":
+ return Math.abs(value - ruleValue) < 0.000001; // 处理浮点数精度
+ case "NE":
+ return Math.abs(value - ruleValue) >= 0.000001;
+ case "GT":
+ return value > ruleValue;
+ case "GE":
+ return value >= ruleValue;
+ case "LT":
+ return value < ruleValue;
+ case "LE":
+ return value <= ruleValue;
+ default:
+ log.warn("不支持的操作符: {}", operator);
+ return false;
+ }
+ }
+
+
+ /**
+ * 判断字符串是否为数字
+ */
+ private boolean isNumeric(String str) {
+ if (StringUtils.isBlank(str)) {
+ return false;
+ }
+ try {
+ Double.parseDouble(str);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * 处理布尔值判断
+ */
+ private boolean matchBooleanRule(String value, String operator) {
+ // 常见布尔值表示
+ boolean booleanValue = parseBoolean(value);
+
+ if ("TRUE".equals(operator)) {
+ return booleanValue;
+ } else if ("FALSE".equals(operator)) {
+ return !booleanValue;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * 解析字符串为布尔值
+ * 支持: true, false, 1, 0, yes, no, on, off等
+ */
+ private boolean parseBoolean(String value) {
+ if (StringUtils.isBlank(value)) {
+ return false;
+ }
+
+ String lowerValue = value.toLowerCase();
+
+ // 常见真值表示
+ if ("true".equals(lowerValue) ||
+ "1".equals(lowerValue) ||
+ "yes".equals(lowerValue) ||
+ "on".equals(lowerValue) ||
+ "是".equals(lowerValue) || // 中文支持
+ "成功".equals(lowerValue)) {
+ return true;
+ }
+
+ // 常见假值表示
+ if ("false".equals(lowerValue) ||
+ "0".equals(lowerValue) ||
+ "no".equals(lowerValue) ||
+ "off".equals(lowerValue) ||
+ "否".equals(lowerValue) || // 中文支持
+ "失败".equals(lowerValue)) {
+ return false;
+ }
+
+ // 尝试转换为布尔值
+ try {
+ return Boolean.parseBoolean(lowerValue);
+ } catch (Exception e) {
+ log.warn("无法解析为布尔值: {}", value);
+ return false;
+ }
+ }
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java
index 68450e2..e73f70e 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.DeviceContactModelPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.devicewarinningrecord.vo.DeviceWarinningRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO;
@@ -13,6 +14,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import org.eclipse.paho.client.mqttv3.MqttException;
import javax.validation.Valid;
+import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -134,7 +136,7 @@ public interface DeviceService {
Boolean scheduledStop(Long id);
- DeviceOperationStatusRespVO getDeviceOperationalStatus();
+ DeviceOperationStatusRespVO getDeviceOperationalStatus(LocalDateTime startTime, LocalDateTime endTime);
List