diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/DiskController.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/DiskController.java new file mode 100644 index 000000000..635522cb0 --- /dev/null +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/controller/DiskController.java @@ -0,0 +1,36 @@ +package com.zhgd.xmgl.modules.basicdata.controller; + +import com.zhgd.jeecg.common.api.vo.Result; +import com.zhgd.xmgl.modules.basicdata.entity.bo.CleanupResult; +import com.zhgd.xmgl.modules.basicdata.entity.bo.DiskStatus; +import com.zhgd.xmgl.modules.basicdata.service.impl.DiskCleanupService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequestMapping("/xmgl/disk") +@Api(tags = "磁盘管理") +@Slf4j +public class DiskController { + + @Autowired + private DiskCleanupService diskCleanupService; + + @GetMapping("/status") + @ApiOperation("获取磁盘状态") + public Result getDiskStatus() { + try { + Double status = diskCleanupService.checkDiskStatus(); + return Result.success(status); + } catch (IOException e) { + log.error("获取磁盘状态失败", e); + return Result.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/entity/bo/CleanupResult.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/entity/bo/CleanupResult.java new file mode 100644 index 000000000..f78335537 --- /dev/null +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/entity/bo/CleanupResult.java @@ -0,0 +1,24 @@ +package com.zhgd.xmgl.modules.basicdata.entity.bo; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +// 清理结果类 +@Data +public class CleanupResult { + private boolean success; + private String errorMessage; + private int deletedCount; + private double finalDiskUsage; + private List deletedFiles = new ArrayList<>(); + + public void incrementDeletedCount() { + deletedCount++; + } + + public void addDeletedFile(String fileName) { + deletedFiles.add(fileName); + } +} diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/entity/bo/DiskStatus.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/entity/bo/DiskStatus.java new file mode 100644 index 000000000..9da956932 --- /dev/null +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/entity/bo/DiskStatus.java @@ -0,0 +1,16 @@ +package com.zhgd.xmgl.modules.basicdata.entity.bo; + +import lombok.Data; + +import java.util.Date; + +// 磁盘状态信息类 +@Data +public class DiskStatus { + private String diskPath; + private double usagePercentage; + private boolean exceedThreshold; + private long totalVideos; + private Date oldestVideoTime; + private String oldestVideoName; +} diff --git a/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/DiskCleanupService.java b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/DiskCleanupService.java new file mode 100644 index 000000000..69d500645 --- /dev/null +++ b/src/main/java/com/zhgd/xmgl/modules/basicdata/service/impl/DiskCleanupService.java @@ -0,0 +1,214 @@ +package com.zhgd.xmgl.modules.basicdata.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zhgd.xmgl.modules.basicdata.entity.bo.CleanupResult; +import com.zhgd.xmgl.modules.basicdata.entity.bo.DiskStatus; +import com.zhgd.xmgl.modules.policecamera.entity.PoliceCameraItemFile; +import com.zhgd.xmgl.modules.policecamera.service.IPoliceCameraItemFileService; +import com.zhgd.xmgl.util.PathUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +@Service +@Slf4j +public class DiskCleanupService { + // 文件类型:3 表示视频 + private static final int FILE_TYPE_VIDEO = 3; + @Lazy + @Autowired + private IPoliceCameraItemFileService policeCameraItemFileService; + // 磁盘路径,可以从配置文件中读取 + @Value("${diskMonitorPath:/datadir/itbgpImage}") + private String diskPath; + // 磁盘使用率阈值(百分比) + @Value("${diskUsageThreshold:75}") + private int diskUsageThreshold; + // 每次检查删除的文件数量限制 + @Value("${cleanupBatchSize:10}") + private int batchSize; + + /** + * 获取磁盘使用率 + * + * @param diskPath + */ + public static double getDiskUsagePercentage(String diskPath) throws IOException { + Path path = Paths.get(diskPath); + FileStore store = Files.getFileStore(path); + + long totalSpace = store.getTotalSpace(); + long usableSpace = store.getUsableSpace(); + long usedSpace = totalSpace - usableSpace; + + return (double) usedSpace / totalSpace * 100; + } + + /** + * 清理旧的视频文件 + */ + @Transactional(rollbackFor = Exception.class) + public void cleanupOldVideos() { + int deletedCount = 0; + double currentUsage; + + try { + currentUsage = getDiskUsagePercentage(diskPath); + + // 循环清理,直到磁盘使用率降到阈值以下 + while (currentUsage > diskUsageThreshold) { + // 查询最旧的视频文件(按上传时间排序) + List oldVideos = policeCameraItemFileService.list( + new LambdaQueryWrapper() + .eq(PoliceCameraItemFile::getFileType, FILE_TYPE_VIDEO) + .eq(PoliceCameraItemFile::getIsFileDeleted, 0) // 只查询未删除的 + .isNotNull(PoliceCameraItemFile::getUploadTime) + .isNotNull(PoliceCameraItemFile::getFileUrl) + .orderByAsc(PoliceCameraItemFile::getUploadTime) // 按上传时间升序,最旧的在前 + .last("LIMIT " + batchSize) // 限制每次处理的记录数 + ); + + if (oldVideos.isEmpty()) { + log.warn("没有找到可清理的视频文件"); + break; + } + + // 删除这批视频文件 + for (PoliceCameraItemFile video : oldVideos) { + deleteVideoFile(video); + // 逻辑删除数据库记录 + video.setIsFileDeleted(1); + video.setUpdateDate(new Date()); + policeCameraItemFileService.updateById(video); + + deletedCount++; + log.info("已删除视频文件: {}, 上传时间: {}", + video.getFileName(), + formatDate(video.getUploadTime())); + } + + // 重新计算磁盘使用率 + currentUsage = getDiskUsagePercentage(diskPath); + log.info("清理后磁盘使用率: {}%, 已删除 {} 个文件", + String.format("%.2f", currentUsage), deletedCount); + + // 防止无限循环,添加安全限制 + if (deletedCount >= 1000) { + log.warn("已达到最大删除数量限制(1000),停止清理"); + break; + } + } + + log.info("视频清理完成,总共删除 {} 个文件,最终磁盘使用率: {}%", + deletedCount, String.format("%.2f", currentUsage)); + + } catch (Exception e) { + log.error("清理视频文件时发生错误", e); + throw new RuntimeException("清理视频文件失败", e); + } + } + + private String formatDate(Date date) { + if (date == null) return "未知时间"; + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return sdf.format(date); + } + + public void dailyDiskInspection() { + log.info("开始执行磁盘巡检任务..."); + + try { + // 检查磁盘使用率 + double usagePercentage = getDiskUsagePercentage(diskPath); + log.info("当前磁盘使用率: {}%", String.format("%.2f", usagePercentage)); + + // 如果使用率超过阈值,执行清理 + if (usagePercentage > diskUsageThreshold) { + log.warn("磁盘使用率超过阈值({}%),开始清理旧视频文件...", diskUsageThreshold); + cleanupOldVideos(); + } else { + log.info("磁盘使用率正常,无需清理"); + } + + } catch (Exception e) { + log.error("磁盘巡检任务执行失败", e); + } + } + + /** + * 删除物理视频文件 + */ + public boolean deleteVideoFile(PoliceCameraItemFile video) { + String fileUrl = video.getFileUrl(); + fileUrl = PathUtil.getBasePath() + "/" + fileUrl; + if (StrUtil.isBlank(fileUrl)) { + log.warn("视频文件URL为空,文件ID: {}", video.getId()); + return false; + } + + try { + // 根据fileUrl构建文件路径 + File file = new File(fileUrl); + + if (!file.exists()) { + log.warn("视频文件不存在: {}", fileUrl); + return false; + } + + // 记录文件信息 + long fileSize = file.length(); + + // 删除文件 + if (file.delete()) { + log.info("成功删除物理文件: {}, 大小: {} KB", + fileUrl, fileSize / 1024); + + // 如果有视频截图,也一并删除 + String videoScreenshot = video.getVideoScreenshot(); + videoScreenshot = PathUtil.getBasePath() + "/" + videoScreenshot; + if (StrUtil.isNotBlank(videoScreenshot)) { + File screenshot = new File(videoScreenshot); + if (screenshot.exists()) { + screenshot.delete(); + log.info("删除视频截图: {}", videoScreenshot); + } + } + + return true; + } else { + log.error("删除物理文件失败: {}", fileUrl); + return false; + } + + } catch (SecurityException e) { + log.error("没有权限删除文件: {}", fileUrl, e); + return false; + } catch (Exception e) { + log.error("删除文件时发生异常: {}", fileUrl, e); + return false; + } + } + + + /** + * 立即执行磁盘检查(可用于手动触发) + */ + public Double checkDiskStatus() throws IOException { + double usagePercentage = getDiskUsagePercentage(diskPath); + return usagePercentage; + } +} diff --git a/src/main/java/com/zhgd/xmgl/modules/policecamera/entity/PoliceCameraItemFile.java b/src/main/java/com/zhgd/xmgl/modules/policecamera/entity/PoliceCameraItemFile.java index 182537c5d..f081cb87f 100644 --- a/src/main/java/com/zhgd/xmgl/modules/policecamera/entity/PoliceCameraItemFile.java +++ b/src/main/java/com/zhgd/xmgl/modules/policecamera/entity/PoliceCameraItemFile.java @@ -134,4 +134,9 @@ public class PoliceCameraItemFile implements Serializable { */ @ApiModelProperty(value = "视频截图url") private java.lang.String videoScreenshot; + /** + * 文件已删除,1删除0未删除 + */ + @ApiModelProperty(value = "文件已删除,1删除0未删除") + private java.lang.Integer isFileDeleted; } diff --git a/src/main/java/com/zhgd/xmgl/modules/policecamera/mapper/xml/PoliceCameraItemMapper.xml b/src/main/java/com/zhgd/xmgl/modules/policecamera/mapper/xml/PoliceCameraItemMapper.xml index d5c5349c9..9a8ac4b46 100644 --- a/src/main/java/com/zhgd/xmgl/modules/policecamera/mapper/xml/PoliceCameraItemMapper.xml +++ b/src/main/java/com/zhgd/xmgl/modules/policecamera/mapper/xml/PoliceCameraItemMapper.xml @@ -113,7 +113,7 @@ ,ifnull(round(sum(pcif.duration),2),0) duration from police_camera_item pci join police_camera_item_file pcif on pci.item_id = pcif.item_id - where 1=1 + where 1=1 and pcif.is_deleted = 0 and pci.project_sn = #{projectSn} diff --git a/src/main/java/com/zhgd/xmgl/modules/policecamera/service/impl/PoliceCameraItemFileServiceImpl.java b/src/main/java/com/zhgd/xmgl/modules/policecamera/service/impl/PoliceCameraItemFileServiceImpl.java index 3e855783d..8397e5f74 100644 --- a/src/main/java/com/zhgd/xmgl/modules/policecamera/service/impl/PoliceCameraItemFileServiceImpl.java +++ b/src/main/java/com/zhgd/xmgl/modules/policecamera/service/impl/PoliceCameraItemFileServiceImpl.java @@ -131,6 +131,7 @@ public class PoliceCameraItemFileServiceImpl extends ServiceImpl