From 7b69bbf207f73554f1eb6c4596cf49b8d5483728 Mon Sep 17 00:00:00 2001 From: xiaolipro <2357729423@qq.com> Date: Wed, 14 Oct 2026 17:07:35 +0800 Subject: [PATCH] test --- Directory.Build.props | 2 +- .../OrderLifecycleTrackAppendEventHandler.cs | 7 +- ...rderParcelCustoms9610UpsertEventHandler.cs | 7 +- .../OrderParcelTrackUpsertEventHandler.cs | 4 +- .../Orders/OrderService.ParcelBilling.cs | 87 ++++++++++++++++--- .../Services/Orders/OrderService.cs | 33 ++----- 6 files changed, 93 insertions(+), 47 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 4676e0a..b45eadd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,6 +4,6 @@ - 8.0.5.26 + 8.0.5.27 diff --git a/src/Oms.Application/Messaging/OrderSideEffects/OrderLifecycleTrackAppendEventHandler.cs b/src/Oms.Application/Messaging/OrderSideEffects/OrderLifecycleTrackAppendEventHandler.cs index f963a48..ddc7e64 100644 --- a/src/Oms.Application/Messaging/OrderSideEffects/OrderLifecycleTrackAppendEventHandler.cs +++ b/src/Oms.Application/Messaging/OrderSideEffects/OrderLifecycleTrackAppendEventHandler.cs @@ -1,7 +1,5 @@ using Fake.DependencyInjection; using Fake.EventBus; -using Fake.UnitOfWork; -using Oms.Application.Messaging; using Microsoft.Extensions.Logging; using Oms.Application.Integrations.SmallBagTrack; @@ -9,7 +7,6 @@ namespace Oms.Application.Messaging.OrderSideEffects; public sealed class OrderLifecycleTrackAppendEventHandler( ISmallBagTrackOrderClient trackClient, - IUnitOfWorkManager unitOfWorkManager, ILogger logger) : IEventHandler, ITransientDependency { @@ -25,7 +22,7 @@ public sealed class OrderLifecycleTrackAppendEventHandler( return; } - using var unitOfWork = unitOfWorkManager.Begin(OmsOutboxUnitOfWork.Transactional()); + // 纯外部 HTTP,无需事务;占 TX 只会拖长连接占用。 try { await trackClient.AppendLifecycleTrackAsync( @@ -44,7 +41,5 @@ public sealed class OrderLifecycleTrackAppendEventHandler( @event.OrderNo, @event.TrackCode); } - - await unitOfWork.CompleteAsync(cancellationToken); } } diff --git a/src/Oms.Application/Messaging/ParcelCreate/OrderParcelCustoms9610UpsertEventHandler.cs b/src/Oms.Application/Messaging/ParcelCreate/OrderParcelCustoms9610UpsertEventHandler.cs index 5ce44e7..b188506 100644 --- a/src/Oms.Application/Messaging/ParcelCreate/OrderParcelCustoms9610UpsertEventHandler.cs +++ b/src/Oms.Application/Messaging/ParcelCreate/OrderParcelCustoms9610UpsertEventHandler.cs @@ -4,6 +4,7 @@ using Fake.UnitOfWork; using Oms.Application.Messaging; using Microsoft.Extensions.Logging; using Oms.Application.Services.Orders; +using Oms.Domain.OrdersAggregate.Consts; using Oms.Domain.OrdersAggregate.Repositories; namespace Oms.Application.Messaging.ParcelCreate; @@ -24,9 +25,11 @@ public sealed class OrderParcelCustoms9610UpsertEventHandler( { using var unitOfWork = unitOfWorkManager.Begin(OmsOutboxUnitOfWork.Transactional()); var order = await orderRepository.FirstOrDefaultAsync(x => x.Id == @event.OrderId, cancellationToken); - if (order == null) + if (order == null + || string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase)) { - logger.LogWarning("9610 upsert skipped, order missing OrderId={OrderId}", @event.OrderId); + logger.LogWarning("9610 upsert skipped, order missing/deleted OrderId={OrderId}", @event.OrderId); + await unitOfWork.CompleteAsync(cancellationToken); return; } diff --git a/src/Oms.Application/Messaging/ParcelCreate/OrderParcelTrackUpsertEventHandler.cs b/src/Oms.Application/Messaging/ParcelCreate/OrderParcelTrackUpsertEventHandler.cs index 0fb3f89..c2572b2 100644 --- a/src/Oms.Application/Messaging/ParcelCreate/OrderParcelTrackUpsertEventHandler.cs +++ b/src/Oms.Application/Messaging/ParcelCreate/OrderParcelTrackUpsertEventHandler.cs @@ -2,7 +2,6 @@ using Fake.Application; using Fake.DependencyInjection; using Fake.EventBus; using Fake.UnitOfWork; -using Oms.Application.Messaging; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Oms.Application.Integrations.SmallBagTrack; @@ -25,7 +24,8 @@ public sealed class OrderParcelTrackUpsertEventHandler( OrderParcelTrackUpsertIntegrationEvent @event, CancellationToken cancellationToken) { - using var unitOfWork = unitOfWorkManager.Begin(OmsOutboxUnitOfWork.Transactional()); + // 仅读库 + 外部 HTTP,不要占事务连接;失败抛出让总线重试。 + using var unitOfWork = unitOfWorkManager.Begin(new UnitOfWorkAttribute { IsTransactional = false }); var request = await ResolveRequestAsync(@event, cancellationToken); request.AppendInitialTrackOnSuccess = @event.AppendInitialTrackOnSuccess; diff --git a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs index 9d3b15a..1085937 100644 --- a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs +++ b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using Fake.Application; +using Fake.UnitOfWork; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -76,6 +77,41 @@ public partial class OrderService public string? InboundMethod { get; init; } } + private static ParcelCreateContext? BuildParcelCreateContext(OrderParcelCreateInput input) + { + var inbound = string.IsNullOrWhiteSpace(input.InboundMethod) + ? null + : input.InboundMethod.Trim(); + if (inbound == null) + { + return null; + } + + var isDoorPickup = string.Equals( + inbound, + OrderInboundMethodCodes.DoorPickup, + StringComparison.OrdinalIgnoreCase); + return new ParcelCreateContext + { + InboundMethod = inbound, + PickupTime = isDoorPickup ? input.ExpectPickupTime : null, + CustomerSenderAddressId = isDoorPickup ? input.SenderAddressId : null + }; + } + + /// + /// 业务写库前先打开 Outbox DbContext,让 EF 先占环境事务连接, + /// 避免 SqlSugar 先开事务后 Outbox UseTransaction 失败而落入「短事务提前 Commit」。 + /// + private async Task EnsureOutboxReadyAsync() + { + var uowManager = LazyServiceProvider.GetRequiredService(); + if (OmsOutboxUnitOfWork.IsAmbientTransactional(uowManager)) + { + await OmsIntegrationEventPublisher.EnsureOutboxDbContextAsync(uowManager); + } + } + private async Task CreateParcelCoreAsync( OrderParcelCreateInput input, int? operatorUserId = null, @@ -92,6 +128,7 @@ public partial class OrderService var bindTime = DateTime.Now; NormalizeParcelCreateInput(input); + createContext ??= BuildParcelCreateContext(input); var sessionCustomer = await ResolveEffectiveCustomerAsync(input.CustomerId); input.CustomerId = sessionCustomer.Id; @@ -130,6 +167,9 @@ public partial class OrderService Order? created = null; try { + // 必须在任何 SqlSugar 写之前:保证 9610 / ForecastBilling / Pickup Outbox 与建单同事务提交。 + await EnsureOutboxReadyAsync(); + var order = BuildDraftOrderEntity( input, product, @@ -148,7 +188,9 @@ public partial class OrderService if (input.SaveAsDraft) { await ParcelCreateEventPublisher.PublishCustoms9610UpsertAsync(order, input.PlatformCode); - return await BuildParcelCreateOutputAsync(order); + var draftOutput = await BuildParcelCreateOutputAsync(order); + await TryAttachPickupAfterParcelCreateAsync(input, draftOutput); + return draftOutput; } // 偏远标记轻量同步;计费重/应收/排货走 ForecastBilling MQ(失败不回滚建单)。 @@ -176,22 +218,18 @@ public partial class OrderService order.PredeclareFailReason, ForecastBillingProcessingReason, StringComparison.Ordinal); + await TryAttachPickupAfterParcelCreateAsync(input, output); return output; } + catch (BusinessException) + { + await TryRollbackFailedParcelCreateAsync(created); + throw; + } catch (Exception ex) { // 创建失败不落库草稿:回滚已插入订单,由前端让客户选择继续修改或主动保存草稿。 - if (created != null) - { - try - { - await RollbackFailedParcelCreateAsync(created); - } - catch - { - // 回滚失败不覆盖原始业务错误;半成品订单需运维清理 - } - } + await TryRollbackFailedParcelCreateAsync(created); var cleanReason = StripDraftSavedMarker(ex.Message); if (string.IsNullOrWhiteSpace(cleanReason)) @@ -203,6 +241,23 @@ public partial class OrderService } } + private async Task TryRollbackFailedParcelCreateAsync(Order? created) + { + if (created == null) + { + return; + } + + try + { + await RollbackFailedParcelCreateAsync(created); + } + catch + { + // 回滚失败不覆盖原始业务错误;半成品订单需运维清理 + } + } + /// /// 创建流程中途失败时删除已落库的半成品订单,避免自动变成草稿。 /// @@ -2271,6 +2326,8 @@ public partial class OrderService try { + await EnsureOutboxReadyAsync(); + var snapshotBinding = await ResolveSnapshotBindingFromOrderAsync(product, order); var address = await ResolveOrderBillingAddressAsync(order); @@ -2627,6 +2684,12 @@ public partial class OrderService return; } + // 创单失败回滚 / 软删后,可能仍有短事务提前投递的孤儿任务 + if (string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase)) + { + return; + } + // 已预报成功则跳过(幂等) if (string.Equals(order.OrderStatus, OrderStatusCodes.Predeclared, StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(order.PredeclareFailReason)) diff --git a/src/Oms.Application/Services/Orders/OrderService.cs b/src/Oms.Application/Services/Orders/OrderService.cs index 6bcbac0..eaccbe2 100644 --- a/src/Oms.Application/Services/Orders/OrderService.cs +++ b/src/Oms.Application/Services/Orders/OrderService.cs @@ -611,30 +611,7 @@ public partial class OrderService : ApplicationService [UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] public async Task CreateParcelAsync(OrderParcelCreateInput input) { - var inbound = string.IsNullOrWhiteSpace(input.InboundMethod) - ? null - : input.InboundMethod.Trim(); - var createContext = inbound == null - ? null - : new ParcelCreateContext - { - InboundMethod = inbound, - PickupTime = string.Equals(inbound, OrderInboundMethodCodes.DoorPickup, StringComparison.OrdinalIgnoreCase) - ? input.ExpectPickupTime - : null, - CustomerSenderAddressId = string.Equals(inbound, OrderInboundMethodCodes.DoorPickup, StringComparison.OrdinalIgnoreCase) - ? input.SenderAddressId - : null - }; - - var output = await CreateParcelCoreAsync(input, createContext: createContext); - - if (string.Equals(inbound, OrderInboundMethodCodes.DoorPickup, StringComparison.OrdinalIgnoreCase)) - { - await TryAttachPickupAfterParcelCreateAsync(input, output); - } - - return output; + return await CreateParcelCoreAsync(input, createContext: BuildParcelCreateContext(input)); } /// @@ -687,6 +664,14 @@ public partial class OrderService : ApplicationService return; } + var inbound = string.IsNullOrWhiteSpace(input.InboundMethod) + ? null + : input.InboundMethod.Trim(); + if (!string.Equals(inbound, OrderInboundMethodCodes.DoorPickup, StringComparison.OrdinalIgnoreCase)) + { + return; + } + if (input.SenderAddressId is not > 0) { output.PickupCreateError = "上门揽收需选择揽收地址";