From 32753e7606842f76410cb752ee04c34f8fc73c61 Mon Sep 17 00:00:00 2001 From: xiaolipro <2357729423@qq.com> Date: Tue, 21 Jul 2026 10:45:57 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Messaging/OrderBatchJobEventHandler.cs | 13 +--- .../Messaging/OrderBatchRabbitMqOptions.cs | 7 +- .../Orders/Batch/OrderBatchJobProcessor.cs | 70 +++---------------- src/Oms.Tests/OrderBatchJobProcessorTests.cs | 51 +++----------- .../appsettings.Development.json | 7 +- 5 files changed, 25 insertions(+), 123 deletions(-) diff --git a/src/Oms.Application/Messaging/OrderBatchJobEventHandler.cs b/src/Oms.Application/Messaging/OrderBatchJobEventHandler.cs index 9228509..580e23c 100644 --- a/src/Oms.Application/Messaging/OrderBatchJobEventHandler.cs +++ b/src/Oms.Application/Messaging/OrderBatchJobEventHandler.cs @@ -8,7 +8,8 @@ using Oms.Application.Services.Orders.Batch; namespace Oms.Application.Messaging; /// -/// 订单批量集成事件处理器。消费通道/Ack/Nack/DLX/重试由 Fake.EventBus.RabbitMQ 负责。 +/// 订单批量集成事件处理器。 +/// 正常返回 Ack;抛异常由 Fake.EventBus.RabbitMQ 重试后 Nack 进 DLX。 /// public sealed class OrderBatchJobEventHandler( OrderBatchJobProcessor processor, @@ -29,15 +30,7 @@ public sealed class OrderBatchJobEventHandler( } using var unitOfWork = unitOfWorkManager.Begin(); - var message = @event.ToMessage(); - var result = await processor.ProcessAsync(message, cancellationToken); + await processor.ProcessAsync(@event.ToMessage(), cancellationToken); await unitOfWork.CompleteAsync(cancellationToken); - - // Fake EventBus:成功返回则 Ack;抛异常则框架内重试后 Nack(requeue=false) 进 DLX。 - if (result.Kind == OrderBatchJobProcessor.ProcessResultKind.Reject && result.Requeue) - { - throw new InvalidOperationException( - $"Order batch system failure, allow Fake EventBus retry. action={message.Action} jobItemId={message.JobItemId} messageId={message.MessageId}"); - } } } diff --git a/src/Oms.Application/Messaging/OrderBatchRabbitMqOptions.cs b/src/Oms.Application/Messaging/OrderBatchRabbitMqOptions.cs index baaa7bb..7158cf3 100644 --- a/src/Oms.Application/Messaging/OrderBatchRabbitMqOptions.cs +++ b/src/Oms.Application/Messaging/OrderBatchRabbitMqOptions.cs @@ -1,7 +1,7 @@ namespace Oms.Application.Messaging; /// -/// 订单批量消息业务开关。连接/消费/QoS/DLX 由 Fake.EventBus.RabbitMQ(RabbitMqEventBus)配置。 +/// 订单批量消息业务开关。连接/消费/QoS/DLX/重试由 Fake.EventBus.RabbitMQ 配置。 /// public class OrderBatchRabbitMqOptions { @@ -11,9 +11,4 @@ public class OrderBatchRabbitMqOptions /// 是否启用订单批量消息发布与处理。 /// public bool Enabled { get; set; } - - /// - /// 系统失败时任务项最大重试次数(业务侧),与 Fake EventBus 进程内重试相互独立。 - /// - public int MaxRetryCount { get; set; } = 3; } diff --git a/src/Oms.Application/Services/Orders/Batch/OrderBatchJobProcessor.cs b/src/Oms.Application/Services/Orders/Batch/OrderBatchJobProcessor.cs index cbd99e7..543d472 100644 --- a/src/Oms.Application/Services/Orders/Batch/OrderBatchJobProcessor.cs +++ b/src/Oms.Application/Services/Orders/Batch/OrderBatchJobProcessor.cs @@ -1,7 +1,5 @@ -using Fake.Application; using Fake.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Newtonsoft.Json; using Oms.Application.Dtos.Orders; using Oms.Application.Messaging; @@ -13,15 +11,15 @@ namespace Oms.Application.Services.Orders.Batch; /// /// 消费 RabbitMQ 消息后更新任务状态并执行业务。 +/// 正常返回由 Fake EventBus Ack;抛异常则框架重试后进 DLX。 /// public sealed class OrderBatchJobProcessor( IOrderBatchJobRepository batchJobRepository, IOrderTaskRepository orderTaskRepository, IOrderBatchActionExecutor actionExecutor, - IOptions rabbitMqOptions, ILogger logger) : IScopedDependency { - public async Task ProcessAsync( + public async Task ProcessAsync( OrderBatchJobMessage message, CancellationToken cancellationToken = default) { @@ -32,18 +30,18 @@ public sealed class OrderBatchJobProcessor( if (item == null) { logger.LogWarning("Job item {JobItemId} not found, ack message {MessageId}", message.JobItemId, message.MessageId); - return ProcessResult.Ack; + return; } if (item.Status == OrderBatchJobItemStatusCodes.Success) { - return ProcessResult.Ack; + return; } if (item.Status == OrderBatchJobItemStatusCodes.Cancelled) { logger.LogInformation("Skip cancelled job item {JobItemId}", item.Id); - return ProcessResult.Ack; + return; } item.Status = OrderBatchJobItemStatusCodes.Running; @@ -56,7 +54,7 @@ public sealed class OrderBatchJobProcessor( if (message.JobItemId <= 0) { - // fire-and-forget(如轨迹同步):失败也不 requeue,避免空转打爆 DB + // fire-and-forget(如轨迹同步):失败只记日志,不抛、不进 DLX if (!result.Success) { logger.LogWarning( @@ -66,13 +64,13 @@ public sealed class OrderBatchJobProcessor( result.FailReason); } - return ProcessResult.Ack; + return; } item = await batchJobRepository.GetItemByIdAsync((int)message.JobItemId, cancellationToken); if (item == null) { - return ProcessResult.Ack; + return; } item.FinishedAt = DateTime.Now; @@ -98,7 +96,7 @@ public sealed class OrderBatchJobProcessor( await batchJobRepository.UpdateItemAsync(item, cancellationToken); await RefreshJobSummaryAsync(item.JobId, cancellationToken); - return ProcessResult.Ack; + return; } if (result.IsBusinessFailure) @@ -112,39 +110,7 @@ public sealed class OrderBatchJobProcessor( } await RefreshJobSummaryAsync(item.JobId, cancellationToken); - return ProcessResult.Ack; } - - item.RetryCount++; - var maxRetryCount = Math.Max(1, rabbitMqOptions.Value.MaxRetryCount); - if (item.RetryCount >= maxRetryCount) - { - item.Status = OrderBatchJobItemStatusCodes.Failed; - item.FailReason = string.IsNullOrWhiteSpace(result.FailReason) - ? "系统重试次数已耗尽" - : result.FailReason; - item.FinishedAt = DateTime.Now; - item.UpdateTime = DateTime.Now; - await batchJobRepository.UpdateItemAsync(item, cancellationToken); - if (message.Action == OrderBatchActionCodes.ImportRow) - { - await RecordImportTaskFailAsync(message, item.FailReason, cancellationToken); - } - - await RefreshJobSummaryAsync(item.JobId, cancellationToken); - logger.LogWarning( - "Job item {JobItemId} retry exhausted after {RetryCount} attempts", - item.Id, - item.RetryCount); - return ProcessResult.Ack; - } - - item.Status = OrderBatchJobItemStatusCodes.Pending; - item.FailReason = null; - item.FinishedAt = null; - item.UpdateTime = DateTime.Now; - await batchJobRepository.UpdateItemAsync(item, cancellationToken); - return ProcessResult.Reject(isBusinessFailure: false); } private async Task RefreshJobSummaryAsync(int jobId, CancellationToken cancellationToken) @@ -251,22 +217,4 @@ public sealed class OrderBatchJobProcessor( } ], cancellationToken); } - - public enum ProcessResultKind - { - Ack, - Reject, - } - - public readonly struct ProcessResult - { - public ProcessResultKind Kind { get; init; } - - public bool Requeue { get; init; } - - public static ProcessResult Ack => new() { Kind = ProcessResultKind.Ack }; - - public static ProcessResult Reject(bool isBusinessFailure) => - new() { Kind = ProcessResultKind.Reject, Requeue = !isBusinessFailure }; - } } diff --git a/src/Oms.Tests/OrderBatchJobProcessorTests.cs b/src/Oms.Tests/OrderBatchJobProcessorTests.cs index 28e11aa..61586e1 100644 --- a/src/Oms.Tests/OrderBatchJobProcessorTests.cs +++ b/src/Oms.Tests/OrderBatchJobProcessorTests.cs @@ -1,6 +1,5 @@ using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using Moq; using Oms.Application.Messaging; using Oms.Application.Services.Orders.Batch; @@ -23,12 +22,11 @@ public class OrderBatchJobProcessorTests _batchJobRepository.Object, _orderTaskRepository.Object, _actionExecutor.Object, - Options.Create(new OrderBatchRabbitMqOptions { MaxRetryCount = 3 }), NullLogger.Instance); } [Fact] - public async Task ProcessAsync_BusinessFailure_ShouldMarkFailedAndAck() + public async Task ProcessAsync_BusinessFailure_ShouldMarkFailed() { var item = CreateItem(id: 10, status: OrderBatchJobItemStatusCodes.Pending); var message = CreateMessage(item.Id); @@ -38,54 +36,30 @@ public class OrderBatchJobProcessorTests .ReturnsAsync(OrderBatchActionExecuteResult.BusinessFail("订单不存在")); SetupJobRefresh(item.JobId, [item]); - var result = await _processor.ProcessAsync(message); + await _processor.ProcessAsync(message); - result.Kind.Should().Be(OrderBatchJobProcessor.ProcessResultKind.Ack); item.Status.Should().Be(OrderBatchJobItemStatusCodes.Failed); item.FailReason.Should().Be("订单不存在"); } [Fact] - public async Task ProcessAsync_SystemFailure_WithRetriesRemaining_ShouldResetPendingAndReject() + public async Task ProcessAsync_SystemFailure_ShouldThrow() { - var item = CreateItem(id: 11, status: OrderBatchJobItemStatusCodes.Pending, retryCount: 1); + var item = CreateItem(id: 11, status: OrderBatchJobItemStatusCodes.Pending); var message = CreateMessage(item.Id); SetupItemLookup(item); _actionExecutor .Setup(x => x.ExecuteAsync(message, It.IsAny())) .ReturnsAsync(OrderBatchActionExecuteResult.SystemFail("下游超时")); - var result = await _processor.ProcessAsync(message); + var act = () => _processor.ProcessAsync(message); - result.Kind.Should().Be(OrderBatchJobProcessor.ProcessResultKind.Reject); - result.Requeue.Should().BeTrue(); - item.Status.Should().Be(OrderBatchJobItemStatusCodes.Pending); - item.RetryCount.Should().Be(2); - item.FailReason.Should().BeNull(); - item.FinishedAt.Should().BeNull(); + await act.Should().ThrowAsync() + .WithMessage("*下游超时*"); } [Fact] - public async Task ProcessAsync_SystemFailure_WhenRetryExhausted_ShouldMarkFailedAndAck() - { - var item = CreateItem(id: 12, status: OrderBatchJobItemStatusCodes.Pending, retryCount: 2); - var message = CreateMessage(item.Id); - SetupItemLookup(item); - _actionExecutor - .Setup(x => x.ExecuteAsync(message, It.IsAny())) - .ReturnsAsync(OrderBatchActionExecuteResult.SystemFail("下游超时")); - SetupJobRefresh(item.JobId, [item]); - - var result = await _processor.ProcessAsync(message); - - result.Kind.Should().Be(OrderBatchJobProcessor.ProcessResultKind.Ack); - item.Status.Should().Be(OrderBatchJobItemStatusCodes.Failed); - item.RetryCount.Should().Be(3); - item.FailReason.Should().Be("下游超时"); - } - - [Fact] - public async Task ProcessAsync_CancelledItem_ShouldAckWithoutExecuting() + public async Task ProcessAsync_CancelledItem_ShouldSkipWithoutExecuting() { var item = CreateItem(id: 13, status: OrderBatchJobItemStatusCodes.Cancelled); var message = CreateMessage(item.Id); @@ -93,16 +67,15 @@ public class OrderBatchJobProcessorTests .Setup(x => x.GetItemByIdAsync(item.Id, It.IsAny())) .ReturnsAsync(item); - var result = await _processor.ProcessAsync(message); + await _processor.ProcessAsync(message); - result.Kind.Should().Be(OrderBatchJobProcessor.ProcessResultKind.Ack); _actionExecutor.Verify( x => x.ExecuteAsync(It.IsAny(), It.IsAny()), Times.Never); } [Fact] - public async Task ProcessAsync_FireAndForgetFailure_ShouldAckWithoutRequeue() + public async Task ProcessAsync_FireAndForgetFailure_ShouldNotThrow() { var message = new OrderBatchJobMessage { @@ -116,10 +89,8 @@ public class OrderBatchJobProcessorTests .Setup(x => x.ExecuteAsync(message, It.IsAny())) .ReturnsAsync(OrderBatchActionExecuteResult.SystemFail("下游超时")); - var result = await _processor.ProcessAsync(message); + await _processor.ProcessAsync(message); - result.Kind.Should().Be(OrderBatchJobProcessor.ProcessResultKind.Ack); - result.Requeue.Should().BeFalse(); _batchJobRepository.Verify( x => x.GetItemByIdAsync(It.IsAny(), It.IsAny()), Times.Never); diff --git a/src/SimpleAdmin.Api/appsettings.Development.json b/src/SimpleAdmin.Api/appsettings.Development.json index 6e8f80d..9680e9d 100644 --- a/src/SimpleAdmin.Api/appsettings.Development.json +++ b/src/SimpleAdmin.Api/appsettings.Development.json @@ -31,10 +31,6 @@ "RefreshExpiryInDays": "7", "ClockSkewMinutes": "5" }, - "FeiShu": { - "Webhook": "https://example.com/webhook", - "EnableFeiShuExceptionSubscribe": false - }, "FeiShuNotice": { "Webhook": "https://example.com/webhook", "EnableFeiShuExceptionSubscribe": false @@ -112,8 +108,7 @@ }, "OrderBatch": { "RabbitMq": { - "Enabled": false, - "MaxRetryCount": 3 + "Enabled": false } } } \ No newline at end of file