1.飞行记录增加媒体文件、飞行视频

2.srs记录优化
3.部署脚本优化
This commit is contained in:
sdy 2026-05-27 15:50:40 +08:00
parent e23e95c511
commit e96fcc42ed
15 changed files with 277 additions and 17 deletions

View File

@ -81,4 +81,6 @@ public interface BusinessConstant {
String DRC = "drc_"; String DRC = "drc_";
String FILE_PATH = "file/"; String FILE_PATH = "file/";
String IMAGE_PATH = "image/"; String IMAGE_PATH = "image/";
String VIDEO_PATH = "video/";
String VIDEO_COVER_SUFFIX = "_cover.jpeg";
} }

View File

@ -8,6 +8,8 @@ import com.multictrl.common.page.PageData;
import com.multictrl.common.utils.Result; import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.AssertUtils; import com.multictrl.common.validator.AssertUtils;
import com.multictrl.modules.business.dto.FlightTaskDTO; import com.multictrl.modules.business.dto.FlightTaskDTO;
import com.multictrl.modules.business.dto.MediaFileDTO;
import com.multictrl.modules.business.entity.SrsRecordEntity;
import com.multictrl.modules.business.service.FlightTaskService; import com.multictrl.modules.business.service.FlightTaskService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -38,11 +40,15 @@ public class FlightTaskController {
@Operation(summary = "分页") @Operation(summary = "分页")
@Parameters({ @Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"), @Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数") @Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
@Parameter(name = "key", description = "机库/航线名称"),
@Parameter(name = "taskType", description = "任务类型 1:航线飞行 2:手动飞行 3:定时飞行"),
@Parameter(name = "routeType", description = "航线类型 航点waypoint"),
@Parameter(name = "taskStatus", description = "任务状态 0:进行中 -1:失败 1:完成 2:阻飞")
}) })
@RequiresPermissions("bus:task:page") @RequiresPermissions("bus:task:page")
public Result<PageData<FlightTaskDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) { public Result<PageData<FlightTaskDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<FlightTaskDTO> page = flightTaskService.page(params); PageData<FlightTaskDTO> page = flightTaskService.pageList(params);
return new Result<PageData<FlightTaskDTO>>().ok(page); return new Result<PageData<FlightTaskDTO>>().ok(page);
} }
@ -76,4 +82,22 @@ public class FlightTaskController {
return new Result<List<JSONObject>>().ok(data); return new Result<List<JSONObject>>().ok(data);
} }
@GetMapping("/getTaskMedia/{taskId}")
@Operation(summary = "媒体文件")
@RequiresPermissions("bus:task:taskMedia")
public Result<List<MediaFileDTO>> getTaskMedia(@PathVariable("taskId") String taskId) {
List<MediaFileDTO> data = flightTaskService.getTaskMedia(taskId);
return new Result<List<MediaFileDTO>>().ok(data);
}
@GetMapping("/getFlightVideo/{taskId}")
@Operation(summary = "飞行视频")
@RequiresPermissions("bus:task:flightVideo")
public Result<List<SrsRecordEntity>> getFlightVideo(@PathVariable("taskId") String taskId) {
List<SrsRecordEntity> data = flightTaskService.getFlightVideo(taskId);
return new Result<List<SrsRecordEntity>>().ok(data);
}
} }

View File

@ -87,7 +87,7 @@ public class SrsController {
@PostMapping("/on_dvr") @PostMapping("/on_dvr")
public String onDvr(@RequestBody SrsCallBackDTO srsCallBack) { public String onDvr(@RequestBody SrsCallBackDTO srsCallBack) {
// log.info("on_dvr {}", srsCallBack); // log.info("on_dvr {}", srsCallBack);
srsService.saveSrsRecord(srsCallBack);
return "0"; return "0";
} }

View File

@ -13,4 +13,9 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper @Mapper
public interface FlightTaskDao extends BaseDao<FlightTaskEntity> { public interface FlightTaskDao extends BaseDao<FlightTaskEntity> {
//获取最新的任务
FlightTaskEntity getLatestRouteTask(String dockSn);
//获取当前任务的上一条任务
FlightTaskEntity getPreviousTask(String taskId, String dockSn);
} }

View File

@ -1,5 +1,6 @@
package com.multictrl.modules.business.dto; package com.multictrl.modules.business.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@ -67,4 +68,8 @@ public class MediaFileDTO implements Serializable {
@Schema(description = "事件收到时间") @Schema(description = "事件收到时间")
private Date createTime; private Date createTime;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "媒体路径")
private String url;
} }

View File

@ -43,4 +43,11 @@ public class SrsRecordEntity {
@TableField(fill = FieldFill.INSERT) @TableField(fill = FieldFill.INSERT)
private Date createDate; private Date createDate;
@TableField(exist = false)
private String url;
@TableField(exist = false)
private String fileName;
@TableField(exist = false)
private String cover;
} }

View File

@ -1,12 +1,16 @@
package com.multictrl.modules.business.service; package com.multictrl.modules.business.service;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import com.multictrl.common.page.PageData;
import com.multictrl.common.service.CrudService; import com.multictrl.common.service.CrudService;
import com.multictrl.modules.business.dto.FlightTaskDTO; import com.multictrl.modules.business.dto.FlightTaskDTO;
import com.multictrl.modules.business.dto.MediaFileDTO;
import com.multictrl.modules.business.entity.FlightTaskEntity; import com.multictrl.modules.business.entity.FlightTaskEntity;
import com.multictrl.modules.business.entity.SrsRecordEntity;
import com.multictrl.modules.business.influxdb.UavReport; import com.multictrl.modules.business.influxdb.UavReport;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 飞行架次 * 飞行架次
@ -16,6 +20,9 @@ import java.util.List;
*/ */
public interface FlightTaskService extends CrudService<FlightTaskEntity, FlightTaskDTO> { public interface FlightTaskService extends CrudService<FlightTaskEntity, FlightTaskDTO> {
//分页列表
PageData<FlightTaskDTO> pageList(Map<String, Object> params);
//新增架次信息 //新增架次信息
void addRouteTask(String taskId, String dockSn, Long routeId);//航线任务 void addRouteTask(String taskId, String dockSn, Long routeId);//航线任务
@ -29,4 +36,13 @@ public interface FlightTaskService extends CrudService<FlightTaskEntity, FlightT
//获取飞行轨迹 //获取飞行轨迹
List<JSONObject> getFlightTrack(String taskId); List<JSONObject> getFlightTrack(String taskId);
//获取架次媒体
List<MediaFileDTO> getTaskMedia(String taskId);
//飞行视频
List<SrsRecordEntity> getFlightVideo(String taskId);
//获取上一条任务
FlightTaskDTO getPreviousTask(String taskId, String dockSn);
} }

View File

@ -1,6 +1,7 @@
package com.multictrl.modules.business.service; package com.multictrl.modules.business.service;
import cn.hutool.json.JSONArray; import cn.hutool.json.JSONArray;
import com.multictrl.modules.business.dao.SrsRecordDao;
import com.multictrl.modules.business.dto.SrsCallBackDTO; import com.multictrl.modules.business.dto.SrsCallBackDTO;
import com.multictrl.modules.business.entity.SrsRecordEntity; import com.multictrl.modules.business.entity.SrsRecordEntity;
@ -26,4 +27,7 @@ public interface SrsService {
//获取飞机推流地址 //获取飞机推流地址
String getUavPushLiveUrl(String dockSn); String getUavPushLiveUrl(String dockSn);
//获取dao
SrsRecordDao getDao();
} }

View File

@ -2,6 +2,7 @@ package com.multictrl.modules.business.service.impl;
import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -9,20 +10,16 @@ import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.constant.FlightTaskStatus; import com.multictrl.common.constant.FlightTaskStatus;
import com.multictrl.common.exception.ErrorCode; import com.multictrl.common.exception.ErrorCode;
import com.multictrl.common.exception.RenException; import com.multictrl.common.exception.RenException;
import com.multictrl.common.page.PageData;
import com.multictrl.common.service.impl.CrudServiceImpl; import com.multictrl.common.service.impl.CrudServiceImpl;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.multictrl.common.utils.CacheUtils; import com.multictrl.common.utils.*;
import com.multictrl.common.utils.ConvertUtils;
import com.multictrl.common.utils.DateUtils;
import com.multictrl.common.utils.Spatial4jUtils;
import com.multictrl.modules.business.dao.DockDeviceDao; import com.multictrl.modules.business.dao.DockDeviceDao;
import com.multictrl.modules.business.dao.FlightTaskDao; import com.multictrl.modules.business.dao.FlightTaskDao;
import com.multictrl.modules.business.dto.FlightTaskDTO; import com.multictrl.modules.business.dto.FlightTaskDTO;
import com.multictrl.modules.business.dto.MediaFileDTO;
import com.multictrl.modules.business.dto.RouteDTO; import com.multictrl.modules.business.dto.RouteDTO;
import com.multictrl.modules.business.entity.DockDeviceEntity; import com.multictrl.modules.business.entity.*;
import com.multictrl.modules.business.entity.DockEntity;
import com.multictrl.modules.business.entity.FlightTaskEntity;
import com.multictrl.modules.business.entity.MediaFileEntity;
import com.multictrl.modules.business.influxdb.UavReport; import com.multictrl.modules.business.influxdb.UavReport;
import com.multictrl.modules.business.service.*; import com.multictrl.modules.business.service.*;
import com.multictrl.modules.security.user.SecurityUser; import com.multictrl.modules.security.user.SecurityUser;
@ -45,17 +42,44 @@ public class FlightTaskServiceImpl extends CrudServiceImpl<FlightTaskDao, Flight
private final InfluxService influxService; private final InfluxService influxService;
private final MediaFileService mediaFileService; private final MediaFileService mediaFileService;
private final DockDeviceDao dockDeviceDao; private final DockDeviceDao dockDeviceDao;
private final SrsService srsService;
@Override @Override
public QueryWrapper<FlightTaskEntity> getWrapper(Map<String, Object> params) { public QueryWrapper<FlightTaskEntity> getWrapper(Map<String, Object> params) {
String id = (String) params.get("id"); String id = (String) params.get("id");
String key = (String) params.get("key");
String taskType = (String) params.get("taskType");
String routeType = (String) params.get("routeType");
String taskStatus = (String) params.get("taskStatus");
QueryWrapper<FlightTaskEntity> wrapper = new QueryWrapper<>(); QueryWrapper<FlightTaskEntity> wrapper = new QueryWrapper<>();
wrapper.eq(StrUtil.isNotBlank(id), "id", id); wrapper.eq(StrUtil.isNotBlank(id), "id", id);
wrapper.and(StrUtil.isNotBlank(key), v ->
v.like("dock_name", key).or().like("route_name", key));
if (StrUtil.isNotBlank(taskType)) {
wrapper.eq("task_type", Integer.parseInt(taskType));
}
if (StrUtil.isNotBlank(taskStatus)) {
wrapper.eq("task_status", Integer.parseInt(taskStatus));
}
wrapper.eq(StrUtil.isNotBlank(routeType), "route_type", routeType);
wrapper.orderByDesc("create_date");
return wrapper; return wrapper;
} }
@Override
public PageData<FlightTaskDTO> pageList(Map<String, Object> params) {
PageData<FlightTaskDTO> page = page(params);
for (FlightTaskDTO dto : page.getList()) {
if (dto.getMediaNum() == null || dto.getMediaNum() == 0) {
Long mediaNum = mediaFileService.getDao().selectCount(new QueryWrapper<MediaFileEntity>().eq("task_id", dto.getTaskId()));
dto.setMediaNum(mediaNum.intValue());
}
}
return page;
}
@Override @Override
public void addRouteTask(String taskId, String dockSn, Long routeId) { public void addRouteTask(String taskId, String dockSn, Long routeId) {
FlightTaskEntity entity = new FlightTaskEntity(); FlightTaskEntity entity = new FlightTaskEntity();
@ -221,6 +245,48 @@ public class FlightTaskServiceImpl extends CrudServiceImpl<FlightTaskDao, Flight
return filledList; return filledList;
} }
@Override
public List<MediaFileDTO> getTaskMedia(String taskId) {
Map<String, Object> params = new HashMap<>();
params.put("taskId", taskId);
List<MediaFileDTO> list = mediaFileService.list(params);
for (MediaFileDTO dto : list) {
String objectKey = dto.getObjectKey();
dto.setUrl(BusinessConstant.IMAGE_PATH + BusinessConstant.DOCK_MEDIA_BUCKET + "/" + objectKey);
}
return list;
}
@Override
public List<SrsRecordEntity> getFlightVideo(String taskId) {
FlightTaskEntity entity = baseDao.selectById(taskId);
QueryWrapper<SrsRecordEntity> wrapper = new QueryWrapper<>();
wrapper.eq("app", entity.getDockSn());
FlightTaskDTO previousTask = this.getPreviousTask(taskId, entity.getDockSn());
if (previousTask == null) {
wrapper.gt("record_date", DateUtils.getBeforeHour(entity.getCreateDate(), -8));
} else {
wrapper.between("record_date", DateUtils.getBeforeHour(entity.getCreateDate(), -8),
DateUtils.getBeforeHour(previousTask.getCreateDate(), -8));
}
wrapper.ne("stream", "dock");
wrapper.orderByAsc("record_date");
List<SrsRecordEntity> list = srsService.getDao().selectList(wrapper);
for (SrsRecordEntity srsRecordEntity : list) {
srsRecordEntity.setRecordDate(DateUtils.getBeforeHour(srsRecordEntity.getRecordDate(), 8));
srsRecordEntity.setUrl(BusinessConstant.VIDEO_PATH + srsRecordEntity.getFile());
srsRecordEntity.setCover(BusinessConstant.IMAGE_PATH + getVideoCover(srsRecordEntity.getFile(), srsRecordEntity.getStreamUrl()));
}
return list;
}
@Override
public FlightTaskDTO getPreviousTask(String taskId, String dockSn) {
FlightTaskEntity previousTask = baseDao.getPreviousTask(taskId, dockSn);
return ConvertUtils.sourceToTarget(previousTask, FlightTaskDTO.class);
}
//添加dock信息 //添加dock信息
private void addDockInfo(FlightTaskEntity entity, String dockSn) { private void addDockInfo(FlightTaskEntity entity, String dockSn) {
entity.setDockSn(dockSn); entity.setDockSn(dockSn);
@ -231,4 +297,13 @@ public class FlightTaskServiceImpl extends CrudServiceImpl<FlightTaskDao, Flight
entity.setDockModel(dockEntity.getDockModel()); entity.setDockModel(dockEntity.getDockModel());
} }
} }
//使用ffmpeg 用视频提取封面
private String getVideoCover(String file, String streamUrl) {
String fileName = file.replace("live_record" + streamUrl + "/", "");
String videoCoverPath = "live_record" + streamUrl + "/" + FileUtil.getPrefix(fileName) + BusinessConstant.VIDEO_COVER_SUFFIX;
FfmpegUtils.generateVideoCover(file, "/" + videoCoverPath);
return videoCoverPath;
}
} }

View File

@ -23,9 +23,12 @@ public class MediaFileServiceImpl extends CrudServiceImpl<MediaFileDao, MediaFil
@Override @Override
public QueryWrapper<MediaFileEntity> getWrapper(Map<String, Object> params) { public QueryWrapper<MediaFileEntity> getWrapper(Map<String, Object> params) {
String id = (String) params.get("id"); String id = (String) params.get("id");
String taskId = (String) params.get("taskId");
QueryWrapper<MediaFileEntity> wrapper = new QueryWrapper<>(); QueryWrapper<MediaFileEntity> wrapper = new QueryWrapper<>();
wrapper.eq(StrUtil.isNotBlank(id), "id", id); wrapper.eq(StrUtil.isNotBlank(id), "id", id);
wrapper.eq(StrUtil.isNotBlank(taskId), "task_id", taskId);
wrapper.orderByDesc("created_time");
return wrapper; return wrapper;
} }

View File

@ -36,14 +36,19 @@ public class SrsServiceImpl implements SrsService {
@Override @Override
public void saveSrsRecord(SrsCallBackDTO srsCallBack) { public void saveSrsRecord(SrsCallBackDTO srsCallBack) {
try { try {
if (StrUtil.isNotBlank(srsCallBack.getFile()) && !srsCallBack.getFile().contains("/out") String callBackFile = srsCallBack.getFile();
&& !srsCallBack.getFile().contains("/in")) { if (StrUtil.isNotBlank(callBackFile) &&
(callBackFile.contains("/dock") || callBackFile.contains("/uav"))) {
SrsRecordEntity srsRecordEntity = new SrsRecordEntity(); SrsRecordEntity srsRecordEntity = new SrsRecordEntity();
srsRecordEntity.setApp(srsCallBack.getApp()); srsRecordEntity.setApp(srsCallBack.getApp());
srsRecordEntity.setStream(srsCallBack.getStream()); srsRecordEntity.setStream(srsCallBack.getStream());
srsRecordEntity.setFile(srsCallBack.getFile()); if (callBackFile.startsWith("/")) {
srsRecordEntity.setFile(callBackFile.substring(1));
} else {
srsRecordEntity.setFile(callBackFile);
}
srsRecordEntity.setStreamUrl(srsCallBack.getStream_url()); srsRecordEntity.setStreamUrl(srsCallBack.getStream_url());
String file = srsCallBack.getFile(); String file = callBackFile;
// /live_record/aaa/bbb/2025-04-03_16-33-43-404.mp4 // /live_record/aaa/bbb/2025-04-03_16-33-43-404.mp4
String dateTime = (file = file.substring(file.lastIndexOf("/") + 1)) String dateTime = (file = file.substring(file.lastIndexOf("/") + 1))
.substring(0, file.indexOf(".")); .substring(0, file.indexOf("."));
@ -133,4 +138,9 @@ public class SrsServiceImpl implements SrsService {
return RTMP_PROTOCOL + srsConfig.getIp() + ":" + srsConfig.getRtmpPort() + "/" + dockSn + "/uav?token=" + srsConfig.getRtmpToken(); return RTMP_PROTOCOL + srsConfig.getIp() + ":" + srsConfig.getRtmpPort() + "/" + dockSn + "/uav?token=" + srsConfig.getRtmpToken();
} }
@Override
public SrsRecordDao getDao() {
return srsRecordDao;
}
} }

View File

@ -0,0 +1,22 @@
<?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.multictrl.modules.business.dao.FlightTaskDao">
<select id="getLatestRouteTask" resultType="com.multictrl.modules.business.entity.FlightTaskEntity">
SELECT *
FROM bus_flight_task
where dock_sn = #{dockSn}
order by create_date desc limit 1
</select>
<select id="getPreviousTask" resultType="com.multictrl.modules.business.entity.FlightTaskEntity">
SELECT *
FROM bus_flight_task
WHERE create_date > (SELECT create_date FROM uav_route_task WHERE task_id = #{param1})
<if test="param2 != null and param2.trim() != ''">
and dock_sn = #{param2}
</if>
ORDER BY create_date ASC LIMIT 1;
</select>
</mapper>

View File

@ -55,6 +55,16 @@ cp ./file/wait/wait-for-it.sh /root/dj_multictrl_data/wait/wait-for-it.sh
mkdir -p /root/dj_multictrl_data/mongodb/data mkdir -p /root/dj_multictrl_data/mongodb/data
mkdir -p /root/dj_multictrl_data/script
mkdir -p /root/dj_multictrl_data/script/logs
cp ./file/script/cleanup_srs_record_videos.sh /root/dj_multictrl_data/script/
chmod +x /root/dj_multictrl_data/script/cleanup_srs_record_videos.sh
crontab -l > cron.cron
echo '*/30 * * * * /root/dj_multictrl_data/script/cleanup_srs_record_videos.sh' >> cron.cron
#echo '0 2 * * * /usr/bin/docker restart aros-api-srs' >> cron.cron
#echo '0 */1 * * * /usr/bin/pkill ffmpeg' >> cron.cron
crontab cron.cron
chmod -R 777 /root/dj_multictrl_data chmod -R 777 /root/dj_multictrl_data
docker-compose --compatibility up docker-compose --compatibility up

View File

@ -0,0 +1,41 @@
#!/bin/bash
# 配置项
TARGET_DIR="/data/dj_multictrl_data/live_record"
MAX_SIZE_GB=5
LOG_FILE="/root/dj_multictrl_data/script/logs/cleanup.log"
# 转换为 KB (1GB = 1048576KB)
MAX_SIZE_KB=$((MAX_SIZE_GB * 1048576))
# 获取当前目录总大小KB
CURRENT_SIZE_KB=$(du -sk "$TARGET_DIR" | cut -f1)
# 如果超过最大大小,开始清理
if [ "$CURRENT_SIZE_KB" -gt "$MAX_SIZE_KB" ]; then
echo "$(date): 当前大小 ${CURRENT_SIZE_KB}KB 超过限制 ${MAX_SIZE_KB}KB开始清理..." >> "$LOG_FILE"
# 循环删除最旧的文件,直到空间达标
while [ "$(du -sk "$TARGET_DIR" | cut -f1)" -gt "$MAX_SIZE_KB" ]; do
# 查找整个目录(包括子目录)中最旧的文件
OLDEST_FILE=$(find "$TARGET_DIR" -type f -printf '%T+ %p\n' | sort | head -n 1 | cut -d' ' -f2-)
if [ -n "$OLDEST_FILE" ]; then
echo "正在删除最旧的文件: $OLDEST_FILE" >> "$LOG_FILE"
rm -f "$OLDEST_FILE"
# 检查是否删除成功
if [ ! -f "$OLDEST_FILE" ]; then
echo "删除成功" >> "$LOG_FILE"
else
echo "删除失败,可能权限不足" >> "$LOG_FILE"
break # 避免无限循环
fi
else
echo "没有可删除的文件(可能只剩下空目录)" >> "$LOG_FILE"
break
fi
done
else
echo "$(date): 当前大小 ${CURRENT_SIZE_KB}KB未超过限制 ${MAX_SIZE_KB}KB" >> "$LOG_FILE"
fi

View File

@ -0,0 +1,36 @@
#!/bin/bash
# 要检查的目录
TARGET_DIR="/data/dj_multictrl_data"
# 最低剩余容量(单位 GiB
THRESHOLD=50
# 当空间不足时,每次清理几个文件(按最旧)
DELETE_COUNT=5
# 当前剩余容量GiB
AVAILABLE=$(df -BG "$TARGET_DIR" | awk 'NR==2 {gsub("G","",$4); print $4}')
# 日志文件
LOG_FILE="/var/log/disk_cleanup.log"
echo "$(date '+%F %T') 当前剩余空间: ${AVAILABLE}G" >> "$LOG_FILE"
if [ "$AVAILABLE" -lt "$THRESHOLD" ]; then
echo "$(date '+%F %T') 空间低于 ${THRESHOLD}G开始清理..." >> "$LOG_FILE"
# 找出最旧的文件并删除
find "$TARGET_DIR" -type f -printf "%T@ %p\n" \
| sort -n \
| head -n "$DELETE_COUNT" \
| awk '{print $2}' \
| while read FILE; do
echo "$(date '+%F %T') 删除文件: $FILE" >> "$LOG_FILE"
rm -f "$FILE"
done
echo "$(date '+%F %T') 清理结束" >> "$LOG_FILE"
else
echo "$(date '+%F %T') 空间充足,无需清理" >> "$LOG_FILE"
fi