1.05 #7
@ -4,6 +4,6 @@
|
||||
<PropertyGroup>
|
||||
<!-- All Fake packages -->
|
||||
<!-- 清本地nuget缓存:dotnet nuget locals -c http-cache -->
|
||||
<FakePackageVersion>8.0.5.26</FakePackageVersion>
|
||||
<FakePackageVersion>8.0.5.27</FakePackageVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@ -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<OrderLifecycleTrackAppendEventHandler> logger)
|
||||
: IEventHandler<OrderLifecycleTrackAppendIntegrationEvent>, 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 业务写库前先打开 Outbox DbContext,让 EF 先占环境事务连接,
|
||||
/// 避免 SqlSugar 先开事务后 Outbox UseTransaction 失败而落入「短事务提前 Commit」。
|
||||
/// </summary>
|
||||
private async Task EnsureOutboxReadyAsync()
|
||||
{
|
||||
var uowManager = LazyServiceProvider.GetRequiredService<IUnitOfWorkManager>();
|
||||
if (OmsOutboxUnitOfWork.IsAmbientTransactional(uowManager))
|
||||
{
|
||||
await OmsIntegrationEventPublisher.EnsureOutboxDbContextAsync(uowManager);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<OrderParcelCreateOutput> 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
|
||||
{
|
||||
// 回滚失败不覆盖原始业务错误;半成品订单需运维清理
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建流程中途失败时删除已落库的半成品订单,避免自动变成草稿。
|
||||
/// </summary>
|
||||
@ -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))
|
||||
|
||||
@ -611,30 +611,7 @@ public partial class OrderService : ApplicationService
|
||||
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)]
|
||||
public async Task<OrderParcelCreateOutput> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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 = "上门揽收需选择揽收地址";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user