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)
验证步骤:
- 检查是否上传了有效的图片文件
- 检查是否选择了标签
处理步骤:
- 将上传的照片转换为字节数组
- 构建存储路径(根据环境和日期)
- 生成唯一文件名(使用 ObjectId + 图片格式)
- 上传文件到对象存储
- 创建
PictureRecord实体并保存到数据库 - 清理相关缓存
- 返回映射后的响应对象
3. 更新照片信息流程 (UpdateInformationAsync)
验证步骤:
- 检查 ID 参数有效性
- 验证照片记录是否存在
- 检查用户权限(只有创建者可以修改)
- 验证标签是否存在
- 不允许修改照片文件本身
处理步骤:
- 使用 MongoDB 的 Update Builder 更新记录
- 清理相关缓存
- 重新获取更新后的记录并返回
辅助方法
1. ValidateCreate - 创建验证
静态方法,验证创建请求的必要参数(照片文件和标签)。
2. ValidateUpdateInformationAsync - 更新验证
异步验证更新请求,包括权限检查和参数验证。
3. ClearPictureRecordCachesAsync - 缓存清理
如果请求中提供了缓存清理方法,则执行缓存清理。
4. CalculateMd5 - MD5 计算
计算文件的 MD5 哈希值,用于文件完整性验证。
5. GetImageFormatAsync - 图片格式检测
使用 ImageSharp 库检测图片格式,如果检测失败则默认使用 "jpg"。
技术特点
- 异步编程:大量使用 async/await 模式
- 错误处理:完善的参数验证和异常处理
- 环境区分:根据配置区分生产环境和测试环境的文件路径
- 缓存管理:支持标签相关的缓存清理
- 对象映射:使用 AutoMapper 进行对象转换
- MongoDB 支持:使用 ObjectId 和 Update Builder
- 图像处理:集成 ImageSharp 进行图片格式检测
这个处理器很好地体现了 CQRS 模式和 MediatR 的使用,代码结构清晰,职责分离明确。
评论加载中...