feat: 新增文件压缩功能和通用确认弹窗
后端新增 POST api/files/compress 接口,使用 DotNetZip 将单个文件压缩为 ZIP, 密码固定为文件名去掉后缀,压缩前自动删除同名旧 ZIP。 前端新增 ConfirmModal 通用确认弹窗,替代所有 window.confirm/alert, 在文件列表中增加压缩按钮,压缩前显示确认弹窗。
This commit is contained in:
@@ -86,6 +86,10 @@ namespace FileShare_Services.Endpoints
|
||||
.WithOpenApi("FileLibrary", "永久删除文件(物理文件+数据库记录)。")
|
||||
.WithName("DeleteFile");
|
||||
|
||||
endpoints.MapPost<IFileLibraryEndpointService, CompressFileRequest>("api/files/compress", (service, request, _) => service.CompressFileAsync(request))
|
||||
.WithOpenApi("FileLibrary", "压缩文件为 ZIP(可选密码)。")
|
||||
.WithName("CompressFile");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService, FileQueryRequest>("api/files/detail", (service, request, _) => service.GetFileAsync(request))
|
||||
.WithOpenApi("FileLibrary", "查询文件详情。")
|
||||
.WithName("GetFileDetail");
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.7" />
|
||||
<PackageReference Include="DotNetZip" Version="1.16.0" />
|
||||
<PackageReference Include="QRCoder" Version="1.8.0" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
|
||||
@@ -40,6 +40,12 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
public sealed record DeleteFileRequest(
|
||||
[property: JsonPropertyName("id")] int Id);
|
||||
|
||||
/// <summary>
|
||||
/// 压缩文件的请求。
|
||||
/// </summary>
|
||||
public sealed record CompressFileRequest(
|
||||
[property: JsonPropertyName("id")] int Id);
|
||||
|
||||
/// <summary>
|
||||
/// 查询服务器子目录的请求。
|
||||
/// </summary>
|
||||
|
||||
@@ -121,6 +121,14 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
return ResponseHelper.Succeed("文件已永久删除。");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IApiResponse> CompressFileAsync(CompressFileRequest request)
|
||||
{
|
||||
ValidateFileId(request.Id);
|
||||
await fileLibrary.CompressFileAsync(request.Id);
|
||||
return ResponseHelper.Succeed("文件已压缩。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证文件 ID 是否有效,无效时抛出 <see cref="ArgumentException"/>。
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@ using FileShare_Common.Core;
|
||||
using FileShare_EFCore.Database;
|
||||
using FileShare_EFCore.Models;
|
||||
using FileShare_Services.Core;
|
||||
using Ionic.Zip;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text;
|
||||
|
||||
@@ -593,6 +594,36 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CompressFileAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var record = await db.ManagedFileRecords
|
||||
.FirstOrDefaultAsync(f => f.Id == id && f.Exists, cancellationToken)
|
||||
?? throw new InvalidOperationException("文件不存在。");
|
||||
|
||||
if (!File.Exists(record.AbsolutePath))
|
||||
throw new FileNotFoundException("源文件不存在于磁盘。");
|
||||
|
||||
var dir = Path.GetDirectoryName(record.AbsolutePath)!;
|
||||
var nameWithoutExt = Path.GetFileNameWithoutExtension(record.FileName);
|
||||
var zipPath = Path.Combine(dir, nameWithoutExt + ".zip");
|
||||
|
||||
// 删除已存在的同名 ZIP
|
||||
if (File.Exists(zipPath))
|
||||
{
|
||||
File.Delete(zipPath);
|
||||
}
|
||||
|
||||
// 密码为文件名去掉后缀
|
||||
var zipPassword = nameWithoutExt.ToLower();
|
||||
|
||||
// 创建 ZIP
|
||||
using var zip = new Ionic.Zip.ZipFile();
|
||||
zip.Password = zipPassword;
|
||||
zip.AddFile(record.AbsolutePath, "");
|
||||
zip.Save(zipPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 深度优先遍历目录树,枚举所有被 <see cref="MediaFileTypes"/> 支持的媒体文件路径。
|
||||
/// 遇到无权限的目录时跳过该分支继续遍历。
|
||||
|
||||
@@ -112,5 +112,12 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
/// <param name="request">包含文件 ID 的请求。</param>
|
||||
/// <returns>API 响应。</returns>
|
||||
Task<IApiResponse> DeleteFileAsync(DeleteFileRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// 压缩指定文件为 ZIP(密码为文件名去掉后缀)。
|
||||
/// </summary>
|
||||
/// <param name="request">包含文件 ID 的请求。</param>
|
||||
/// <returns>API 响应。</returns>
|
||||
Task<IApiResponse> CompressFileAsync(CompressFileRequest request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,5 +128,12 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
/// <param name="id">文件记录 ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
Task DeleteFileAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 将指定文件压缩为 ZIP(密码为文件名去掉后缀),存储在源文件同级目录。
|
||||
/// </summary>
|
||||
/// <param name="id">文件记录 ID。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
Task CompressFileAsync(int id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user