feat(pictureManagement): improve thumbnail generation and handling for uploaded images

This commit is contained in:
shiyu
2025-06-02 19:51:43 +08:00
parent 09279b0ce4
commit a73752bcc8
2 changed files with 123 additions and 34 deletions
+48 -28
View File
@@ -236,38 +236,58 @@ public sealed class BackgroundTaskQueue : IBackgroundTaskQueue, IDisposable
throw new Exception($"找不到图片文件: {localFilePath}"); throw new Exception($"找不到图片文件: {localFilePath}");
} }
// 创建缩略图 // 检查并生成缩略图(如果不存在)
await UpdatePictureStatus(task.PictureId, ProcessingStatus.Processing, 20); await UpdatePictureStatus(task.PictureId, ProcessingStatus.Processing, 20);
var thumbnailPath = Path.Combine(
Path.GetDirectoryName(localFilePath)!, string thumbnailForAI = localFilePath; // 用于AI分析的缩略图路径
Path.GetFileNameWithoutExtension(Path.GetFileName(localFilePath)) + "_thumb.webp");
if (string.IsNullOrEmpty(picture.ThumbnailPath))
await ImageHelper.CreateThumbnailAsync(localFilePath, thumbnailPath, 500);
// 更新缩略图路径到数据库
await UpdatePictureStatus(task.PictureId, ProcessingStatus.Processing, 25);
if (picture.StorageType == StorageType.Local)
{ {
// 本地存储缩略图 // 如果缩略图不存在,生成缩略图
var relativeThumbnailPath = var thumbnailPath = Path.Combine(
$"/Uploads/{Path.GetRelativePath("Uploads", Path.GetDirectoryName(thumbnailPath)!)}/{Path.GetFileName(thumbnailPath)}"; Path.GetDirectoryName(localFilePath)!,
picture.ThumbnailPath = relativeThumbnailPath.Replace('\\', '/'); Path.GetFileNameWithoutExtension(Path.GetFileName(localFilePath)) + "_thumb.webp");
await ImageHelper.CreateThumbnailAsync(localFilePath, thumbnailPath, 500);
thumbnailForAI = thumbnailPath;
// 更新缩略图路径到数据库
await UpdatePictureStatus(task.PictureId, ProcessingStatus.Processing, 25);
if (picture.StorageType == StorageType.Local)
{
// 本地存储缩略图
var relativeThumbnailPath =
$"/Uploads/{Path.GetRelativePath("Uploads", Path.GetDirectoryName(thumbnailPath)!)}/{Path.GetFileName(thumbnailPath)}";
picture.ThumbnailPath = relativeThumbnailPath.Replace('\\', '/');
}
else
{
// 上传缩略图并获取存储路径或元数据
await using var thumbnailFileStream = new FileStream(thumbnailPath, FileMode.Open, FileAccess.Read);
var thumbnailFileName = Path.GetFileName(thumbnailPath);
var thumbnailContentType = "image/webp";
string thumbnailStoragePath = await storageService.ExecuteAsync(
picture.StorageType,
provider => provider.SaveAsync(thumbnailFileStream, thumbnailFileName, thumbnailContentType));
// 将路径或元数据存储到ThumbnailPath
picture.ThumbnailPath = thumbnailStoragePath;
}
} }
else else
{ {
// 非本地存储,上传缩略图到对应的存储服务 // 如果缩略图已存在,下载用于AI分析
await using var thumbnailFileStream = new FileStream(thumbnailPath, FileMode.Open, FileAccess.Read); if (picture.StorageType != StorageType.Local)
var thumbnailFileName = Path.GetFileName(thumbnailPath); {
var thumbnailContentType = "image/webp"; thumbnailForAI = await storageService.ExecuteAsync(picture.StorageType,
provider => provider.DownloadFileAsync(picture.ThumbnailPath));
// 上传缩略图并获取存储路径或元数据 }
string thumbnailStoragePath = await storageService.ExecuteAsync( else
picture.StorageType, {
provider => provider.SaveAsync(thumbnailFileStream, thumbnailFileName, thumbnailContentType)); thumbnailForAI = Path.Combine(Directory.GetCurrentDirectory(), picture.ThumbnailPath.TrimStart('/'));
}
// 将路径或元数据存储到ThumbnailPath
picture.ThumbnailPath = thumbnailStoragePath;
} }
// 3. 提取EXIF信息 // 3. 提取EXIF信息
@@ -283,7 +303,7 @@ public sealed class BackgroundTaskQueue : IBackgroundTaskQueue, IDisposable
// 5. 将缩略图转换为Base64并调用AI分析 // 5. 将缩略图转换为Base64并调用AI分析
await UpdatePictureStatus(task.PictureId, ProcessingStatus.Processing, 50); await UpdatePictureStatus(task.PictureId, ProcessingStatus.Processing, 50);
string base64Image = await ImageHelper.ConvertImageToBase64(thumbnailPath); string base64Image = await ImageHelper.ConvertImageToBase64(thumbnailForAI);
var (title, description) = await aiService.AnalyzeImageAsync(base64Image); var (title, description) = await aiService.AnalyzeImageAsync(base64Image);
// 6. 确定最终标题和描述 // 6. 确定最终标题和描述
+75 -6
View File
@@ -516,12 +516,67 @@ public class PictureService(
} }
} }
string? tempOriginalFileForThumbnail = null;
string? tempThumbnailFile = null;
try try
{ {
// 如果是匿名用户或者需要立即生成缩略图,先保存到临时文件
bool shouldGenerateThumbnail = userId.HasValue; // 只为注册用户生成缩略图
if (shouldGenerateThumbnail)
{
tempOriginalFileForThumbnail = Path.GetTempFileName();
// 保存原始文件到临时位置用于生成缩略图
await using (var tempFileStream = new FileStream(tempOriginalFileForThumbnail, FileMode.Create))
{
if (finalStream != fileStream) // 已经是转换后的流
{
finalStream.Position = 0;
await finalStream.CopyToAsync(tempFileStream);
finalStream.Position = 0; // 重置位置用于后续上传
}
else
{
await finalStream.CopyToAsync(tempFileStream);
finalStream.Position = 0; // 重置位置用于后续上传
}
}
}
// 使用存储服务保存文件 // 使用存储服务保存文件
string relativePath = await storageService.ExecuteAsync(storageType.Value, string relativePath = await storageService.ExecuteAsync(storageType.Value,
provider => provider.SaveAsync(finalStream, finalFileName, finalContentType)); provider => provider.SaveAsync(finalStream, finalFileName, finalContentType));
string? thumbnailPath = null;
// 生成缩略图
if (shouldGenerateThumbnail && !string.IsNullOrEmpty(tempOriginalFileForThumbnail))
{
try
{
tempThumbnailFile = Path.GetTempFileName();
string thumbnailFileName = Path.ChangeExtension(tempThumbnailFile, ".webp");
File.Move(tempThumbnailFile, thumbnailFileName);
tempThumbnailFile = thumbnailFileName;
// 生成缩略图
await ImageHelper.CreateThumbnailAsync(tempOriginalFileForThumbnail, tempThumbnailFile, 500);
// 上传缩略图
await using var thumbnailFileStream = new FileStream(tempThumbnailFile, FileMode.Open, FileAccess.Read);
var thumbnailStorageFileName = Path.GetFileNameWithoutExtension(finalFileName) + "_thumb.webp";
thumbnailPath = await storageService.ExecuteAsync(storageType.Value,
provider => provider.SaveAsync(thumbnailFileStream, thumbnailStorageFileName, "image/webp"));
}
catch (Exception ex)
{
Console.WriteLine($"生成缩略图失败: {ex.Message}");
}
}
// 创建基本的Picture对象,使用文件名作为标题和描述 // 创建基本的Picture对象,使用文件名作为标题和描述
string initialTitle = Path.GetFileNameWithoutExtension(originalFileName); string initialTitle = Path.GetFileNameWithoutExtension(originalFileName);
string initialDescription = $"Uploaded on {DateTime.UtcNow}"; string initialDescription = $"Uploaded on {DateTime.UtcNow}";
@@ -570,7 +625,7 @@ public class PictureService(
AlbumId = albumId, AlbumId = albumId,
StorageType = storageType.Value, StorageType = storageType.Value,
ProcessingStatus = isAnonymous ? ProcessingStatus.Completed : ProcessingStatus.Pending, ProcessingStatus = isAnonymous ? ProcessingStatus.Completed : ProcessingStatus.Pending,
ThumbnailPath = isAnonymous ? relativePath : null ThumbnailPath = thumbnailPath ?? (isAnonymous ? relativePath : null) // 匿名用户使用原图作为缩略图
}; };
dbContext.Pictures.Add(picture); dbContext.Pictures.Add(picture);
@@ -588,10 +643,13 @@ public class PictureService(
Name = picture.Name, Name = picture.Name,
Path = await storageService.ExecuteAsync(picture.StorageType, provider => Path = await storageService.ExecuteAsync(picture.StorageType, provider =>
Task.FromResult(provider.GetUrl(relativePath))), Task.FromResult(provider.GetUrl(relativePath))),
ThumbnailPath = isAnonymous ThumbnailPath = !string.IsNullOrEmpty(thumbnailPath)
? await storageService.ExecuteAsync(picture.StorageType, provider => ? await storageService.ExecuteAsync(picture.StorageType, provider =>
Task.FromResult(provider.GetUrl(relativePath))) Task.FromResult(provider.GetUrl(thumbnailPath)))
: null, : (isAnonymous
? await storageService.ExecuteAsync(picture.StorageType, provider =>
Task.FromResult(provider.GetUrl(relativePath)))
: null),
Description = picture.Description, Description = picture.Description,
CreatedAt = picture.CreatedAt, CreatedAt = picture.CreatedAt,
Tags = new List<string>(), Tags = new List<string>(),
@@ -605,6 +663,17 @@ public class PictureService(
} }
finally finally
{ {
// 清理临时文件
if (!string.IsNullOrEmpty(tempOriginalFileForThumbnail) && File.Exists(tempOriginalFileForThumbnail))
{
try { File.Delete(tempOriginalFileForThumbnail); } catch { }
}
if (!string.IsNullOrEmpty(tempThumbnailFile) && File.Exists(tempThumbnailFile))
{
try { File.Delete(tempThumbnailFile); } catch { }
}
// 清理转换后的临时流 // 清理转换后的临时流
if (finalStream != fileStream && finalStream is FileStream tempFileStream) if (finalStream != fileStream && finalStream is FileStream tempFileStream)
{ {
@@ -613,8 +682,8 @@ public class PictureService(
if (File.Exists(tempFilePath)) File.Delete(tempFilePath); if (File.Exists(tempFilePath)) File.Delete(tempFilePath);
// 同时清理原始临时文件 // 同时清理原始临时文件
string tempOriginalFile = Path.ChangeExtension(tempFilePath, null); string tempOriginalFileFromConvert = Path.ChangeExtension(tempFilePath, null);
if (File.Exists(tempOriginalFile)) File.Delete(tempOriginalFile); if (File.Exists(tempOriginalFileFromConvert)) File.Delete(tempOriginalFileFromConvert);
} }
} }
} }