diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
index 6ad5e4bbe9..59e4cf48c7 100644
--- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
@@ -10,6 +10,8 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
public interface ErrorCodeConstants {
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_003_000_000, "设备不存在");
+ ErrorCode DEVICE_EXISTS = new ErrorCode(1_003_000_000, "同名设备已存在");
+
ErrorCode DEVICE_ATTRIBUTE_NOT_EXISTS = new ErrorCode(1_003_000_000, "设备属性不存在");
ErrorCode FORMULA_NOT_EXISTS = new ErrorCode(1_003_000_000, "公式不存在");
ErrorCode FORMULA_DETAIL_NOT_EXISTS = new ErrorCode(1_003_000_000, "公式明细不存在");
diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml
index 146560c788..de090f1812 100644
--- a/yudao-module-iot/yudao-module-iot-biz/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -81,5 +81,12 @@
org.apache.httpcomponents
httpclient
+
+
+ com.alibaba
+ QLExpress
+ 3.3.3
+
+
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/AlertTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/AlertTypeEnum.java
new file mode 100644
index 0000000000..1a78c93a94
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/AlertTypeEnum.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.iot.controller.admin.alert.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum AlertTypeEnum {
+
+ 联动("联动"),
+ 属性("属性"),
+ 设备("设备"),
+ 产品("产品");
+
+ private final String value;
+
+ // 一个可选的方法,用于根据整数值获取对应的枚举实例
+ public static AlertTypeEnum fromValue(String value) {
+ for (AlertTypeEnum status : AlertTypeEnum.values()) {
+ if (status.getValue().equals(value)) {
+ return status;
+ }
+ }
+ throw new IllegalArgumentException("Unknown value: " + value);
+ }
+
+}
\ 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/DeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/DeviceController.java
index 225926df56..eaea7c9bfd 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
@@ -1,34 +1,32 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
-import org.springframework.web.bind.annotation.*;
-import javax.annotation.Resource;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.security.access.prepost.PreAuthorize;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.Operation;
-
-import javax.validation.constraints.*;
-import javax.validation.*;
-import javax.servlet.http.*;
-import java.util.*;
-import java.io.IOException;
-
+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.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
-
-import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
-import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
-
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DevicePageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceSaveReqVO;
+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.deviceattribute.DeviceAttributeDO;
import cn.iocoder.yudao.module.iot.service.device.DeviceService;
+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.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 物联设备")
@RestController
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/mqttdatarecord/MqttDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/mqttdatarecord/MqttDataService.java
new file mode 100644
index 0000000000..26d47fec51
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/mqttdatarecord/MqttDataService.java
@@ -0,0 +1,122 @@
+package cn.iocoder.yudao.module.iot.controller.admin.mqttdatarecord;
+
+import cn.iocoder.yudao.module.iot.controller.admin.alertrecord.vo.AlertRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DevicePageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.AlertDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alertrecord.AlertRecordDO;
+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.mysql.alert.AlertMapper;
+import cn.iocoder.yudao.module.iot.dal.mysql.alertrecord.AlertRecordMapper;
+import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceAttributeMapper;
+import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceMapper;
+import cn.iocoder.yudao.module.iot.dal.mysql.formula.FormulaRecordMapper;
+import cn.iocoder.yudao.module.iot.framework.constant.Constants;
+import cn.iocoder.yudao.module.iot.framework.constant.DeviceStatusEnum;
+import cn.iocoder.yudao.module.iot.framework.util.FormulaCalculator;
+import cn.iocoder.yudao.module.iot.service.device.DeviceService;
+import cn.iocoder.yudao.module.iot.service.mqttrecord.MqttRecordService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@EnableAsync
+@EnableScheduling
+public class MqttDataService {
+ @Resource
+ private FormulaRecordMapper formulaRecordMapper;
+ @Resource
+ private DeviceService deviceService;
+ @Resource
+ private DeviceMapper deviceMapper;
+ @Resource
+ private MqttRecordService mqttRecordService;
+ @Resource
+ private DeviceAttributeMapper attributeMapper;
+ @Resource
+ private AlertMapper alertMapper;
+ @Resource
+ private AlertRecordMapper alertRecordMapper;
+ /**定时检查设备状态*/
+ //@Scheduled(fixedDelay = 2000)
+ public void checkDeviceStatus() {
+ DevicePageReqVO respVO = new DevicePageReqVO().setStatus(DeviceStatusEnum.在线.getValue()).setIsEnable(true);
+ List deviceList = deviceService.selectList(respVO);
+ for (DeviceDO device : deviceList) {
+ if(device.getLastOnlineTime()==null){
+ device.setStatus(DeviceStatusEnum.离线.getValue());
+ deviceMapper.updateById(device);
+ }else {
+ Duration duration = Duration.between(device.getLastOnlineTime(), LocalDateTime.now());
+ //long seconds = duration.getSeconds();
+ long millis = duration.toMillis();
+ long second = device.getOffLineDuration()==null? Constants.DEVICE_OFFLINE_SECOND : device.getOffLineDuration();
+ if(millis > second*1000){//认为设备已经离线
+ device.setStatus(DeviceStatusEnum.离线.getValue());
+ deviceMapper.updateById(device);
+ }
+ }
+ }
+ }
+ @Async
+ public void updateDeviceStatus(DeviceDO deviceDO){
+ deviceDO.setStatus(DeviceStatusEnum.在线.getValue());
+ deviceDO.setLastOnlineTime(LocalDateTime.now());
+ deviceMapper.updateById(deviceDO);
+ }
+ @Async
+ public void checkDeviceAlert(Map value, DeviceDO deviceDO){
+ DeviceAttributeDO reqVO = new DeviceAttributeDO().setDeviceId(deviceDO.getId()).setAlertId(1L).setIsEnable(true);
+ List attributeList = attributeMapper.selectAlertList(reqVO);
+ if(attributeList!=null){
+ //拿到该设备关联有告警的属性,进行逻辑计算
+ for (DeviceAttributeDO attr : attributeList) {
+ if(StringUtils.isBlank(value.get(attr.getAttributeName())))continue;
+
+ AlertDO alertDO = alertMapper.selectById(attr.getAlertId());
+ //告警设置有效
+ if (alertDO!=null && alertDO.getIsEnable() && !alertDO.getConditionFormula().isEmpty()){
+ //允许重复报警
+ if(alertDO.getIsRepeat() || alertDO.getNoRepeatDuration()==null || alertDO.getNoRepeatDuration() < 1){
+ insertRecord(value,alertDO,attr);
+ }else{//不允许重复报警,检查下次报警间隔,以 告警id和属性id查找上次报警记录
+ AlertRecordPageReqVO pageReqVO = new AlertRecordPageReqVO().setAlertId(alertDO.getId()).setDataId(attr.getId());
+ AlertRecordDO last = alertRecordMapper.selectLastOne(pageReqVO);
+ if(last==null){
+ insertRecord(value,alertDO,attr);
+ }else{
+ Duration duration = Duration.between(last.getCreateTime(), LocalDateTime.now());
+ //long seconds = duration.getSeconds();
+ long millis = duration.toMillis();
+ //可以继续报警了
+ if(millis > alertDO.getNoRepeatDuration()*1000){
+ insertRecord(value,alertDO,attr);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void insertRecord(Map value, AlertDO alertDO,DeviceAttributeDO attr){
+ boolean result = FormulaCalculator.calFormulaBoolean(alertDO.getConditionFormula(),
+ Constants.DefaultFormulaVar,value.get(attr.getAttributeName()));
+ //触发告警
+ if(result){
+ AlertRecordDO recordDO = new AlertRecordDO(alertDO);
+ recordDO.setData(attr);
+ recordDO.setDataValue(value.get(attr.getAttributeName()));
+ alertRecordMapper.insert(recordDO);
+ }
+ }
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alertrecord/AlertRecordDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alertrecord/AlertRecordDO.java
index c8d5dcbaec..7382ed062e 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alertrecord/AlertRecordDO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/alertrecord/AlertRecordDO.java
@@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.alertrecord;
+import cn.iocoder.yudao.module.iot.dal.dataobject.alert.AlertDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
import lombok.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
@@ -35,13 +37,11 @@ public class AlertRecordDO extends BaseDO {
/**
* 告警类型
*
- * 枚举 {@link TODO iot_alert_type 对应的类}
*/
private String alertType;
/**
* 告警等级
*
- * 枚举 {@link TODO iot_alert_level 对应的类}
*/
private String alertLevel;
/**
@@ -63,7 +63,6 @@ public class AlertRecordDO extends BaseDO {
/**
* 数据类型
*
- * 枚举 {@link TODO iot_device_data_type 对应的类}
*/
private String dataType;
/**
@@ -85,7 +84,6 @@ public class AlertRecordDO extends BaseDO {
/**
* 重复告警
*
- * 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean isRepeat;
/**
@@ -105,4 +103,25 @@ public class AlertRecordDO extends BaseDO {
*/
private String dataName;
+ public AlertRecordDO(AlertDO alertDO){
+ this.alertId = alertDO.getId();
+ this.alertCode = alertDO.getAlertCode();
+ this.alertName = alertDO.getAlertName();
+ this.alertType = alertDO.getAlertType();
+ this.alertLevel = alertDO.getAlertLevel();
+ this.alertAudio = alertDO.getAlertAudio();
+ this.isRepeat = alertDO.getIsRepeat();
+ this.content = alertDO.getContent();
+ this.conditionFormula = alertDO.getConditionFormula();
+ this.conditionInfo = alertDO.getConditionInfo();
+ }
+ //属性报警时
+ public void setData(DeviceAttributeDO attributeDO){
+ this.dataId = attributeDO.getId();
+ this.dataCode = attributeDO.getAttributeCode();
+ this.dataName = attributeDO.getAttributeName();
+ this.dataType = attributeDO.getDataType();
+ this.dataUnit = attributeDO.getDataUnit();
+ this.dataTypeRemark = attributeDO.getDataTypeRemark();
+ }
}
\ 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/device/DeviceAttributeDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/DeviceAttributeDO.java
index 6e5ad3e75c..b09433baa7 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/DeviceAttributeDO.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/DeviceAttributeDO.java
@@ -1,11 +1,11 @@
-package cn.iocoder.yudao.module.iot.dal.dataobject.deviceattribute;
+package cn.iocoder.yudao.module.iot.dal.dataobject.device;
-import lombok.*;
-import java.util.*;
-import java.time.LocalDateTime;
-import java.time.LocalDateTime;
-import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.sun.xml.bind.v2.TODO;
+import lombok.*;
/**
* 设备属性 DO
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alertrecord/AlertRecordMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alertrecord/AlertRecordMapper.java
index 48e01adabc..b9b4598dc7 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alertrecord/AlertRecordMapper.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alertrecord/AlertRecordMapper.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.alertrecord.vo.AlertRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alertrecord.AlertRecordDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
/**
@@ -48,4 +49,12 @@ public interface AlertRecordMapper extends BaseMapperX {
default int deleteByAlertId(Long alertId) {
return delete(AlertRecordDO::getAlertId, alertId);
}
+
+ default AlertRecordDO selectLastOne(AlertRecordPageReqVO reqVO) {
+ return selectOne(new QueryWrapper()
+ .eq(reqVO.getAlertId()!=null,"alert_id", reqVO.getAlertId())
+ .eq(reqVO.getDataId()!=null,"data_id", reqVO.getDataId())
+ .orderByDesc("id")
+ .last("limit 1"));
+ }
}
\ 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/device/DeviceAttributeMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceAttributeMapper.java
index 48f71e63e9..7e02645b41 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceAttributeMapper.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/DeviceAttributeMapper.java
@@ -1,14 +1,14 @@
-package cn.iocoder.yudao.module.iot.dal.mysql.deviceattribute;
+package cn.iocoder.yudao.module.iot.dal.mysql.device;
-import java.util.*;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattribute.DeviceAttributeDO;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
/**
* 设备属性 Mapper
*
@@ -27,4 +27,24 @@ public interface DeviceAttributeMapper extends BaseMapperX {
return delete(DeviceAttributeDO::getDeviceId, deviceId);
}
+ default List selectList(DeviceAttributeDO reqVO) {
+ return selectList(new LambdaQueryWrapperX()
+ .eqIfPresent(DeviceAttributeDO::getDeviceId, reqVO.getDeviceId())
+ .likeIfPresent(DeviceAttributeDO::getAttributeName, reqVO.getAttributeName())
+ .eqIfPresent(DeviceAttributeDO::getAlertId, reqVO.getAlertId())
+ .eqIfPresent(DeviceAttributeDO::getGatewayId, reqVO.getGatewayId())
+ .eqIfPresent(DeviceAttributeDO::getIsEnable, reqVO.getIsEnable())
+ .eqIfPresent(DeviceAttributeDO::getIoType, reqVO.getIoType())
+ .orderByDesc(DeviceAttributeDO::getId));
+ }
+ default List selectAlertList(DeviceAttributeDO reqVO) {
+ return selectList(new LambdaQueryWrapperX()
+ .eqIfPresent(DeviceAttributeDO::getDeviceId, reqVO.getDeviceId())
+ .likeIfPresent(DeviceAttributeDO::getAttributeName, reqVO.getAttributeName())
+ .eqIfPresent(DeviceAttributeDO::getGatewayId, reqVO.getGatewayId())
+ .eqIfPresent(DeviceAttributeDO::getIsEnable, reqVO.getIsEnable())
+ .eqIfPresent(DeviceAttributeDO::getIoType, reqVO.getIoType())
+ .gtIfPresent(DeviceAttributeDO::getAlertId, 0)
+ .orderByDesc(DeviceAttributeDO::getId));
+ }
}
\ 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/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 e2cbd8df8d..95f0005ee3 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
@@ -48,4 +48,24 @@ public interface DeviceMapper extends BaseMapperX {
default List selectByGatewayId(Long gatewayId) {
return selectList(DeviceDO::getGatewayId, gatewayId);
}
+ default DeviceDO selectByName(String name) {
+ return selectOne(DeviceDO::getDeviceName,name);
+ }
+
+ default List selectList(DevicePageReqVO reqVO) {
+ return selectList(new LambdaQueryWrapperX()
+ .eqIfPresent(DeviceDO::getDeviceCode, reqVO.getDeviceCode())
+ .likeIfPresent(DeviceDO::getDeviceName, reqVO.getDeviceName())
+ .eqIfPresent(DeviceDO::getDeviceType, reqVO.getDeviceType())
+ .eqIfPresent(DeviceDO::getStatus, reqVO.getStatus())
+ .eqIfPresent(DeviceDO::getReadTopic, reqVO.getReadTopic())
+ .eqIfPresent(DeviceDO::getWriteTopic, reqVO.getWriteTopic())
+ .eqIfPresent(DeviceDO::getGatewayId, reqVO.getGatewayId())
+ .eqIfPresent(DeviceDO::getDeviceBrandId, reqVO.getDeviceBrandId())
+ .eqIfPresent(DeviceDO::getOffLineDuration, reqVO.getOffLineDuration())
+ .betweenIfPresent(DeviceDO::getLastOnlineTime, reqVO.getLastOnlineTime())
+ .eqIfPresent(DeviceDO::getRemark, reqVO.getRemark())
+ .eqIfPresent(DeviceDO::getIsEnable, reqVO.getIsEnable())
+ .orderByDesc(DeviceDO::getId));
+ }
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/constant/Constants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/constant/Constants.java
index 890984c8c7..b213aaf98a 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/constant/Constants.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/constant/Constants.java
@@ -1,6 +1,17 @@
package cn.iocoder.yudao.module.iot.framework.constant;
public class Constants {
+ //mqtt时间默认格式
public final static String MQTT_timestamp_format = "yyyy-MM-dd HH:mm:ss.SSS";
+ //iot设备默认离线时长,秒;距离上次超过这个时间
+ public final static int DEVICE_OFFLINE_SECOND = 30;
+
+ /**
+ * 默认逻辑表达式的变量名称
+ */
+ public final static String DefaultFormulaVar = "data";
+
+
+
}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/constant/DeviceStatusEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/constant/DeviceStatusEnum.java
new file mode 100644
index 0000000000..bc639280ae
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/constant/DeviceStatusEnum.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.iot.framework.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum DeviceStatusEnum {
+
+ 不可用("3"),
+ 离线("2"),
+ 在线("1");
+
+ private final String value;
+
+ // 一个可选的方法,用于根据整数值获取对应的枚举实例
+ public static DeviceStatusEnum fromValue(String value) {
+ for (DeviceStatusEnum status : DeviceStatusEnum.values()) {
+ if (status.getValue().equals(value)) {
+ return status;
+ }
+ }
+ throw new IllegalArgumentException("Unknown value: " + value);
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/CheckDeviceOfflineJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/CheckDeviceOfflineJob.java
new file mode 100644
index 0000000000..ff2ae409c7
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/CheckDeviceOfflineJob.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.iot.framework.util;
+
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DevicePageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
+import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceMapper;
+import cn.iocoder.yudao.module.iot.framework.constant.Constants;
+import cn.iocoder.yudao.module.iot.framework.constant.DeviceStatusEnum;
+import cn.iocoder.yudao.module.iot.service.device.DeviceService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 定时检查设备mqtt消息是否离线,离线定义为默认时间内没有新消息
+ *
+ * @author j-sentinel
+ */
+@Component
+@Slf4j
+public class CheckDeviceOfflineJob implements JobHandler {
+
+ @Resource
+ private DeviceMapper deviceMapper;
+ @Resource
+ private DeviceService deviceService;
+
+ @Override
+ @TenantIgnore
+ public String execute(String param) {
+ DevicePageReqVO respVO = new DevicePageReqVO().setStatus(DeviceStatusEnum.在线.getValue()).setIsEnable(true);
+ List deviceList = deviceService.selectList(respVO);
+ int count = 0;
+ for (DeviceDO device : deviceList) {
+ if(device.getLastOnlineTime()==null){
+ device.setStatus(DeviceStatusEnum.离线.getValue());
+ deviceMapper.updateById(device);
+ count++;
+ }else {
+ Duration duration = Duration.between(device.getLastOnlineTime(), LocalDateTime.now());
+ //long seconds = duration.getSeconds();
+ long millis = duration.toMillis();
+ long second = device.getOffLineDuration()==null? Constants.DEVICE_OFFLINE_SECOND : device.getOffLineDuration();
+ if(millis > second*1000){//认为设备已经离线
+ device.setStatus(DeviceStatusEnum.离线.getValue());
+ deviceMapper.updateById(device);
+ count++;
+ }
+ }
+ }
+ log.info("[job execute][定时检查设备mqtt消息是否离线,数量 ({}) 个]", count);
+ return String.format("定时检查设备mqtt消息是否离线,数量 %s 个", count);
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/FormulaCalculator.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/FormulaCalculator.java
new file mode 100644
index 0000000000..f06926c756
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/FormulaCalculator.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.iot.framework.util;
+
+import com.ql.util.express.DefaultContext;
+import com.ql.util.express.ExpressRunner;
+
+import java.util.Map;
+
+
+public class FormulaCalculator {
+
+ public static String calFormula(String formula){
+ ExpressRunner runner = new ExpressRunner();
+ DefaultContext context = new DefaultContext();
+ try {
+ Object result = runner.execute(formula, context, null, false, false);
+ return result.toString();
+ } catch (Exception e) {
+ System.out.println("---计算出错!:"+formula);
+ }
+ return "";
+ }
+ public static String calFormula(String formula,String key,String value){
+ formula = formula.replace(key,value);
+ return calFormula(formula);
+ }
+ public static String calFormula(String formula, Map map){
+ for (String key: map.keySet()){
+ formula = formula.replaceAll(key, map.get(key));
+ }
+ return calFormula(formula);
+ }
+ public static Boolean calFormulaBoolean(String formula,String key,String value) {
+ formula = formula.replace(key,value);
+ String result = calFormula(formula);
+ if("true".equals(result))
+ return true;
+ return false;
+ }
+ public static Boolean calFormulaBoolean(String formula, Map map) {
+ for (String key: map.keySet()){
+ formula = formula.replaceAll(key, map.get(key));
+ }
+ String result = calFormula(formula);
+ if("true".equals(result))
+ return true;
+ return false;
+ }
+
+ public static void main(String[] args) throws Exception {
+ String formula = "2 == 1";
+ ExpressRunner runner = new ExpressRunner();
+ DefaultContext context = new DefaultContext();
+ context.put("a", 1);
+ context.put("b", 2);
+ context.put("c", 3);
+ //下面五个参数意义分别是 表达式,上下文,errorList,是否缓存,是否输出日志
+ Object result = runner.execute("a+b+c", context, null, true, false);
+ System.out.println("a+b+c=" + result);
+ //下面五个参数意义分别是 表达式,上下文,errorList,是否缓存,是否输出日志
+ Object result2 = runner.execute(formula, context, null, true, false);
+ System.out.println("formula=" + result2);
+
+ //下面五个参数意义分别是 表达式,上下文,errorList,是否缓存,是否输出日志
+ DefaultContext context3 = new DefaultContext();
+ context3.put("a", "1");
+ context3.put("b", "2");
+ context3.put("c", "3");
+ Object result3 = runner.execute("1+2+3==6", context3, null, true, false);
+ System.out.println("a+b+c=" + result3);
+ }
+}
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 80f6430475..859d3965fc 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
@@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.iot.service.device;
-import java.util.*;
-import javax.validation.*;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
-import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattribute.DeviceAttributeDO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DevicePageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
+
+import javax.validation.Valid;
+import java.util.List;
/**
* 物联设备 Service 接口
@@ -44,7 +46,7 @@ public interface DeviceService {
* @return 物联设备
*/
DeviceDO getDevice(Long id);
-
+ DeviceDO getDeviceByName(String name);
/**
* 获得物联设备分页
*
@@ -52,7 +54,7 @@ public interface DeviceService {
* @return 物联设备分页
*/
PageResult getDevicePage(DevicePageReqVO pageReqVO);
-
+ List selectList(DevicePageReqVO reqVO);
// ==================== 子表(设备属性) ====================
/**
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java
index a3395d9a75..22cbdad288 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java
@@ -1,20 +1,21 @@
package cn.iocoder.yudao.module.iot.service.device;
+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.module.iot.controller.admin.device.vo.DevicePageReqVO;
+import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceAttributeDO;
+import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceMapper;
+import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceAttributeMapper;
import org.springframework.stereotype.Service;
-import javax.annotation.Resource;
-import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
-import java.util.*;
-import cn.iocoder.yudao.module.iot.controller.admin.device.vo.*;
-import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.deviceattribute.DeviceAttributeDO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import javax.annotation.Resource;
-import cn.iocoder.yudao.module.iot.dal.mysql.device.DeviceMapper;
-import cn.iocoder.yudao.module.iot.dal.mysql.deviceattribute.DeviceAttributeMapper;
+import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
@@ -35,6 +36,10 @@ public class DeviceServiceImpl implements DeviceService {
@Override
public Long createDevice(DeviceSaveReqVO createReqVO) {
+ DeviceDO temp = deviceMapper.selectByName(createReqVO.getDeviceName());
+ if (temp!=null){
+ throw exception(DEVICE_EXISTS);
+ }
// 插入
DeviceDO device = BeanUtils.toBean(createReqVO, DeviceDO.class);
deviceMapper.insert(device);
@@ -73,12 +78,20 @@ public class DeviceServiceImpl implements DeviceService {
public DeviceDO getDevice(Long id) {
return deviceMapper.selectById(id);
}
+ @Override
+ public DeviceDO getDeviceByName(String name) {
+ return deviceMapper.selectByName(name);
+ }
@Override
public PageResult getDevicePage(DevicePageReqVO pageReqVO) {
return deviceMapper.selectPage(pageReqVO);
}
+ @Override
+ public List selectList(DevicePageReqVO reqVO){
+ return deviceMapper.selectList(reqVO);
+ }
// ==================== 子表(设备属性) ====================
@Override
diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/mqtt/MqttServerDataController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/mqtt/MqttServerDataController.java
index f5c0ef01a7..25462cc7d8 100644
--- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/mqtt/MqttServerDataController.java
+++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/mqtt/MqttServerDataController.java
@@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.mes.controller.admin.mqtt;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.iot.controller.admin.mqttdatarecord.MqttDataService;
import cn.iocoder.yudao.module.iot.controller.admin.mqttdatarecord.vo.MqttDataRecordPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.mqttrecord.vo.MqttRecordSaveReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.formula.FormulaRecordDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.mqttdatarecord.MqttDataRecordDO;
import cn.iocoder.yudao.module.iot.dal.mysql.formula.FormulaRecordMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.mqttdatarecord.MqttDataRecordMapper;
+import cn.iocoder.yudao.module.iot.service.device.DeviceService;
import cn.iocoder.yudao.module.iot.service.mqttrecord.MqttRecordService;
import com.alibaba.fastjson.JSON;
import io.swagger.v3.oas.annotations.Operation;
@@ -34,6 +37,11 @@ public class MqttServerDataController {
private MqttDataRecordMapper mqttDataRecordMapper;
@Resource
private MqttRecordService mqttRecordService;
+ @Resource
+ private DeviceService deviceService;
+ @Resource
+ private MqttDataService mqttDataService;
+
@PostMapping("/create")
@Operation(summary = "创建设备数据记录")
public CommonResult createMqttDataRecord(@RequestBody MqttDataSaveVO saveVO) {
@@ -42,23 +50,36 @@ public class MqttServerDataController {
List list = new ArrayList<>();
boolean valid = false;
for (String key: saveVO.getPayload().keySet()) {
- MqttDataRecordDO recordDO = new MqttDataRecordDO().setDeviceName(saveVO.getTopic())
- .setAttribute(key).setAttrValue(saveVO.getPayload().get(key));
- list.add(recordDO);
+ String value = saveVO.getPayload().get(key);
//检查值是否有效,有可能都是空值那就不存
- if(StringUtils.isNotBlank(recordDO.getAttrValue()))
+ if(StringUtils.isNotBlank(value))
valid = true;
+ else continue;
+
+ MqttDataRecordDO recordDO = new MqttDataRecordDO().setDeviceName(saveVO.getTopic())
+ .setAttribute(key).setAttrValue(value);
+ list.add(recordDO);
+
}
if(list.size()>0 && valid){
//保存单个值
mqttDataRecordMapper.insertBatch(list);
//保存原值
MqttRecordSaveReqVO createReqVO = new MqttRecordSaveReqVO();
- createReqVO.setDeviceCode(saveVO.getTopic());
+ //createReqVO.setDeviceCode(saveVO.getTopic());
+ createReqVO.setDeviceName(saveVO.getTopic());
createReqVO.setGatewayCode("");
createReqVO.setDeviceDataTime(LocalDateTime.now());
createReqVO.setDeviceData(JSON.toJSONString(saveVO.getPayload()));
mqttRecordService.createMqttRecord(createReqVO);
+
+ DeviceDO deviceDO = deviceService.getDeviceByName(saveVO.getTopic());
+ if(deviceDO!=null){
+ //告警检测
+ mqttDataService.checkDeviceAlert(saveVO.getPayload(), deviceDO);
+ //更新iot设备在线状态
+ mqttDataService.updateDeviceStatus(deviceDO);
+ }
}
}
return success(1L);
@@ -110,7 +131,7 @@ public class MqttServerDataController {
}
return success(BigDecimal.ZERO.doubleValue());
}
- @PostMapping("/setSimulateValue")
+ @GetMapping("/setSimulateValue")
@Operation(summary = "设置模拟数据默认值")
public CommonResult setSimulateValue(@RequestParam("id")Long id,@RequestParam("resetValue") double resetValue) {
if(id!=null){
diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml
index 327a344410..66245ea8ea 100644
--- a/yudao-server/src/main/resources/application.yaml
+++ b/yudao-server/src/main/resources/application.yaml
@@ -243,6 +243,8 @@ yudao:
- iot_gateway
- iot_mqtt_record
- iot_mqtt_data_record
+ - iot_device
+ - iot_device_attribute
- mes_view_task_product_summary
- mes_view_report_plan_summary
- mes_view_report_user_date_summary