From 838ac67842e6024cc51b5e5e3615e096bb34b303 Mon Sep 17 00:00:00 2001 From: chenshuichuan <1154693969@qq.com> Date: Fri, 13 Sep 2024 10:38:51 +0800 Subject: [PATCH] fix alert record --- .../module/iot/enums/ErrorCodeConstants.java | 2 + yudao-module-iot/yudao-module-iot-biz/pom.xml | 7 + .../admin/alert/vo/AlertTypeEnum.java | 27 ++++ .../admin/device/DeviceController.java | 44 +++---- .../admin/mqttdatarecord/MqttDataService.java | 122 ++++++++++++++++++ .../dataobject/alertrecord/AlertRecordDO.java | 27 +++- .../dataobject/device/DeviceAttributeDO.java | 12 +- .../mysql/alertrecord/AlertRecordMapper.java | 9 ++ .../mysql/device/DeviceAttributeMapper.java | 32 ++++- .../iot/dal/mysql/device/DeviceMapper.java | 20 +++ .../iot/framework/constant/Constants.java | 11 ++ .../framework/constant/DeviceStatusEnum.java | 26 ++++ .../framework/util/CheckDeviceOfflineJob.java | 60 +++++++++ .../iot/framework/util/FormulaCalculator.java | 71 ++++++++++ .../iot/service/device/DeviceService.java | 18 +-- .../iot/service/device/DeviceServiceImpl.java | 35 +++-- .../admin/mqtt/MqttServerDataController.java | 33 ++++- .../src/main/resources/application.yaml | 2 + 18 files changed, 494 insertions(+), 64 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/alert/vo/AlertTypeEnum.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/mqttdatarecord/MqttDataService.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/constant/DeviceStatusEnum.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/CheckDeviceOfflineJob.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/util/FormulaCalculator.java 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