Files
FileShare/FileShare-Services/Endpoints/AppEndpoints.cs
T
admin777 87f5c1ffb8 feat: 新增文件压缩功能和通用确认弹窗
后端新增 POST api/files/compress 接口,使用 DotNetZip 将单个文件压缩为 ZIP,
密码固定为文件名去掉后缀,压缩前自动删除同名旧 ZIP。
前端新增 ConfirmModal 通用确认弹窗,替代所有 window.confirm/alert,
在文件列表中增加压缩按钮,压缩前显示确认弹窗。
2026-07-10 10:35:32 +08:00

146 lines
8.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using FileShare_Common.Core;
using FileShare_Services.Core;
using FileShare_Services.Services.FileLibrary;
using FileShare_Services.Services.QrCode;
using Microsoft.Extensions.DependencyInjection;
namespace FileShare_Services.Endpoints
{
/// <summary>
/// 统一端点配置 —— 所有业务端点在此定义一次。
/// 这是 FileShare-API 和 FileShare-PC 的唯一入口。
/// </summary>
public static class AppEndpoints
{
/// <summary>
/// 配置所有业务端点。调用方传入 builder,按需叠加鉴权、过滤器等。
/// </summary>
/// <param name="builder">端点构建器</param>
/// <param name="includeDetails">是否在错误响应中包含异常详情(开发环境 true</param>
public static ServiceEndpointBuilder Configure(ServiceEndpointBuilder builder, bool includeDetails = false)
{
// ---- 全局异常拦截(自动捕获所有端点中未处理的异常) ----
builder.Endpoints.AddGlobalFilter(new GlobalExceptionFilter(includeDetails));
builder.ConfigureEndpoints(endpoints =>
{
// ---- 全局日志过滤器(记录每个请求) ----
endpoints.AddGlobalFilter(async (ctx, next) =>
{
Serilog.Log.Information("→ {Method} {Path}", ctx.Method, ctx.Path);
await next(ctx);
Serilog.Log.Information("← {Method} {Path} | {StatusCode}", ctx.Method, ctx.Path, ctx.StatusCode);
});
// ---- 业务端点注册 ----
endpoints.MapGet<IFileLibraryEndpointService>("api/library/drives", (service, ctx) => service.GetDrivesAsync(ctx))
.WithOpenApi("FileLibrary", "查询服务器磁盘。")
.WithName("GetLibraryDrives");
endpoints.MapGet<IFileLibraryEndpointService, DirectoryQueryRequest>("api/library/directories", (service, request, _) => service.GetDirectoriesAsync(request))
.WithOpenApi("FileLibrary", "查询服务器目录。")
.WithName("GetLibraryDirectories");
endpoints.MapGet<IFileLibraryEndpointService>("api/library/roots", (service, ctx) => service.GetRootsAsync(ctx))
.WithOpenApi("FileLibrary", "查询文件库目录。")
.WithName("GetLibraryRoots");
endpoints.MapPost<IFileLibraryEndpointService, AddLibraryRootRequest>("api/library/roots", (service, request, _) => service.AddRootAsync(request))
.WithOpenApi("FileLibrary", "添加文件库目录。")
.WithName("AddLibraryRoot");
endpoints.MapPost<IFileLibraryEndpointService, UpdateLibraryRootRequest>("api/library/roots/enabled", (service, request, _) => service.SetRootEnabledAsync(request))
.WithOpenApi("FileLibrary", "启用或禁用文件库目录。")
.WithName("SetLibraryRootEnabled");
endpoints.MapPost<IFileLibraryEndpointService, DeleteLibraryRootRequest>("api/library/roots/delete", (service, request, _) => service.DeleteRootAsync(request))
.WithOpenApi("FileLibrary", "删除文件库目录。")
.WithName("DeleteLibraryRoot");
endpoints.MapPost<IFileLibraryEndpointService, ScanLibraryRootRequest>("api/library/roots/scan", (service, request, _) => service.ScanRootAsync(request))
.WithOpenApi("FileLibrary", "立即扫描文件库目录。")
.WithName("ScanLibraryRoot");
endpoints.MapGet<IFileLibraryEndpointService, SearchFilesRequest>("api/files", (service, request, _) => service.SearchFilesAsync(request))
.WithOpenApi("FileLibrary", "分页查询已扫描文件。")
.WithName("SearchFiles");
endpoints.MapGet<IFileLibraryEndpointService, BrowseDirectoryRequest>("api/files/browse", (service, request, _) => service.BrowseDirectoryAsync(request))
.WithOpenApi("FileLibrary", "浏览文件库目录结构。")
.WithName("BrowseDirectory");
endpoints.MapGet<IFileLibraryEndpointService, RecentFilesRequest>("api/files/recent", (service, request, _) => service.GetRecentFilesAsync(request))
.WithOpenApi("FileLibrary", "获取最近添加或最近播放的文件。")
.WithName("GetRecentFiles");
endpoints.MapPost<IFileLibraryEndpointService, MarkFilePlayedRequest>("api/files/played", (service, request, _) => service.MarkFilePlayedAsync(request))
.WithOpenApi("FileLibrary", "标记文件已播放。")
.WithName("MarkFilePlayed");
endpoints.MapPost<IFileLibraryEndpointService, SaveFileProgressRequest>("api/files/progress", (service, request, _) => service.SaveFileProgressAsync(request))
.WithOpenApi("FileLibrary", "保存文件播放进度。")
.WithName("SaveFileProgress");
endpoints.MapPost<IFileLibraryEndpointService, DeleteFileRequest>("api/files/delete", (service, request, _) => service.DeleteFileAsync(request))
.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");
endpoints.MapGet<IFileLibraryEndpointService, FileQueryRequest>("api/files/text", (service, request, _) => service.GetTextPreviewAsync(request))
.WithOpenApi("FileLibrary", "预览文本文件。")
.WithName("GetTextPreview");
endpoints.MapGet("api/files/stream", GetFileStreamAsync)
.WithOpenApi("FileLibrary", "流式传输文件(支持 Range 请求)。")
.WithName("StreamManagedFile");
endpoints.MapGet<IQrCodeService>("api/qrcode", (service, ctx) => service.GenerateQrCodeAsync(ctx))
.WithOpenApi("Utility", "生成局域网访问二维码。")
.WithName("GetQrCode");
// ---- 需要鉴权的端点示例 ----
// endpoints.MapGet("api/admin/dashboard", AdminDashboardAsync)
// .WithName("AdminDashboard")
// .RequireAuthorization = true
// .Policy = "AdminOnly";
});
return builder;
}
#region 业务处理方法
/// <summary>
/// 从 <see cref="ServiceEndpointContext.Items"/> 中解析 <see cref="IFileStreamService"/>
/// 读取查询参数中的文件 ID,返回文件流响应。
/// </summary>
/// <param name="ctx">端点上下文。</param>
/// <returns>文件流响应对象,服务不可用或 ID 无效时返回 null。</returns>
private static async Task<object?> GetFileStreamAsync(ServiceEndpointContext ctx)
{
var sp = ctx.Items["ServiceProvider"] as IServiceProvider;
if (sp is null) return null;
if (!int.TryParse(ctx.Query.GetValueOrDefault("id"), out var id) || id <= 0)
return null;
using var scope = sp.CreateScope();
var service = scope.ServiceProvider.GetService<IFileStreamService>();
if (service is null) return null;
return await service.GetFileStreamAsync(id);
}
#endregion
}
}