using Microsoft.Extensions.Logging; using QYZH.InteractiveMagazine.Common.Extensions; using QYZH.InteractiveMagazine.Models.Common; using QYZH.InteractiveMagazine.Models.Dto; namespace QYZH.InteractiveMagazine.Infrastructure.OSS; /// /// OSS 图片搬运辅助类 /// 负责将 temp 临时目录下的文件搬运到正式目录 /// public class OssImageHelper { private readonly OssService _ossService; private readonly ILogger _logger; public OssImageHelper(OssService ossService, ILogger logger) { _ossService = ossService; _logger = logger; } /// /// 判断图片是否在 temp 临时目录 /// public static bool IsTempImage(string? imageUrl) { if (string.IsNullOrWhiteSpace(imageUrl)) return false; return imageUrl.Contains("/temp/", StringComparison.OrdinalIgnoreCase); } /// /// 将 temp 目录下的图片搬运到正式目录,返回正式路径 /// 如果图片不在 temp 目录,原样返回 /// /// 原始图片 URL(可能是完整 URL 或相对路径) /// 正式目录前缀,如 "product/makeupcard/123" 或 "pet/skin/456" /// 正式路径(相对路径),搬运失败时抛出异常 public async Task MoveToFormalAsync(string imageUrl, string formalFolder) { if (!IsTempImage(imageUrl)) return imageUrl; // 提取 OSS 相对路径(去掉域名前缀) var sourcePath = imageUrl.RemoveDomain(); if (string.IsNullOrWhiteSpace(sourcePath)) { _logger.LogWarning("图片路径解析失败,ImageUrl: {ImageUrl}", imageUrl); return imageUrl; } // URL 解码(处理中文文件名) sourcePath = Uri.UnescapeDataString(sourcePath); // 提取文件名 var fileName = Path.GetFileName(sourcePath); if (string.IsNullOrWhiteSpace(fileName)) { _logger.LogWarning("无法从路径中提取文件名,Path: {Path}", sourcePath); return imageUrl; } // 生成正式路径 var targetPath = $"{formalFolder}/{fileName}"; _logger.LogInformation("开始搬运图片,Source: {Source} -> Target: {Target}", sourcePath, targetPath); try { // 拷贝文件到正式目录 var copyResult = _ossService.CopyObject(sourcePath, targetPath); if (!copyResult) { _logger.LogError("OSS 图片拷贝失败,Source: {Source}, Target: {Target}", sourcePath, targetPath); throw new Exception("OSS 图片拷贝失败"); } // 删除临时文件(失败不影响主流程) var deleteResult = _ossService.DeleteObject(sourcePath); if (!deleteResult) { _logger.LogWarning("OSS 临时文件删除失败(不影响主流程),Source: {Source}", sourcePath); } _logger.LogInformation("图片搬运成功,Target: {Target}", targetPath); return targetPath; } catch (Exception ex) when (ex is not BusinessException) { _logger.LogError(ex, "OSS 图片搬运异常,Source: {Source}, Target: {Target}", sourcePath, targetPath); throw new BusinessException("图片搬运失败,请重试", ResultCode.GLOBAL_ERROR); } } /// /// 批量搬运 temp 图片到正式目录 /// /// 原始图片 URL 列表 /// 正式目录前缀 /// 正式路径列表(顺序与输入一致) public async Task> MoveBatchToFormalAsync(IEnumerable imageUrls, string formalFolder) { var results = new List(); foreach (var url in imageUrls) { var formal = await MoveToFormalAsync(url, formalFolder); results.Add(formal); } return results; } }