feat: 新增永久删除文件接口和前端删除按钮
后端新增 POST api/files/delete 接口,同时删除物理文件、缩略图和数据库记录。 前端在 FileCard 和 FileListItem 中增加 hover 可见的删除按钮,点击后弹出确认提示。
This commit is contained in:
@@ -82,6 +82,10 @@ namespace FileShare_Services.Endpoints
|
||||
.WithOpenApi("FileLibrary", "保存文件播放进度。")
|
||||
.WithName("SaveFileProgress");
|
||||
|
||||
endpoints.MapPost<IFileLibraryEndpointService, DeleteFileRequest>("api/files/delete", (service, request, _) => service.DeleteFileAsync(request))
|
||||
.WithOpenApi("FileLibrary", "永久删除文件(物理文件+数据库记录)。")
|
||||
.WithName("DeleteFile");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService, FileQueryRequest>("api/files/detail", (service, request, _) => service.GetFileAsync(request))
|
||||
.WithOpenApi("FileLibrary", "查询文件详情。")
|
||||
.WithName("GetFileDetail");
|
||||
|
||||
@@ -34,6 +34,12 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
public sealed record DeleteLibraryRootRequest(
|
||||
[property: JsonPropertyName("id")] int Id);
|
||||
|
||||
/// <summary>
|
||||
/// 永久删除文件的请求。
|
||||
/// </summary>
|
||||
public sealed record DeleteFileRequest(
|
||||
[property: JsonPropertyName("id")] int Id);
|
||||
|
||||
/// <summary>
|
||||
/// 查询服务器子目录的请求。
|
||||
/// </summary>
|
||||
|
||||
@@ -113,6 +113,14 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
return ResponseHelper.Succeed();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IApiResponse> DeleteFileAsync(DeleteFileRequest request)
|
||||
{
|
||||
ValidateFileId(request.Id);
|
||||
await fileLibrary.DeleteFileAsync(request.Id);
|
||||
return ResponseHelper.Succeed("文件已永久删除。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证文件 ID 是否有效,无效时抛出 <see cref="ArgumentException"/>。
|
||||
/// </summary>
|
||||
|
||||
@@ -545,6 +545,54 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteFileAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var record = await db.ManagedFileRecords
|
||||
.Include(f => f.Thumbnail)
|
||||
.FirstOrDefaultAsync(f => f.Id == id, cancellationToken);
|
||||
|
||||
if (record is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 删除物理文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(record.AbsolutePath))
|
||||
{
|
||||
File.Delete(record.AbsolutePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Warning(ex, "删除物理文件失败 FilePath={FilePath}", record.AbsolutePath);
|
||||
}
|
||||
|
||||
// 删除缩略图文件
|
||||
if (record.Thumbnail is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var thumbnailPath = thumbnailService.GetAbsolutePath(record.Thumbnail.RelativePath);
|
||||
if (File.Exists(thumbnailPath))
|
||||
{
|
||||
File.Delete(thumbnailPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Warning(ex, "删除缩略图文件失败 ThumbnailId={ThumbnailId}", record.ThumbnailId);
|
||||
}
|
||||
|
||||
db.ManagedThumbnailMaps.Remove(record.Thumbnail);
|
||||
}
|
||||
|
||||
db.ManagedFileRecords.Remove(record);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 深度优先遍历目录树,枚举所有被 <see cref="MediaFileTypes"/> 支持的媒体文件路径。
|
||||
/// 遇到无权限的目录时跳过该分支继续遍历。
|
||||
|
||||
@@ -105,5 +105,12 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
/// <param name="request">包含文件 ID 和播放位置的请求。</param>
|
||||
/// <returns>API 响应。</returns>
|
||||
Task<IApiResponse> SaveFileProgressAsync(SaveFileProgressRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// 永久删除指定文件。
|
||||
/// </summary>
|
||||
/// <param name="request">包含文件 ID 的请求。</param>
|
||||
/// <returns>API 响应。</returns>
|
||||
Task<IApiResponse> DeleteFileAsync(DeleteFileRequest request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,5 +121,12 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
/// <param name="position">播放位置(秒)。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
Task SaveFileProgressAsync(int id, double position, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 永久删除指定文件,同时删除物理文件和缩略图。
|
||||
/// </summary>
|
||||
/// <param name="id">文件记录 ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
Task DeleteFileAsync(int id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user