1. 更新Book实体的Source字段为SourceId并修改数据库列映射 2. 修正BookCreatedMessage的中文注释与字段类型 3. 新增阿里云OSS配置类、OssService实现与接口 4. 新增OtherSystem实体用于存储外部系统配置 5. 在BookCreatedHandler中添加OSS文件跨桶复制逻辑 6. 新增Aliyun.OSS.SDK.NetCore依赖包 7. 配置OSS服务依赖注入
171 lines
6.8 KiB
C#
171 lines
6.8 KiB
C#
using QuestionLibraryMQConsumer.Data;
|
||
using QuestionLibraryMQConsumer.Entities;
|
||
using QuestionLibraryMQConsumer.Models;
|
||
using QuestionLibraryMQConsumer.Services;
|
||
using RabbitMQ.Client;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace QuestionLibraryMQConsumer.Handlers
|
||
{
|
||
public class BookCreatedHandler : IMessageHandler
|
||
{
|
||
private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
|
||
{
|
||
PropertyNameCaseInsensitive = true
|
||
};
|
||
|
||
private readonly ILogger<BookCreatedHandler> _logger;
|
||
private readonly QuestionLibraryDb _db;
|
||
private readonly IOssService _ossService;
|
||
|
||
public string RoutingKey => "questionLibrary.book.created";
|
||
public Type MessageType => typeof(BookCreatedMessage);
|
||
public bool RequiresManualAck => false;
|
||
|
||
public BookCreatedHandler(
|
||
ILogger<BookCreatedHandler> logger,
|
||
QuestionLibraryDb db,
|
||
IOssService ossService)
|
||
{
|
||
_logger = logger;
|
||
_db = db;
|
||
_ossService = ossService;
|
||
}
|
||
|
||
public async Task HandleJsonAsync(string messageJson, CancellationToken cancellationToken)
|
||
{
|
||
_logger.LogInformation("[Handler入口] HandleJsonAsync被调用,messageJson长度:{msg} messageJson长度: {Length}", messageJson, messageJson?.Length ?? 0);
|
||
|
||
var message = JsonSerializer.Deserialize<BookCreatedMessage>(messageJson, _jsonOptions);
|
||
|
||
if (message == null)
|
||
{
|
||
_logger.LogError("[Handler错误] 反序列化失败, messageJson: {Json}", messageJson);
|
||
throw new JsonException("Failed to deserialize BookCreatedMessage");
|
||
}
|
||
|
||
_logger.LogInformation("[Handler反序列化] 成功, MessageId: {MessageId}, BookId: {BookId}", message.MessageId, message.BookId);
|
||
await HandleAsync(message, cancellationToken);
|
||
}
|
||
|
||
public async Task HandleAsync(BookCreatedMessage message, CancellationToken cancellationToken)
|
||
{
|
||
_logger.LogInformation("处理书籍创建消息: {MessageId}, 书籍ID: {BookId}, 书籍名称: {BookName}",
|
||
message.MessageId, message.BookId, message.BookName);
|
||
|
||
var exists = await _db.Db.Queryable<Book>()
|
||
.AnyAsync(b => b.Id == message.BookId);
|
||
|
||
if (exists)
|
||
{
|
||
_logger.LogWarning("书籍 {BookId} 已存在,跳过创建", message.BookId);
|
||
return;
|
||
}
|
||
|
||
// 查询外部系统配置获取源bucket信息
|
||
var otherSystem = await _db.Db.Queryable<OtherSystem>()
|
||
.FirstAsync(s => s.Id == message.SourceId);
|
||
|
||
string pdfUrl = message.PdfUrl ?? string.Empty;
|
||
string coverUrl = message.CoverUrl ?? string.Empty;
|
||
string backCoverUrl = message.BackCoverUrl ?? string.Empty;
|
||
|
||
// 如果找到外部系统配置,执行OSS跨bucket复制
|
||
if (otherSystem != null && !string.IsNullOrEmpty(otherSystem.BucketName) && !string.IsNullOrEmpty(otherSystem.Domain))
|
||
{
|
||
_logger.LogInformation("找到外部系统配置 SourceId: {SourceId}, Bucket: {Bucket}, Domain: {Domain}",
|
||
message.SourceId, otherSystem.BucketName, otherSystem.Domain);
|
||
|
||
// 并行复制所有URL
|
||
var copyTasks = new List<Task>();
|
||
|
||
if (!string.IsNullOrEmpty(pdfUrl))
|
||
{
|
||
copyTasks.Add(CopyUrlAsync("PdfUrl", pdfUrl, otherSystem, newUrl => pdfUrl = newUrl, cancellationToken));
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(coverUrl))
|
||
{
|
||
copyTasks.Add(CopyUrlAsync("CoverUrl", coverUrl, otherSystem, newUrl => coverUrl = newUrl, cancellationToken));
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(backCoverUrl))
|
||
{
|
||
copyTasks.Add(CopyUrlAsync("BackCoverUrl", backCoverUrl, otherSystem, newUrl => backCoverUrl = newUrl, cancellationToken));
|
||
}
|
||
|
||
if (copyTasks.Count > 0)
|
||
{
|
||
await Task.WhenAll(copyTasks);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_logger.LogWarning("未找到外部系统配置 SourceId: {SourceId},使用原始URL", message.SourceId);
|
||
}
|
||
|
||
var book = new Book
|
||
{
|
||
Id = message.BookId,
|
||
Name = message.BookName,
|
||
Title = message.Subtitle,
|
||
Width = message.PaperWidth,
|
||
Height = message.PaperHeight,
|
||
Cover = coverUrl,
|
||
PdfUrl = pdfUrl,
|
||
BackCover = backCoverUrl,
|
||
CreatedTime = DateTime.UtcNow,
|
||
UpdatedTime = DateTime.UtcNow,
|
||
SourceId = message.SourceId,
|
||
Status = (int)BookStatusEnum.MissCatalog
|
||
};
|
||
|
||
await _db.Db.Insertable(book).ExecuteCommandAsync();
|
||
|
||
_logger.LogInformation("书籍 {BookId} 创建成功", message.BookId);
|
||
}
|
||
|
||
private async Task CopyUrlAsync(string fieldName, string originalPath, OtherSystem otherSystem, Action<string> setNewPath, CancellationToken cancellationToken)
|
||
{
|
||
try
|
||
{
|
||
var newPath = await _ossService.CopyToOwnBucketAsync(originalPath, otherSystem.BucketName, cancellationToken);
|
||
setNewPath(newPath);
|
||
_logger.LogInformation("{FieldName} OSS复制成功: {OriginalPath} -> {NewPath}", fieldName, originalPath, newPath);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "{FieldName} OSS复制失败,使用原始路径: {OriginalPath}", fieldName, originalPath);
|
||
// 保持原始路径不变
|
||
}
|
||
}
|
||
|
||
public async Task HandleWithManualAckAsync(string messageJson, IChannel channel, ulong deliveryTag, CancellationToken cancellationToken)
|
||
{
|
||
var message = JsonSerializer.Deserialize<BookCreatedMessage>(messageJson, _jsonOptions);
|
||
|
||
if (message == null)
|
||
throw new JsonException("Failed to deserialize BookCreatedMessage");
|
||
|
||
try
|
||
{
|
||
await HandleAsync(message, cancellationToken);
|
||
|
||
await channel.BasicAckAsync(deliveryTag, false, CancellationToken.None);
|
||
_logger.LogInformation("消息 {MessageId} 处理成功并已确认", message.MessageId);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "消息 {MessageId} 处理失败,拒绝消息", message.MessageId);
|
||
await channel.BasicNackAsync(deliveryTag, false, false, CancellationToken.None);
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|