using Dpz.Core.Public.ViewModel.Response;
using SixLabors.ImageSharp;

namespace Dpz.Core.Service.Mediator.Features.Media.Commands;

/// <summary>
/// 处理相册照片发布与信息更新请求。
/// </summary>
public class PublishPhotoEventHandler(
    IRepository<PictureRecord> pictureRecordRepository,
    IMapper mapper,
    IConfiguration configuration,
    ILogger<PublishPhotoEventHandler> logger
) : IRequestHandler<PublishPhotoRequest, ResponseResult>
{
    /// <summary>
    /// 根据是否存在记录 ID 分派到创建或更新流程。
    /// </summary>
    public async ValueTask<ResponseResult> Handle(
        PublishPhotoRequest request,
        CancellationToken cancellationToken
    )
    {
        return string.IsNullOrWhiteSpace(request.Id)
            ? await CreatePhotoRecordAsync(request, cancellationToken)
            : await UpdateInformationAsync(request, cancellationToken);
    }

    private static string? ValidateCreate(PublishPhotoRequest request)
    {
        if (request.Photo is not { Length: > 0 } || !request.Photo.ContentType.Contains("image"))
        {
            return "请选择照片";
        }

        if (request.Tags is not { Count: > 0 })
        {
            return "请选择标签";
        }

        return null;
    }

    private async Task<ResponseResult> CreatePhotoRecordAsync(
        PublishPhotoRequest request,
        CancellationToken cancellationToken = default
    )
    {
        var validateMessage = ValidateCreate(request);
        if (!string.IsNullOrWhiteSpace(validateMessage))
        {
            return ResponseResult.Fail(validateMessage);
        }

        using var memoryStream = new MemoryStream();
        await request.Photo!.CopyToAsync(memoryStream, cancellationToken);
        var bytes = memoryStream.ToArray();

        var path = new List<string> { "images", "album", DateTime.Now.ToString("yyyy-MM") };
        if (
            !string.Equals(
                configuration["AgileConfig:env"],
                "PROD",
                StringComparison.OrdinalIgnoreCase
            )
        )
        {
            path.Insert(0, "test");
        }
        var filename = $"{ObjectId.GenerateNewId()}.{await GetImageFormatAsync(bytes)}";
        var result = await request.UploadAsync(bytes, path, filename, cancellationToken);

        var entity = new PictureRecord
        {
            Creator = mapper.Map<UserInfo>(request.Creator),
            UploadTime = DateTime.Now,
            Tags = request.Tags,
            Description = request.Description,
            Category = PictureCategory.Album,
            AccessUrl = result.AccessUrl,
            Length = bytes.Length,
            ObjectStorageUploadTime = DateTime.Now,
            Md5 = CalculateMd5(bytes),
            Width = result.Width,
            Height = result.Height,
        };
        await pictureRecordRepository.InsertAsync(entity, cancellationToken);
        await ClearPictureRecordCachesAsync(request, entity.Tags, cancellationToken);

        var photo = mapper.Map<PictureRecordResponse>(entity);
        return ResponseResult<PictureRecordResponse>.Ok(photo);
    }

    private async Task<string?> ValidateUpdateInformationAsync(PublishPhotoRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Id))
        {
            return "参数错误";
        }
        if (!ObjectId.TryParse(request.Id, out var oid))
        {
            return "参数错误";
        }

        var pictureRecord = await pictureRecordRepository.FindAsync(oid);
        if (pictureRecord == null)
        {
            return "该照片记录不存在";
        }
        if (pictureRecord.Creator.Id != request.Creator.Id)
        {
            return "没有权限修改该照片信息";
        }
        if (request.Tags is not { Count: > 0 })
        {
            return "请选择标签";
        }

        if (request.Photo?.Length > 0)
        {
            return "选择修改相片为什么不选择删除后再重新上传呢?不支持修改!";
        }

        return null;
    }

    private async Task<ResponseResult> UpdateInformationAsync(
        PublishPhotoRequest request,
        CancellationToken cancellationToken = default
    )
    {
        var validateMessage = await ValidateUpdateInformationAsync(request);
        if (!string.IsNullOrWhiteSpace(validateMessage))
        {
            return ResponseResult.Fail(validateMessage);
        }

        if (!ObjectId.TryParse(request.Id, out var oid))
        {
            return ResponseResult.Fail("参数错误");
        }

        var update = Builders<PictureRecord>
            .Update.Set(x => x.Category, PictureCategory.Album)
            .Set(x => x.Description, request.Description)
            .Set(x => x.Tags, request.Tags);
        await pictureRecordRepository.UpdateAsync(x => x.Id == oid, update, cancellationToken);
        await ClearPictureRecordCachesAsync(request, request.Tags, cancellationToken);

        var photoEntity = await pictureRecordRepository.FindAsync(oid, cancellationToken);
        if (photoEntity == null)
        {
            return ResponseResult.Fail("该照片记录不存在");
        }

        var photo = mapper.Map<PictureRecordResponse>(photoEntity);
        return ResponseResult<PictureRecordResponse>.Ok(photo);
    }

    private static async Task ClearPictureRecordCachesAsync(
        PublishPhotoRequest request,
        IReadOnlyCollection<string> tags,
        CancellationToken cancellationToken
    )
    {
        if (request.ClearCacheAsync != null)
        {
            await request.ClearCacheAsync(tags, cancellationToken);
        }
    }

    private static string CalculateMd5(byte[] bytes)
    {
        var hash = MD5.HashData(bytes);
        return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
    }

    private async Task<string> GetImageFormatAsync(byte[] bytes)
    {
        try
        {
            using var stream = new MemoryStream(bytes);
            using var image = await Image.LoadAsync(stream);
            return image.Metadata.DecodedImageFormat?.FileExtensions.FirstOrDefault() ?? "jpg";
        }
        catch (Exception e)
        {
            logger.LogError(e, "get image format fail");
            return "jpg";
        }
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个使用 .NET 的 MediatR 模式实现的照片发布事件处理器类。让我为您详细解释这个代码的功能和结构:

类的整体功能

PublishPhotoEventHandler 是一个处理相册照片发布与信息更新的命令处理器,它实现了 IRequestHandler 接口,用于处理 PublishPhotoRequest 请求并返回 ResponseResult

主要组件

依赖注入

public class PublishPhotoEventHandler(
    IRepository<PictureRecord> pictureRecordRepository,  // 照片记录仓储
    IMapper mapper,                                      // 对象映射器
    IConfiguration configuration,                        // 配置服务
    ILogger<PublishPhotoEventHandler> logger            // 日志记录器
)

核心处理方法

1. Handle 方法

这是主入口方法,根据请求中是否存在 ID 来决定执行创建还是更新操作:

  • 如果 request.Id 为空 → 执行创建新照片记录流程
  • 如果 request.Id 不为空 → 执行更新照片信息流程

2. 创建照片记录流程 (CreatePhotoRecordAsync)

验证步骤:

  • 检查是否上传了有效的图片文件
  • 检查是否选择了标签

处理步骤:

  1. 将上传的照片转换为字节数组
  2. 构建存储路径(根据环境和日期)
  3. 生成唯一文件名(使用 ObjectId + 图片格式)
  4. 上传文件到对象存储
  5. 创建 PictureRecord 实体并保存到数据库
  6. 清理相关缓存
  7. 返回映射后的响应对象

3. 更新照片信息流程 (UpdateInformationAsync)

验证步骤:

  • 检查 ID 参数有效性
  • 验证照片记录是否存在
  • 检查用户权限(只有创建者可以修改)
  • 验证标签是否存在
  • 不允许修改照片文件本身

处理步骤:

  1. 使用 MongoDB 的 Update Builder 更新记录
  2. 清理相关缓存
  3. 重新获取更新后的记录并返回

辅助方法

1. ValidateCreate - 创建验证

静态方法,验证创建请求的必要参数(照片文件和标签)。

2. ValidateUpdateInformationAsync - 更新验证

异步验证更新请求,包括权限检查和参数验证。

3. ClearPictureRecordCachesAsync - 缓存清理

如果请求中提供了缓存清理方法,则执行缓存清理。

4. CalculateMd5 - MD5 计算

计算文件的 MD5 哈希值,用于文件完整性验证。

5. GetImageFormatAsync - 图片格式检测

使用 ImageSharp 库检测图片格式,如果检测失败则默认使用 "jpg"。

技术特点

  1. 异步编程:大量使用 async/await 模式
  2. 错误处理:完善的参数验证和异常处理
  3. 环境区分:根据配置区分生产环境和测试环境的文件路径
  4. 缓存管理:支持标签相关的缓存清理
  5. 对象映射:使用 AutoMapper 进行对象转换
  6. MongoDB 支持:使用 ObjectId 和 Update Builder
  7. 图像处理:集成 ImageSharp 进行图片格式检测

这个处理器很好地体现了 CQRS 模式和 MediatR 的使用,代码结构清晰,职责分离明确。

评论加载中...