فهرست منبع

feat: device error rocord and status task

lihao16 8 ماه پیش
والد
کامیت
230270b6da
30فایلهای تغییر یافته به همراه1177 افزوده شده و 43 حذف شده
  1. 11 7
      smsb-admin/src/main/resources/application-dev.yml
  2. 2 2
      smsb-admin/src/main/resources/application.yml
  3. 3 1
      smsb-common/smsb-common-core/src/main/java/org/dromara/common/core/enums/HttpApiResult.java
  4. 2 1
      smsb-extend/smsb-monitor-admin/src/main/resources/application.yml
  5. 4 3
      smsb-extend/smsb-snailjob-server/src/main/resources/application-dev.yml
  6. 99 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/controller/SmsbDeviceErrorRecordController.java
  7. 65 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/SmsbDeviceErrorRecord.java
  8. 55 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/bo/SmsbDeviceErrorRecordBo.java
  9. 18 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/constants/DeviceConstants.java
  10. 62 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/vo/SmsbDeviceErrorRecordVo.java
  11. 2 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/vo/SmsbDeviceVo.java
  12. 15 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/mapper/SmsbDeviceErrorRecordMapper.java
  13. 68 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDeviceErrorRecordService.java
  14. 9 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDeviceService.java
  15. 132 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDeviceErrorRecordServiceImpl.java
  16. 20 1
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDeviceServiceImpl.java
  17. 7 0
      smsb-modules/smsb-device/src/main/resources/mapper/device/SmsbDeviceErrorRecordMapper.xml
  18. 11 0
      smsb-modules/smsb-job/pom.xml
  19. 99 0
      smsb-modules/smsb-job/src/main/java/org/dromara/job/snailjob/DeviceStatusReviewExecutor.java
  20. 40 9
      smsb-modules/smsb-netty/src/main/java/com/inspur/netty/handler/ConnectServerHandler.java
  21. 8 2
      smsb-modules/smsb-source/src/main/java/com/inspur/source/domain/SmsbItem.java
  22. 10 0
      smsb-modules/smsb-source/src/main/java/com/inspur/source/mapper/SmsbItemMapper.java
  23. 18 3
      smsb-modules/smsb-source/src/main/java/com/inspur/source/service/impl/SmsbItemServiceImpl.java
  24. 9 7
      smsb-modules/smsb-source/src/main/java/com/inspur/source/service/impl/SmsbMinioDataServiceImpl.java
  25. 11 0
      smsb-modules/smsb-source/src/main/resources/mapper/SmsbItemMapper.xml
  26. 63 0
      smsb-plus-ui/src/api/smsb/device/errorRecord.ts
  27. 80 0
      smsb-plus-ui/src/api/smsb/device/errorRecord_type.ts
  28. 242 0
      smsb-plus-ui/src/views/smsb/dashboard/device.vue
  29. 4 3
      smsb-plus-ui/src/views/smsb/item/index.vue
  30. 8 4
      smsb-plus-ui/src/views/smsb/minioData/index.vue

+ 11 - 7
smsb-admin/src/main/resources/application-dev.yml

@@ -1,7 +1,7 @@
 --- # 监控中心配置
 spring.boot.admin.client:
   # 增加客户端开关
-  enabled: false
+  enabled: true
   url: http://localhost:9090/admin
   instance:
     service-host-type: IP
@@ -15,20 +15,24 @@ spring.boot.admin.client:
 
 --- # snail-job 配置
 snail-job:
-  enabled: false
+  enabled: true
   # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
-  group: "ruoyi_group"
+  # group: "ruoyi_group"
+  group: "smsb_group"
   # SnailJob 接入验证令牌 详见 script/sql/snail_job.sql `sj_group_config` 表
-  token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
+  # token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
+  token: "SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj"
   server:
     host: 127.0.0.1
     port: 17888
   # 详见 script/sql/snail_job.sql `sj_namespace` 表
-  namespace: ${spring.profiles.active}
+  #namespace: ${spring.profiles.active}
+  namespace: dev
   # 随主应用端口飘逸
-  port: 2${server.port}
+  # port: 2${server.port}
+  port: 28080
   # 客户端ip指定
-  host:
+  host: 127.0.0.1
 
 --- # 数据源配置
 spring:

+ 2 - 2
smsb-admin/src/main/resources/application.yml

@@ -77,9 +77,9 @@ spring:
   servlet:
     multipart:
       # 单个文件大小
-      max-file-size: 10MB
+      max-file-size: 1024MB
       # 设置总上传的文件大小
-      max-request-size: 20MB
+      max-request-size: 2048MB
   mvc:
     # 设置静态资源路径 防止所有请求都去查静态资源
     static-path-pattern: /static/**

+ 3 - 1
smsb-common/smsb-common-core/src/main/java/org/dromara/common/core/enums/HttpApiResult.java

@@ -33,7 +33,9 @@ public enum HttpApiResult {
     /**
      * 内容排期为空
      */
-    PUSH_TIME_IS_NULL(1004, "PUSH TIME IS NULL");
+    PUSH_TIME_IS_NULL(1004, "PUSH TIME IS NULL"),
+
+    PUSH_STATUS_IS_WAITING(1005,"正在审核中!");
 
     private final Integer code;
     private final String info;

+ 2 - 1
smsb-extend/smsb-monitor-admin/src/main/resources/application.yml

@@ -4,7 +4,8 @@ spring:
   application:
     name: smsb-monitor-admin
   profiles:
-    active: @profiles.active@
+    #active: @profiles.active@
+    active: dev
 
 logging:
   config: classpath:logback-plus.xml

+ 4 - 3
smsb-extend/smsb-snailjob-server/src/main/resources/application-dev.yml

@@ -2,9 +2,10 @@ spring:
   datasource:
     type: com.zaxxer.hikari.HikariDataSource
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://localhost:3306/smsb-plus?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+    url: jdbc:mysql://127.0.0.1:3306/smsb-plus?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
     username: root
-    password: password
+    # password: password
+    password: Hycpb@123
     hikari:
       connection-timeout: 30000
       validation-timeout: 5000
@@ -39,7 +40,7 @@ snail-job:
 --- # 监控中心配置
 spring.boot.admin.client:
   # 增加客户端开关
-  enabled: true
+  enabled: false
   url: http://localhost:9090/admin
   instance:
     service-host-type: IP

+ 99 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/controller/SmsbDeviceErrorRecordController.java

@@ -0,0 +1,99 @@
+package com.inspur.device.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import com.inspur.device.domain.vo.SmsbDeviceErrorRecordVo;
+import com.inspur.device.domain.bo.SmsbDeviceErrorRecordBo;
+import com.inspur.device.service.ISmsbDeviceErrorRecordService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 设备告警记录
+ *
+ * @author Hao Li
+ * @date 2025-03-12
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/device/errorRecord")
+public class SmsbDeviceErrorRecordController extends BaseController {
+
+    private final ISmsbDeviceErrorRecordService smsbDeviceErrorRecordService;
+
+    /**
+     * 查询设备告警记录列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<SmsbDeviceErrorRecordVo> list(SmsbDeviceErrorRecordBo bo, PageQuery pageQuery) {
+        return smsbDeviceErrorRecordService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出设备告警记录列表
+     */
+    @Log(title = "设备告警记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SmsbDeviceErrorRecordBo bo, HttpServletResponse response) {
+        List<SmsbDeviceErrorRecordVo> list = smsbDeviceErrorRecordService.queryList(bo);
+        ExcelUtil.exportExcel(list, "设备告警记录", SmsbDeviceErrorRecordVo.class, response);
+    }
+
+    /**
+     * 获取设备告警记录详细信息
+     *
+     * @param id 主键
+     */
+    @GetMapping("/{id}")
+    public R<SmsbDeviceErrorRecordVo> getInfo(@NotNull(message = "主键不能为空")
+                                              @PathVariable Long id) {
+        return R.ok(smsbDeviceErrorRecordService.queryById(id));
+    }
+
+    /**
+     * 新增设备告警记录
+     */
+    @Log(title = "设备告警记录", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SmsbDeviceErrorRecordBo bo) {
+        return toAjax(smsbDeviceErrorRecordService.insertByBo(bo));
+    }
+
+    /**
+     * 修改设备告警记录
+     */
+    @Log(title = "设备告警记录", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SmsbDeviceErrorRecordBo bo) {
+        return toAjax(smsbDeviceErrorRecordService.updateByBo(bo));
+    }
+
+    /**
+     * 删除设备告警记录
+     *
+     * @param ids 主键串
+     */
+    @Log(title = "设备告警记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ids) {
+        return toAjax(smsbDeviceErrorRecordService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 65 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/SmsbDeviceErrorRecord.java

@@ -0,0 +1,65 @@
+package com.inspur.device.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 设备告警记录对象 smsb_device_error_record
+ *
+ * @author Hao Li
+ * @date 2025-03-12
+ */
+@Data
+@EqualsAndHashCode
+@TableName("smsb_device_error_record")
+public class SmsbDeviceErrorRecord {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 设备ID
+     */
+    private Long deviceId;
+
+    /**
+     * 设备名称
+     */
+    private String deviceName;
+
+    /**
+     * 告警等级 1-普通 2-紧急
+     */
+    private Integer errorLevel;
+
+    /**
+     * 告警类型 1-上线 2-离线 3-波动 4-超30分钟
+     */
+    private Integer errorType;
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+
+}

+ 55 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/bo/SmsbDeviceErrorRecordBo.java

@@ -0,0 +1,55 @@
+package com.inspur.device.domain.bo;
+
+import com.inspur.device.domain.SmsbDeviceErrorRecord;
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+/**
+ * 设备告警记录业务对象 smsb_device_error_record
+ *
+ * @author Hao Li
+ * @date 2025-03-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SmsbDeviceErrorRecord.class, reverseConvertGenerate = false)
+public class SmsbDeviceErrorRecordBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    @NotNull(message = "主键ID不能为空", groups = {EditGroup.class})
+    private Long id;
+
+    /**
+     * 设备ID
+     */
+    @NotNull(message = "设备ID不能为空", groups = {AddGroup.class, EditGroup.class})
+    private Long deviceId;
+
+    /**
+     * 设备名称
+     */
+    @NotBlank(message = "设备名称不能为空", groups = {AddGroup.class, EditGroup.class})
+    private String deviceName;
+
+    /**
+     * 告警等级
+     */
+    @NotNull(message = "告警等级不能为空", groups = {AddGroup.class, EditGroup.class})
+    private Long errorLevel;
+
+    /**
+     * 告警类型
+     */
+    @NotNull(message = "告警类型不能为空", groups = {AddGroup.class, EditGroup.class})
+    private Long errorType;
+
+
+}

+ 18 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/constants/DeviceConstants.java

@@ -8,4 +8,22 @@ public class DeviceConstants {
 
     public static final String LIMIT_ONE = "LIMIT 1";
 
+    public static final Integer DEVICE_STATUS_ONLINE = 1;
+
+    public static final Integer DEVICE_STATUS_OFFLINE = 2;
+
+    public static final Integer DEVICE_ERROR_ONLINE = 1;
+
+    public static final Integer DEVICE_ERROR_OFFLINE = 2;
+
+    public static final Integer DEVICE_ERROR_NET_SLOW = 3;
+
+    public static final Integer DEVICE_ERROR_OFFLINE_THAN_30 = 4;
+
+    public static final Integer DEVICE_ERROR_LEVEL_GENERAL = 1;
+
+    public static final Integer DEVICE_ERROR_LEVEL_URGENT = 2;
+
+
+
 }

+ 62 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/vo/SmsbDeviceErrorRecordVo.java

@@ -0,0 +1,62 @@
+package com.inspur.device.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.inspur.device.domain.SmsbDeviceErrorRecord;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 设备告警记录视图对象 smsb_device_error_record
+ *
+ * @author Hao Li
+ * @date 2025-03-12
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SmsbDeviceErrorRecord.class)
+public class SmsbDeviceErrorRecordVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 设备ID
+     */
+    @ExcelProperty(value = "设备ID")
+    private Long deviceId;
+
+    /**
+     * 设备名称
+     */
+    @ExcelProperty(value = "设备名称")
+    private String deviceName;
+
+    /**
+     * 告警等级
+     */
+    @ExcelProperty(value = "告警等级", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "smsb_device_error_level")
+    private Long errorLevel;
+
+    /**
+     * 告警类型
+     */
+    @ExcelProperty(value = "告警类型", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "smsb_device_error_type")
+    private Long errorType;
+
+
+}

+ 2 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/vo/SmsbDeviceVo.java

@@ -239,4 +239,6 @@ public class SmsbDeviceVo implements Serializable {
 
     private Integer isWatch;
 
+    private String tenantId;
+
 }

+ 15 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/mapper/SmsbDeviceErrorRecordMapper.java

@@ -0,0 +1,15 @@
+package com.inspur.device.mapper;
+
+import com.inspur.device.domain.SmsbDeviceErrorRecord;
+import com.inspur.device.domain.vo.SmsbDeviceErrorRecordVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 设备告警记录Mapper接口
+ *
+ * @author Hao Li
+ * @date 2025-03-12
+ */
+public interface SmsbDeviceErrorRecordMapper extends BaseMapperPlus<SmsbDeviceErrorRecord, SmsbDeviceErrorRecordVo> {
+
+}

+ 68 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDeviceErrorRecordService.java

@@ -0,0 +1,68 @@
+package com.inspur.device.service;
+
+import com.inspur.device.domain.bo.SmsbDeviceErrorRecordBo;
+import com.inspur.device.domain.vo.SmsbDeviceErrorRecordVo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 设备告警记录Service接口
+ *
+ * @author Hao Li
+ * @date 2025-03-12
+ */
+public interface ISmsbDeviceErrorRecordService {
+
+    /**
+     * 查询设备告警记录
+     *
+     * @param id 主键
+     * @return 设备告警记录
+     */
+    SmsbDeviceErrorRecordVo queryById(Long id);
+
+    /**
+     * 分页查询设备告警记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 设备告警记录分页列表
+     */
+    TableDataInfo<SmsbDeviceErrorRecordVo> queryPageList(SmsbDeviceErrorRecordBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的设备告警记录列表
+     *
+     * @param bo 查询条件
+     * @return 设备告警记录列表
+     */
+    List<SmsbDeviceErrorRecordVo> queryList(SmsbDeviceErrorRecordBo bo);
+
+    /**
+     * 新增设备告警记录
+     *
+     * @param bo 设备告警记录
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(SmsbDeviceErrorRecordBo bo);
+
+    /**
+     * 修改设备告警记录
+     *
+     * @param bo 设备告警记录
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(SmsbDeviceErrorRecordBo bo);
+
+    /**
+     * 校验并批量删除设备告警记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 9 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDeviceService.java

@@ -1,5 +1,6 @@
 package com.inspur.device.service;
 
+import com.inspur.device.domain.SmsbDevice;
 import com.inspur.device.domain.bo.SmsbDeviceBo;
 import com.inspur.device.domain.vo.DeviceStatisticsVo;
 import com.inspur.device.domain.vo.SmsbDeviceRunInfoVo;
@@ -103,4 +104,12 @@ public interface ISmsbDeviceService {
      * @return
      */
     SmsbDeviceRunInfoVo getRunInfo(Long id);
+
+
+    /**
+     * 更新设备状态
+     * @param smsbDeviceVo
+     * @return
+     */
+    SmsbDeviceVo updateDeviceStatus(SmsbDeviceVo smsbDeviceVo);
 }

+ 132 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDeviceErrorRecordServiceImpl.java

@@ -0,0 +1,132 @@
+package com.inspur.device.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.inspur.device.domain.SmsbDeviceErrorRecord;
+import com.inspur.device.domain.bo.SmsbDeviceErrorRecordBo;
+import com.inspur.device.domain.vo.SmsbDeviceErrorRecordVo;
+import com.inspur.device.mapper.SmsbDeviceErrorRecordMapper;
+import com.inspur.device.service.ISmsbDeviceErrorRecordService;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 设备告警记录Service业务层处理
+ *
+ * @author Hao Li
+ * @date 2025-03-12
+ */
+@RequiredArgsConstructor
+@Service
+public class SmsbDeviceErrorRecordServiceImpl implements ISmsbDeviceErrorRecordService {
+
+    private final SmsbDeviceErrorRecordMapper baseMapper;
+
+    /**
+     * 查询设备告警记录
+     *
+     * @param id 主键
+     * @return 设备告警记录
+     */
+    @Override
+    public SmsbDeviceErrorRecordVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询设备告警记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 设备告警记录分页列表
+     */
+    @Override
+    public TableDataInfo<SmsbDeviceErrorRecordVo> queryPageList(SmsbDeviceErrorRecordBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<SmsbDeviceErrorRecord> lqw = buildQueryWrapper(bo);
+        Page<SmsbDeviceErrorRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的设备告警记录列表
+     *
+     * @param bo 查询条件
+     * @return 设备告警记录列表
+     */
+    @Override
+    public List<SmsbDeviceErrorRecordVo> queryList(SmsbDeviceErrorRecordBo bo) {
+        LambdaQueryWrapper<SmsbDeviceErrorRecord> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<SmsbDeviceErrorRecord> buildQueryWrapper(SmsbDeviceErrorRecordBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<SmsbDeviceErrorRecord> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getDeviceId() != null, SmsbDeviceErrorRecord::getDeviceId, bo.getDeviceId());
+        lqw.like(StringUtils.isNotBlank(bo.getDeviceName()), SmsbDeviceErrorRecord::getDeviceName, bo.getDeviceName());
+        lqw.eq(bo.getErrorLevel() != null, SmsbDeviceErrorRecord::getErrorLevel, bo.getErrorLevel());
+        lqw.eq(bo.getErrorType() != null, SmsbDeviceErrorRecord::getErrorType, bo.getErrorType());
+        return lqw;
+    }
+
+    /**
+     * 新增设备告警记录
+     *
+     * @param bo 设备告警记录
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(SmsbDeviceErrorRecordBo bo) {
+        SmsbDeviceErrorRecord add = MapstructUtils.convert(bo, SmsbDeviceErrorRecord.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改设备告警记录
+     *
+     * @param bo 设备告警记录
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(SmsbDeviceErrorRecordBo bo) {
+        SmsbDeviceErrorRecord update = MapstructUtils.convert(bo, SmsbDeviceErrorRecord.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(SmsbDeviceErrorRecord entity) {
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除设备告警记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 20 - 1
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDeviceServiceImpl.java

@@ -2,6 +2,7 @@ package com.inspur.device.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.google.common.hash.Hashing;
@@ -10,7 +11,10 @@ import com.inspur.device.domain.SmsbDeviceRunInfo;
 import com.inspur.device.domain.bo.SmsbDeviceBo;
 import com.inspur.device.domain.constants.DeviceConstants;
 import com.inspur.device.domain.constants.ResultCodeEnum;
-import com.inspur.device.domain.vo.*;
+import com.inspur.device.domain.vo.DeviceStatisticsVo;
+import com.inspur.device.domain.vo.SmsbDeviceManufacturerVo;
+import com.inspur.device.domain.vo.SmsbDeviceRunInfoVo;
+import com.inspur.device.domain.vo.SmsbDeviceVo;
 import com.inspur.device.mapper.SmsbDeviceMapper;
 import com.inspur.device.mapper.SmsbDeviceRunInfoMapper;
 import com.inspur.device.service.ISmsbDeviceManufacturerService;
@@ -23,6 +27,7 @@ import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 
@@ -244,4 +249,18 @@ public class SmsbDeviceServiceImpl implements ISmsbDeviceService {
         result.setDeviceBase(deviceVo);
         return result;
     }
+
+    @Override
+    @CachePut(cacheNames = "global:msr:device:identifier", key = "#smsbDeviceVo.identifier")
+    public SmsbDeviceVo updateDeviceStatus(SmsbDeviceVo smsbDeviceVo) {
+        SmsbDeviceVo updateResult = getDeviceByIdentifier(smsbDeviceVo.getIdentifier());
+        updateResult.setOnlineStatus(smsbDeviceVo.getOnlineStatus());
+
+        baseMapper.update(null, new LambdaUpdateWrapper<SmsbDevice>()
+            .eq(SmsbDevice::getIdentifier, updateResult.getIdentifier())
+            .set(SmsbDevice::getOnlineStatus, updateResult.getOnlineStatus())
+            .set(SmsbDevice::getOfflineTime, updateResult.getOfflineTime())
+            .set(SmsbDevice::getLastOnline, updateResult.getLastOnline()));
+        return updateResult;
+    }
 }

+ 7 - 0
smsb-modules/smsb-device/src/main/resources/mapper/device/SmsbDeviceErrorRecordMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.inspur.device.mapper.SmsbDeviceErrorRecordMapper">
+
+</mapper>

+ 11 - 0
smsb-modules/smsb-job/pom.xml

@@ -28,6 +28,17 @@
             <artifactId>smsb-common-job</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.inspur</groupId>
+            <artifactId>smsb-device</artifactId>
+            <version>5.2.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.inspur</groupId>
+            <artifactId>smsb-common-redis</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 99 - 0
smsb-modules/smsb-job/src/main/java/org/dromara/job/snailjob/DeviceStatusReviewExecutor.java

@@ -0,0 +1,99 @@
+package org.dromara.job.snailjob;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
+import com.aizuda.snailjob.client.model.ExecuteResult;
+import com.aizuda.snailjob.common.log.SnailJobLog;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.inspur.device.domain.SmsbDeviceErrorRecord;
+import com.inspur.device.domain.constants.DeviceConstants;
+import com.inspur.device.domain.vo.SmsbDeviceVo;
+import com.inspur.device.mapper.SmsbDeviceErrorRecordMapper;
+import com.inspur.device.mapper.SmsbDeviceMapper;
+import com.inspur.device.service.ISmsbDeviceService;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author opensnail
+ * @date 2024-05-17
+ */
+@Component
+@JobExecutor(name = "deviceStatusReview")
+public class DeviceStatusReviewExecutor {
+
+    /**
+     * redis netty client last heartbeat key
+     */
+    public static final String DEVICE_LAST_HEART_PREFIX = "device:heart:last:";
+
+
+    @Autowired
+    private ISmsbDeviceService smsbDeviceService;
+
+    @Autowired
+    private SmsbDeviceMapper smsbDeviceMapper;
+
+    @Autowired
+    private SmsbDeviceErrorRecordMapper smsbDeviceErrorRecordMapper;
+
+
+    public ExecuteResult jobExecute() {
+        SnailJobLog.REMOTE.info("deviceStatusReview job start...");
+        Collection<String> keys = RedisUtils.keys(DEVICE_LAST_HEART_PREFIX + "*");
+        List<String> redisKeys = new ArrayList<>(keys);
+        if (CollectionUtil.isEmpty(redisKeys)) {
+            return ExecuteResult.success("deviceStatusReview job execute end , redis keys is empty");
+        }
+        Long nowTime = System.currentTimeMillis();
+        redisKeys.forEach(key -> {
+            Long lastHeartTime = RedisUtils.getCacheObject(key);
+            String identifier = key.replace(DEVICE_LAST_HEART_PREFIX, "");
+            SmsbDeviceVo smsbDeviceVo = smsbDeviceService.getDeviceByIdentifier(identifier);
+            SmsbDeviceErrorRecord errorRecord = new SmsbDeviceErrorRecord();
+            // 如果最后一次心跳已经超过60秒,设备离线 记录告警
+            if (nowTime - lastHeartTime > 60 * 1000 && smsbDeviceVo.getOnlineStatus() == DeviceConstants.DEVICE_STATUS_ONLINE.longValue()) {
+                // 设备离线
+                smsbDeviceVo.setOnlineStatus(DeviceConstants.DEVICE_STATUS_OFFLINE.longValue());
+                smsbDeviceVo.setOfflineTime(new Date());
+                smsbDeviceService.updateDeviceStatus(smsbDeviceVo);
+                SnailJobLog.REMOTE.info("update device: " + smsbDeviceVo.getId() + " to offline success !");
+                // 记录设备告警记录
+                buildErrorRecord(errorRecord, smsbDeviceVo, DeviceConstants.DEVICE_ERROR_LEVEL_URGENT, DeviceConstants.DEVICE_ERROR_OFFLINE);
+                smsbDeviceErrorRecordMapper.insert(errorRecord);
+            }
+            // 如果离线已经超时30分钟,且为第一次超时,记录超时告警
+            if (nowTime - lastHeartTime > 30 * 60 * 1000) {
+                // 判断最近一条告警是否为超时30分钟
+                SmsbDeviceErrorRecord lastErrorRecord = smsbDeviceErrorRecordMapper.selectOne(new LambdaQueryWrapper<SmsbDeviceErrorRecord>()
+                    .eq(SmsbDeviceErrorRecord::getDeviceId, smsbDeviceVo.getId())
+                    .orderByDesc(SmsbDeviceErrorRecord::getCreateTime).last("limit 1"));
+                if (lastErrorRecord == null || !lastErrorRecord.getErrorType().equals(DeviceConstants.DEVICE_ERROR_OFFLINE_THAN_30)) {
+                    // 记录设备告警记录
+                    buildErrorRecord(errorRecord, smsbDeviceVo, DeviceConstants.DEVICE_ERROR_LEVEL_URGENT, DeviceConstants.DEVICE_ERROR_OFFLINE_THAN_30);
+                    errorRecord.setId(null);
+                    smsbDeviceErrorRecordMapper.insert(errorRecord);
+                    SnailJobLog.REMOTE.info("record device: " + smsbDeviceVo.getId() + " to offline than 30 min success !");
+                }
+
+            }
+        });
+
+        SnailJobLog.REMOTE.info("deviceStatusReview job end...");
+        return ExecuteResult.success("deviceStatusReview job execute success");
+    }
+
+    private static void buildErrorRecord(SmsbDeviceErrorRecord errorRecord, SmsbDeviceVo smsbDeviceVo, Integer errorLevel, Integer errorType) {
+        errorRecord.setDeviceId(smsbDeviceVo.getId());
+        errorRecord.setDeviceName(smsbDeviceVo.getName());
+        errorRecord.setErrorLevel(errorLevel);
+        errorRecord.setErrorType(errorType);
+        errorRecord.setTenantId(smsbDeviceVo.getTenantId());
+    }
+}

+ 40 - 9
smsb-modules/smsb-netty/src/main/java/com/inspur/netty/handler/ConnectServerHandler.java

@@ -2,7 +2,13 @@ package com.inspur.netty.handler;
 
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.inspur.device.domain.SmsbDevice;
+import com.inspur.device.domain.SmsbDeviceErrorRecord;
+import com.inspur.device.domain.constants.DeviceConstants;
+import com.inspur.device.domain.vo.SmsbDeviceVo;
+import com.inspur.device.mapper.SmsbDeviceErrorRecordMapper;
 import com.inspur.device.mapper.SmsbDeviceMapper;
+import com.inspur.device.service.ISmsbDeviceService;
+import com.inspur.device.service.impl.SmsbDeviceServiceImpl;
 import com.inspur.netty.message.push.PushMessageType;
 import com.inspur.netty.message.receive.ReceiveMessageType;
 import com.inspur.netty.util.NettyConstants;
@@ -14,6 +20,7 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.utils.SpringUtils;
 import org.dromara.common.core.utils.StringUtils;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.nio.charset.Charset;
 import java.util.Date;
@@ -42,6 +49,16 @@ public class ConnectServerHandler extends ChannelInboundHandlerAdapter {
      */
     private static final SmsbDeviceMapper smsbDeviceMapper = SpringUtils.getBean(SmsbDeviceMapper.class);
 
+    /**
+     * error record mapper
+     */
+    private static final SmsbDeviceErrorRecordMapper smsbDeviceErrorRecordMapper = SpringUtils.getBean(SmsbDeviceErrorRecordMapper.class);
+
+    /**
+     * device service
+     */
+    private static final ISmsbDeviceService smsbDeviceService = SpringUtils.getBean(SmsbDeviceServiceImpl.class);
+
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
         String message = (String) msg;
@@ -74,22 +91,28 @@ public class ConnectServerHandler extends ChannelInboundHandlerAdapter {
         }
 
     }
-
     public static void updateDeviceOnlineStatue(String identifier, Integer onlineStatus) {
         log.info("ConnectServerHandler: update device identifier : " + identifier + " online status to : " + onlineStatus);
         try {
+            SmsbDeviceVo smsbDeviceVo = smsbDeviceService.getDeviceByIdentifier(identifier);
+            SmsbDeviceErrorRecord errorRecord = new SmsbDeviceErrorRecord();
             // 设备上线
             if (onlineStatus.equals(NettyConstants.DEVICE_ONLINE_STATUS)) {
-                smsbDeviceMapper.update(null, new LambdaUpdateWrapper<SmsbDevice>()
-                    .eq(SmsbDevice::getIdentifier, identifier)
-                    .set(SmsbDevice::getOnlineStatus, onlineStatus)
-                    .set(SmsbDevice::getLastOnline, new Date()));
+                // 设备离线
+                smsbDeviceVo.setOnlineStatus(DeviceConstants.DEVICE_STATUS_ONLINE.longValue());
+                smsbDeviceVo.setLastOnline(new Date());
+                smsbDeviceService.updateDeviceStatus(smsbDeviceVo);
+                // 记录设备告警记录
+                buildErrorRecord(errorRecord, smsbDeviceVo, DeviceConstants.DEVICE_ERROR_LEVEL_GENERAL,DeviceConstants.DEVICE_ERROR_ONLINE);
+                smsbDeviceErrorRecordMapper.insert(errorRecord);
             }else {
                 // 设备离线
-                smsbDeviceMapper.update(null, new LambdaUpdateWrapper<SmsbDevice>()
-                    .eq(SmsbDevice::getIdentifier, identifier)
-                    .set(SmsbDevice::getOnlineStatus, onlineStatus)
-                    .set(SmsbDevice::getOfflineTime, new Date()));
+                smsbDeviceVo.setOnlineStatus(DeviceConstants.DEVICE_STATUS_OFFLINE.longValue());
+                smsbDeviceVo.setOfflineTime(new Date());
+                smsbDeviceService.updateDeviceStatus(smsbDeviceVo);
+
+                buildErrorRecord(errorRecord, smsbDeviceVo, DeviceConstants.DEVICE_ERROR_LEVEL_URGENT,DeviceConstants.DEVICE_ERROR_OFFLINE);
+                smsbDeviceErrorRecordMapper.insert(errorRecord);
             }
         } catch (Exception e) {
             log.error("ConnectServerHandler: update remote device status error {}", e.getMessage());
@@ -97,6 +120,14 @@ public class ConnectServerHandler extends ChannelInboundHandlerAdapter {
 
     }
 
+    private static void buildErrorRecord(SmsbDeviceErrorRecord errorRecord, SmsbDeviceVo smsbDeviceVo, Integer errorLevel, Integer errorType) {
+        errorRecord.setDeviceId(smsbDeviceVo.getId());
+        errorRecord.setDeviceName(smsbDeviceVo.getName());
+        errorRecord.setErrorLevel(errorLevel);
+        errorRecord.setErrorType(errorType);
+        errorRecord.setTenantId(smsbDeviceVo.getTenantId());
+    }
+
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
         log.info("ConnectServerHandler:Server,exceptionCaught.ctx.close");

+ 8 - 2
smsb-modules/smsb-source/src/main/java/com/inspur/source/domain/SmsbItem.java

@@ -1,9 +1,10 @@
 package com.inspur.source.domain;
 
-import org.dromara.common.tenant.core.TenantEntity;
-import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
 
 import java.io.Serial;
 
@@ -52,5 +53,10 @@ public class SmsbItem extends TenantEntity {
      */
     private String createUser;
 
+    /**
+     * 删除标识 0-否 1-是
+     */
+    private Integer delFlag;
+
 
 }

+ 10 - 0
smsb-modules/smsb-source/src/main/java/com/inspur/source/mapper/SmsbItemMapper.java

@@ -3,11 +3,14 @@ package com.inspur.source.mapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.inspur.source.domain.SmsbItem;
 import com.inspur.source.domain.vo.ItemStatisticsVo;
+import com.inspur.source.domain.vo.SmsbItemPushVo;
 import com.inspur.source.domain.vo.SmsbItemVo;
 import com.inspur.source.domain.vo.SmsbMinioDataVo;
 import org.apache.ibatis.annotations.Param;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 
+import java.util.List;
+
 /**
  * 节目管理Mapper接口
  *
@@ -29,4 +32,11 @@ public interface SmsbItemMapper extends BaseMapperPlus<SmsbItem, SmsbItemVo> {
      * @return
      */
     Page<SmsbItemVo> selectListByFileId(@Param("page") Page<SmsbMinioDataVo> page,@Param("fileId") Long fileId);
+
+    /**
+     * 根据id查询节目推送信息
+     * @param itemId
+     * @return
+     */
+    List<SmsbItemPushVo> selectRunItemPushVoById(@Param("itemId") Long itemId);
 }

+ 18 - 3
smsb-modules/smsb-source/src/main/java/com/inspur/source/service/impl/SmsbItemServiceImpl.java

@@ -2,6 +2,7 @@ package com.inspur.source.service.impl;
 
 import cn.hutool.core.collection.CollectionUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.inspur.source.domain.SmsbItem;
@@ -11,6 +12,7 @@ import com.inspur.source.domain.bo.ItemFileRelReqBo;
 import com.inspur.source.domain.bo.ItemSplitUploadReqBo;
 import com.inspur.source.domain.bo.SmsbItemBo;
 import com.inspur.source.domain.vo.ItemStatisticsVo;
+import com.inspur.source.domain.vo.SmsbItemPushVo;
 import com.inspur.source.domain.vo.SmsbItemSplitScreenVo;
 import com.inspur.source.domain.vo.SmsbItemVo;
 import com.inspur.source.mapper.SmsbItemFileRelMapper;
@@ -18,6 +20,7 @@ import com.inspur.source.mapper.SmsbItemMapper;
 import com.inspur.source.mapper.SmsbItemSplitScreenMapper;
 import com.inspur.source.service.ISmsbItemService;
 import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.enums.HttpApiResult;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
@@ -113,6 +116,8 @@ public class SmsbItemServiceImpl implements ISmsbItemService {
         lqw.eq(StringUtils.isNotBlank(bo.getCreateUser()), SmsbItem::getCreateUser, bo.getCreateUser());
         lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
             SmsbItem::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
+        lqw.eq(SmsbItem::getDelFlag, 0);
+        lqw.orderByDesc(SmsbItem::getCreateTime);
         return lqw;
     }
 
@@ -130,6 +135,7 @@ public class SmsbItemServiceImpl implements ISmsbItemService {
         validEntityBeforeSave(add);
         add.setSourceNum(CollectionUtil.isEmpty(bo.getSelectedFiles()) ? 0 : bo.getSelectedFiles().size());
         add.setCreateUser(LoginHelper.getUsername());
+        add.setDelFlag(0);
         boolean flag = baseMapper.insert(add) > 0;
         if (!flag) {
             return null;
@@ -252,11 +258,20 @@ public class SmsbItemServiceImpl implements ISmsbItemService {
      * @return 是否删除成功
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
-        if (isValid) {
-            //TODO 做一些业务上的校验,判断是否需要校验
+        // 校验当前节目是否存在 审核中的内容发布
+        for (Long id : ids) {
+            List<SmsbItemPushVo> itemPushVo = baseMapper.selectRunItemPushVoById(id);
+            if (CollectionUtil.isNotEmpty(itemPushVo)) {
+                throw new ServiceException(id + HttpApiResult.PUSH_STATUS_IS_WAITING.getInfo(), HttpApiResult.PUSH_STATUS_IS_WAITING.getCode());
+            }
+            // 更新当前节目del_flag
+            UpdateWrapper<SmsbItem> updateWrapper = new UpdateWrapper<>();
+            updateWrapper.eq("id", id).set("del_flag", 1);
+            baseMapper.update(updateWrapper);
         }
-        return baseMapper.deleteByIds(ids) > 0;
+        return true;
     }
 
     @Override

+ 9 - 7
smsb-modules/smsb-source/src/main/java/com/inspur/source/service/impl/SmsbMinioDataServiceImpl.java

@@ -18,6 +18,7 @@ import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.SysOss;
 import org.dromara.system.domain.vo.SysOssVo;
 import org.dromara.system.mapper.SysOssMapper;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -38,17 +39,12 @@ import java.util.stream.Collectors;
 public class SmsbMinioDataServiceImpl implements ISmsbMinioDataService {
 
     private final SmsbMinioDataMapper baseMapper;
-
     private final SmsbSourceTreeRelMapper smsbSourceTreeRelMapper;
-
     private final SmsbSourceTreeMapper smsbSourceTreeMapper;
-
     private final SmsbMinioTransRecordMapper transRecordMapper;
-
     private final SmsbItemFileRelMapper smsbItemFileRelMapper;
-
     @Autowired
-    private SysOssMapper ossMapper;
+    private SysOssMapper sysOssMapper;
 
     /**
      * 查询文件资源
@@ -146,7 +142,7 @@ public class SmsbMinioDataServiceImpl implements ISmsbMinioDataService {
         }
         List<Long> sourceTreeIds = bo.getSourceTreeIds();
         for (String ossId : ossIdList) {
-            SysOssVo sysOssVo = ossMapper.selectVoById(ossId);
+            SysOssVo sysOssVo = sysOssMapper.selectVoById(ossId);
             // 创建add
             createSaveMinioData(sysOssVo, add);
             boolean flag = baseMapper.insert(add) > 0;
@@ -237,11 +233,17 @@ public class SmsbMinioDataServiceImpl implements ISmsbMinioDataService {
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
         // 逻辑删除  更新del_flag
         for (Long id : ids) {
+            SmsbMinioData minioData = baseMapper.selectById(id);
             UpdateWrapper<SmsbMinioData> updateWrapper = new UpdateWrapper<>();
             updateWrapper.eq("id", id).set("del_flag", 1);
             baseMapper.update(updateWrapper);
             // 删除与item的关联关系
             smsbItemFileRelMapper.delete(new LambdaQueryWrapper<SmsbItemFileRel>().eq(SmsbItemFileRel::getFileId, id));
+            // 删除sys_oss表里的数据 根据md5
+            String md5 = minioData.getMd5();
+            if (StringUtils.isNotBlank(md5)) {
+                sysOssMapper.delete(new LambdaQueryWrapper<SysOss>().eq(SysOss::getFileMd5, md5));
+            }
         }
         return true;
     }

+ 11 - 0
smsb-modules/smsb-source/src/main/resources/mapper/SmsbItemMapper.xml

@@ -18,6 +18,17 @@
         INNER JOIN
             (SELECT DISTINCT item_id FROM smsb_item_file_rel WHERE file_id = #{fileId}) rel
         ON it.id = rel.item_id
+        where it.del_flag = 0
+        order by it.create_time desc
+    </select>
+
+    <select id="selectRunItemPushVoById" parameterType="Long" resultType="com.inspur.source.domain.vo.SmsbItemPushVo">
+        SELECT
+            sip.*
+        from smsb_item_push sip
+        INNER JOIN smsb_item_push_rel sipr on sip.id = sipr.push_id
+        WHERE
+            sipr.item_id = #{itemId} and sip.status = 'waiting'
     </select>
 
 </mapper>

+ 63 - 0
smsb-plus-ui/src/api/smsb/device/errorRecord.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { DeviceErrorRecordVO, DeviceErrorRecordForm, DeviceErrorRecordQuery } from '@/api/smsb/device/errorRecord_type';
+
+/**
+ * 查询设备告警记录列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listDeviceErrorRecord = (query?: DeviceErrorRecordQuery): AxiosPromise<DeviceErrorRecordVO[]> => {
+  return request({
+    url: '/device/errorRecord/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询设备告警记录详细
+ * @param id
+ */
+export const getDeviceErrorRecord = (id: string | number): AxiosPromise<DeviceErrorRecordVO> => {
+  return request({
+    url: '/device/errorRecord/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增设备告警记录
+ * @param data
+ */
+export const addDeviceErrorRecord = (data: DeviceErrorRecordForm) => {
+  return request({
+    url: '/device/errorRecord',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改设备告警记录
+ * @param data
+ */
+export const updateDeviceErrorRecord = (data: DeviceErrorRecordForm) => {
+  return request({
+    url: '/device/errorRecord',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除设备告警记录
+ * @param id
+ */
+export const delDeviceErrorRecord = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/device/errorRecord/' + id,
+    method: 'delete'
+  });
+};

+ 80 - 0
smsb-plus-ui/src/api/smsb/device/errorRecord_type.ts

@@ -0,0 +1,80 @@
+export interface DeviceErrorRecordVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 设备ID
+   */
+  deviceId: string | number;
+
+  /**
+   * 设备名称
+   */
+  deviceName: string;
+
+  /**
+   * 告警等级
+   */
+  errorLevel: number;
+
+  /**
+   * 告警类型
+   */
+  errorType: number;
+}
+
+export interface DeviceErrorRecordForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 设备ID
+   */
+  deviceId?: string | number;
+
+  /**
+   * 设备名称
+   */
+  deviceName?: string;
+
+  /**
+   * 告警等级
+   */
+  errorLevel?: number;
+
+  /**
+   * 告警类型
+   */
+  errorType?: number;
+}
+
+export interface DeviceErrorRecordQuery extends PageQuery {
+  /**
+   * 设备ID
+   */
+  deviceId?: string | number;
+
+  /**
+   * 设备名称
+   */
+  deviceName?: string;
+
+  /**
+   * 告警等级
+   */
+  errorLevel?: number;
+
+  /**
+   * 告警类型
+   */
+  errorType?: number;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 242 - 0
smsb-plus-ui/src/views/smsb/dashboard/device.vue

@@ -0,0 +1,242 @@
+<template>
+  <el-container>
+    <el-header>
+      <el-card shadow="hover" style="margin-top: 10px">
+        <el-row justify="end" align="middle">
+          <el-col :span="19" style="text-align: right">
+            <el-radio-group v-model="timeRadio" size="large" @change="handleDateRangeChange">
+              <el-radio-button label="今日" value="today" />
+              <el-radio-button label="本周" value="week" />
+              <el-radio-button label="本月" value="month" />
+            </el-radio-group>
+          </el-col>
+          <el-col :span="5" style="text-align: right">
+            <el-date-picker
+              v-model="dateRange"
+              type="daterange"
+              range-separator="-"
+              start-placeholder="开始日期"
+              end-placeholder="结束日期"
+              style="margin-left: 10px; margin-right: 20px"
+            />
+          </el-col>
+        </el-row>
+      </el-card>
+    </el-header>
+
+    <el-main style="margin-top: 20px">
+      <el-row :gutter="20">
+        <el-col :span="6" v-for="(item, index) in stats" :key="index">
+          <el-card shadow="hover" class="stat-card">
+            <h3>{{ item.label }}</h3>
+            <p :class="['number', item.class]">{{ item.value }}</p>
+          </el-card>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20" style="margin-top: 20px">
+        <el-col :span="18">
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-card shadow="hover">
+                <h3>类型占比</h3>
+                <div ref="typePie" class="chart-placeholder"></div>
+              </el-card>
+            </el-col>
+            <el-col :span="8">
+              <el-card shadow="hover">
+                <h3>告警级别</h3>
+                <div class="chart-placeholder"></div>
+              </el-card>
+            </el-col>
+            <el-col :span="8">
+              <el-card shadow="hover">
+                <h3>告警问题统计</h3>
+                <div class="chart-placeholder"></div>
+              </el-card>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20" style="margin-top: 20px">
+            <el-col :span="12">
+              <el-card shadow="hover">
+                <h3>在线时长排行</h3>
+                <el-table :data="onlineTimeRanking" style="width: 100%">
+                  <el-table-column prop="rank" label="排名" width="60"></el-table-column>
+                  <el-table-column prop="device" label="设备名称"></el-table-column>
+                  <el-table-column prop="value" label="时长"></el-table-column>
+                </el-table>
+              </el-card>
+            </el-col>
+            <el-col :span="12">
+              <el-card shadow="hover">
+                <h3>告警数量排行</h3>
+                <el-table :data="alarmRanking" style="width: 100%">
+                  <el-table-column prop="rank" label="排名" width="60"></el-table-column>
+                  <el-table-column prop="device" label="设备名称"></el-table-column>
+                  <el-table-column prop="value" label="告警数量"></el-table-column>
+                </el-table>
+              </el-card>
+            </el-col>
+          </el-row>
+        </el-col>
+        <el-col :span="6">
+          <el-card shadow="hover">
+            <h3>告警清单</h3>
+            <el-table :data="alarmList" style="width: 100%">
+              <el-table-column prop="time" label="时间" width="160"></el-table-column>
+              <el-table-column prop="device" label="设备名称"></el-table-column>
+              <el-table-column prop="type" label="告警类型"></el-table-column>
+              <el-table-column prop="level" label="告警级别"></el-table-column>
+            </el-table>
+          </el-card>
+        </el-col>
+      </el-row>
+    </el-main>
+  </el-container>
+</template>
+
+<script setup lang="ts">
+import { reactive } from 'vue';
+import { deviceStatistics } from '@/api/smsb/device/device';
+import * as echarts from 'echarts';
+
+const timeRadio = ref('today');
+const dateRange = ref(['2025-01-01', '2025-01-01']);
+const typePie = ref();
+const stats = reactive([
+  { label: '设备总量', value: 0, class: '' },
+  { label: '已上线设备', value: 0, class: 'success' },
+  { label: '离线设备', value: 0, class: 'danger' },
+  { label: '待接入设备', value: 0, class: 'warning' }
+]);
+
+const onlineTimeRanking = reactive([
+  { rank: 1, device: '设备A', value: '6000' },
+  { rank: 2, device: '设备B', value: '5900' },
+  { rank: 3, device: '设备C', value: '5800' }
+]);
+
+const alarmRanking = reactive([
+  { rank: 1, device: '设备A', value: '6000' },
+  { rank: 2, device: '设备B', value: '5900' },
+  { rank: 3, device: '设备C', value: '5800' }
+]);
+
+const alarmList = reactive([
+  { time: '2025-02-20', device: '摄像头01', type: '紧急SOS', level: '设备报警' },
+  { time: '2025-02-20', device: '摄像头02', type: '紧急SOS', level: '设备报警' },
+  { time: '2025-02-20', device: '摄像头03', type: '紧急SOS', level: '设备报警' }
+]);
+
+const handleDateRangeChange = () => {
+  const rangeType = timeRadio.value;
+  const today = new Date();
+  const startDate = new Date();
+  const endDate = new Date();
+  switch (rangeType) {
+    case 'today':
+      break;
+    case 'week':
+      startDate.setDate(today.getDate() - 7);
+      break;
+    case 'month':
+      startDate.setMonth(today.getMonth() - 1);
+      break;
+    default:
+      throw new Error('Invalid range type');
+  }
+  dateRange.value = [formatDate(startDate), formatDate(endDate)];
+};
+
+const formatDate = (date: Date) => {
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, '0');
+  const day = String(date.getDate()).padStart(2, '0');
+  return `${year}-${month}-${day}`;
+};
+const getDeviceStatistics = async () => {
+  const res = await deviceStatistics();
+  stats.forEach((item) => {
+    switch (item.label) {
+      case '设备总量':
+        item.value = res.data.totalNum;
+        break;
+      case '已上线设备':
+        item.value = res.data.onlineNum;
+        break;
+      case '离线设备':
+        item.value = res.data.offlineNum;
+        break;
+      case '待接入设备':
+        item.value = res.data.initNum;
+        break;
+    }
+  });
+  const typePieInstance = echarts.init(typePie.value, 'macaroons');
+  typePieInstance.setOption({
+    title: {
+      text: '',
+      subtext: '',
+      left: 'center'
+    },
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'vertical',
+      left: 'left'
+    },
+    series: [
+      {
+        name: '类型占比',
+        type: 'pie',
+        radius: '60%',
+        data: [
+          { value: (res.data.onlineNum / res.data.totalNum) * 100, name: '在线' },
+          { value: (res.data.offlineNum / res.data.totalNum) * 100, name: '离线' },
+          { value: (res.data.initNum / res.data.totalNum) * 100, name: '待接入' }
+        ],
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowOffsetX: 0,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  });
+};
+onMounted(() => {
+  handleDateRangeChange();
+  getDeviceStatistics();
+});
+</script>
+
+<style scoped>
+.stat-card {
+  text-align: center;
+}
+
+.number {
+  font-size: 24px;
+  font-weight: bold;
+}
+
+.success {
+  color: green;
+}
+
+.danger {
+  color: red;
+}
+
+.warning {
+  color: orange;
+}
+
+.chart-placeholder {
+  height: 250px;
+  /*background: #f5f5f5;*/
+  border-radius: 8px;
+}
+</style>

+ 4 - 3
smsb-plus-ui/src/views/smsb/item/index.vue

@@ -151,7 +151,7 @@
             </el-table-column>
             <el-table-column label="播放时长">
               <template #default="{ row }">
-                <el-input-number v-model="row.duration" :min="1" :max="300"></el-input-number>
+                <el-input-number :disabled="row.type !== 1" v-model="row.duration" :min="1" :max="300"></el-input-number>
               </template>
             </el-table-column>
           </el-table>
@@ -344,7 +344,7 @@ const minioDataList = ref<MinioDataVO[]>([]);
 const dateRangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']);
 
 // 选中的文件
-const selectedFiles = ref<{ id: number; name: string; duration: number; order: number }[]>([]);
+const selectedFiles = ref<{ id: number; name: string; duration: number; order: number; type: number }[]>([]);
 const dialog = reactive<DialogOption>({
   visible: false,
   title: ''
@@ -452,7 +452,8 @@ const handleSelectionFile = (selection: MinioDataVO[]) => {
   selectedFiles.value = selection.map((item: any, index: number) => ({
     id: item.id,
     name: item.originalName,
-    duration: 10, // 默认播放时长 10s
+    type: item.type,
+    duration: item.type === 1 ? 10 : item.duration, // 默认播放时长 10s
     order: index + 1 // 默认排序号
   }));
 };

+ 8 - 4
smsb-plus-ui/src/views/smsb/minioData/index.vue

@@ -116,7 +116,10 @@
             </div>
             <div v-else-if="scope.row.type === 2">
               <!-- 视频类型 -->
-              <image-preview :src="scope.row.screenshot" style="width: 40px; height: 40px; cursor: pointer" @click="viewVideo(scope.row.url)" />
+              <!--
+                            <image-preview :src="scope.row.screenshot" style="width: 40px; height: 40px; cursor: pointer" @click="viewVideo(scope.row.url)" />
+              -->
+              <el-icon class="VideoPlay" @click="viewVideo(scope.row.screenshot)"></el-icon>
             </div>
           </template>
         </el-table-column>
@@ -138,9 +141,9 @@
         </el-table-column>
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
           <template #default="scope">
-<!--            <el-tooltip content="修改" placement="top">
-              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['source:minioData:edit']"></el-button>
-            </el-tooltip>-->
+            <!--            <el-tooltip content="修改" placement="top">
+                          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['source:minioData:edit']"></el-button>
+                        </el-tooltip>-->
             <el-tooltip content="删除" placement="top">
               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['source:minioData:remove']"></el-button>
             </el-tooltip>
@@ -363,6 +366,7 @@ const { dialogQueryParams } = toRefs(itemData);
 // 播放视频
 const viewVideo = (url: string) => {
   videoUrl.value = url;
+  console.log(videoUrl.value);
   videoDialogVisible.value = true;
 };