feat: 文件搜索、视频续播与目录文件过滤

- 前端 Header 新增搜索栏,接入已有 SearchFiles API,结果支持列表/网格视图
- 新增 PlaybackPosition 数据库列与 /api/files/progress 端点,播放进度存服务端
- 播放中每 5 秒自动保存进度,再次打开视频时弹出"继续播放"提示
- 目录浏览新增媒体类型过滤条(全部/视频/音频/文本),前端即时过滤
- 新增 4 种数据库迁移(AddPlaybackPosition)
This commit is contained in:
2026-05-22 17:44:35 +08:00
parent ff19f4759f
commit 6ef410fdfa
22 changed files with 2386 additions and 14 deletions
@@ -77,6 +77,10 @@ namespace FileShare_Services.Endpoints
.WithOpenApi("FileLibrary", "标记文件已播放。")
.WithName("MarkFilePlayed");
endpoints.MapPost<IFileLibraryEndpointService, SaveFileProgressRequest>("api/files/progress", (service, request, _) => service.SaveFileProgressAsync(request))
.WithOpenApi("FileLibrary", "保存文件播放进度。")
.WithName("SaveFileProgress");
endpoints.MapGet<IFileLibraryEndpointService, FileQueryRequest>("api/files/detail", (service, request, _) => service.GetFileAsync(request))
.WithOpenApi("FileLibrary", "查询文件详情。")
.WithName("GetFileDetail");
@@ -59,6 +59,13 @@ namespace FileShare_Services.Services.FileLibrary
public sealed record MarkFilePlayedRequest(
[property: JsonPropertyName("id")] int Id);
/// <summary>
/// 保存文件播放进度的请求。
/// </summary>
public sealed record SaveFileProgressRequest(
[property: JsonPropertyName("id")] int Id,
[property: JsonPropertyName("position")] double Position);
/// <summary>
/// 分页搜索已扫描文件的请求。
/// </summary>
@@ -121,7 +128,8 @@ namespace FileShare_Services.Services.FileLibrary
bool BrowserPlayable,
string? ThumbnailUrl = null,
double? VideoDuration = null,
DateTime? LastPlayedAt = null);
DateTime? LastPlayedAt = null,
double? PlaybackPosition = null);
/// <summary>
/// 浏览文件库目录结构的请求。
@@ -101,6 +101,13 @@ namespace FileShare_Services.Services.FileLibrary
return ResponseHelper.Succeed();
}
/// <inheritdoc />
public async Task<IApiResponse> SaveFileProgressAsync(SaveFileProgressRequest request)
{
await fileLibrary.SaveFileProgressAsync(request.Id, request.Position);
return ResponseHelper.Succeed();
}
/// <summary>
/// 验证文件 ID 是否有效,无效时抛出 <see cref="ArgumentException"/>。
/// </summary>
@@ -404,6 +404,17 @@ namespace FileShare_Services.Services.FileLibrary
}
}
/// <inheritdoc />
public async Task SaveFileProgressAsync(int id, double position, CancellationToken cancellationToken = default)
{
var record = await db.ManagedFileRecords.FindAsync([id], cancellationToken);
if (record is not null)
{
record.PlaybackPosition = Math.Max(0, position);
await db.SaveChangesAsync(cancellationToken);
}
}
/// <summary>
/// 深度优先遍历目录树,枚举所有被 <see cref="MediaFileTypes"/> 支持的媒体文件路径。
/// 遇到无权限的目录时跳过该分支继续遍历。
@@ -559,7 +570,8 @@ namespace FileShare_Services.Services.FileLibrary
MediaFileTypes.IsBrowserPlayable(file.Extension),
file.ThumbnailId is null ? null : $"/api/thumbnails/{file.ThumbnailId}",
file.VideoDuration,
file.LastPlayedAt);
file.LastPlayedAt,
file.PlaybackPosition);
}
}
@@ -98,5 +98,12 @@ namespace FileShare_Services.Services.FileLibrary
/// <param name="request">包含文件 ID 的请求。</param>
/// <returns>API 响应。</returns>
Task<IApiResponse> MarkFilePlayedAsync(MarkFilePlayedRequest request);
/// <summary>
/// 保存文件播放进度位置。
/// </summary>
/// <param name="request">包含文件 ID 和播放位置的请求。</param>
/// <returns>API 响应。</returns>
Task<IApiResponse> SaveFileProgressAsync(SaveFileProgressRequest request);
}
}
@@ -113,5 +113,13 @@ namespace FileShare_Services.Services.FileLibrary
/// <param name="id">文件记录 ID。</param>
/// <param name="cancellationToken">取消令牌。</param>
Task MarkFilePlayedAsync(int id, CancellationToken cancellationToken = default);
/// <summary>
/// 保存文件的播放进度位置。
/// </summary>
/// <param name="id">文件记录 ID。</param>
/// <param name="position">播放位置(秒)。</param>
/// <param name="cancellationToken">取消令牌。</param>
Task SaveFileProgressAsync(int id, double position, CancellationToken cancellationToken = default);
}
}