feat: 新增可配置的文件库扫描日志记录及缩略图清理功能
- 为文件库的定时轮询及根目录扫描过程,新增扫描生命周期日志 - 允许通过 `appsettings` 配置文件,自定义定时扫描的轮询间隔 - 当检测到媒体文件已被删除时,自动清理过期的缩略图映射记录及对应的缩略图文件
This commit is contained in:
@@ -129,10 +129,14 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
var root = await db.ManagedLibraryRoots.FirstOrDefaultAsync(item => item.Id == rootId, cancellationToken)
|
||||
?? throw new InvalidOperationException("文件库目录不存在。");
|
||||
|
||||
Serilog.Log.Information("开始扫描文件库根目录 RootId={RootId} Path={Path}", root.Id, root.Path);
|
||||
root.LastScanStartedAt = DateTime.UtcNow;
|
||||
root.LastScanError = null;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var staleFileCount = 0;
|
||||
var staleThumbnailPaths = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(root.Path))
|
||||
@@ -200,13 +204,48 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var stale in existing.Values.Where(file => !seen.Contains(file.AbsolutePath)))
|
||||
var staleFiles = existing.Values
|
||||
.Where(file => !seen.Contains(file.AbsolutePath))
|
||||
.ToList();
|
||||
staleFileCount = staleFiles.Count;
|
||||
|
||||
foreach (var stale in staleFiles)
|
||||
{
|
||||
stale.Exists = false;
|
||||
}
|
||||
|
||||
var activeThumbnailIds = existing.Values
|
||||
.Where(file => file.Exists && file.ThumbnailId is not null)
|
||||
.Select(file => file.ThumbnailId!.Value)
|
||||
.ToHashSet();
|
||||
var staleThumbnailIds = staleFiles
|
||||
.Where(file => file.ThumbnailId is not null && !activeThumbnailIds.Contains(file.ThumbnailId.Value))
|
||||
.Select(file => file.ThumbnailId!.Value)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (staleThumbnailIds.Count > 0)
|
||||
{
|
||||
var staleThumbnails = await db.ManagedThumbnailMaps
|
||||
.Where(thumbnail => thumbnail.LibraryRootId == root.Id && staleThumbnailIds.Contains(thumbnail.Id))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
staleThumbnailPaths = staleThumbnails
|
||||
.Select(thumbnail => thumbnail.RelativePath)
|
||||
.ToList();
|
||||
|
||||
foreach (var stale in staleFiles.Where(file => file.ThumbnailId is not null && staleThumbnailIds.Contains(file.ThumbnailId.Value)))
|
||||
{
|
||||
stale.ThumbnailId = null;
|
||||
stale.Thumbnail = null;
|
||||
}
|
||||
|
||||
db.ManagedThumbnailMaps.RemoveRange(staleThumbnails);
|
||||
}
|
||||
|
||||
root.LastScanCompletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
DeleteThumbnailFiles(staleThumbnailPaths);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -219,6 +258,12 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
}
|
||||
|
||||
var count = await db.ManagedFileRecords.CountAsync(file => file.LibraryRootId == root.Id && file.Exists, cancellationToken);
|
||||
Serilog.Log.Information(
|
||||
"扫描文件库根目录完成 RootId={RootId} FileCount={FileCount} StaleFiles={StaleFiles} RemovedThumbnails={RemovedThumbnails}",
|
||||
root.Id,
|
||||
count,
|
||||
staleFileCount,
|
||||
staleThumbnailPaths.Count);
|
||||
return ToRootDto(root, count);
|
||||
}
|
||||
|
||||
@@ -229,19 +274,29 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
var roots = await db.ManagedLibraryRoots
|
||||
.Where(root => root.IsEnabled && root.IsAvailable)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var root in roots)
|
||||
{
|
||||
var interval = Math.Max(1, root.ScanIntervalMinutes);
|
||||
var isDue = root.LastScanCompletedAt is null || root.LastScanCompletedAt.Value.AddMinutes(interval) <= now;
|
||||
if (!isDue)
|
||||
var dueRoots = roots
|
||||
.Where(root =>
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var interval = Math.Max(1, root.ScanIntervalMinutes);
|
||||
return root.LastScanCompletedAt is null || root.LastScanCompletedAt.Value.AddMinutes(interval) <= now;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
Serilog.Log.Information(
|
||||
"文件库定时扫描检查完成 CheckedRoots={CheckedRoots} DueRoots={DueRoots}",
|
||||
roots.Count,
|
||||
dueRoots.Count);
|
||||
|
||||
foreach (var root in dueRoots)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ScanRootAsync(root.Id, cancellationToken);
|
||||
Serilog.Log.Information("文件库定时扫描开始 RootId={RootId} Path={Path}", root.Id, root.Path);
|
||||
var scannedRoot = await ScanRootAsync(root.Id, cancellationToken);
|
||||
Serilog.Log.Information(
|
||||
"文件库定时扫描完成 RootId={RootId} FileCount={FileCount}",
|
||||
scannedRoot.Id,
|
||||
scannedRoot.FileCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -498,6 +553,29 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
return string.IsNullOrWhiteSpace(directory.Name) ? directory.FullName : directory.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除数据库中已释放的缩略图文件,失败时保留扫描结果并记录告警。
|
||||
/// </summary>
|
||||
/// <param name="relativePaths">待删除缩略图相对路径。</param>
|
||||
private void DeleteThumbnailFiles(IEnumerable<string> relativePaths)
|
||||
{
|
||||
foreach (var relativePath in relativePaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
var absolutePath = thumbnailService.GetAbsolutePath(relativePath);
|
||||
if (File.Exists(absolutePath))
|
||||
{
|
||||
File.Delete(absolutePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Warning(ex, "清理失效缩略图文件失败 ThumbnailPath={ThumbnailPath}", relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规范化扫描间隔,限制在 1 到 1440 分钟范围内,未指定时使用默认值。
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user