Jelajahi Sumber

feat:1、设备日志抓取命令下发2、停止抓取指令3、查看抓取结果,支持文件下载4、前端上报日志文件接口

lihao16 3 bulan lalu
induk
melakukan
ba1bd0fd55
16 mengubah file dengan 1229 tambahan dan 12 penghapusan
  1. 105 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/controller/SmsbDeviceLogPushController.java
  2. 99 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/SmsbDeviceLogPush.java
  3. 78 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/bo/SmsbDeviceLogPushBo.java
  4. 6 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/constants/DeviceTaskConstants.java
  5. 90 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/vo/SmsbDeviceLogPushVo.java
  6. 15 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/mapper/SmsbDeviceLogPushMapper.java
  7. 78 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDeviceLogPushService.java
  8. 200 0
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDeviceLogPushServiceImpl.java
  9. 0 4
      smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDeviceTaskServiceImpl.java
  10. 7 0
      smsb-modules/smsb-device/src/main/resources/mapper/device/SmsbDeviceLogPushMapper.xml
  11. 55 7
      smsb-modules/smsb-netty/src/main/java/com/inspur/netty/controller/DeviceController.java
  12. 11 1
      smsb-modules/smsb-netty/src/main/java/com/inspur/netty/message/push/PushMessageType.java
  13. 14 0
      smsb-modules/smsb-source/src/main/java/com/inspur/source/controller/SmsbFrontController.java
  14. 70 0
      smsb-plus-ui/src/api/smsb/device/logPush/api.ts
  15. 136 0
      smsb-plus-ui/src/api/smsb/device/logPush/types.ts
  16. 265 0
      smsb-plus-ui/src/views/smsb/deviceLogPush/index.vue

+ 105 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/controller/SmsbDeviceLogPushController.java

@@ -0,0 +1,105 @@
+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.SmsbDeviceLogPushVo;
+import com.inspur.device.domain.bo.SmsbDeviceLogPushBo;
+import com.inspur.device.service.ISmsbDeviceLogPushService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 日志抓取
+ *
+ * @author Hao Li
+ * @date 2025-07-15
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/device/logPush")
+public class SmsbDeviceLogPushController extends BaseController {
+
+    private final ISmsbDeviceLogPushService smsbDeviceLogPushService;
+
+    /**
+     * 查询日志抓取列表
+     */
+    @SaCheckPermission("device:log:list")
+    @GetMapping("/list")
+    public TableDataInfo<SmsbDeviceLogPushVo> list(SmsbDeviceLogPushBo bo, PageQuery pageQuery) {
+        return smsbDeviceLogPushService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出日志抓取列表
+     */
+    @SaCheckPermission("device:log:export")
+    @Log(title = "日志抓取", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SmsbDeviceLogPushBo bo, HttpServletResponse response) {
+        List<SmsbDeviceLogPushVo> list = smsbDeviceLogPushService.queryList(bo);
+        ExcelUtil.exportExcel(list, "日志抓取", SmsbDeviceLogPushVo.class, response);
+    }
+
+    /**
+     * 获取日志抓取详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("device:log:query")
+    @GetMapping("/{id}")
+    public R<SmsbDeviceLogPushVo> getInfo(@NotNull(message = "主键不能为空")
+                                          @PathVariable Long id) {
+        return R.ok(smsbDeviceLogPushService.queryById(id));
+    }
+
+    /**
+     * 新增日志抓取
+     */
+    @SaCheckPermission("device:log:add")
+    @Log(title = "日志抓取", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SmsbDeviceLogPushBo bo) {
+        return toAjax(smsbDeviceLogPushService.insertByBo(bo));
+    }
+
+    /**
+     * 修改日志抓取
+     */
+    @SaCheckPermission("device:log:edit")
+    @Log(title = "日志抓取", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SmsbDeviceLogPushBo bo) {
+        return toAjax(smsbDeviceLogPushService.updateByBo(bo));
+    }
+
+    /**
+     * 删除日志抓取
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("device:log:remove")
+    @Log(title = "日志抓取", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ids) {
+        return toAjax(smsbDeviceLogPushService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 99 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/SmsbDeviceLogPush.java

@@ -0,0 +1,99 @@
+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 java.io.Serial;
+import java.util.Date;
+
+/**
+ * 日志抓取对象 smsb_device_log_push
+ *
+ * @author Hao Li
+ * @date 2025-07-15
+ */
+@Data
+@TableName("smsb_device_log_push")
+public class SmsbDeviceLogPush {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 设备ID
+     */
+    private Long deviceId;
+
+    /**
+     * 设备名称
+     */
+    private String deviceName;
+
+    /**
+     * 设备标识符
+     */
+    private String identifier;
+
+    /**
+     * 抓取时长,单位秒
+     */
+    private Integer duration;
+
+    /**
+     * 抓取命令
+     */
+    private String logCmd;
+
+    /**
+     * 是否重启 0-否1-是
+     */
+    private String isReboot;
+
+    /**
+     * 抓取状态 0-未开始1-进行中2-完成3-失败
+     */
+    private Integer logStatus;
+
+    /**
+     * 日志文件
+     */
+    private String logFile;
+
+    /**
+     * 完成时间
+     */
+    private Date finishTime;
+
+    /**
+     * 创建人
+     */
+    private String createUser;
+
+    /**
+     * 租户编号
+     */
+    private String tenantId;
+
+    /**
+     * 创建者
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Long createBy;
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+
+}

+ 78 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/bo/SmsbDeviceLogPushBo.java

@@ -0,0 +1,78 @@
+package com.inspur.device.domain.bo;
+
+import com.inspur.device.domain.SmsbDeviceLogPush;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.util.Date;
+
+/**
+ * 日志抓取业务对象 smsb_device_log_push
+ *
+ * @author Hao Li
+ * @date 2025-07-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SmsbDeviceLogPush.class, reverseConvertGenerate = false)
+public class SmsbDeviceLogPushBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 设备ID
+     */
+    private Long deviceId;
+
+    /**
+     * 设备名称
+     */
+    private String deviceName;
+
+    /**
+     * 设备标识符
+     */
+    private String identifier;
+
+    /**
+     * 抓取时长,单位秒
+     */
+    private Integer duration;
+
+    /**
+     * 抓取命令
+     */
+    private String logCmd;
+
+    /**
+     * 是否重启 0-否1-是
+     */
+    private String isReboot;
+
+    /**
+     * 抓取状态 0-未开始 1-进行中 2-完成 3-失败
+     */
+    private Integer logStatus;
+
+    /**
+     * 日志文件
+     */
+    private String logFile;
+
+    /**
+     * 完成时间
+     */
+    private Date finishTime;
+
+    /**
+     * 创建人
+     */
+    private String createUser;
+
+
+}

+ 6 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/constants/DeviceTaskConstants.java

@@ -55,6 +55,12 @@ public class DeviceTaskConstants {
     /** 播放上一个资源 */
     public static final Integer DEVICE_PLAY_LAST = 1016;
 
+    /** 设备日志抓取开始 */
+    public static final Integer DEVICE_LOG_PUSH_START = 1017;
+
+    /** 设备日志抓取结束 */
+    public static final Integer DEVICE_LOG_PUSH_END = 1018;
+
     /** 设备任务类型 end */
 
     /** 设备任务状态 begin */

+ 90 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/domain/vo/SmsbDeviceLogPushVo.java

@@ -0,0 +1,90 @@
+package com.inspur.device.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.inspur.device.domain.SmsbDeviceLogPush;
+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;
+import java.util.Date;
+
+
+/**
+ * 日志抓取视图对象 smsb_device_log_push
+ *
+ * @author Hao Li
+ * @date 2025-07-15
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SmsbDeviceLogPush.class)
+public class SmsbDeviceLogPushVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 设备名称
+     */
+    @ExcelProperty(value = "设备名称")
+    private String deviceName;
+
+    /**
+     * 抓取时长,单位秒
+     */
+    @ExcelProperty(value = "抓取时长,单位秒")
+    private Integer duration;
+
+    /**
+     * 抓取命令
+     */
+    @ExcelProperty(value = "抓取命令")
+    private String logCmd;
+
+    /**
+     * 是否重启 0-否1-是
+     */
+    @ExcelProperty(value = "是否重启 0-否1-是", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "smsb_yes_no")
+    private String isReboot;
+
+    /**
+     * 抓取状态 0-未开始1-进行中2-完成3-失败
+     */
+    @ExcelProperty(value = "抓取状态")
+    private Integer logStatus;
+
+    /**
+     * 日志文件
+     */
+    @ExcelProperty(value = "日志文件")
+    private String logFile;
+
+    /**
+     * 完成时间
+     */
+    @ExcelProperty(value = "完成时间")
+    private Date finishTime;
+
+    /**
+     * 创建人
+     */
+    @ExcelProperty(value = "创建人")
+    private String createUser;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+
+}

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

@@ -0,0 +1,15 @@
+package com.inspur.device.mapper;
+
+import com.inspur.device.domain.SmsbDeviceLogPush;
+import com.inspur.device.domain.vo.SmsbDeviceLogPushVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 日志抓取Mapper接口
+ *
+ * @author Hao Li
+ * @date 2025-07-15
+ */
+public interface SmsbDeviceLogPushMapper extends BaseMapperPlus<SmsbDeviceLogPush, SmsbDeviceLogPushVo> {
+
+}

+ 78 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/ISmsbDeviceLogPushService.java

@@ -0,0 +1,78 @@
+package com.inspur.device.service;
+
+import com.inspur.device.domain.vo.SmsbDeviceLogPushVo;
+import com.inspur.device.domain.bo.SmsbDeviceLogPushBo;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 日志抓取Service接口
+ *
+ * @author Hao Li
+ * @date 2025-07-15
+ */
+public interface ISmsbDeviceLogPushService {
+
+    /**
+     * 查询日志抓取
+     *
+     * @param id 主键
+     * @return 日志抓取
+     */
+    SmsbDeviceLogPushVo queryById(Long id);
+
+    /**
+     * 分页查询日志抓取列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 日志抓取分页列表
+     */
+    TableDataInfo<SmsbDeviceLogPushVo> queryPageList(SmsbDeviceLogPushBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的日志抓取列表
+     *
+     * @param bo 查询条件
+     * @return 日志抓取列表
+     */
+    List<SmsbDeviceLogPushVo> queryList(SmsbDeviceLogPushBo bo);
+
+    /**
+     * 新增日志抓取
+     *
+     * @param bo 日志抓取
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(SmsbDeviceLogPushBo bo);
+
+    /**
+     * 修改日志抓取
+     *
+     * @param bo 日志抓取
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(SmsbDeviceLogPushBo bo);
+
+    /**
+     * 校验并批量删除日志抓取信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 设备日志上传
+      * @param identifier 设备标识
+      * @param file 日志文件
+      * @return 上传结果
+     */
+    R<Void> deviceLogUpload(String identifier, MultipartFile file);
+}

+ 200 - 0
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDeviceLogPushServiceImpl.java

@@ -0,0 +1,200 @@
+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.SmsbDeviceLogPush;
+import com.inspur.device.domain.bo.SmsbDeviceLogPushBo;
+import com.inspur.device.domain.constants.ResultCodeEnum;
+import com.inspur.device.domain.vo.SmsbDeviceLogPushVo;
+import com.inspur.device.domain.vo.SmsbDeviceVo;
+import com.inspur.device.mapper.SmsbDeviceLogPushMapper;
+import com.inspur.device.service.ISmsbDeviceLogPushService;
+import com.inspur.device.service.ISmsbDeviceService;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+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.dromara.common.oss.core.OssClient;
+import org.dromara.common.oss.entity.UploadResult;
+import org.dromara.common.oss.factory.OssFactory;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 日志抓取Service业务层处理
+ *
+ * @author Hao Li
+ * @date 2025-07-15
+ */
+@RequiredArgsConstructor
+@Service
+public class SmsbDeviceLogPushServiceImpl implements ISmsbDeviceLogPushService {
+
+    private final SmsbDeviceLogPushMapper baseMapper;
+    private final ISmsbDeviceService smsbDeviceService;
+    @Value("${server.minio.ip}")
+    private String minioServerIp;
+
+    /**
+     * 查询日志抓取
+     *
+     * @param id 主键
+     * @return 日志抓取
+     */
+    @Override
+    public SmsbDeviceLogPushVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询日志抓取列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 日志抓取分页列表
+     */
+    @Override
+    public TableDataInfo<SmsbDeviceLogPushVo> queryPageList(SmsbDeviceLogPushBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<SmsbDeviceLogPush> lqw = buildQueryWrapper(bo);
+        Page<SmsbDeviceLogPushVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的日志抓取列表
+     *
+     * @param bo 查询条件
+     * @return 日志抓取列表
+     */
+    @Override
+    public List<SmsbDeviceLogPushVo> queryList(SmsbDeviceLogPushBo bo) {
+        LambdaQueryWrapper<SmsbDeviceLogPush> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<SmsbDeviceLogPush> buildQueryWrapper(SmsbDeviceLogPushBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<SmsbDeviceLogPush> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getDeviceId() != null, SmsbDeviceLogPush::getDeviceId, bo.getDeviceId());
+        lqw.like(StringUtils.isNotBlank(bo.getDeviceName()), SmsbDeviceLogPush::getDeviceName, bo.getDeviceName());
+        lqw.eq(StringUtils.isNotBlank(bo.getIdentifier()), SmsbDeviceLogPush::getIdentifier, bo.getIdentifier());
+        lqw.eq(bo.getLogStatus() != null, SmsbDeviceLogPush::getLogStatus, bo.getLogStatus());
+        lqw.orderByDesc(SmsbDeviceLogPush::getId);
+        return lqw;
+    }
+
+    /**
+     * 新增日志抓取
+     *
+     * @param bo 日志抓取
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(SmsbDeviceLogPushBo bo) {
+        // 1 查询设备信息
+        SmsbDeviceVo smsbDeviceVo = smsbDeviceService.getDeviceCacheById(bo.getDeviceId());
+        if (smsbDeviceVo == null) {
+            return false;
+        }
+        SmsbDeviceLogPush add = MapstructUtils.convert(bo, SmsbDeviceLogPush.class);
+        add.setDeviceName(smsbDeviceVo.getName());
+        add.setIdentifier(smsbDeviceVo.getIdentifier());
+        add.setCreateUser(LoginHelper.getLoginUser().getNickname());
+        add.setLogStatus(0);
+        // 1 数据库保存抓取记录
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改日志抓取
+     *
+     * @param bo 日志抓取
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(SmsbDeviceLogPushBo bo) {
+        SmsbDeviceLogPush update = MapstructUtils.convert(bo, SmsbDeviceLogPush.class);
+        if (null == update.getId()) {
+            // 无数据ID,根据设备查询出当前设备最新的一条记录
+            update = baseMapper.selectOne(new LambdaQueryWrapper<SmsbDeviceLogPush>()
+                .eq(SmsbDeviceLogPush::getDeviceId, update.getDeviceId())
+                .orderByDesc(SmsbDeviceLogPush::getId)
+                .last("limit 1"));
+            if (update != null && update.getLogStatus() != 2) {
+                update.setLogStatus(3);
+            }
+            if (update == null) {
+                return true;
+            }
+        }
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(SmsbDeviceLogPush entity) {
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除日志抓取信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    @Override
+    public R<Void> deviceLogUpload(String identifier, MultipartFile file) {
+        // 根据identifier获取设备信息
+        SmsbDeviceVo smsbDeviceVo = smsbDeviceService.getDeviceByIdentifier(identifier);
+        // 文件上传
+        String originalfileName = file.getOriginalFilename();
+        String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
+        OssClient storage = OssFactory.instance();
+        UploadResult uploadResult;
+        try {
+            uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
+        } catch (IOException e) {
+            return R.fail(Integer.parseInt(ResultCodeEnum.UPLOAD_FAIL.getValue()), ResultCodeEnum.UPLOAD_FAIL.getMessage());
+        }
+        String logFileUrl = uploadResult.getUrl().replace("http://127.0.0.1:20002/",minioServerIp);
+        // 根据设备ID 查询出该设备最近的一次日志抓取记录
+        SmsbDeviceLogPush smsbDeviceLogPush = baseMapper.selectOne(new LambdaQueryWrapper<SmsbDeviceLogPush>()
+            .eq(SmsbDeviceLogPush::getDeviceId, smsbDeviceVo.getId())
+            .orderByDesc(SmsbDeviceLogPush::getId)
+            .last("limit 1"));
+        if (smsbDeviceLogPush == null) {
+            return R.ok();
+        }
+        smsbDeviceLogPush.setLogFile(logFileUrl);
+        smsbDeviceLogPush.setLogStatus(2);
+        smsbDeviceLogPush.setFinishTime(new Date());
+        // 更新数据
+        baseMapper.updateById(smsbDeviceLogPush);
+        return R.ok();
+    }
+}

+ 0 - 4
smsb-modules/smsb-device/src/main/java/com/inspur/device/service/impl/SmsbDeviceTaskServiceImpl.java

@@ -186,7 +186,6 @@ public class SmsbDeviceTaskServiceImpl implements ISmsbDeviceTaskService {
      * 保存前的数据校验
      */
     private void validEntityBeforeSave(SmsbDeviceTask entity) {
-        //TODO 做一些数据校验,如唯一约束
     }
 
     /**
@@ -198,9 +197,6 @@ public class SmsbDeviceTaskServiceImpl implements ISmsbDeviceTaskService {
      */
     @Override
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
-        if (isValid) {
-            //TODO 做一些业务上的校验,判断是否需要校验
-        }
         return baseMapper.deleteByIds(ids) > 0;
     }
 }

+ 7 - 0
smsb-modules/smsb-device/src/main/resources/mapper/device/SmsbDeviceLogPushMapper.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.SmsbDeviceLogPushMapper">
+
+</mapper>

+ 55 - 7
smsb-modules/smsb-netty/src/main/java/com/inspur/netty/controller/DeviceController.java

@@ -3,14 +3,12 @@ package com.inspur.netty.controller;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
+import com.inspur.device.domain.bo.SmsbDeviceLogPushBo;
 import com.inspur.device.domain.bo.SmsbOtaRecordBo;
 import com.inspur.device.domain.constants.DeviceConstants;
 import com.inspur.device.domain.constants.DeviceTaskConstants;
 import com.inspur.device.domain.vo.SmsbDeviceVo;
-import com.inspur.device.service.ISmsbDevicePowerScheduleService;
-import com.inspur.device.service.ISmsbDeviceService;
-import com.inspur.device.service.ISmsbDeviceTaskService;
-import com.inspur.device.service.ISmsbOtaRecordService;
+import com.inspur.device.service.*;
 import com.inspur.netty.message.push.PushMessageType;
 import com.inspur.netty.util.NettyConstants;
 import com.inspur.netty.util.PushMsgUtil;
@@ -39,15 +37,14 @@ public class DeviceController {
 
     @Autowired
     private ISmsbDeviceService smsbDeviceService;
-
     @Autowired
     private ISmsbOtaRecordService smsbOtaRecordService;
-
     @Autowired
     private ISmsbDeviceTaskService deviceTaskService;
-
     @Autowired
     private ISmsbDevicePowerScheduleService smsbDevicePowerScheduleService;
+    @Autowired
+    private ISmsbDeviceLogPushService smsbDeviceLogPushService;
 
     /**
      * 重启设备
@@ -381,4 +378,55 @@ public class DeviceController {
         return addResult;
     }
 
+    /**
+     * 设备日志开启抓取
+     *
+     * @param bo
+     * @return
+     */
+    @PostMapping("/logPush/start")
+    public R<Void> logPushStart(@RequestBody SmsbDeviceLogPushBo bo) {
+        boolean insertResult = smsbDeviceLogPushService.insertByBo(bo);
+        if (!insertResult) {
+            return R.fail("数据库新增失败");
+        }
+        // 2 任务中心创建任务
+        JSONObject taskParamJson = new JSONObject();
+        taskParamJson.set("logCmd", bo.getLogCmd());
+        taskParamJson.set("duration", bo.getDuration());
+        taskParamJson.set("isReboot", bo.getIsReboot());
+        String taskParam = taskParamJson.toString();
+        SmsbDeviceVo deviceVo = smsbDeviceService.getDeviceCacheById(bo.getDeviceId());
+        deviceTaskService.createNewDeviceTask(DeviceTaskConstants.DEVICE_LOG_PUSH_START, deviceVo, taskParam);
+        // 3 组装日志抓取命令 发送长连接
+        String logPushCmd = deviceVo.getIdentifier() + PushMessageType.DEVICE_LOG_PUSH_START.getValue() + NettyConstants.DATA_PACK_SEPARATOR;
+        boolean isSend = PushMsgUtil.sendV2(deviceVo.getIdentifier(), logPushCmd);
+        return isSend ? R.ok() : R.fail("长连接发送失败,设备长连接已断开");
+    }
+
+    /**
+     * 设备日志开启抓取
+     *
+     * @param deviceId
+     * @return
+     */
+    @GetMapping("/logPush/stop/{deviceId}")
+    public R<Void> logPushStop(@PathVariable Long deviceId) {
+        SmsbDeviceLogPushBo bo = new SmsbDeviceLogPushBo();
+        bo.setDeviceId(deviceId);
+        bo.setLogStatus(3);
+        boolean insertResult = smsbDeviceLogPushService.updateByBo(bo);
+        if (!insertResult) {
+            return R.fail("数据库更新失败");
+        }
+        // 2 任务中心创建任务
+        SmsbDeviceVo deviceVo = smsbDeviceService.getDeviceCacheById(bo.getDeviceId());
+        String taskParam = PushMessageType.DEVICE_LOG_PUSH_END.getValue();
+        deviceTaskService.createNewDeviceTask(DeviceTaskConstants.DEVICE_LOG_PUSH_END, deviceVo, taskParam);
+        // 3 组装日志抓取命令 发送长连接
+        String logPushCmd = deviceVo.getIdentifier() + PushMessageType.DEVICE_LOG_PUSH_END.getValue() + NettyConstants.DATA_PACK_SEPARATOR;
+        boolean isSend = PushMsgUtil.sendV2(deviceVo.getIdentifier(), logPushCmd);
+        return isSend ? R.ok() : R.fail("长连接发送失败,设备长连接已断开");
+    }
+
 }

+ 11 - 1
smsb-modules/smsb-netty/src/main/java/com/inspur/netty/message/push/PushMessageType.java

@@ -110,7 +110,17 @@ public enum PushMessageType {
     /**
      * 播放上一个
      */
-    CONTROL_PLAY_LAST("/play/last");
+    CONTROL_PLAY_LAST("/play/last"),
+
+    /**
+     * 设备日志开始抓取
+     */
+    DEVICE_LOG_PUSH_START("/log/push/start"),
+
+    /**
+     * 设备日志停止抓取
+     */
+    DEVICE_LOG_PUSH_END("/log/push/end");
 
     private String value;
 

+ 14 - 0
smsb-modules/smsb-source/src/main/java/com/inspur/source/controller/SmsbFrontController.java

@@ -50,6 +50,8 @@ public class SmsbFrontController {
     private ISmsbDeviceChatKeyService smsbDeviceChatKeyService;
     @Autowired
     private ISmsbVisitorsFlowRateService smsbVisitorsFlowRateService;
+    @Autowired
+    private ISmsbDeviceLogPushService smsbDeviceLogPushService;
 
     /**
      * 根据设备identifier 获取该设备最新内容下发记录
@@ -223,4 +225,16 @@ public class SmsbFrontController {
         return smsbVisitorsFlowRateService.visitorsUpload(identifier,personNum);
     }
 
+    /**
+     * 前端设备人流量上报
+     *
+     * @param identifier
+     * @param file
+     */
+    @SaIgnore
+    @GetMapping("/deviceLog/upload")
+    public R<Void> deviceLogUpload(@RequestParam("identifier") String identifier,@RequestBody MultipartFile file) {
+        return smsbDeviceLogPushService.deviceLogUpload(identifier,file);
+    }
+
 }

+ 70 - 0
smsb-plus-ui/src/api/smsb/device/logPush/api.ts

@@ -0,0 +1,70 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { DeviceLogPushVO, DeviceLogPushForm, DeviceLogPushQuery } from '@/api/smsb/device/logPush/types';
+
+/**
+ * 查询日志抓取列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listDeviceLogPush = (query?: DeviceLogPushQuery): AxiosPromise<DeviceLogPushVO[]> => {
+  return request({
+    url: '/device/logPush/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询日志抓取详细
+ * @param id
+ */
+export const getDeviceLogPush = (id: string | number): AxiosPromise<DeviceLogPushVO> => {
+  return request({
+    url: '/device/logPush/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增日志抓取
+ * @param data
+ */
+export const addDeviceLogPush = (data: DeviceLogPushForm) => {
+  return request({
+    url: '/netty/device/logPush/start',
+    method: 'post',
+    data: data
+  });
+};
+
+export const stopDeviceLogPush = (id: string | number) => {
+  return request({
+    url: '/netty/device/logPush/stop/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 修改日志抓取
+ * @param data
+ */
+export const updateDeviceLogPush = (data: DeviceLogPushForm) => {
+  return request({
+    url: '/device/logPush',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除日志抓取
+ * @param id
+ */
+export const delDeviceLogPush = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/device/logPush/' + id,
+    method: 'delete'
+  });
+};

+ 136 - 0
smsb-plus-ui/src/api/smsb/device/logPush/types.ts

@@ -0,0 +1,136 @@
+export interface DeviceLogPushVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 设备名称
+   */
+  deviceName: string;
+
+  /**
+   * 抓取时长,单位秒
+   */
+  duration: number;
+
+  /**
+   * 抓取命令
+   */
+  logCmd: string;
+
+  /**
+   * 是否重启 0-否1-是
+   */
+  isReboot: string;
+
+  /**
+   * 抓取状态
+   */
+  logStatus: number;
+
+  /**
+   * 日志文件
+   */
+  logFile: string;
+
+  /**
+   * 完成时间
+   */
+  finishTime: string;
+
+  /**
+   * 创建人
+   */
+  createUser: string;
+
+}
+
+export interface DeviceLogPushForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 设备ID
+   */
+  deviceId?: string | number;
+
+  /**
+   * 设备名称
+   */
+  deviceName?: string;
+
+  /**
+   * 设备标识符
+   */
+  identifier?: string | number;
+
+  /**
+   * 抓取时长,单位秒
+   */
+  duration?: number;
+
+  /**
+   * 抓取命令
+   */
+  logCmd?: string;
+
+  /**
+   * 是否重启 0-否1-是
+   */
+  isReboot?: string;
+
+  /**
+   * 抓取状态
+   */
+  logStatus?: number;
+
+  /**
+   * 日志文件
+   */
+  logFile?: string;
+
+  /**
+   * 完成时间
+   */
+  finishTime?: string;
+
+  /**
+   * 创建人
+   */
+  createUser?: string;
+
+}
+
+export interface DeviceLogPushQuery extends PageQuery {
+
+  /**
+   * 设备ID
+   */
+  deviceId?: string | number;
+
+  /**
+   * 设备名称
+   */
+  deviceName?: string;
+
+  /**
+   * 设备标识符
+   */
+  identifier?: string | number;
+
+  /**
+   * 抓取状态
+   */
+  logStatus?: number;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}
+
+
+

+ 265 - 0
smsb-plus-ui/src/views/smsb/deviceLogPush/index.vue

@@ -0,0 +1,265 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+                :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]" :style="{ marginTop: '10px', height: '60px' }">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="设备名称" prop="name">
+              <el-input v-model="queryParams.name" placeholder="请输入设备名称" clearable @keyup.enter="handleQuery"/>
+            </el-form-item>
+            <el-form-item label="设备SN" prop="serialNumber">
+              <el-input v-model="queryParams.serialNumber" placeholder="请输入设备SN" clearable @keyup.enter="handleQuery"/>
+            </el-form-item>
+            <el-form-item label="MAC" prop="mac">
+              <el-input v-model="queryParams.mac" placeholder="请输入设备MAC" clearable @keyup.enter="handleQuery"/>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <div class="table-content">
+        <el-table v-loading="loading" :data="deviceList">
+          <el-table-column label="" align="left" prop="" width="10"/>
+          <el-table-column label="设备ID" align="left" prop="id" width="175" v-if="true"/>
+          <el-table-column label="设备标识" align="left" width="250" prop="identifier"/>
+          <el-table-column label="设备名称" align="left" prop="name" :show-overflow-tooltip="true"/>
+          <el-table-column label="设备SN" align="left" prop="serialNumber" :show-overflow-tooltip="true"/>
+          <el-table-column label="设备MAC" align="left" prop="mac" width="220"/>
+          <el-table-column label="在线状态" width="150" align="center" prop="onlineStatus">
+            <template #default="scope">
+              <dict-tag :options="sys_device_online" :value="scope.row.onlineStatus"/>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="300" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+              <el-tooltip content="开始抓取" placement="top">
+                <el-button link type="primary" icon="Refresh" @click="handleStartLog(scope.row)"
+                           v-hasPermi="['device:log:add']">开始抓取</el-button>
+              </el-tooltip>
+              <el-tooltip content="结束抓取" placement="top">
+                <el-button link type="primary" icon="Close" @click="handleEndLog(scope.row)"
+                           v-hasPermi="['device:log:edit']">结束抓取</el-button>
+              </el-tooltip>
+              <el-tooltip content="抓取结果" placement="top">
+                <el-button link type="primary" icon="CircleCheck" @click="handleLogResult(scope.row)"
+                           v-hasPermi="['device:log:list']">抓取结果</el-button>
+              </el-tooltip>
+<!--              <el-tooltip content="设备心跳" placement="top">
+                <el-button link type="primary" icon="Clock" @click="handleHeartbeat(scope.row)"
+                           v-hasPermi="['device:log:list']">设备心跳</el-button>
+              </el-tooltip>-->
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                  v-model:limit="queryParams.pageSize" @pagination="getList"/>
+    </el-card>
+    <!-- 添加或修改设备对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="700px" append-to-body>
+      <el-form ref="deviceFormRef" :model="form" :rules="rules" label-width="70px">
+        <el-form-item label="抓取时长" prop="duration">
+          <el-input-number :min="60" v-model="form.duration" placeholder="请输入抓取时长" clearable/>
+        </el-form-item>
+        <el-form-item label="抓取命令" prop="logCmd">
+          <el-input type="textarea" v-model="form.logCmd" :rows="5" placeholder="请输入抓取命令" clearable/>
+        </el-form-item>
+        <el-form-item label="是否重启" prop="isReboot">
+          <el-radio-group v-model="form.isReboot">
+            <el-radio label="0">否</el-radio>
+            <el-radio label="1">是</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">开始抓取</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 抓取结果对话框 -->
+    <el-dialog :title="listDialog.title" v-model="listDialog.visible" width="1000px" style="height: 800px" append-to-body>
+      <el-table v-loading="listLoading" :data="deviceLogPushList">
+        <el-table-column label="设备名称" align="left" prop="deviceName" width="200" :show-overflow-tooltip="true"/>
+        <el-table-column label="抓取时长" align="center" prop="duration" width="80"/>
+        <el-table-column label="抓取结果" align="center" prop="logStatus" width="80">
+          <template #default="scope">
+            <dict-tag :options="smsb_device_log_status" :value="scope.row.logStatus"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="是否重启" align="center" prop="isReboot"  width="80">
+          <template #default="scope">
+            <dict-tag :options="smsb_yes_no" :value="scope.row.isReboot"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="抓取命令" align="left" prop="logCmd" :show-overflow-tooltip="true"/>
+        <el-table-column label="抓取时间" align="left" prop="createTime" width="160" :show-overflow-tooltip="true"/>
+        <el-table-column label="操作" width="120" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="日志下载" placement="top">
+              <el-button link type="primary" :disabled="!scope.row.logFile" icon="Download" @click="handleDownloadLog(scope.row)"
+                         v-hasPermi="['device:log:add']">日志下载</el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Device" lang="ts">
+import {listDevice} from '@/api/smsb/device/device';
+import {DeviceQuery, DeviceVO} from '@/api/smsb/device/device_type';
+import {DeviceLogPushForm, DeviceLogPushQuery, DeviceLogPushVO} from "@/api/smsb/device/logPush/types";
+import {addDeviceLogPush, listDeviceLogPush, stopDeviceLogPush} from "@/api/smsb/device/logPush/api";
+
+const {proxy} = getCurrentInstance() as ComponentInternalInstance;
+const {
+  sys_device_online,smsb_yes_no,smsb_device_log_status
+} = toRefs<any>(
+  proxy?.useDict('sys_device_online','smsb_yes_no','smsb_device_log_status')
+);
+
+const deviceList = ref<DeviceVO[]>([]);
+const deviceLogPushList = ref<DeviceLogPushVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const listLoading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const deviceFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const listDialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: DeviceLogPushForm = {
+  duration: 60,
+  deviceId: undefined,
+  logCmd: 'logcat -vtime | grep -i -E "MqttClient|PushCallback|MqttService"',
+  isReboot: '0',
+}
+const data = reactive<PageData<DeviceLogPushForm, DeviceQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    name: undefined,
+    serialNumber: undefined,
+    params: {}
+  },
+  rules: {}
+});
+
+const {queryParams, form, rules} = toRefs(data);
+
+/** 查询设备列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listDevice(queryParams.value);
+  deviceList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+const handleStartLog = (row: DeviceVO) => {
+  const deviceId = row.id;
+  form.value.deviceId = deviceId;
+  dialog.title = "日志抓取";
+  dialog.visible = true;
+}
+
+const handleDownloadLog = (row: DeviceLogPushVO) => {
+  const fileUrl = row.logFile;
+  if (!fileUrl) {
+    proxy?.$modal.msgWarning("无日志文件可下载");
+    return;
+  }
+  // 创建隐藏的下载链接并触发下载
+  const link = document.createElement('a');
+  link.href = fileUrl;
+  link.download = fileUrl.substring(fileUrl.lastIndexOf('/') + 1); // 从URL提取文件名
+  link.style.display = 'none';
+  document.body.appendChild(link);
+  link.click();
+  document.body.removeChild(link);
+}
+const handleLogResult = async (row: DeviceVO) => {
+  const deviceId = row.id;
+  listDialog.title = "抓取结果";
+  listDialog.visible = true;
+  listLoading.value = true;
+  const params = {
+    deviceId : deviceId
+  }
+  const res = await listDeviceLogPush(params as DeviceLogPushQuery);
+  deviceLogPushList.value = res.rows;
+  listLoading.value = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  deviceFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+const handleEndLog = async (row: DeviceVO) => {
+  const deviceId = row.id;
+  await stopDeviceLogPush(deviceId);
+  proxy?.$modal.msgSuccess("操作成功");
+}
+/** 提交按钮 */
+const submitForm = () => {
+  deviceFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      await addDeviceLogPush(form.value).finally(() => buttonLoading.value = false);
+      proxy?.$modal.msgSuccess("操作成功");
+      await getList();
+    }
+  });
+}
+
+
+onMounted(() => {
+  getList();
+});
+</script>