diff --git a/FileShare-EFCore/Database/AppDataContext.cs b/FileShare-EFCore/Database/AppDataContext.cs
index 79a7fd4..9b10a90 100644
--- a/FileShare-EFCore/Database/AppDataContext.cs
+++ b/FileShare-EFCore/Database/AppDataContext.cs
@@ -25,6 +25,7 @@ namespace FileShare_EFCore.Database
/// 文件库文件记录数据
public DbSet ManagedFileRecords => Set();
+ /// 文件缩略图映射数据
public DbSet ManagedThumbnailMaps => Set();
///
diff --git a/FileShare-EFCore/Models/ManagedFileRecord.cs b/FileShare-EFCore/Models/ManagedFileRecord.cs
index 779eb93..1f37e8f 100644
--- a/FileShare-EFCore/Models/ManagedFileRecord.cs
+++ b/FileShare-EFCore/Models/ManagedFileRecord.cs
@@ -90,6 +90,7 @@ namespace FileShare_EFCore.Models
/// 所属根目录。
public ManagedLibraryRoot? LibraryRoot { get; set; }
+ /// 缩略图映射记录。
public ManagedThumbnailMap? Thumbnail { get; set; }
}
}
diff --git a/FileShare-EFCore/Models/ManagedLibraryRoot.cs b/FileShare-EFCore/Models/ManagedLibraryRoot.cs
index 80ba977..8d73fd2 100644
--- a/FileShare-EFCore/Models/ManagedLibraryRoot.cs
+++ b/FileShare-EFCore/Models/ManagedLibraryRoot.cs
@@ -63,6 +63,7 @@ namespace FileShare_EFCore.Models
/// 文件记录。
public List Files { get; set; } = new();
+ /// 缩略图记录。
public List Thumbnails { get; set; } = new();
}
}
diff --git a/FileShare-EFCore/Models/ManagedThumbnailMap.cs b/FileShare-EFCore/Models/ManagedThumbnailMap.cs
index 9420340..b65835e 100644
--- a/FileShare-EFCore/Models/ManagedThumbnailMap.cs
+++ b/FileShare-EFCore/Models/ManagedThumbnailMap.cs
@@ -4,34 +4,45 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace FileShare_EFCore.Models
{
+ ///
+ /// 文件缩略图映射记录,存储视频缩略图的文件路径与内容类型。
+ ///
[Comment("文件缩略图映射记录")]
[Table("managed-thumbnail-map")]
public class ManagedThumbnailMap
{
+ /// 主键 ID。
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
+ /// 所属根目录 ID。
[Column("library-root-id")]
public int LibraryRootId { get; set; }
+ /// 缩略图相对于缩略图存储根目录的路径。
[Column("relative-path")]
[MaxLength(1024)]
public string RelativePath { get; set; } = string.Empty;
+ /// 缩略图的 MIME 类型。
[Column("content-type")]
[MaxLength(100)]
public string ContentType { get; set; } = "image/jpeg";
+ /// 创建时间 UTC。
[Column("created-at")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ /// 更新时间 UTC。
[Column("updated-at")]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
+ /// 所属根目录。
public ManagedLibraryRoot? LibraryRoot { get; set; }
+ /// 引用了此缩略图的文件记录。
public List Files { get; set; } = new();
}
}
diff --git a/FileShare-PC/Views/MainWindow.axaml.cs b/FileShare-PC/Views/MainWindow.axaml.cs
index c77ebd3..5459ff8 100644
--- a/FileShare-PC/Views/MainWindow.axaml.cs
+++ b/FileShare-PC/Views/MainWindow.axaml.cs
@@ -692,6 +692,11 @@ namespace FileShare_PC.Views
return true;
}
+ ///
+ /// 尝试处理本地缩略图请求,匹配 /api/thumbnails/{id} 路径并返回缩略图文件流。
+ ///
+ /// HTTP 监听器上下文。
+ /// 如果请求路径匹配缩略图端点则返回 true,否则返回 false。
private async Task TryHandleLocalThumbnailAsync(HttpListenerContext context)
{
var request = context.Request;
diff --git a/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs b/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs
index 994b432..ed8a515 100644
--- a/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs
+++ b/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs
@@ -85,8 +85,18 @@ namespace FileShare_Services.Services.FileLibrary
/// API 响应。
Task BrowseDirectoryAsync(BrowseDirectoryRequest request);
+ ///
+ /// 获取最近添加或最近播放的文件列表。
+ ///
+ /// 包含类型和数量的请求。
+ /// API 响应。
Task GetRecentFilesAsync(RecentFilesRequest request);
+ ///
+ /// 标记文件为已播放,更新最近播放时间。
+ ///
+ /// 包含文件 ID 的请求。
+ /// API 响应。
Task MarkFilePlayedAsync(MarkFilePlayedRequest request);
}
}
diff --git a/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs b/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs
index a4e5d6f..485b473 100644
--- a/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs
+++ b/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs
@@ -98,8 +98,20 @@ namespace FileShare_Services.Services.FileLibrary
/// 目录浏览响应,包含子目录和文件列表。
Task BrowseDirectoryAsync(BrowseDirectoryRequest request, CancellationToken cancellationToken = default);
+ ///
+ /// 获取最近添加或最近播放的文件列表。
+ ///
+ /// 筛选类型,"added" 按创建时间排序,"played" 按最近播放时间排序。
+ /// 返回数量,范围 1-48,默认 12。
+ /// 取消令牌。
+ /// 文件记录 DTO 列表。
Task> GetRecentFilesAsync(string type, int count = 12, CancellationToken cancellationToken = default);
+ ///
+ /// 将指定文件的最近播放时间更新为当前 UTC 时间。
+ ///
+ /// 文件记录 ID。
+ /// 取消令牌。
Task MarkFilePlayedAsync(int id, CancellationToken cancellationToken = default);
}
}
diff --git a/FileShare-Services/Services/FileLibrary/IVideoThumbnailService.cs b/FileShare-Services/Services/FileLibrary/IVideoThumbnailService.cs
index 14872b5..cdd932f 100644
--- a/FileShare-Services/Services/FileLibrary/IVideoThumbnailService.cs
+++ b/FileShare-Services/Services/FileLibrary/IVideoThumbnailService.cs
@@ -1,11 +1,39 @@
namespace FileShare_Services.Services.FileLibrary
{
+ ///
+ /// 视频缩略图生成服务接口,负责从视频文件中截取缩略图并获取时长信息。
+ ///
public interface IVideoThumbnailService
{
+ ///
+ /// 为指定视频文件生成缩略图,如果已存在则直接返回。
+ ///
+ /// 文件库根目录 ID。
+ /// 视频文件的绝对路径。
+ /// 取消令牌。
+ /// 生成的缩略图信息,失败时返回 null。
Task GenerateThumbnailAsync(int libraryRootId, string videoPath, CancellationToken ct = default);
+
+ ///
+ /// 将相对路径转换为缩略图存储目录下的绝对路径,并校验路径安全性。
+ ///
+ /// 相对路径。
+ /// 绝对路径。
+ /// 当路径越权访问缩略图存储根目录之外的位置时抛出。
string GetAbsolutePath(string relativePath);
+
+ ///
+ /// 获取视频文件的总时长。
+ ///
+ /// 视频文件的绝对路径。
+ /// 视频时长(秒),获取失败时返回 null。
double? GetVideoDuration(string videoPath);
}
+ ///
+ /// 缩略图生成结果记录。
+ ///
+ /// 缩略图相对于存储根目录的路径。
+ /// 缩略图的 MIME 类型。
public sealed record GeneratedThumbnail(string RelativePath, string ContentType);
}
diff --git a/FileShare-Services/Services/FileLibrary/ThumbnailStorageOptions.cs b/FileShare-Services/Services/FileLibrary/ThumbnailStorageOptions.cs
index 6ccf26c..c3d9474 100644
--- a/FileShare-Services/Services/FileLibrary/ThumbnailStorageOptions.cs
+++ b/FileShare-Services/Services/FileLibrary/ThumbnailStorageOptions.cs
@@ -1,9 +1,17 @@
namespace FileShare_Services.Services.FileLibrary
{
+ ///
+ /// 缩略图存储与服务配置选项。
+ ///
public sealed class ThumbnailStorageOptions
{
+ /// 缩略图文件存储根目录路径。
public string RootPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "thumbnails");
+
+ /// ffmpeg 可执行文件路径。
public string FfmpegPath { get; set; } = Path.Combine("tools", "ffmpeg", "bin", "ffmpeg.exe");
+
+ /// ffprobe 可执行文件路径。
public string FfprobePath { get; set; } = Path.Combine("tools", "ffmpeg", "bin", "ffprobe.exe");
}
}
diff --git a/FileShare-Services/Services/FileLibrary/ThumbnailStreamService.cs b/FileShare-Services/Services/FileLibrary/ThumbnailStreamService.cs
index 7fa8077..39de82e 100644
--- a/FileShare-Services/Services/FileLibrary/ThumbnailStreamService.cs
+++ b/FileShare-Services/Services/FileLibrary/ThumbnailStreamService.cs
@@ -4,13 +4,26 @@ using Microsoft.EntityFrameworkCore;
namespace FileShare_Services.Services.FileLibrary
{
+ ///
+ /// 缩略图流服务接口,根据缩略图 ID 返回文件流响应。
+ ///
public interface IThumbnailStreamService
{
+ ///
+ /// 根据缩略图记录 ID 获取缩略图文件流响应。
+ ///
+ /// 缩略图记录 ID。
+ /// 取消令牌。
+ /// 文件流响应,不存在或文件丢失时返回 null。
Task GetThumbnailAsync(int id, CancellationToken cancellationToken = default);
}
+ ///
+ /// 缩略图流服务实现,从数据库查找缩略图映射并返回对应的物理文件流。
+ ///
public sealed class ThumbnailStreamService(AppDataContext db, IVideoThumbnailService thumbnails) : IThumbnailStreamService
{
+ ///
public async Task GetThumbnailAsync(int id, CancellationToken cancellationToken = default)
{
var thumbnail = await db.ManagedThumbnailMaps
diff --git a/FileShare-Services/Services/FileLibrary/VideoThumbnailService.cs b/FileShare-Services/Services/FileLibrary/VideoThumbnailService.cs
index d1ce836..ad4e056 100644
--- a/FileShare-Services/Services/FileLibrary/VideoThumbnailService.cs
+++ b/FileShare-Services/Services/FileLibrary/VideoThumbnailService.cs
@@ -4,12 +4,22 @@ using System.Text;
namespace FileShare_Services.Services.FileLibrary
{
+ ///
+ /// 视频缩略图服务实现,使用 ffmpeg 截取视频帧作为缩略图,使用 ffprobe 提取视频时长。
+ ///
public sealed class VideoThumbnailService : IVideoThumbnailService
{
+ /// 缩略图文件存储目录的绝对路径。
private readonly string _thumbnailDir;
+ /// ffmpeg 可执行文件路径。
private readonly string _ffmpegPath;
+ /// ffprobe 可执行文件路径。
private readonly string _ffprobePath;
+ ///
+ /// 初始化视频缩略图服务,解析并验证配置路径,创建缩略图存储目录。
+ ///
+ /// 缩略图存储配置选项。
public VideoThumbnailService(ThumbnailStorageOptions options)
{
_thumbnailDir = Path.IsPathRooted(options.RootPath)
@@ -20,6 +30,7 @@ namespace FileShare_Services.Services.FileLibrary
Directory.CreateDirectory(_thumbnailDir);
}
+ ///
public Task GenerateThumbnailAsync(int libraryRootId, string videoPath, CancellationToken ct = default)
{
var hash = ComputeHash(videoPath);
@@ -69,6 +80,7 @@ namespace FileShare_Services.Services.FileLibrary
}
}
+ ///
public string GetAbsolutePath(string relativePath)
{
var root = Path.GetFullPath(_thumbnailDir);
@@ -82,6 +94,11 @@ namespace FileShare_Services.Services.FileLibrary
return fullPath;
}
+ ///
+ /// 解析可执行文件路径,支持绝对路径和相对于应用程序基目录的相对路径。
+ ///
+ /// 配置中的原始可执行文件路径。
+ /// 可用的绝对路径。
private static string ResolveExecutablePath(string executablePath)
{
if (Path.IsPathRooted(executablePath))
@@ -95,6 +112,7 @@ namespace FileShare_Services.Services.FileLibrary
: executablePath;
}
+ ///
public double? GetVideoDuration(string videoPath)
{
try
@@ -130,6 +148,11 @@ namespace FileShare_Services.Services.FileLibrary
}
}
+ ///
+ /// 计算视频文件路径的 MD5 哈希值,用于生成唯一的缩略图文件名。
+ ///
+ /// 视频文件的绝对路径。
+ /// 小写的十六进制 MD5 哈希字符串。
public string ComputeHash(string videoPath) =>
Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(videoPath))).ToLowerInvariant();
}