后端: - 新增 ManagedLibraryRoot / ManagedFileRecord 数据模型及 SQLite 迁移 - 新增文件库服务、端点服务及定时扫描后台任务 - 新增 REST API: drives、directories、roots CRUD、files 分页搜索、文本预览 - 新增文件流端点支持视频/音频流式传输 - 数据库切换为 SQLite,Kestrel 绑定 0.0.0.0 支持局域网访问 前端: - 管理端:磁盘浏览、目录选择、根目录添加/启用/删除/扫描 - 客户端:根目录选择、文件搜索/筛选/分页、音视频播放、文本预览 - 全新响应式 UI(桌面+移动端),CSS 变量设计系统 - HTTP 客户端支持 Vite 开发代理与生产同源自动切换 - 移除 HTTPS 强制重定向以提升移动端视频流兼容性
100 lines
3.7 KiB
C#
100 lines
3.7 KiB
C#
using Avalonia_Common.Core;
|
|
using Avalonia_Services.Core;
|
|
using System.Text.Json;
|
|
|
|
namespace Avalonia_Services.Services.FileLibrary
|
|
{
|
|
public sealed class FileLibraryEndpointService(IFileLibraryService fileLibrary) : IFileLibraryEndpointService
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
PropertyNameCaseInsensitive = true,
|
|
};
|
|
|
|
public async Task<object?> GetDrivesAsync(ServiceEndpointContext ctx)
|
|
{
|
|
return ResponseHelper.Ok(await fileLibrary.GetDrivesAsync());
|
|
}
|
|
|
|
public async Task<object?> GetDirectoriesAsync(ServiceEndpointContext ctx)
|
|
{
|
|
var path = ctx.Query.GetValueOrDefault("path");
|
|
return ResponseHelper.Ok(await fileLibrary.GetDirectoriesAsync(path));
|
|
}
|
|
|
|
public async Task<object?> GetRootsAsync(ServiceEndpointContext ctx)
|
|
{
|
|
return ResponseHelper.Ok(await fileLibrary.GetRootsAsync());
|
|
}
|
|
|
|
public async Task<object?> AddRootAsync(ServiceEndpointContext ctx)
|
|
{
|
|
var request = ReadBody<AddLibraryRootRequest>(ctx);
|
|
return ResponseHelper.Ok(await fileLibrary.AddRootAsync(request), "文件库目录已添加并完成扫描。");
|
|
}
|
|
|
|
public async Task<object?> SetRootEnabledAsync(ServiceEndpointContext ctx)
|
|
{
|
|
var request = ReadBody<UpdateLibraryRootRequest>(ctx);
|
|
return ResponseHelper.Ok(await fileLibrary.SetRootEnabledAsync(request), "文件库目录状态已更新。");
|
|
}
|
|
|
|
public async Task<object?> DeleteRootAsync(ServiceEndpointContext ctx)
|
|
{
|
|
var request = ReadBody<DeleteLibraryRootRequest>(ctx);
|
|
await fileLibrary.DeleteRootAsync(request);
|
|
return ResponseHelper.Succeed("文件库目录已删除。");
|
|
}
|
|
|
|
public async Task<object?> ScanRootAsync(ServiceEndpointContext ctx)
|
|
{
|
|
var request = ReadBody<ScanLibraryRootRequest>(ctx);
|
|
return ResponseHelper.Ok(await fileLibrary.ScanRootAsync(request.Id), "文件库目录扫描完成。");
|
|
}
|
|
|
|
public async Task<object?> SearchFilesAsync(ServiceEndpointContext ctx)
|
|
{
|
|
return await fileLibrary.SearchFilesAsync(ctx);
|
|
}
|
|
|
|
public async Task<object?> GetFileAsync(ServiceEndpointContext ctx)
|
|
{
|
|
var id = ReadId(ctx);
|
|
var file = await fileLibrary.GetFileAsync(id);
|
|
return file is null
|
|
? ResponseHelper.Failure(404, "文件不存在或尚未扫描入库。")
|
|
: ResponseHelper.Ok(file);
|
|
}
|
|
|
|
public async Task<object?> GetTextPreviewAsync(ServiceEndpointContext ctx)
|
|
{
|
|
var id = ReadId(ctx);
|
|
var preview = await fileLibrary.GetTextPreviewAsync(id);
|
|
return preview is null
|
|
? ResponseHelper.Failure(404, "文本文件不存在或无法预览。")
|
|
: ResponseHelper.Ok(preview);
|
|
}
|
|
|
|
private static T ReadBody<T>(ServiceEndpointContext ctx)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(ctx.Body))
|
|
{
|
|
throw new InvalidOperationException("请求体不能为空。");
|
|
}
|
|
|
|
var body = JsonSerializer.Deserialize<T>(ctx.Body, JsonOptions);
|
|
return body ?? throw new InvalidOperationException("请求体格式错误。");
|
|
}
|
|
|
|
private static int ReadId(ServiceEndpointContext ctx)
|
|
{
|
|
if (int.TryParse(ctx.Query.GetValueOrDefault("id"), out var id) && id > 0)
|
|
{
|
|
return id;
|
|
}
|
|
|
|
throw new InvalidOperationException("id 参数无效。");
|
|
}
|
|
}
|
|
}
|