diff --git a/FileShare-Services/Endpoints/AppEndpoints.cs b/FileShare-Services/Endpoints/AppEndpoints.cs index 0726cf3..782539b 100644 --- a/FileShare-Services/Endpoints/AppEndpoints.cs +++ b/FileShare-Services/Endpoints/AppEndpoints.cs @@ -82,6 +82,10 @@ namespace FileShare_Services.Endpoints .WithOpenApi("FileLibrary", "保存文件播放进度。") .WithName("SaveFileProgress"); + endpoints.MapPost("api/files/delete", (service, request, _) => service.DeleteFileAsync(request)) + .WithOpenApi("FileLibrary", "永久删除文件(物理文件+数据库记录)。") + .WithName("DeleteFile"); + endpoints.MapGet("api/files/detail", (service, request, _) => service.GetFileAsync(request)) .WithOpenApi("FileLibrary", "查询文件详情。") .WithName("GetFileDetail"); diff --git a/FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs b/FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs index e71150c..b3073af 100644 --- a/FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs +++ b/FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs @@ -34,6 +34,12 @@ namespace FileShare_Services.Services.FileLibrary public sealed record DeleteLibraryRootRequest( [property: JsonPropertyName("id")] int Id); + /// + /// 永久删除文件的请求。 + /// + public sealed record DeleteFileRequest( + [property: JsonPropertyName("id")] int Id); + /// /// 查询服务器子目录的请求。 /// diff --git a/FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs b/FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs index 4bb5c60..d3e6995 100644 --- a/FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs +++ b/FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs @@ -113,6 +113,14 @@ namespace FileShare_Services.Services.FileLibrary return ResponseHelper.Succeed(); } + /// + public async Task DeleteFileAsync(DeleteFileRequest request) + { + ValidateFileId(request.Id); + await fileLibrary.DeleteFileAsync(request.Id); + return ResponseHelper.Succeed("文件已永久删除。"); + } + /// /// 验证文件 ID 是否有效,无效时抛出 。 /// diff --git a/FileShare-Services/Services/FileLibrary/FileLibraryService.cs b/FileShare-Services/Services/FileLibrary/FileLibraryService.cs index 728e1d1..cb43f03 100644 --- a/FileShare-Services/Services/FileLibrary/FileLibraryService.cs +++ b/FileShare-Services/Services/FileLibrary/FileLibraryService.cs @@ -545,6 +545,54 @@ namespace FileShare_Services.Services.FileLibrary } } + /// + 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); + } + /// /// 深度优先遍历目录树,枚举所有被 支持的媒体文件路径。 /// 遇到无权限的目录时跳过该分支继续遍历。 diff --git a/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs b/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs index df68761..c96b327 100644 --- a/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs +++ b/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs @@ -105,5 +105,12 @@ namespace FileShare_Services.Services.FileLibrary /// 包含文件 ID 和播放位置的请求。 /// API 响应。 Task SaveFileProgressAsync(SaveFileProgressRequest request); + + /// + /// 永久删除指定文件。 + /// + /// 包含文件 ID 的请求。 + /// API 响应。 + Task DeleteFileAsync(DeleteFileRequest request); } } diff --git a/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs b/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs index 84688d6..691786c 100644 --- a/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs +++ b/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs @@ -121,5 +121,12 @@ namespace FileShare_Services.Services.FileLibrary /// 播放位置(秒)。 /// 取消令牌。 Task SaveFileProgressAsync(int id, double position, CancellationToken cancellationToken = default); + + /// + /// 永久删除指定文件,同时删除物理文件和缩略图。 + /// + /// 文件记录 ID。 + /// 取消令牌。 + Task DeleteFileAsync(int id, CancellationToken cancellationToken = default); } } diff --git a/FileShare-Web-VUE/src/api/index.ts b/FileShare-Web-VUE/src/api/index.ts index 56b872d..57887f7 100644 --- a/FileShare-Web-VUE/src/api/index.ts +++ b/FileShare-Web-VUE/src/api/index.ts @@ -114,5 +114,7 @@ export const api = { request('files/played', { method: 'POST', body: { id } }), saveFileProgress: (id: number, position: number) => request('files/progress', { method: 'POST', body: { id, position } }), + deleteFile: (id: number) => + request('files/delete', { method: 'POST', body: { id } }), qrCode: () => request<{ url: string; qrCodeBase64: string }>('qrcode'), } diff --git a/FileShare-Web-VUE/src/components/ClientPage.vue b/FileShare-Web-VUE/src/components/ClientPage.vue index 133dc81..4d64568 100644 --- a/FileShare-Web-VUE/src/components/ClientPage.vue +++ b/FileShare-Web-VUE/src/components/ClientPage.vue @@ -272,6 +272,24 @@ function exitSearch() { searchTotalPages.value = 0 } +async function deleteFile(file: FileRecordDto) { + if (!window.confirm(`确定要永久删除 "${file.fileName}" 吗?\n\n删除后无法恢复!`)) { + return + } + try { + await api.deleteFile(file.id) + await browseDirectory() + if (isSearching.value) { + await doSearch(searchPage.value) + } + if (selectedFile.value?.id === file.id) { + selectedFile.value = null + } + } catch (error) { + setError(error) + } +} + function updatePlaybackPosition(id: number, position: number) { if (selectedFile.value?.id === id) { selectedFile.value.playbackPosition = position @@ -463,7 +481,7 @@ onBeforeUnmount(() => {

无匹配文件