Compare commits

..

No commits in common. "f0c53bc31939ab7099f088f12559a68669d0e948" and "65368e7c16195404bc4c5f70d8f8215a44eb94e6" have entirely different histories.

46 changed files with 1428 additions and 846 deletions

View File

@ -1,9 +1,6 @@
<Project> <Project>
<Import Project="Fake.Local.props" Condition="Exists('Fake.Local.props')" />
<PropertyGroup> <PropertyGroup>
<!-- All Fake packages --> <!-- All Fake packages -->
<!-- 清本地nuget缓存:dotnet nuget locals -c http-cache --> <FakePackageVersion>8.0.5.17</FakePackageVersion>
<FakePackageVersion>8.0.6.2</FakePackageVersion>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -1,26 +0,0 @@
<Project>
<!--
UseLocalFake=true:每个 Fake.* PackageReference 换成本地工程。
NuGet 资产全部排除,避免同名 1.0.0.0 程序集盖掉本地输出。
-->
<ItemGroup Condition="'$(UseLocalFake)' == 'true'">
<PackageReference Update="Fake.*" ExcludeAssets="all" PrivateAssets="all" />
<_LocalFakeCandidate Include="@(PackageReference -> '$(FakeRepoRoot)\src\%(Identity)\%(Identity).csproj')" />
<_LocalFakeCandidate Include="@(PackageReference -> '$(FakeRepoRoot)\modules\rbac\%(Identity)\%(Identity).csproj')" />
<_LocalFakeCandidate Include="@(PackageReference -> '$(FakeRepoRoot)\modules\file-management\%(Identity)\%(Identity).csproj')" />
<!-- 变换会带上 PackageReference 的 ExcludeAssets/PrivateAssets,必须清掉,否则传递引用进不了编译。 -->
<ProjectReference Include="@(_LocalFakeCandidate)">
<ExcludeAssets></ExcludeAssets>
<PrivateAssets></PrivateAssets>
</ProjectReference>
</ItemGroup>
<Target Name="DropMissingLocalFakeProjects"
BeforeTargets="AssignProjectConfiguration"
Condition="'$(UseLocalFake)' == 'true'">
<ItemGroup>
<_MissingLocalFake Include="@(_LocalFakeCandidate)" Condition="!Exists('%(Identity)')" />
<ProjectReference Remove="@(_MissingLocalFake)" />
</ItemGroup>
</Target>
</Project>

View File

@ -1,10 +0,0 @@
<Project>
<!--
UseLocalFake=true:所有 Fake.* 包改为引用 $(FakeRepoRoot) 下的本地工程。
UseLocalFake=false:全部走 NuGet(FakePackageVersion)。
-->
<PropertyGroup>
<FakeRepoRoot Condition="'$(FakeRepoRoot)' == ''">C:\Users\BSI\RiderProjects\fake2</FakeRepoRoot>
<UseLocalFake Condition="'$(UseLocalFake)' == ''">false</UseLocalFake>
</PropertyGroup>
</Project>

View File

@ -20,8 +20,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution
.editorconfig = .editorconfig .editorconfig = .editorconfig
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
.gitea\workflows\deploy.yml = .gitea\workflows\deploy.yml .gitea\workflows\deploy.yml = .gitea\workflows\deploy.yml
Fake.Local.props = Fake.Local.props
Directory.Build.targets = Directory.Build.targets
EndProjectSection EndProjectSection
EndProject EndProject
Global Global

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../../common.props"/> <Import Project="../../common.props"/>
@ -20,8 +20,6 @@
<ProjectReference Include="..\TrackCenter.Repository\TrackCenter.Repository.csproj" /> <ProjectReference Include="..\TrackCenter.Repository\TrackCenter.Repository.csproj" />
<PackageReference Include="Fake.Autofac" Version="$(FakePackageVersion)" /> <PackageReference Include="Fake.Autofac" Version="$(FakePackageVersion)" />
<PackageReference Include="Fake.Consul" Version="$(FakePackageVersion)" /> <PackageReference Include="Fake.Consul" Version="$(FakePackageVersion)" />
<PackageReference Include="Fake.Messaging.Dashboard" Version="$(FakePackageVersion)" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
</ItemGroup> </ItemGroup>

View File

@ -5,8 +5,6 @@ using Fake.AspNetCore.ExceptionHandling;
using Fake.AspNetCore.Newtonsoft; using Fake.AspNetCore.Newtonsoft;
using Fake.AspNetCore.Swagger; using Fake.AspNetCore.Swagger;
using Fake.Autofac; using Fake.Autofac;
using Fake.Messaging.Dashboard;
using Fake.Messaging.EntityFrameworkCore;
using Fake.FeiShu; using Fake.FeiShu;
using Fake.Json; using Fake.Json;
using Fake.Modularity; using Fake.Modularity;
@ -17,7 +15,6 @@ using Hangfire;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using OpenTelemetry.Resources; using OpenTelemetry.Resources;
using OpenTelemetry.Trace; using OpenTelemetry.Trace;
using TrackCenter.Application; using TrackCenter.Application;
@ -33,8 +30,6 @@ namespace TrackCenter.Api;
[DependsOn(typeof(FakeAspNetCoreModule))] [DependsOn(typeof(FakeAspNetCoreModule))]
[DependsOn(typeof(TrackCenterApplicationModule))] [DependsOn(typeof(TrackCenterApplicationModule))]
[DependsOn(typeof(TrackCenterRepositoryModule))] [DependsOn(typeof(TrackCenterRepositoryModule))]
// Messaging 独立队列(Outbox/Inbox + Dashboard);物理传输由 FakeMessagingRabbitMqModule 提供
[DependsOn(typeof(FakeMessagingDashboardModule))]
[DependsOn(typeof(FakeSerilogModule))] [DependsOn(typeof(FakeSerilogModule))]
public class TrackCenterApiModule : FakeModule public class TrackCenterApiModule : FakeModule
{ {
@ -58,28 +53,6 @@ public class TrackCenterApiModule : FakeModule
// 配置签名验证选项 // 配置签名验证选项
services.Configure<SignatureValidationOptions>(configuration.GetSection("SignatureValidation")); services.Configure<SignatureValidationOptions>(configuration.GetSection("SignatureValidation"));
// Outbox / Inbox(fk_msg_*)与业务同库(EF),通过 DbTransaction 征用 SqlSugar 事务
services.AddDbContext<MessagingDbContext>(options =>
{
var connectionString = configuration.GetConnectionString("Messaging")
?? configuration.GetConnectionString("IntegrationEventLog")
?? configuration.GetConnectionString("Default")
?? configuration["ConnectionStrings:Default"]
?? string.Empty;
var serverVersion = ServerVersion.AutoDetect(connectionString);
options.UseMySql(connectionString, serverVersion);
#if DEBUG
options.EnableSensitiveDataLogging();
#endif
});
// Dashboard 路径保持 /eventbus,兼容旧书签与网关
services.Configure<MessagingDashboardOptions>(o =>
{
if (string.IsNullOrWhiteSpace(o.PathMatch) || o.PathMatch == "/messaging")
o.PathMatch = "/eventbus";
});
// 全局授权配置:所有接口默认需要授权 // 全局授权配置:所有接口默认需要授权
services.AddMvc(options => services.AddMvc(options =>
{ {
@ -213,9 +186,6 @@ public class TrackCenterApiModule : FakeModule
Authorization = [new AnonymousHangfireAuthorizationFilter()] Authorization = [new AnonymousHangfireAuthorizationFilter()]
}); });
// 必须在 UsePathBase 之后:完整对外路径为 {PathBase}/eventbus
app.UseFakeMessagingDashboard();
app.Services.GetRequiredService<IFeiShuNotificationService>().Send("Application started"); app.Services.GetRequiredService<IFeiShuNotificationService>().Send("Application started");
} }
} }

View File

@ -3,8 +3,5 @@
"Consul": { "Consul": {
"Address": "http://test.ideship.com:30850", "Address": "http://test.ideship.com:30850",
"Token": "c0aea902-dca0-ac29-299b-a0d47e5402aa" "Token": "c0aea902-dca0-ac29-299b-a0d47e5402aa"
},
"DataSeed": {
"Enable": false
} }
} }

View File

@ -0,0 +1,69 @@
using DotNetCore.CAP.Filter;
using InterfaceForward.Domain.Shared.Helpers;
using TrackCenter.Application.Helpers;
namespace TrackCenter.Application.CAP;
public class CapFilter : SubscribeFilter
{
/// <summary>
/// 执行前
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override async Task OnSubscribeExecutingAsync(ExecutingContext context)
{
// 轨迹新增、更新要做幂等
if (!context.DeliverMessage.Headers["cap-msg-name"]
.IsIn(GlobalConst.TrackUpsertSubscribe, GlobalConst.TrackManualPushSubscribe)) return;
var argumentJsonStr = context.Arguments.ToJson();
if (!string.IsNullOrEmpty(argumentJsonStr))
{
context.DeliverMessage.Headers.TryGetValue("track-arguments-hash", out var hash);
if (hash.IsNullOrEmpty()) // 计算参数hash
{
hash = StringHelper.GetHash(argumentJsonStr);
context.DeliverMessage.Headers["track-arguments-hash"] = hash;
}
if (await RedisHelper.Client.ExistsAsync($"CapExecuteFilter:{hash}{DateTime.Now:G}"))
{
throw new CapNoRetryException($"检测到重复消费,消费参数:{argumentJsonStr}");
}
}
}
public override async Task OnSubscribeExecutedAsync(ExecutedContext context)
{
context.DeliverMessage.Headers.TryGetValue("track-arguments-hash", out var hash);
if (!hash.IsNullOrEmpty())
{
await RedisHelper.Client.SetAsync($"CapExecuteFilter:{hash}{DateTime.Now:G}", 1, TimeSpan.FromMinutes(3));
}
}
/// <summary>
/// 消费异常事件
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task OnSubscribeExceptionAsync(ExceptionContext context)
{
context.DeliverMessage.Headers.TryGetValue("cap-msg-id", out var messageId);
if (context.Exception is CapNoRetryException ex)
{
context.ExceptionHandled = true;
Log.Warning(ex.ToString(), true);
}
else
{
// 发飞书,异常继续上抛,进入重试机制
// tips:重试有冷却期(默认4分钟)
Log.Error($"cap-msg-id:{messageId}\n{context.Exception}");
}
return Task.CompletedTask;
}
}

View File

@ -0,0 +1,12 @@
using Fake;
using Fake.ExceptionHandling;
using Fake.Logging;
using Microsoft.Extensions.Logging;
namespace TrackCenter.Application.CAP;
public class CapNoRetryException(string? message = null, Exception? innerException = null)
: FakeException(message, innerException), IHasLogLevel
{
public LogLevel LogLevel { get; set; } = LogLevel.Warning;
}

View File

@ -1,16 +1,17 @@
using Fake.Messaging; using DotNetCore.CAP;
using Fake.UnitOfWork;
using TrackCenter.Application.Events; using TrackCenter.Application.Events;
using TrackCenter.Application.Services.Warning.Handlers; using TrackCenter.Application.Services.Warning.Handlers;
using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.OrderAggregate;
namespace TrackCenter.Application.Messaging; namespace TrackCenter.Application.CAP.EventHandlers;
public class TrackChangeAfterUpdateOrderEventHandler(IOrderRepository orderRepository, IServiceProvider serviceProvider) public class TrackChangeAfterUpdateOrderEventHandler(IOrderRepository orderRepository, IServiceProvider serviceProvider)
: IMessageHandle<TrackChangeAfterUpdateOrderEvent>, ITransientDependency : ITransientDependency, ICapSubscribe
{ {
public virtual async Task HandleAsync( [UnitOfWork]
TrackChangeAfterUpdateOrderEvent @event, [CapSubscribe(GlobalConst.TrackChangeAfterUpdateOrderSubscribe)]
CancellationToken cancellationToken = default) public virtual async Task ConsumerAsync(TrackChangeAfterUpdateOrderEvent @event)
{ {
var orders = await GetOrdersAsync(@event.OrderIds); var orders = await GetOrdersAsync(@event.OrderIds);

View File

@ -1,4 +1,4 @@
using Fake.Messaging; using DotNetCore.CAP;
using Fake.UnitOfWork; using Fake.UnitOfWork;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
@ -11,33 +11,53 @@ using TrackCenter.Domain.Aggregates.OrderAggregate;
using TrackCenter.Domain.Aggregates.TrackDetailsAggregate; using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
using TrackCenter.Repository.Aggregates.OrderAggregate; using TrackCenter.Repository.Aggregates.OrderAggregate;
namespace TrackCenter.Application.Messaging; namespace TrackCenter.Application.CAP.EventHandlers;
public class TrackDeleteEventHandler( public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
{
private readonly ICapPublisher _capPublisher;
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
private readonly IOrderRepository _orderRepository;
private readonly ProvideQueries _provideQueries;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly BroadcastingService _broadcastingService;
private readonly WarningSignalPublisher _warningSignalPublisher;
public TrackDeleteEventHandler(
ProvideQueries provideQueries, ProvideQueries provideQueries,
IOrderRepository orderRepository, IOrderRepository orderRepository,
IMessagePublisher messagePublisher, ICapPublisher capPublisher,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackDetailsEsRepository trackDetailsEsRepository,
IOrderOperationRecordEsRepository orderOperationRecordEsRepository, IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
BroadcastingService broadcastingService, BroadcastingService broadcastingService,
WarningSignalPublisher warningSignalPublisher) WarningSignalPublisher warningSignalPublisher
: IMessageHandle<TrackDeleteEvent>, ITransientDependency )
{ {
[UnitOfWork(IsTransactional = true)] _provideQueries = provideQueries;
public virtual async Task HandleAsync(TrackDeleteEvent message, CancellationToken cancellationToken = default) _orderRepository = orderRepository;
_capPublisher = capPublisher;
_trackDetailsEsRepository = trackDetailsEsRepository;
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
_broadcastingService = broadcastingService;
_warningSignalPublisher = warningSignalPublisher;
}
[UnitOfWork]
[CapSubscribe(GlobalConst.TrackDeleteSubscribe, Group = GlobalConst.TrackDeleteSubscribe)]
public virtual async Task ConsumerAsync(TrackDeleteEvent @event)
{ {
if (!CanDelete(message)) return; if (!CanDelete(@event)) return;
// 处理删除轨迹 // 处理删除轨迹
await HandleDeleteTrackAsync(message); await HandleDeleteTrackAsync(@event);
// 根据订单关联关系异步向下刷(异步) // 根据订单关联关系异步向下刷(异步)
await DownByOrderLink(message); await DownByOrderLink(@event);
} }
private async Task DownByOrderLink(TrackDeleteEvent @event) private async Task DownByOrderLink(TrackDeleteEvent @event)
{ {
var linkedOrders = await orderRepository.GetLinkedOrdersAsync(@event.OrderIds); var linkedOrders = await _orderRepository.GetLinkedOrdersAsync(@event.OrderIds);
foreach (var orderId in @event.OrderIds) foreach (var orderId in @event.OrderIds)
{ {
foreach (var group in linkedOrders.Where(x => x.OwnerOrderId == orderId).GroupBy(x => x.AppKey)) foreach (var group in linkedOrders.Where(x => x.OwnerOrderId == orderId).GroupBy(x => x.AppKey))
@ -50,7 +70,7 @@ public class TrackDeleteEventHandler(
// 不同应用需要做内容映射 // 不同应用需要做内容映射
if (@event.AppKey != nextEvent.AppKey) if (@event.AppKey != nextEvent.AppKey)
{ {
var codeMaps = await provideQueries.GetTrackCodeMapsAsync( var codeMaps = await _provideQueries.GetTrackCodeMapsAsync(
@event.AppKey, @event.AppKey,
nextEvent.AppKey, nextEvent.AppKey,
@event.TrackIdentifiers.Select(x => x.TrackCode).ToArray() @event.TrackIdentifiers.Select(x => x.TrackCode).ToArray()
@ -75,8 +95,7 @@ public class TrackDeleteEventHandler(
{ {
var subEvent = nextEvent.DeepCopy(); var subEvent = nextEvent.DeepCopy();
subEvent.OrderIds = subOrderIds; subEvent.OrderIds = subOrderIds;
// 关联单继续走删除链路(原 CAP 误发到 Upsert topic,此处修正) await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, subEvent);
await messagePublisher.PublishAsync(subEvent);
} }
} }
} }
@ -106,7 +125,7 @@ public class TrackDeleteEventHandler(
// 优先根据EsId查询轨迹 // 优先根据EsId查询轨迹
if (@event.EsIds.Length > 0) if (@event.EsIds.Length > 0)
{ {
deleteTracks = await trackDetailsEsRepository.GetDataByIds(@event.EsIds); deleteTracks = await _trackDetailsEsRepository.GetDataByIds(@event.EsIds);
// 外部api获取的轨迹不允许删除 // 外部api获取的轨迹不允许删除
if (deleteTracks.Any(m => m.Source == (int) TrackSourceEnum.ExternalServiceProvide)) return; if (deleteTracks.Any(m => m.Source == (int) TrackSourceEnum.ExternalServiceProvide)) return;
@ -124,12 +143,12 @@ public class TrackDeleteEventHandler(
}).ToList(); }).ToList();
// 查出订单下的所有轨迹,以供后面回退使用 // 查出订单下的所有轨迹,以供后面回退使用
oldTracks = await trackDetailsEsRepository.GetListAsync(@event.OrderIds); oldTracks = await _trackDetailsEsRepository.GetListAsync(@event.OrderIds);
} }
else else
{ {
// 根据订单查询出所有的轨迹,查所有是因为主单上需要进行回退更新 // 根据订单查询出所有的轨迹,查所有是因为主单上需要进行回退更新
oldTracks = await trackDetailsEsRepository.GetListAsync(@event.OrderIds); oldTracks = await _trackDetailsEsRepository.GetListAsync(@event.OrderIds);
// 构建轨迹标识符 // 构建轨迹标识符
var trackIds = @event.TrackIdentifiers.Select(y => $"{y.TrackCode}{y.GetUtc0TrackTime():G}").ToHashSet(); var trackIds = @event.TrackIdentifiers.Select(y => $"{y.TrackCode}{y.GetUtc0TrackTime():G}").ToHashSet();
@ -158,7 +177,7 @@ public class TrackDeleteEventHandler(
if (deleteTracks.Count == 0) return; if (deleteTracks.Count == 0) return;
// 删除轨迹批量 // 删除轨迹批量
await trackDetailsEsRepository.DeleteTrackByIdsAsync(deleteTracks.Select(x => x.Id).ToHashSet()); await _trackDetailsEsRepository.DeleteTrackByIdsAsync(deleteTracks.Select(x => x.Id).ToHashSet());
// 删除轨迹后,更新订单上的轨迹信息 // 删除轨迹后,更新订单上的轨迹信息
await TrackDeleteAfterUpdateOrderAsync(@event, oldTracks, deleteTracks, orderIds); await TrackDeleteAfterUpdateOrderAsync(@event, oldTracks, deleteTracks, orderIds);
@ -167,13 +186,13 @@ public class TrackDeleteEventHandler(
await PublicTrackTopicAsync(@event, deleteTracks); await PublicTrackTopicAsync(@event, deleteTracks);
// 发布预警信号,稍后将进行预警计算 // 发布预警信号,稍后将进行预警计算
warningSignalPublisher.PublishSignal(WarningSignalType.轨迹删除, deleteTracks); _warningSignalPublisher.PublishSignal(WarningSignalType.轨迹删除, deleteTracks);
} }
private async Task PublicTrackTopicAsync(TrackDeleteEvent @event, List<TrackDetailsIndex> delTracks) private async Task PublicTrackTopicAsync(TrackDeleteEvent @event, List<TrackDetailsIndex> delTracks)
{ {
var trackContentIds = delTracks.Select(x => x.TrackContentId).ToHashSet(); var trackContentIds = delTracks.Select(x => x.TrackContentId).ToHashSet();
var trackContents = await provideQueries.GetTrackContentAsync(trackContentIds); var trackContents = await _provideQueries.GetTrackContentAsync(trackContentIds);
var contentDict = trackContents.ToDictionary(x => x.Id); var contentDict = trackContents.ToDictionary(x => x.Id);
var needPublicTracks = delTracks var needPublicTracks = delTracks
@ -236,7 +255,7 @@ public class TrackDeleteEventHandler(
}; };
var content = contentDict[track.TrackContentId]; var content = contentDict[track.TrackContentId];
broadcastingService.TrackChangePublicTopic(eto, content.AppKey); _broadcastingService.TrackChangePublicTopic(eto, content.AppKey);
} }
} }
@ -251,7 +270,7 @@ public class TrackDeleteEventHandler(
var deleteTrackIds = deleteTracks.Select(m => m.Id).ToList(); var deleteTrackIds = deleteTracks.Select(m => m.Id).ToList();
var newTracks = oldTracks.Where(x => !deleteTrackIds.Contains(x.Id)).ToList(); var newTracks = oldTracks.Where(x => !deleteTrackIds.Contains(x.Id)).ToList();
var orders = await orderRepository.GetListAsync(x => orderIds.Contains(x.Id), var orders = await _orderRepository.GetListAsync(x => orderIds.Contains(x.Id),
x => new OrderEntity x => new OrderEntity
{ {
Id = x.Id, Id = x.Id,
@ -296,7 +315,7 @@ public class TrackDeleteEventHandler(
} }
//更新订单上冗余轨迹明细的字段 //更新订单上冗余轨迹明细的字段
await orderRepository.UpdateRangeAsync(orders, await _orderRepository.UpdateRangeAsync(orders,
x => new x => new
{ {
x.LatestTrackStatus, x.LatestTrackStatus,
@ -310,7 +329,7 @@ public class TrackDeleteEventHandler(
}); });
//批量写入订单操作日志 //批量写入订单操作日志
_ = orderOperationRecordEsRepository.AddManyAsync(orders.Select(order => new OrderOperationRecordIndex _ = _orderOperationRecordEsRepository.AddManyAsync(orders.Select(order => new OrderOperationRecordIndex
{ {
OrderId = order.Id, OrderId = order.Id,
OrderNo = order.OrderNo, OrderNo = order.OrderNo,

View File

@ -1,6 +1,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Text.Json; using System.Text.Json;
using Fake.Messaging; using DotNetCore.CAP;
using Fake.UnitOfWork; using Fake.UnitOfWork;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -18,9 +18,24 @@ using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
using TrackCenter.Domain.Aggregates.TrackPushAggregate; using TrackCenter.Domain.Aggregates.TrackPushAggregate;
using TrackCenter.Repository.Otel; using TrackCenter.Repository.Otel;
namespace TrackCenter.Application.Messaging; namespace TrackCenter.Application.CAP.EventHandlers;
public class TrackPushEventHandler( public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
{
private readonly IOptionsMonitor<InterfaceRelayParamsConfigOptions> _options;
private readonly IOrderRepository _orderRepository;
private readonly TrackPushCacheService _trackPushCacheService;
private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly InterfaceForwardService _interfaceForwardService;
private readonly BroadcastingService _broadcastingService;
private List<OrderEntity> _orders = null!;
private List<TrackPushContentConfigDto> _pushConfigs = null!;
private Dictionary<TrackOrderType, List<TrackPushContentConfigDto>>
_orderTypeSortedConfigsDict = null!; //订单类型,根据sort从小到大排序后的配置字典
public TrackPushEventHandler(
IOrderRepository orderRepository, IOrderRepository orderRepository,
TrackPushCacheService trackPushCacheService, TrackPushCacheService trackPushCacheService,
ITrackPushRecordEsRepository trackPushRecordEsRepository, ITrackPushRecordEsRepository trackPushRecordEsRepository,
@ -28,18 +43,23 @@ public class TrackPushEventHandler(
BroadcastingService broadcastingService, BroadcastingService broadcastingService,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackDetailsEsRepository trackDetailsEsRepository,
InterfaceForwardService interfaceForwardService) InterfaceForwardService interfaceForwardService)
: IMessageHandle<TrackPushEvent>, ITransientDependency
{ {
private List<OrderEntity> _orders = null!; _orderRepository = orderRepository;
private List<TrackPushContentConfigDto> _pushConfigs = null!; _trackPushCacheService = trackPushCacheService;
_trackPushRecordEsRepository = trackPushRecordEsRepository;
_options = options;
_broadcastingService = broadcastingService;
_trackDetailsEsRepository = trackDetailsEsRepository;
_interfaceForwardService = interfaceForwardService;
}
private Dictionary<TrackOrderType, List<TrackPushContentConfigDto>> [UnitOfWork]
_orderTypeSortedConfigsDict = null!; //订单类型,根据sort从小到大排序后的配置字典 [CapSubscribe(GlobalConst.TrackPushSubscribe, Group = GlobalConst.TrackPushSubscribe)]
[CapSubscribe(GlobalConst.TrackManualPushSubscribe, Group = GlobalConst.TrackPushSubscribe)]
public virtual async Task HandleAsync(TrackPushEvent @event, CancellationToken cancellationToken = default) public virtual async Task ConsumerAsync(TrackPushEvent @event)
{ {
// 应用+客户id找推送映射配置 // 应用+客户id找推送映射配置
var pushConfigs = await trackPushCacheService.GetPushContentConfigsAsync(@event.AppKey, @event.Customer); var pushConfigs = await _trackPushCacheService.GetPushContentConfigsAsync(@event.AppKey, @event.Customer);
if (pushConfigs == null || pushConfigs.Count == 0) return; if (pushConfigs == null || pushConfigs.Count == 0) return;
if (pushConfigs.First().ServiceProviderCode.IsNullOrWhiteSpace()) if (pushConfigs.First().ServiceProviderCode.IsNullOrWhiteSpace())
{ {
@ -55,7 +75,7 @@ public class TrackPushEventHandler(
// 查找所有订单 // 查找所有订单
var orderIds = @event.OrderIdTrackMetadataDict.Keys.ToHashSet(); var orderIds = @event.OrderIdTrackMetadataDict.Keys.ToHashSet();
var orders = await orderRepository.GetListAsync(x => orderIds.Contains(x.Id), x => var orders = await _orderRepository.GetListAsync(x => orderIds.Contains(x.Id), x =>
new OrderEntity new OrderEntity
{ {
Id = x.Id, Id = x.Id,
@ -114,8 +134,8 @@ public class TrackPushEventHandler(
{ {
var asciiString = Base64Helper.SerializeObject(OptionType.新增.ToString()); var asciiString = Base64Helper.SerializeObject(OptionType.新增.ToString());
var serviceProviderCode = _pushConfigs.First().ServiceProviderCode; var serviceProviderCode = _pushConfigs.First().ServiceProviderCode;
var r = await interfaceForwardService.BatchForwardThenPushToQueueAsync( var r = await _interfaceForwardService.BatchForwardThenPushToQueueAsync(
options.CurrentValue.PushTrackInterfaceCode, _options.CurrentValue.PushTrackInterfaceCode,
serviceProviderCode, serviceProviderCode,
items.Cast<object>().ToList(), items.Cast<object>().ToList(),
GlobalConst.TrackPushCompletedSubscribe, GlobalConst.TrackPushCompletedSubscribe,
@ -349,8 +369,8 @@ public class TrackPushEventHandler(
{ {
var asciiString = Base64Helper.SerializeObject(OptionType.修改.ToString()); var asciiString = Base64Helper.SerializeObject(OptionType.修改.ToString());
var serviceProviderCode = _pushConfigs.First().ServiceProviderCode; var serviceProviderCode = _pushConfigs.First().ServiceProviderCode;
var r = await interfaceForwardService.BatchForwardThenPushToQueueAsync( var r = await _interfaceForwardService.BatchForwardThenPushToQueueAsync(
options.CurrentValue.TrackUpdateInterfaceCode, _options.CurrentValue.TrackUpdateInterfaceCode,
serviceProviderCode, serviceProviderCode,
items.Cast<object>().ToList(), items.Cast<object>().ToList(),
GlobalConst.TrackPushCompletedSubscribe, GlobalConst.TrackPushCompletedSubscribe,
@ -428,13 +448,13 @@ public class TrackPushEventHandler(
if (config.IsAllPush) if (config.IsAllPush)
{ {
var history = await trackPushRecordEsRepository.GetLatestTrackPushRecordAsync(log.OrderId, true); var history = await _trackPushRecordEsRepository.GetLatestTrackPushRecordAsync(log.OrderId, true);
if (history != null) if (history != null)
{ {
body = TextJsonHelper.Deserialize<TrackPushInput>(history.PushContent); body = TextJsonHelper.Deserialize<TrackPushInput>(history.PushContent);
//在推送历史中,选出当前轨迹明细一致的轨迹 //在推送历史中,选出当前轨迹明细一致的轨迹
var tracks = await trackDetailsEsRepository.GetListAsync(log.OrderId); var tracks = await _trackDetailsEsRepository.GetListAsync(log.OrderId);
//确定轨迹标识符 //确定轨迹标识符
var trackIdentifies = tracks.Select(x => var trackIdentifies = tracks.Select(x =>
{ {
@ -599,7 +619,7 @@ public class TrackPushEventHandler(
{ {
using var activity = ActivityHelper.CreateSpanActivity($"TrackPushFailedPublicTopic-{order.OrderNo}"); using var activity = ActivityHelper.CreateSpanActivity($"TrackPushFailedPublicTopic-{order.OrderNo}");
activity.SetTag("failedMessage", failedMessage); activity.SetTag("failedMessage", failedMessage);
broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto _broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto
{ {
AppKey = @event.AppKey, AppKey = @event.AppKey,
IsManualPush = @event.OperationMan != null, IsManualPush = @event.OperationMan != null,
@ -636,7 +656,7 @@ public class TrackPushEventHandler(
activity.SetTag("@event", @event.ToJson()); activity.SetTag("@event", @event.ToJson());
foreach (var pushInput in pushOrUpdateInputs) foreach (var pushInput in pushOrUpdateInputs)
{ {
broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto _broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto
{ {
AppKey = @event.AppKey, AppKey = @event.AppKey,
IsManualPush = @event.OperationMan != null, IsManualPush = @event.OperationMan != null,

View File

@ -1,4 +1,4 @@
using Fake.Messaging; using DotNetCore.CAP;
using Fake.UnitOfWork; using Fake.UnitOfWork;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using TrackCenter.Application.Events; using TrackCenter.Application.Events;
@ -13,28 +13,41 @@ using TrackCenter.Repository.Aggregates.OrderAggregate;
using TrackCenter.Repository.Otel; using TrackCenter.Repository.Otel;
using OrderIdentifier = TrackCenter.Application.Events.OrderIdentifier; using OrderIdentifier = TrackCenter.Application.Events.OrderIdentifier;
namespace TrackCenter.Application.Messaging; namespace TrackCenter.Application.CAP.EventHandlers;
public class TrackUpsertEventHandler( public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
{
private readonly ICapPublisher _capPublisher;
private readonly IOrderRepository _orderRepository;
private readonly ProvideQueries _provideQueries;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly BroadcastingService _broadcastingService;
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
private readonly WarningSignalPublisher _warningSignalPublisher;
public TrackUpsertEventHandler(
IOrderRepository orderRepository, IOrderRepository orderRepository,
IMessagePublisher messagePublisher, ICapPublisher capPublisher,
ProvideQueries provideQueries, ProvideQueries provideQueries,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackDetailsEsRepository trackDetailsEsRepository,
IOrderOperationRecordEsRepository orderOperationRecordEsRepository, IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
BroadcastingService broadcastingService, BroadcastingService broadcastingService,
WarningSignalPublisher warningSignalPublisher) WarningSignalPublisher warningSignalPublisher
: )
IMessageHandle<TrackUpsertEvent>,
IMessageHandle<TrackUpsertForRefreshEvent>,
ITransientDependency
{ {
public Task HandleAsync(TrackUpsertEvent message, CancellationToken cancellationToken = default) _orderRepository = orderRepository;
=> ConsumerAsync(message); _capPublisher = capPublisher;
_provideQueries = provideQueries;
_trackDetailsEsRepository = trackDetailsEsRepository;
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
_broadcastingService = broadcastingService;
_warningSignalPublisher = warningSignalPublisher;
}
public Task HandleAsync(TrackUpsertForRefreshEvent message, CancellationToken cancellationToken = default) [UnitOfWork]
=> ConsumerAsync(message); [CapSubscribe(GlobalConst.TrackUpsertSubscribe, Group = GlobalConst.TrackUpsertSubscribe)]
[CapSubscribe(GlobalConst.TrackUpsertForRefreshSubscribe, Group = GlobalConst.TrackUpsertForRefreshSubscribe)]
private async Task ConsumerAsync(TrackUpsertEvent @event) public virtual async Task ConsumerAsync(TrackUpsertEvent @event)
{ {
if (!CanDelete(@event)) return; if (!CanDelete(@event)) return;
@ -43,22 +56,22 @@ public class TrackUpsertEventHandler(
await HandleTrackAsync(@event, canNextOrderIds); await HandleTrackAsync(@event, canNextOrderIds);
if (canNextOrderIds.Count == 0) return; if (canNextOrderIds.Count == 0) return;
// 根据订单关联关系异步向下刷(异步) // ?????????????????
await DownByOrderLink(@event, canNextOrderIds); await DownByOrderLink(@event, canNextOrderIds);
} }
private async Task DownByOrderLink(TrackUpsertEvent @event, List<int> canNextOrderIds) private async Task DownByOrderLink(TrackUpsertEvent @event, List<int> canNextOrderIds)
{ {
// 根据订单关联关系异步向下刷(异步) // ?????????????????
var linkedOrders = await orderRepository.GetLinkedOrdersAsync(canNextOrderIds.ToArray()); var linkedOrders = await _orderRepository.GetLinkedOrdersAsync(canNextOrderIds.ToArray());
foreach (var order in @event.OrderIdentifiers) foreach (var order in @event.OrderIdentifiers)
{ {
// 理论上小包在这不会分组 // ???????????
foreach (var group in linkedOrders.Where(x => x.OwnerOrderId == order.OrderId) foreach (var group in linkedOrders.Where(x => x.OwnerOrderId == order.OrderId)
.GroupBy(x => new .GroupBy(x => new
{ {
x.AppKey, x.AppKey,
x.Customer // 这里按客户分组是为了后续的推送方便 x.Customer // ?????????????????
})) }))
{ {
var nextEvent = @event.DeepCopy(); var nextEvent = @event.DeepCopy();
@ -72,15 +85,15 @@ public class TrackUpsertEventHandler(
OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash, OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash,
}; };
// 不同应用需要做内容映射 // ???????????
if (@event.AppKey != nextEvent.AppKey) if (@event.AppKey != nextEvent.AppKey)
{ {
var mapCode = (await provideQueries.MapTrackCodesAsync(@event.AppKey, nextEvent.AppKey, @event.TrackCode)) var mapCode = (await _provideQueries.MapTrackCodesAsync(@event.AppKey, nextEvent.AppKey, @event.TrackCode))
.FirstOrDefault(); .FirstOrDefault();
if (mapCode == null) if (mapCode == null)
{ {
// 跨应用没做映射的轨迹不处理 // ?????????????
continue; continue;
} }
@ -98,7 +111,7 @@ public class TrackUpsertEventHandler(
{ {
var subEvent = nextEvent.DeepCopy(); var subEvent = nextEvent.DeepCopy();
subEvent.OrderIdentifiers = subOrderIds.ToArray(); subEvent.OrderIdentifiers = subOrderIds.ToArray();
await messagePublisher.PublishAsync(subEvent); await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, subEvent);
} }
} }
} }
@ -106,10 +119,10 @@ public class TrackUpsertEventHandler(
private static bool CanDelete(TrackUpsertEvent @event) private static bool CanDelete(TrackUpsertEvent @event)
{ {
// 系统创建的轨迹不允许修改 // ????????????
if (@event.Visited.IsNullOrEmpty() && @event is { OperationMan: GlobalConst.SystemUser, IsAdd: false }) if (@event.Visited.IsNullOrEmpty() && @event is { OperationMan: GlobalConst.SystemUser, IsAdd: false })
{ {
Log.Warning($"系统创建的轨迹不允许被修改:{@event}", true); Log.Warning($"??????????????{@event}", true);
return false; return false;
} }
@ -118,7 +131,7 @@ public class TrackUpsertEventHandler(
var dup = @event.Visited.Intersect(orderIds).ToArray(); var dup = @event.Visited.Intersect(orderIds).ToArray();
if (dup.Any()) if (dup.Any())
{ {
Log.Warning($"{(@event.IsAdd ? "添加" : "修改")}轨迹时,发现循环依赖:{dup.JoinAsString(",")}", true); Log.Warning($"{(@event.IsAdd ? "??" : "??")}???????????{dup.JoinAsString(",")}", true);
return false; return false;
} }
@ -127,29 +140,29 @@ public class TrackUpsertEventHandler(
private async Task HandleTrackAsync(TrackUpsertEvent @event, List<int> canNextOrderIds) private async Task HandleTrackAsync(TrackUpsertEvent @event, List<int> canNextOrderIds)
{ {
//#7001:未来潜在需求,当轨迹内容匹配预设,记录一个标记,保证下刷过程都是使用预设内容 //#7001:??????????????????????????????????????
var trackContent = await provideQueries.GetTrackContentAsync(@event.AppKey, @event.TrackCode); var trackContent = await _provideQueries.GetTrackContentAsync(@event.AppKey, @event.TrackCode);
if (trackContent == null) if (trackContent == null)
{ {
// 找不到轨迹内容,直接结束 // ????????????
Log.Warning($"找不到轨迹内容:{@event}", true); Log.Warning($"????????{@event}", true);
return; return;
} }
// 根据轨迹内容标识,确定操作权限 // ???????????????
var manualOperation = @event.Source == (int) TrackSourceEnum.Artificial; var manualOperation = @event.Source == (int) TrackSourceEnum.Artificial;
// 不允许手动创建 // ???????
if (manualOperation && @event.IsAdd && trackContent.IsAllowManuallyCreate == false) if (manualOperation && @event.IsAdd && trackContent.IsAllowManuallyCreate == false)
{ {
PublicTopicAllFailed(@event, trackContent, "轨迹内容标识不允许手动创建"); PublicTopicAllFailed(@event, trackContent, "?????????????");
return; return;
} }
// 不允许手动更新 // ???????
if (manualOperation && !@event.IsAdd && trackContent.IsAllowManuallyUpdate == false) if (manualOperation && !@event.IsAdd && trackContent.IsAllowManuallyUpdate == false)
{ {
PublicTopicAllFailed(@event, trackContent, "轨迹标识内容不允许手动更新"); PublicTopicAllFailed(@event, trackContent, "?????????????");
return; return;
} }
@ -168,30 +181,30 @@ public class TrackUpsertEventHandler(
if (insertTracks.Any()) if (insertTracks.Any())
{ {
// 批量add to es tips:注意副作用填充版本号 // ??add to es tips:??????????
await trackDetailsEsRepository.AddManyAsync(insertTracks); await _trackDetailsEsRepository.AddManyAsync(insertTracks);
// es新增后填充版本号字典 // es??????????
insertTracks.ForEach(t => orderIdTrackMetadataDict.Add(t.OrderId, TrackMetadata.Build(t.Id, t.Version, t.CreateTime))); insertTracks.ForEach(t => orderIdTrackMetadataDict.Add(t.OrderId, TrackMetadata.Build(t.Id, t.Version, t.CreateTime)));
// 批量写入订单操作日志 //??????????
_ = orderOperationRecordEsRepository.AddManyAsync(insertTracks.Select(t => BuildOrderOperationRecord(@event, t, new OrderIdentifier _ = _orderOperationRecordEsRepository.AddManyAsync(insertTracks.Select(t => BuildOrderOperationRecord(@event, t, new OrderIdentifier
{ {
OrderNo = t.OrderNo, OrderNo = t.OrderNo,
OrderId = t.OrderId OrderId = t.OrderId
})).ToList()); })).ToList());
// 添加轨迹触发订单拉取状态更新 // ??????????????
await orderRepository.UpdatePullStatusToSilenceAsync(insertTracks.Select(x => x.OrderId).ToList(), await _orderRepository.UpdatePullStatusToSilenceAsync(insertTracks.Select(x => x.OrderId).ToList(),
@event.TrackCode); @event.TrackCode);
// 发布预警信号,稍后将进行预警计算 // ????????????????
warningSignalPublisher.PublishSignal(WarningSignalType.轨迹新增, insertTracks); _warningSignalPublisher.PublishSignal(WarningSignalType.轨迹新增, insertTracks);
// 如果存在轨迹标记,则触发订单pod拉取 // ??????????????pod??
if (!string.IsNullOrWhiteSpace(trackContent.TrackFlagId)) if (!string.IsNullOrWhiteSpace(trackContent.TrackFlagId))
{ {
await messagePublisher.PublishAsync(new OrderPodPullEvent await _capPublisher.PublishAsync(GlobalConst.OrderPodPullSubscribe, new OrderPodPullEvent
{ {
AppKey = @event.AppKey, AppKey = @event.AppKey,
OrderIdEsIdDict = insertTracks.ToDictionary(x => x.OrderId, y => y.Id), OrderIdEsIdDict = insertTracks.ToDictionary(x => x.OrderId, y => y.Id),
@ -200,11 +213,12 @@ public class TrackUpsertEventHandler(
} }
} }
// 触发轨迹变更广播 // ????????
await TrackChangePublicTopicAsync(trackContent, trackChangeEtos, true); await TrackChangePublicTopicAsync(trackContent, trackChangeEtos, true);
// 触发订单主表更新事件(异步) // ??????????????
await messagePublisher.PublishAsync(new TrackChangeAfterUpdateOrderEvent await _capPublisher.PublishAsync(GlobalConst.TrackChangeAfterUpdateOrderSubscribe,
new TrackChangeAfterUpdateOrderEvent
{ {
OrderIds = canNextOrderIds.ToArray(), OrderIds = canNextOrderIds.ToArray(),
IsCompleted = trackContent.IsCompleted, IsCompleted = trackContent.IsCompleted,
@ -217,7 +231,7 @@ public class TrackUpsertEventHandler(
}); });
// 触发轨迹推送事件(异步) // 触发轨迹推送事件(异步)
// tips:同一个轨迹内容,多个订单多条轨迹 // tips:????????????????
var trackPushEvent = new TrackPushEvent var trackPushEvent = new TrackPushEvent
{ {
OptionType = @event.IsAdd ? OptionType.新增 : OptionType.修改, OptionType = @event.IsAdd ? OptionType.新增 : OptionType.修改,
@ -239,7 +253,7 @@ public class TrackUpsertEventHandler(
ForcedPush = @event.ForcedPush, ForcedPush = @event.ForcedPush,
IsToMq = (trackContent.IsToMq || @event.IsToMq) IsToMq = (trackContent.IsToMq || @event.IsToMq)
}; };
await messagePublisher.PublishAsync(trackPushEvent); await _capPublisher.PublishAsync(GlobalConst.TrackPushSubscribe, trackPushEvent);
} }
private async Task<List<TrackDetailsIndex>> UpsertTrackAsync( private async Task<List<TrackDetailsIndex>> UpsertTrackAsync(
@ -253,7 +267,7 @@ public class TrackUpsertEventHandler(
var orderIds = @event.OrderIdentifiers.Select(x => x.OrderId).ToArray(); var orderIds = @event.OrderIdentifiers.Select(x => x.OrderId).ToArray();
// important:轨迹code相同,时间、发生地不同的,可能存在多条!! // important:轨迹code相同,时间、发生地不同的,可能存在多条!!
var tracks = await trackDetailsEsRepository.GetListAsync(orderIds, @event.TrackCode, true); var tracks = await _trackDetailsEsRepository.GetListAsync(orderIds, @event.TrackCode, true);
var insertTracks = new List<TrackDetailsIndex>(); var insertTracks = new List<TrackDetailsIndex>();
var updateTracks = new List<TrackDetailsIndex>(); var updateTracks = new List<TrackDetailsIndex>();
@ -276,7 +290,7 @@ public class TrackUpsertEventHandler(
} }
else else
{ {
var message = $"没有通过传入的EsId:[{@event.EsId}]查询到轨迹信息,更新失败"; var message = $"???????EsId:[{@event.EsId}]????????????";
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message); AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
} }
@ -285,21 +299,21 @@ public class TrackUpsertEventHandler(
var existTracks = tracks.Where(x => x.OrderId == order.OrderId).ToList(); var existTracks = tracks.Where(x => x.OrderId == order.OrderId).ToList();
// tips:如果没传时区则使用+8兜底进行过滤 // tips:??????????8??????
var timezone = Math.Abs(@event.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 0.01f ? 8.0f : @event.TrackTimeZone; var timezone = Math.Abs(@event.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 0.01f ? 8.0f : @event.TrackTimeZone;
// 根据时间过滤 // ??????
existTracks = existTracks.Where(x => existTracks = existTracks.Where(x =>
x.GetUtc0TrackTime() == @event.TrackTime.Subtract(TimeSpan.FromHours(timezone))).ToList(); x.GetUtc0TrackTime() == @event.TrackTime.Subtract(TimeSpan.FromHours(timezone))).ToList();
// 根据扩展信息过滤 // ????????
existTracks = existTracks.Where(x => x.Extra == @event.Extra).ToList(); existTracks = existTracks.Where(x => x.Extra == @event.Extra).ToList();
if (@event.IsAdd) if (@event.IsAdd)
{ {
if (existTracks.Count > 0) if (existTracks.Count > 0)
{ {
const string message = "订单已经存在不允许重复的轨迹,不允许重复添加"; const string message = "??????????????????????";
traceLog.Add($"OrderNo-{order.OrderNo}", message); traceLog.Add($"OrderNo-{order.OrderNo}", message);
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message); AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
continue; continue;
@ -311,10 +325,10 @@ public class TrackUpsertEventHandler(
} }
else else
{ {
// 不存在不让改 // ??????
if (existTracks.Count == 0) if (existTracks.Count == 0)
{ {
const string message = "订单不存在轨迹,不允许修改"; const string message = "?????????????";
traceLog.Add($"OrderNo-{order.OrderNo}", message); traceLog.Add($"OrderNo-{order.OrderNo}", message);
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message); AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
continue; continue;
@ -322,7 +336,7 @@ public class TrackUpsertEventHandler(
if (existTracks.Count > 1) if (existTracks.Count > 1)
{ {
const string message = "订单存在多条轨迹不允许修改"; const string message = "?????????????";
traceLog.Add($"OrderNo-{order.OrderNo}", message); traceLog.Add($"OrderNo-{order.OrderNo}", message);
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message); AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
continue; continue;
@ -342,22 +356,22 @@ public class TrackUpsertEventHandler(
if (updateTracks.Any()) if (updateTracks.Any())
{ {
// 批量差异化更新轨迹 // ?????????
await trackDetailsEsRepository.BulkUpdateAsync(updateTracks); await _trackDetailsEsRepository.BulkUpdateAsync(updateTracks);
// 填充轨迹元数据 // ???????? updateTracks.ForEach(x => orderIdTrackMetadataDict.Add(x.OrderId, TrackMetadata.Build(x.Id, x.Version, x.CreateTime)));
updateTracks.ForEach(x => orderIdTrackMetadataDict.Add(x.OrderId, TrackMetadata.Build(x.Id, x.Version, x.CreateTime))); // ????????
// 批量写入操作日志 _ = _orderOperationRecordEsRepository.AddManyAsync(operationRecords);
_ = orderOperationRecordEsRepository.AddManyAsync(operationRecords); // ???????????????
// 发布预警信号稍后将进行预警计算 _warningSignalPublisher.PublishSignal(WarningSignalType.轨迹更新, tracks);
warningSignalPublisher.PublishSignal(WarningSignalType.轨迹更新, tracks);
} }
return insertTracks; return insertTracks;
} }
private static TrackDetailsIndex BuiltUpdateTrack(TrackUpsertEvent @event, TrackDetailsIndex track, OrderIdentifier order, TrackContentVO trackContent) private static TrackDetailsIndex BuiltUpdateTrack(TrackUpsertEvent @event, TrackDetailsIndex track, OrderIdentifier order, TrackContentVO trackContent)
{ {
// 非外部轨迹才允许修改 【发生地】【时间】【时区】 // ?????????? ?????????????
if (track.Source != (int) TrackSourceEnum.ExternalServiceProvide) if (track.Source != (int) TrackSourceEnum.ExternalServiceProvide)
{ {
track.TrackPlace = @event.TrackPlace; track.TrackPlace = @event.TrackPlace;
@ -365,11 +379,11 @@ public class TrackUpsertEventHandler(
track.TrackTimeZone = @event.TrackTimeZone; track.TrackTimeZone = @event.TrackTimeZone;
} }
// 允许二次修改轨迹内容 //??????????
track.TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content; track.TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content;
track.TrackEnContent = string.IsNullOrWhiteSpace(@event.EnStatus) ? trackContent.TrackEnContent : @event.EnStatus; track.TrackEnContent = string.IsNullOrWhiteSpace(@event.EnStatus) ? trackContent.TrackEnContent : @event.EnStatus;
// 轨迹发生地的更多信息 // ??????????
track.TrackPlaceCity = GlobalConst.EmptyString; track.TrackPlaceCity = GlobalConst.EmptyString;
track.TrackPlaceCityId = GlobalConst.NotExistId; track.TrackPlaceCityId = GlobalConst.NotExistId;
track.TrackPlaceProvince = GlobalConst.EmptyString; track.TrackPlaceProvince = GlobalConst.EmptyString;
@ -378,16 +392,16 @@ public class TrackUpsertEventHandler(
track.TrackPlaceNationId = GlobalConst.NotExistId; track.TrackPlaceNationId = GlobalConst.NotExistId;
track.TrackPlacePostcode = GlobalConst.EmptyString; track.TrackPlacePostcode = GlobalConst.EmptyString;
// 签收地时区,签收地时间 // ??????????????????????
track.DestinationTimeZone = null; track.DestinationTimeZone = null;
track.EstimatedDeliveryDate = null; track.EstimatedDeliveryDate = null;
// 转换为utc0的时间 // ???utc0???
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1 track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.TrackTime ? track.TrackTime
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone)); : track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
// 轨迹标记ids,是否对外,备注,扩展信息,附件 // ????ids????????????????
track.TrackFlagIds = trackContent.TrackFlagId; track.TrackFlagIds = trackContent.TrackFlagId;
track.IsExternal = @event.IsExternal ?? trackContent.IsExternal; track.IsExternal = @event.IsExternal ?? trackContent.IsExternal;
track.Remarks = @event.Remarks ?? GlobalConst.EmptyString; track.Remarks = @event.Remarks ?? GlobalConst.EmptyString;
@ -398,7 +412,8 @@ public class TrackUpsertEventHandler(
FileName = x.FileName FileName = x.FileName
}).ToList(); }).ToList();
// 审计属性
// ????
track.UpdateUser = @event.OperationMan ?? GlobalConst.SystemUser; track.UpdateUser = @event.OperationMan ?? GlobalConst.SystemUser;
track.UpdateTime = DateTime.Now; track.UpdateTime = DateTime.Now;
@ -409,13 +424,13 @@ public class TrackUpsertEventHandler(
{ {
var track = new TrackDetailsIndex var track = new TrackDetailsIndex
{ {
// 订单信息 // ????
AppKey = @event.AppKey, AppKey = @event.AppKey,
OrderId = order.OrderId, OrderId = order.OrderId,
OrderNo = order.OrderNo, OrderNo = order.OrderNo,
OrderType = order.OrderType, OrderType = order.OrderType,
// 轨迹信息 // ????
TrackCode = @event.TrackCode, TrackCode = @event.TrackCode,
TrackContentId = trackContent.TrackContentId, TrackContentId = trackContent.TrackContentId,
TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content, TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content,
@ -427,12 +442,12 @@ public class TrackUpsertEventHandler(
TrackTimeZone = @event.TrackTimeZone, TrackTimeZone = @event.TrackTimeZone,
TrackPlace = @event.TrackPlace, TrackPlace = @event.TrackPlace,
// 服务商轨迹信息 // ???????
ServiceProviderCode = @event.TrackRefreshData.ServiceProviderCode ?? GlobalConst.EmptyString, ServiceProviderCode = @event.TrackRefreshData.ServiceProviderCode ?? GlobalConst.EmptyString,
OriginTrackContent = @event.TrackRefreshData.OriginTrackContent ?? GlobalConst.EmptyString, OriginTrackContent = @event.TrackRefreshData.OriginTrackContent ?? GlobalConst.EmptyString,
OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash ?? GlobalConst.EmptyString, OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash ?? GlobalConst.EmptyString,
// 轨迹发生地的更多描述 // ??????????
TrackPlaceCity = GlobalConst.EmptyString, TrackPlaceCity = GlobalConst.EmptyString,
TrackPlaceCityId = GlobalConst.NotExistId, TrackPlaceCityId = GlobalConst.NotExistId,
TrackPlaceProvince = GlobalConst.EmptyString, TrackPlaceProvince = GlobalConst.EmptyString,
@ -440,7 +455,7 @@ public class TrackUpsertEventHandler(
TrackPlaceNation = GlobalConst.EmptyString, TrackPlaceNation = GlobalConst.EmptyString,
TrackPlaceNationId = GlobalConst.NotExistId, TrackPlaceNationId = GlobalConst.NotExistId,
TrackPlacePostcode = GlobalConst.EmptyString, TrackPlacePostcode = GlobalConst.EmptyString,
// 轨迹标记ids,轨迹来源,是否对外,备注,扩展信息,附件 // ????ids?????????????????????
TrackFlagIds = trackContent.TrackFlagId, TrackFlagIds = trackContent.TrackFlagId,
Source = @event.Source, Source = @event.Source,
IsExternal = @event.IsExternal ?? trackContent.IsExternal, IsExternal = @event.IsExternal ?? trackContent.IsExternal,
@ -452,12 +467,12 @@ public class TrackUpsertEventHandler(
FileName = x.FileName FileName = x.FileName
}).ToList(), }).ToList(),
// 审计属性 // ????
CreateUser = @event.OperationMan ?? GlobalConst.SystemUser, CreateUser = @event.OperationMan ?? GlobalConst.SystemUser,
CreateTime = DateTime.Now CreateTime = DateTime.Now
}; };
// 转换为utc0的时间 // ???utc0???
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1 track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.TrackTime ? track.TrackTime
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone)); : track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
@ -482,17 +497,15 @@ public class TrackUpsertEventHandler(
private async Task TrackChangePublicTopicAsync(TrackContentVO trackContent, List<TrackChangeEto> list, bool isDelay = false) private async Task TrackChangePublicTopicAsync(TrackContentVO trackContent, List<TrackChangeEto> list, bool isDelay = false)
{ {
if (list.Count == 0) return; if (list.Count == 0) return;
// 理想情况在写入之后1s会刷盘 https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-refresh.html //??????????s????https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-refresh.html
// 增加一个版本号来确定是否真的变更了 //?????????????????
// 没有刷盘成功事件回调 //??????????
// 方案1:!!【轮询等待静默刷盘】查出版本号确实发生了变更在发送广播 //??1??!???????????????????????????? //??2?????????es???????? //??3????????????????
// 方案2:考虑强制刷盘(对es压力大)后发送
// 方案3:采用一个较大延时,不查询直接发
if (isDelay) if (isDelay)
await Task.Delay(1000); await Task.Delay(1000);
foreach (var eto in list) foreach (var eto in list)
broadcastingService.TrackChangePublicTopic(eto, trackContent.AppKey); _broadcastingService.TrackChangePublicTopic(eto, trackContent.AppKey);
} }
private void PublicTopicAllFailed(TrackUpsertEvent @event, TrackContentVO trackContent, string failedReason) private void PublicTopicAllFailed(TrackUpsertEvent @event, TrackContentVO trackContent, string failedReason)
@ -501,7 +514,7 @@ public class TrackUpsertEventHandler(
foreach (var orderNo in @event.OrderIdentifiers.Select(x => x.OrderNo)) foreach (var orderNo in @event.OrderIdentifiers.Select(x => x.OrderNo))
{ {
var eto = BuildEto(@event, trackContent, orderNo, null, failedReason); var eto = BuildEto(@event, trackContent, orderNo, null, failedReason);
broadcastingService.TrackChangePublicTopic(eto, trackContent.AppKey); _broadcastingService.TrackChangePublicTopic(eto, trackContent.AppKey);
} }
} }

View File

@ -0,0 +1,90 @@
using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace TrackCenter.Application.CAP;
internal static class LoggerExtensions
{
public static void ConsumerExecutedAfterThreshold(this ILogger logger, string messageId, int retries)
{
logger.LogWarning(
$"The Subscriber of the message({messageId}) still fails after {retries}th executions and we will stop retrying.");
}
public static void SenderAfterThreshold(this ILogger logger, string messageId, int retries)
{
logger.LogWarning(
$"The Publisher of the message({messageId}) still fails after {retries}th sends and we will stop retrying.");
}
public static void ExecutedThresholdCallbackFailed(this ILogger logger, Exception ex)
{
logger.LogWarning(ex, "FailedThresholdCallback action raised an exception:" + ex.Message);
}
public static void ConsumerDuplicates(this ILogger logger, string subscriber, string group)
{
logger.LogWarning(
$"We detected that you have duplicate subscribers ({subscriber}) in same group ({group}), this will cause diversity behavior.");
}
public static void ConsumerExecutionRetrying(this ILogger logger, string messageId, int retries)
{
logger.LogWarning($"The {retries}th retrying consume a message failed. message id: {messageId}");
}
public static void SenderRetrying(this ILogger logger, string messageId, int retries)
{
logger.LogWarning($"The {retries}th retrying send a message failed. message id: {messageId} ");
}
public static void MessageReceived(this ILogger logger, string messageId, string name)
{
logger.LogDebug($"Received message. id:{messageId}, name: {name}");
}
public static void MessagePublishException(this ILogger logger, string? messageId, string reason, Exception? ex)
{
logger.LogError(ex,
$"An exception occurred while publishing a message, reason:{reason}. message id:{messageId}");
}
public static void ConsumerExecuting(this ILogger logger, string className, string methodName, string group)
{
logger.LogInformation($"Executing subscriber method '{className}.{methodName}' on group '{group}'");
}
public static void ConsumerExecuted(this ILogger logger, string className, string methodName, string group,
double milliseconds, string? instance)
{
logger.LogInformation(
$"Executed subscriber method '{className}.{methodName}' on group '{group}' with instance '{instance}' in {milliseconds}ms");
}
public static void ConsumerExecuteFailed(this ILogger logger, string topic, string id, string? instance,
Exception? ex)
{
logger.LogError(ex,
$"An exception occurred while executing the subscription method. Topic:{topic}, Id:{id}, Instance: {instance}");
}
public static void ServerStarting(this ILogger logger)
{
logger.LogInformation("Starting the processing server.");
}
public static void ProcessorsStartedError(this ILogger logger, Exception ex)
{
logger.LogError(ex, "Starting the processors throw an exception.");
}
public static void ServerShuttingDown(this ILogger logger)
{
logger.LogInformation("Shutting down the processing server...");
}
public static void ExpectedOperationCanceledException(this ILogger logger, Exception ex)
{
logger.LogWarning(ex, $"Expected an OperationCanceledException, but found '{ex.Message}'.");
}
}

View File

@ -0,0 +1,269 @@
using System.Diagnostics;
using System.Reflection;
using DotNetCore.CAP;
using DotNetCore.CAP.Diagnostics;
using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Messages;
using DotNetCore.CAP.Persistence;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace TrackCenter.Application.CAP;
public class MySubscribeExecutor : ISubscribeExecutor
{
// diagnostics listener
// ReSharper disable once InconsistentNaming
private static readonly DiagnosticListener s_diagnosticListener =
new(CapDiagnosticListenerNames.DiagnosticListenerName);
private readonly IDataStorage _dataStorage;
private readonly string? _hostName;
private readonly ILogger _logger;
private readonly CapOptions _options;
private readonly IServiceProvider _provider;
public MySubscribeExecutor(
ILogger<MySubscribeExecutor> logger,
IOptions<CapOptions> options,
IServiceProvider provider)
{
_provider = provider;
_logger = logger;
_options = options.Value;
_dataStorage = _provider.GetRequiredService<IDataStorage>();
Invoker = _provider.GetRequiredService<ISubscribeInvoker>();
_hostName = Helper.GetInstanceHostname();
}
private ISubscribeInvoker Invoker { get; }
public async Task<OperateResult> ExecuteAsync(MediumMessage message, ConsumerExecutorDescriptor? descriptor = null,
CancellationToken cancellationToken = default)
{
if (descriptor == null)
{
var selector = _provider.GetRequiredService<MethodMatcherCache>();
if (!selector.TryGetTopicExecutor(message.Origin.GetName(), message.Origin.GetGroup()!, out descriptor))
{
var error =
$"Message (Name:{message.Origin.GetName()},Group:{message.Origin.GetGroup()}) can not be found subscriber." +
$"{Environment.NewLine} see: https://github.com/dotnetcore/CAP/issues/63";
_logger.LogError(error);
TracingError(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), message.Origin, null,
new Exception(error));
var ex = new SubscriberNotFoundException(error);
await SetFailedState(message, ex);
return OperateResult.Failed(ex);
}
}
bool retry;
OperateResult result;
//record instance id
message.Origin.Headers[Headers.ExecutionInstanceId] = _hostName;
do
{
var (shouldRetry, operateResult) = await ExecuteWithoutRetryAsync(message, descriptor, cancellationToken)
.ConfigureAwait(false);
result = operateResult;
if (result.Equals(OperateResult.Success)) return result;
retry = shouldRetry;
} while (retry);
return result;
}
private async Task<(bool, OperateResult)> ExecuteWithoutRetryAsync(MediumMessage message,
ConsumerExecutorDescriptor descriptor, CancellationToken cancellationToken)
{
if (message == null) throw new ArgumentNullException(nameof(message));
cancellationToken.ThrowIfCancellationRequested();
try
{
_logger.ConsumerExecuting(descriptor.ImplTypeInfo.Name, descriptor.MethodInfo.Name,
descriptor.Attribute.Group);
var sp = Stopwatch.StartNew();
await InvokeConsumerMethodAsync(message, descriptor, cancellationToken).ConfigureAwait(false);
sp.Stop();
await SetSuccessfulState(message).ConfigureAwait(false);
CapEventCounterSource.Log.WriteInvokeTimeMetrics(sp.Elapsed.TotalMilliseconds);
_logger.ConsumerExecuted(descriptor.ImplTypeInfo.Name, descriptor.MethodInfo.Name,
descriptor.Attribute.Group, sp.Elapsed.TotalMilliseconds, message.Origin.GetExecutionInstanceId());
return (false, OperateResult.Success);
}
catch (Exception ex)
{
_logger.ConsumerExecuteFailed(message.Origin.GetName(), message.DbId,
message.Origin.GetExecutionInstanceId(), ex);
return (await SetFailedState(message, ex).ConfigureAwait(false), OperateResult.Failed(ex));
}
}
private Task SetSuccessfulState(MediumMessage message)
{
message.ExpiresAt = DateTime.Now.AddSeconds(_options.SucceedMessageExpiredAfter);
return _dataStorage.ChangeReceiveStateAsync(message, StatusName.Succeeded);
}
private async Task<bool> SetFailedState(MediumMessage message, Exception ex)
{
if (ex is SubscriberNotFoundException)
message.Retries = _options.FailedRetryCount; // not retry if SubscriberNotFoundException
var needRetry = UpdateMessageForRetry(message);
message.Origin.AddOrUpdateException(ex);
message.ExpiresAt = message.Added.AddSeconds(_options.FailedMessageExpiredAfter);
await _dataStorage.ChangeReceiveStateAsync(message, StatusName.Failed).ConfigureAwait(false);
return needRetry;
}
private bool UpdateMessageForRetry(MediumMessage message)
{
var retries = ++message.Retries;
if (retries == _options.FailedRetryCount)
{
try
{
_options.FailedThresholdCallback?.Invoke(new FailedInfo
{
ServiceProvider = _provider,
MessageType = MessageType.Subscribe,
Message = message.Origin
});
_logger.ConsumerExecutedAfterThreshold(message.DbId, _options.FailedRetryCount);
}
catch (Exception ex)
{
_logger.ExecutedThresholdCallbackFailed(ex);
}
}
return false;
}
private async Task InvokeConsumerMethodAsync(MediumMessage message, ConsumerExecutorDescriptor descriptor,
CancellationToken cancellationToken)
{
var consumerContext = new ConsumerContext(descriptor, message);
var tracingTimestamp = TracingBefore(message.Origin, descriptor.MethodInfo);
try
{
var ret = await Invoker.InvokeAsync(consumerContext, cancellationToken).ConfigureAwait(false);
TracingAfter(tracingTimestamp, message.Origin, descriptor.MethodInfo);
if (!string.IsNullOrEmpty(ret.CallbackName))
{
var header = new Dictionary<string, string?>
{
[Headers.CorrelationId] = message.Origin.GetId(),
[Headers.CorrelationSequence] = (message.Origin.GetCorrelationSequence() + 1).ToString()
};
if (message.Origin.Headers.TryGetValue(Headers.TraceParent, out var traceparent))
header[Headers.TraceParent] = traceparent;
await _provider.GetRequiredService<ICapPublisher>()
.PublishAsync(ret.CallbackName, ret.Result, header, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
//ignore
}
catch (Exception ex)
{
var e = new SubscriberExecutionFailedException(ex.Message, ex);
TracingError(tracingTimestamp, message.Origin, descriptor.MethodInfo, e);
e.ReThrow();
}
}
#region tracing
private static long? TracingBefore(Message message, MethodInfo method)
{
if (s_diagnosticListener.IsEnabled(CapDiagnosticListenerNames.BeforeSubscriberInvoke))
{
var eventData = new CapEventDataSubExecute
{
OperationTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Operation = message.GetName(),
Message = message,
MethodInfo = method
};
s_diagnosticListener.Write(CapDiagnosticListenerNames.BeforeSubscriberInvoke, eventData);
return eventData.OperationTimestamp;
}
return null;
}
private static void TracingAfter(long? tracingTimestamp, Message message, MethodInfo method)
{
CapEventCounterSource.Log.WriteInvokeMetrics();
if (tracingTimestamp != null &&
s_diagnosticListener.IsEnabled(CapDiagnosticListenerNames.AfterSubscriberInvoke))
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var eventData = new CapEventDataSubExecute
{
OperationTimestamp = now,
Operation = message.GetName(),
Message = message,
MethodInfo = method,
ElapsedTimeMs = now - tracingTimestamp.Value
};
s_diagnosticListener.Write(CapDiagnosticListenerNames.AfterSubscriberInvoke, eventData);
}
}
private static void TracingError(long? tracingTimestamp, Message message, MethodInfo? method, Exception ex)
{
if (tracingTimestamp != null &&
s_diagnosticListener.IsEnabled(CapDiagnosticListenerNames.ErrorSubscriberInvoke))
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var eventData = new CapEventDataSubExecute
{
OperationTimestamp = now,
Operation = message.GetName(),
Message = message,
MethodInfo = method,
ElapsedTimeMs = now - tracingTimestamp.Value,
Exception = ex
};
s_diagnosticListener.Write(CapDiagnosticListenerNames.ErrorSubscriberInvoke, eventData);
}
}
#endregion
}

View File

@ -0,0 +1,15 @@
namespace TrackCenter.Application.CAP;
internal sealed class SubscriberExecutionFailedException : Exception
{
private readonly Exception _originException;
public SubscriberExecutionFailedException(string message, Exception ex) : base(message, ex)
{
_originException = ex;
}
public override string? StackTrace => _originException.StackTrace;
public override string? Source => _originException.Source;
}

View File

@ -46,111 +46,6 @@ public class GetPageTrackOrderInput : PageInfoInput
/// 创建时间截止 /// 创建时间截止
/// </summary> /// </summary>
public DateTime? CreateTimeEnd { get; set; } public DateTime? CreateTimeEnd { get; set; }
/// <summary>
/// 尾程单号(支持逗号分隔多值,等值匹配)
/// </summary>
public string? TrackNo { get; set; }
/// <summary>
/// 其他单号(支持逗号分隔多值,等值匹配)
/// </summary>
public string? OtherNo { get; set; }
/// <summary>
/// 销售产品
/// </summary>
public string? Product { get; set; }
/// <summary>
/// 渠道
/// </summary>
public string? Channel { get; set; }
/// <summary>
/// API 编码 / 服务商代码
/// </summary>
public string? ApiCode { get; set; }
/// <summary>
/// 国家编码
/// </summary>
public string? CountryCode { get; set; }
/// <summary>
/// 州省
/// </summary>
public string? Province { get; set; }
/// <summary>
/// 城市
/// </summary>
public string? City { get; set; }
/// <summary>
/// 邮编
/// </summary>
public string? Postcode { get; set; }
/// <summary>
/// 邮箱
/// </summary>
public string? Email { get; set; }
/// <summary>
/// 最新轨迹状态
/// </summary>
public string? NewestTrackStatus { get; set; }
/// <summary>
/// 最新轨迹内容
/// </summary>
public string? NewestTrackContent { get; set; }
/// <summary>
/// 最新轨迹内容英文
/// </summary>
public string? NewestTrackEnContent { get; set; }
/// <summary>
/// 最新轨迹发生地
/// </summary>
public string? NewestTrackPlace { get; set; }
/// <summary>
/// 最新轨迹时间开始
/// </summary>
public DateTime? NewestTrackTimeStart { get; set; }
/// <summary>
/// 最新轨迹时间截止
/// </summary>
public DateTime? NewestTrackTimeEnd { get; set; }
/// <summary>
/// 最新轨迹时区
/// </summary>
public float? NewestTrackTimeZone { get; set; }
/// <summary>
/// 是否完结
/// </summary>
public bool? IsFinish { get; set; }
/// <summary>
/// 预计到达时间开始
/// </summary>
public DateTime? EstimatedDeliveryDateStart { get; set; }
/// <summary>
/// 预计到达时间截止
/// </summary>
public DateTime? EstimatedDeliveryDateEnd { get; set; }
/// <summary>
/// 目的地时区
/// </summary>
public float? DestinationTimeZone { get; set; }
} }
/// <summary> /// <summary>

View File

@ -1,13 +1,6 @@
using Fake.Messaging;
namespace TrackCenter.Application.Events; namespace TrackCenter.Application.Events;
/// <summary> public class OrderPodPullEvent
/// 订单 POD 拉取(当前仓库无订阅方,仍走 Outbox 投递以兼容后续消费者)。
/// </summary>
[Serializable]
[MessageRoute(nameof(OrderPodPullEvent))]
public class OrderPodPullEvent : Message
{ {
// 确定应用 // 确定应用
public string AppKey { get; set; } public string AppKey { get; set; }

View File

@ -1,10 +1,6 @@
using Fake.Messaging;
namespace TrackCenter.Application.Events; namespace TrackCenter.Application.Events;
[Serializable] public class TrackChangeAfterUpdateOrderEvent
[MessageRoute(nameof(TrackChangeAfterUpdateOrderEvent))]
public class TrackChangeAfterUpdateOrderEvent : Message
{ {
/// <summary> /// <summary>
/// 订单Id /// 订单Id

View File

@ -1,11 +1,8 @@
using Fake.Messaging;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
namespace TrackCenter.Application.Events; namespace TrackCenter.Application.Events;
[Serializable] public class TrackDeleteEvent
[MessageRoute(nameof(TrackDeleteEvent))]
public class TrackDeleteEvent : Message
{ {
/// <summary> /// <summary>
/// 根据esId定位轨迹,优先级最高 /// 根据esId定位轨迹,优先级最高

View File

@ -1,14 +1,10 @@
using Fake.Messaging;
namespace TrackCenter.Application.Events; namespace TrackCenter.Application.Events;
/// <summary> /// <summary>
/// 轨迹推送事件 /// 轨迹推送事件
/// 每次可传递多个订单,但仅能传递同一个轨迹内容 /// 每次可传递多个订单,但仅能传递同一个轨迹内容
/// </summary> /// </summary>
[Serializable] public class TrackPushEvent
[MessageRoute(nameof(TrackPushEvent))]
public class TrackPushEvent : Message
{ {
/// <summary> /// <summary>
/// 操作类型 /// 操作类型

View File

@ -1,11 +1,8 @@
using Fake.Messaging;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
namespace TrackCenter.Application.Events; namespace TrackCenter.Application.Events;
[Serializable] public class TrackUpsertEvent
[MessageRoute(nameof(TrackUpsertEvent))]
public class TrackUpsertEvent : Message
{ {
/// <summary> /// <summary>
/// 应用 /// 应用

View File

@ -1,11 +0,0 @@
using Fake.Messaging;
namespace TrackCenter.Application.Events;
/// <summary>
/// 轨迹刷新引发的新增/更新(独立队列,与 <see cref="TrackUpsertEvent"/> 隔离)。
/// </summary>
[Serializable]
[MessageRoute(nameof(TrackUpsertForRefreshEvent))]
public class TrackUpsertForRefreshEvent : TrackUpsertEvent
{
}

View File

@ -0,0 +1,10 @@
using RabbitMQ.Client.Events;
namespace TrackCenter.Application.Rabbit;
public interface IRabbitHandler
{
bool Enable(ConsumeOptions options);
Task Handle(BasicDeliverEventArgs args);
}

View File

@ -1,12 +1,6 @@
using Fake.Messaging; namespace TrackCenter.Application.Rabbit;
namespace TrackCenter.Application.Events; public class InterfaceRelayForwardReplayEvent
/// <summary>
/// 接口通回调报文(外部生产,无 Fake MessageId / DLX)。
/// </summary>
[MessageRoute(Topic = GlobalConst.InterfaceRelayExchange, TopicType = "direct")]
public class InterfaceRelayForwardReplayEvent : Message
{ {
/// <summary> /// <summary>
/// 请求Id /// 请求Id

View File

@ -0,0 +1,166 @@
using Fake.RabbitMQ;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
namespace TrackCenter.Application.Rabbit;
public class RabbitClient(
IRabbitMqChannelPool channelPool,
IServiceScopeFactory serviceScopeFactory,
ILogger<RabbitClient> logger,
IOptions<RabbitOptions> options) : ISingletonDependency, IHostedService
{
public void Subscribe(ConsumeOptions consumeOptions)
{
using var channelAccessor = channelPool.Acquire(consumeOptions.Queue);
if (!channelAccessor.IsNew) return;
var channel = channelAccessor.Channel;
var decl = new DefaultDeclaration(channel);
consumeOptions.Declaration.Invoke(decl);
if (consumeOptions.FetchCount > 0)
{
channel.BasicQos(0, consumeOptions.FetchCount, false);
}
var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
_ = Task.Run(async () =>
{
try
{
await using var scope = serviceScopeFactory.CreateAsyncScope();
foreach (var handler in scope.ServiceProvider.GetServices<IRabbitHandler>())
{
if (handler.Enable(consumeOptions))
{
await handler.Handle(ea);
}
}
channel.BasicAck(ea.DeliveryTag, false);
}
catch (Exception ex)
{
logger.LogError($"rabbit on queue({consumeOptions.Queue}) received error: {ex}");
channel.BasicNack(ea.DeliveryTag, false, consumeOptions.FailedRequeue);
}
});
return Task.CompletedTask;
};
channel.CallbackException += (sender, args) =>
{
logger.LogError(args.Exception, "RabbitMQ channel exception");
};
channel.BasicConsume(queue: consumeOptions.Queue,
autoAck: false,
consumer: consumer);
}
public void Publish(string exchange, string routingKey, byte[] data, Action<IBasicProperties>? options = null)
{
using var channelAccessor = channelPool.Acquire(routingKey);
var props = channelAccessor.Channel.CreateBasicProperties();
options?.Invoke(props);
channelAccessor.Channel.BasicPublish(exchange, routingKey, props, data);
}
public void PublishByConfirmSelect(
string exchange,
string routingKey,
byte[] data,
Action<IBasicProperties>? options = null
)
{
using var activity = RabbitInstrumentation.ActivitySource.StartActivity("/" + exchange + "/" + routingKey);
activity?.SetTag("kind", "publish");
using var channelAccessor = channelPool.Acquire(routingKey);
var basicProperties = channelAccessor.Channel.CreateBasicProperties();
// 配置属性
if (options != null)
options(basicProperties);
var model = channelAccessor.Channel;
// 启用发布确认
model.ConfirmSelect();
var readOnlyMemory = (ReadOnlyMemory<byte>) data;
model.BasicPublish(exchange, routingKey, basicProperties, readOnlyMemory);
model.WaitForConfirmsOrDie();
}
public void PublishByConfirmSelect(
string exchange,
string routingKey,
IEnumerable<byte[]> datas,
Action<IBasicProperties>? options = null
)
{
using var activity = RabbitInstrumentation.ActivitySource.StartActivity("/" + exchange + "/" + routingKey);
activity?.SetTag("kind", "publish");
using var channelAccessor = channelPool.Acquire(routingKey);
var basicProperties = channelAccessor.Channel.CreateBasicProperties();
// 配置属性
if (options != null)
options(basicProperties);
var model = channelAccessor.Channel;
// 启用发布确认
model.ConfirmSelect();
foreach (var data in datas)
{
var readOnlyMemory = (ReadOnlyMemory<byte>) data;
model.BasicPublish(exchange, routingKey, basicProperties, readOnlyMemory);
}
model.WaitForConfirmsOrDie();
}
public void UnSubscribe(string queueName, string exchange, string routingKey, bool deleteQueue = false)
{
logger.LogInformation(
$"UnSubscribe queue: {queueName} exchange: {exchange} routingKey: {routingKey} deleteQueue: {deleteQueue}");
using var channelAccessor = channelPool.Acquire(queueName);
if (deleteQueue)
{
var passive = channelAccessor.Channel.QueueDeclarePassive(queueName);
logger.LogInformation($"Queue {queueName} exists, deleting... {passive.MessageCount} messages in queue");
channelAccessor.Channel.QueueDelete(queueName);
}
channelPool.Release(queueName);
}
public Task StartAsync(CancellationToken cancellationToken)
{
if (options.Value.Declarations.Count > 0)
{
using var channelAccessor = channelPool.Acquire();
var channel = channelAccessor.Channel;
var decl = new DefaultDeclaration(channel);
options.Value.Declarations.ForEach(q => q.Invoke(decl));
}
foreach (var opt in options.Value.Consumes)
{
Subscribe(opt);
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
channelPool.Dispose();
return Task.CompletedTask;
}
}

View File

@ -0,0 +1,21 @@
using System.Diagnostics;
using System.Diagnostics.Metrics;
namespace TrackCenter.Application.Rabbit;
public class RabbitInstrumentation
{
public const string MeterName = "Fake.Rabbit.Meter";
public const string ActivitySourceName = "Fake.Rabbit.ActivitySource";
static RabbitInstrumentation()
{
string version = typeof (RabbitInstrumentation).Assembly.GetName().Version?.ToString();
Meter = new Meter(MeterName, version);
ActivitySource = new ActivitySource(ActivitySourceName, version);
}
public static Meter Meter { get; }
public static ActivitySource ActivitySource { get; }
}

View File

@ -0,0 +1,84 @@
using RabbitMQ.Client;
namespace TrackCenter.Application.Rabbit;
public class RabbitOptions
{
public List<ConsumeOptions> Consumes { get; set; } = new();
public List<Action<IDeclaration>> Declarations { get; set; } = new();
}
public class ConsumeOptions
{
/// <summary>
/// 队列名
/// </summary>
public string Queue { get; set; }
/// <summary>
/// 并行度
/// </summary>
public ushort FetchCount { get; set; } = 1;
/// <summary>
/// handle发生异常,是否重回队列
/// </summary>
public bool FailedRequeue { get; set; }
public Action<IDeclaration> Declaration { get; set; }
}
public interface IDeclaration
{
void QueueDeclare(string queue, bool durable, bool autoDelete = false, bool exclusive = false,
IDictionary<string, object> arguments = null);
void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete = false,
IDictionary<string, object> arguments = null);
void QueueBind(string queue, string exchange, string routingKey,
IDictionary<string, object> arguments = null);
}
public class DefaultDeclaration : IDeclaration
{
private readonly IModel _chnl;
// ReSharper disable once ConvertToPrimaryConstructor
public DefaultDeclaration(IModel chnl)
{
_chnl = chnl;
}
public void QueueDeclare(string queue, bool durable, bool autoDelete = false, bool exclusive = false,
IDictionary<string, object> arguments = null)
{
_chnl.QueueDeclare(
queue: queue,
durable: durable,
autoDelete: autoDelete,
exclusive: exclusive,
arguments: arguments);
}
public void ExchangeDeclare(string exchange, string type, bool durable, bool autoDelete = false,
IDictionary<string, object> arguments = null)
{
_chnl.ExchangeDeclare(
exchange: exchange,
type: type,
durable: durable,
autoDelete: autoDelete,
arguments: arguments);
}
public void QueueBind(string queue, string exchange, string routingKey, IDictionary<string, object> arguments)
{
_chnl.QueueBind(
queue: queue,
exchange: exchange,
routingKey: routingKey,
arguments: arguments);
}
}

View File

@ -1,44 +1,63 @@
using Fake.Messaging; using System.Text;
using Fake.UnitOfWork; using Fake.UnitOfWork;
using InterfaceForward.Domain.Shared.Helpers;
using Newtonsoft.Json; using Newtonsoft.Json;
using RabbitMQ.Client.Events;
using TrackCenter.Application.Dtos.TrackPush; using TrackCenter.Application.Dtos.TrackPush;
using TrackCenter.Application.Events;
using TrackCenter.Application.Helpers; using TrackCenter.Application.Helpers;
using TrackCenter.Application.Services.Broadcasting; using TrackCenter.Application.Services.Broadcasting;
using TrackCenter.Application.Services.Warning.Handlers; using TrackCenter.Application.Services.Warning.Handlers;
using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.OrderAggregate;
using TrackCenter.Domain.Aggregates.TrackPushAggregate; using TrackCenter.Domain.Aggregates.TrackPushAggregate;
namespace TrackCenter.Application.Messaging; namespace TrackCenter.Application.Rabbit;
/// <summary> [ExposeServices(typeof(IRabbitHandler))]
/// 接口通轨迹推送完成回调 → 独立队列 <see cref="GlobalConst.TrackPushCompletedSubscribe"/>。 public class TrackPushCompletedEventHandler : ITransientDependency, IRabbitHandler
/// </summary> {
[MessageSubscribe(GlobalConst.TrackPushCompletedSubscribe, ExternalQueue = true)] private readonly IOrderRepository _orderRepository;
public class TrackPushCompletedEventHandler( private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository;
IOrderRepository orderRepository, private readonly BroadcastingService _broadcastingService;
private readonly IReturnWarningHandler _returnWarningHandler;
public TrackPushCompletedEventHandler(IOrderRepository orderRepository,
ITrackPushRecordEsRepository trackPushRecordEsRepository, ITrackPushRecordEsRepository trackPushRecordEsRepository,
BroadcastingService broadcastingService, BroadcastingService broadcastingService, IReturnWarningHandler returnWarningHandler)
IReturnWarningHandler returnWarningHandler)
: IMessageHandle<InterfaceRelayForwardReplayEvent>, ITransientDependency
{ {
public virtual async Task HandleAsync( _orderRepository = orderRepository;
InterfaceRelayForwardReplayEvent message, _trackPushRecordEsRepository = trackPushRecordEsRepository;
CancellationToken cancellationToken = default) _broadcastingService = broadcastingService;
_returnWarningHandler = returnWarningHandler;
}
public bool Enable(ConsumeOptions options)
{ {
return options.Queue.StartsWith(GlobalConst.TrackPushCompletedSubscribe);
}
[UnitOfWork]
public virtual async Task Handle(BasicDeliverEventArgs args)
{
var msg = string.Empty;
try try
{ {
await ConsumerAsync(message); msg = Encoding.UTF8.GetString(args.Body.ToArray());
var @event = System.Text.Json.JsonSerializer.Deserialize<InterfaceRelayForwardReplayEvent>(msg);
if (@event == null)
{
Log.Warning(
$"rabbit on queue received msg can't convert to {nameof(InterfaceRelayForwardReplayEvent)}");
return;
}
await ConsumerAsync(@event);
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error($"TrackPushCompletedEventHandler出错,错误信息:{ex}\r\n-----\r\n消息:{message.ToJson()}"); Log.Error($"TrackPushCompletedEventHandler出错,错误信息:{ex}\r\n-----\r\n消息:{msg}");
throw;
} }
} }
private async Task ConsumerAsync(InterfaceRelayForwardReplayEvent @event) public async Task ConsumerAsync(InterfaceRelayForwardReplayEvent @event)
{ {
var optionType = Base64Helper.DeSerialize<string>(@event.Attach); var optionType = Base64Helper.DeSerialize<string>(@event.Attach);
var pushInputs = new List<TrackPushInput>(); var pushInputs = new List<TrackPushInput>();
@ -72,16 +91,16 @@ public class TrackPushCompletedEventHandler(
// 推送广播 // 推送广播
if (logs.First().IsToMq) if (logs.First().IsToMq)
broadcastingService.TrackPushCompletedPublicTopic(logs.Select(log => BuildDto(log, @event.Data)).ToList()); _broadcastingService.TrackPushCompletedPublicTopic(logs.Select(log => BuildDto(log, @event.Data)).ToList());
// 写入推送日志 // 写入推送日志
await trackPushRecordEsRepository.AddManyAsync(logs); await _trackPushRecordEsRepository.AddManyAsync(logs);
// 同步更新订单主表 // 同步更新订单主表
await TrackPushAfterUpdateOrderAsync(isAdd, @event.IsSuccess, logs); await TrackPushAfterUpdateOrderAsync(isAdd, @event.IsSuccess, logs);
// 引发预警计算 // 引发预警计算
await returnWarningHandler.ReceiveTrackPushRecordHandleAsync(@event.IsSuccess, @event.RequestParams, logs, pushInputs, updateInputs); await _returnWarningHandler.ReceiveTrackPushRecordHandleAsync(@event.IsSuccess, @event.RequestParams, logs, pushInputs, updateInputs);
} }
private async Task TrackPushAfterUpdateOrderAsync(bool isAdd, bool isSuccess, List<TrackPushRecordIndex> logs) private async Task TrackPushAfterUpdateOrderAsync(bool isAdd, bool isSuccess, List<TrackPushRecordIndex> logs)
@ -99,7 +118,7 @@ public class TrackPushCompletedEventHandler(
LatestPushError = x.Message LatestPushError = x.Message
}).ToList(); }).ToList();
await orderRepository.UpdateRangeAsync(orders, x => new await _orderRepository.UpdateRangeAsync(orders, x => new
{ {
x.QuestionType, x.QuestionType,
x.LatestPushError x.LatestPushError
@ -117,7 +136,7 @@ public class TrackPushCompletedEventHandler(
LatestPushTime = x.CreateTime, LatestPushTime = x.CreateTime,
}).ToList(); }).ToList();
await orderRepository.UpdateRangeAsync(orders, x => new await _orderRepository.UpdateRangeAsync(orders, x => new
{ {
x.QuestionType, x.QuestionType,
x.LatestPushError, x.LatestPushError,

View File

@ -1,11 +1,13 @@
using System.Diagnostics; using System.Diagnostics;
using Fake.Messaging; using System.Text;
using DotNetCore.CAP;
using Fake.UnitOfWork; using Fake.UnitOfWork;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using RabbitMQ.Client.Events;
using TrackCenter.Application.Dtos.TrackPull; using TrackCenter.Application.Dtos.TrackPull;
using TrackCenter.Application.Events; using TrackCenter.Application.Events;
using TrackCenter.Application.Helpers; using TrackCenter.Application.Helpers;
@ -14,37 +16,47 @@ using TrackCenter.Application.Services.TrackPull;
using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.OrderAggregate;
using TrackCenter.Domain.Aggregates.TrackDetailsAggregate; using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
namespace TrackCenter.Application.Messaging; namespace TrackCenter.Application.Rabbit;
/// <summary> [ExposeServices(typeof(IRabbitHandler))]
/// 接口通轨迹刷新回调 → 独立队列 <see cref="GlobalConst.TrackRefreshSubscribe"/>。
/// </summary>
[MessageSubscribe(GlobalConst.TrackRefreshSubscribe, ExternalQueue = true)]
public class TrackRefreshEventHandler( public class TrackRefreshEventHandler(
IOrderRepository orderRepository, IOrderRepository orderRepository,
IMessagePublisher messagePublisher, ICapPublisher capPublisher,
TrackPullCacheService trackPullCacheService, TrackPullCacheService trackPullCacheService,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackDetailsEsRepository trackDetailsEsRepository,
ILogger<TrackRefreshEventHandler> logger, ILogger<TrackRefreshEventHandler> logger,
IOptionsMonitor<InterfaceRelayParamsConfigOptions> options) IOptionsMonitor<InterfaceRelayParamsConfigOptions> options)
: IMessageHandle<InterfaceRelayForwardReplayEvent>, ITransientDependency : ITransientDependency, IRabbitHandler
{ {
public virtual async Task HandleAsync( public bool Enable(ConsumeOptions coptions)
InterfaceRelayForwardReplayEvent message,
CancellationToken cancellationToken = default)
{ {
return coptions.Queue.StartsWith(GlobalConst.TrackRefreshSubscribe);
}
[UnitOfWork]
public virtual async Task Handle(BasicDeliverEventArgs args)
{
var msg = string.Empty;
try try
{ {
await ConsumerAsync(message); msg = Encoding.UTF8.GetString(args.Body.ToArray());
var @event = System.Text.Json.JsonSerializer.Deserialize<InterfaceRelayForwardReplayEvent>(msg);
if (@event == null)
{
logger.LogWarning(
$"rabbit on queue received msg can't convert to {nameof(InterfaceRelayForwardReplayEvent)}");
return;
}
await ConsumerAsync(@event);
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error($"TrackRefreshEventHandler出错,错误信息:{ex}\r\n-----\r\n消息:{message.ToJson()}"); Log.Error($"TrackRefreshEventHandler出错,错误信息:{ex}\r\n-----\r\n消息:{msg}");
throw;
} }
} }
private async Task ConsumerAsync(InterfaceRelayForwardReplayEvent @event) public async Task ConsumerAsync(InterfaceRelayForwardReplayEvent @event)
{ {
if (!@event.IsSuccess) if (!@event.IsSuccess)
{ {
@ -59,6 +71,15 @@ public class TrackRefreshEventHandler(
return; return;
} }
// 最优先级,最匹配的 权重 sort
// 客户、渠道、产品
// */*/* 全部
// 客户A/渠道A/产品A
// 客户A
// 客户A 渠道B 产品A
var config = await trackPullCacheService.GetPullParamsWithContentAsync(attach.AppKey, var config = await trackPullCacheService.GetPullParamsWithContentAsync(attach.AppKey,
attach.ServiceProviderCode, attach.OrderType); attach.ServiceProviderCode, attach.OrderType);
if (config is null) if (config is null)
@ -113,6 +134,7 @@ public class TrackRefreshEventHandler(
ServiceProviderCode = attach.ServiceProviderCode ServiceProviderCode = attach.ServiceProviderCode
}; };
if (trackToken is JArray trackTokens) if (trackToken is JArray trackTokens)
{ {
foreach (var item in trackTokens) foreach (var item in trackTokens)
@ -230,7 +252,7 @@ public class TrackRefreshEventHandler(
} }
// 新增/更新轨迹 // 新增/更新轨迹
var trackUpsertEvent = new TrackUpsertForRefreshEvent var trackUpsertEvent = new TrackUpsertEvent
{ {
OrderIdentifiers = OrderIdentifiers =
new[] new[]
@ -262,7 +284,7 @@ public class TrackRefreshEventHandler(
Extra = trackExtra Extra = trackExtra
}; };
await messagePublisher.PublishAsync(trackUpsertEvent); await capPublisher.PublishAsync(GlobalConst.TrackUpsertForRefreshSubscribe, trackUpsertEvent);
} }
private async Task InsertNotMappedTrackAsync(JToken trackToken, TrackRefreshContext context, string? trackPlace, private async Task InsertNotMappedTrackAsync(JToken trackToken, TrackRefreshContext context, string? trackPlace,

View File

@ -1,102 +1,74 @@
using System.Text; using System.Text;
using Fake.RabbitMQ;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using RabbitMQ.Client;
using TrackCenter.Application.Dtos.TrackPush; using TrackCenter.Application.Dtos.TrackPush;
using TrackCenter.Application.Events; using TrackCenter.Application.Events;
using TrackCenter.Application.Rabbit;
namespace TrackCenter.Application.Services.Broadcasting; namespace TrackCenter.Application.Services.Broadcasting;
/// <summary> /// <summary>
/// 对外 Topic 广播(下游仍按 Newtonsoft PascalCase 契约消费,不经 Messaging 信封)。 /// 广播服务
/// </summary> /// </summary>
public class BroadcastingService(IRabbitMqChannelPool channelPool) : ITransientDependency public class BroadcastingService : ITransientDependency
{ {
private readonly RabbitClient _rabbitClient;
public BroadcastingService(RabbitClient rabbitClient)
{
_rabbitClient = rabbitClient;
}
public void TrackChangePublicTopic(TrackChangeEto eto, string AppCode) public void TrackChangePublicTopic(TrackChangeEto eto, string AppCode)
{ {
PublishByConfirmSelect( _rabbitClient.PublishByConfirmSelect(
GlobalConst.TrackChangeExchange, GlobalConst.TrackChangeExchange,
ExchangeType.Topic,
$"TrackChange.{AppCode}", $"TrackChange.{AppCode}",
Encoding.UTF8.GetBytes(eto.ToJson()), Encoding.UTF8.GetBytes(eto.ToJson()),
prop => { prop.Persistent = true; }); prop => { prop.Persistent = true; }
);
} }
/// <summary> /// <summary>
/// 轨迹推送完成广播(批量) /// 轨迹推送完成广播(批量)
/// </summary> /// </summary>
/// <param name="list"></param>
/// <returns></returns>
public void TrackPushCompletedPublicTopic(List<TrackPushRecordDto> list) public void TrackPushCompletedPublicTopic(List<TrackPushRecordDto> list)
{ {
var data = list var data = list
.Select(TrackPushRecordDtoToEvent) .Select(TrackPushRecordDtoToEvent)
.Select(x => Encoding.UTF8.GetBytes(x.ToJson())); .Select(x => Encoding.UTF8.GetBytes(x.ToJson()));
var log = list.First(); var log = list.First();
PublishByConfirmSelect( _rabbitClient.PublishByConfirmSelect(
GlobalConst.TrackPushExchange, GlobalConst.TrackPushExchange,
ExchangeType.Topic,
$"TrackPush.{log.AppKey}", $"TrackPush.{log.AppKey}",
data, data,
prop => { prop.Persistent = true; }); prop => { prop.Persistent = true; }
);
} }
/// <summary> /// <summary>
/// 轨迹推送完成广播 /// 轨迹推送完成广播
/// </summary> /// </summary>
/// <param name="log"></param>
/// <returns></returns>
public void TrackPushCompletedPublicTopic(TrackPushRecordDto log) public void TrackPushCompletedPublicTopic(TrackPushRecordDto log)
{ {
var data = TrackPushRecordDtoToEvent(log); var data = TrackPushRecordDtoToEvent(log);
PublishByConfirmSelect( _rabbitClient.PublishByConfirmSelect(GlobalConst.TrackPushExchange, $"TrackPush.{log.AppKey}",
GlobalConst.TrackPushExchange, Encoding.UTF8.GetBytes(data.ToJson()), prop => { prop.Persistent = true; });
ExchangeType.Topic,
$"TrackPush.{log.AppKey}",
Encoding.UTF8.GetBytes(data.ToJson()),
prop => { prop.Persistent = true; });
} }
/// <summary> /// <summary>
/// 轨迹推送失败广播 /// 轨迹推送失败广播
/// </summary> /// </summary>
/// <param name="log"></param>
public void TrackPushFailedPubilcTopic(TrackPushRecordDto log) public void TrackPushFailedPubilcTopic(TrackPushRecordDto log)
{ {
//暂时使用推送完成模型,后续可能会出现有区分度的模型 //暂时使用推送完成模型,后续可能会出现有区分度的模型
TrackPushCompletedPublicTopic(log); TrackPushCompletedPublicTopic(log);
} }
private void PublishByConfirmSelect(
string exchange,
string exchangeType,
string routingKey,
byte[] data,
Action<IBasicProperties>? options = null)
{
using var channelAccessor = channelPool.Acquire(routingKey);
var channel = channelAccessor.Channel;
channel.ExchangeDeclare(exchange, exchangeType, durable: true);
var props = channel.CreateBasicProperties();
options?.Invoke(props);
channel.ConfirmSelect();
channel.BasicPublish(exchange, routingKey, props, data);
channel.WaitForConfirmsOrDie();
}
private void PublishByConfirmSelect(
string exchange,
string exchangeType,
string routingKey,
IEnumerable<byte[]> datas,
Action<IBasicProperties>? options = null)
{
using var channelAccessor = channelPool.Acquire(routingKey);
var channel = channelAccessor.Channel;
channel.ExchangeDeclare(exchange, exchangeType, durable: true);
var props = channel.CreateBasicProperties();
options?.Invoke(props);
channel.ConfirmSelect();
foreach (var data in datas)
channel.BasicPublish(exchange, routingKey, props, data);
channel.WaitForConfirmsOrDie();
}
private static TrackPushCompletedEvent TrackPushRecordDtoToEvent(TrackPushRecordDto log) private static TrackPushCompletedEvent TrackPushRecordDtoToEvent(TrackPushRecordDto log)
{ {
return new TrackPushCompletedEvent return new TrackPushCompletedEvent

View File

@ -1,6 +1,4 @@
using System.Data; using DotNetCore.CAP;
using Fake.Messaging;
using Fake.UnitOfWork;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Events; using TrackCenter.Application.Events;
using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.OrderAggregate;
@ -13,8 +11,8 @@ namespace TrackCenter.Application.Services.ProvideServices;
/// </summary> /// </summary>
public partial class OrderService public partial class OrderService
{ {
private readonly IMessagePublisher _messagePublisher = messagePublisher; private readonly ICapPublisher _capPublisher;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository = trackDetailsEsRepository; private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
/// <summary> /// <summary>
/// 关联、解除关联订单 /// 关联、解除关联订单
@ -23,10 +21,9 @@ public partial class OrderService
/// <returns></returns> /// <returns></returns>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
[HttpPost("Order/Link")] [HttpPost("Order/Link")]
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] public async Task LinkOrderAsync(LinkOrderInput input)
public virtual async Task LinkOrderAsync(LinkOrderInput input)
{ {
var orderId = await orderRepository.GetFirstAsync(x => var orderId = await _orderRepository.GetFirstAsync(x =>
x.AppKey == input.AppKey && x.OrderType == input.OrderType && x.OrderNo == input.OrderNo, x.AppKey == input.AppKey && x.OrderType == input.OrderType && x.OrderNo == input.OrderNo,
x => x.Id); x => x.Id);
if (orderId == default) if (orderId == default)
@ -63,13 +60,13 @@ public partial class OrderService
{ {
// 获取已关联的订单 // 获取已关联的订单
var linkOrderNos = var linkOrderNos =
await orderRepository.GetLinkedOrdersAsync(orderId, input.LinkAppKey, input.LinkOrderType); await _orderRepository.GetLinkedOrdersAsync(orderId, input.LinkAppKey, input.LinkOrderType);
// 计算出未关联的订单 // 计算出未关联的订单
var noLinkOrderNos = input.LinkOrderNos.Except(linkOrderNos).ToList(); var noLinkOrderNos = input.LinkOrderNos.Except(linkOrderNos).ToList();
// 查询存在的订单 // 查询存在的订单
var existedOrders = await orderRepository.GetListAsync(x => var existedOrders = await _orderRepository.GetListAsync(x =>
x.AppKey == input.LinkAppKey && x.OrderType == input.LinkOrderType && x.AppKey == input.LinkAppKey && x.OrderType == input.LinkOrderType &&
noLinkOrderNos.Contains(x.OrderNo), noLinkOrderNos.Contains(x.OrderNo),
x => new x => new
@ -93,7 +90,7 @@ public partial class OrderService
return 0; return 0;
} }
await orderRepository.LinkOrderAsync(existedOrders.Select(x => new OrderLinkEntity await _orderRepository.LinkOrderAsync(existedOrders.Select(x => new OrderLinkEntity
{ {
OrderId = orderId, OrderId = orderId,
LinkOrderId = x.Id, LinkOrderId = x.Id,
@ -130,7 +127,7 @@ public partial class OrderService
IsAdd = true // 存在则不新增 IsAdd = true // 存在则不新增
}; };
await _messagePublisher.PublishAsync(fillTrackEvent); await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, fillTrackEvent);
} }
} }
@ -139,11 +136,11 @@ public partial class OrderService
private async Task<int> UnLinkOrderAsync(LinkOrderInput input, int orderId) private async Task<int> UnLinkOrderAsync(LinkOrderInput input, int orderId)
{ {
var unLinkIds = await orderRepository.GetLinkedOrdersAsync(orderId, input.LinkAppKey, input.LinkOrderType, var unLinkIds = await _orderRepository.GetLinkedOrdersAsync(orderId, input.LinkAppKey, input.LinkOrderType,
input.LinkOrderNos); input.LinkOrderNos);
// real del // real del
await orderRepository.UnLinkByIdsAsync(unLinkIds); await _orderRepository.UnLinkByIdsAsync(unLinkIds);
return unLinkIds.Count; return unLinkIds.Count;
} }
} }

View File

@ -23,7 +23,7 @@ public partial class OrderService
return new FindOrderPullConfigsOutput(); return new FindOrderPullConfigsOutput();
} }
var pullConfigs = await orderRepository.FindOrderPullConfigsAsync(input.AppKey, input.OrderType, orderNos); var pullConfigs = await _orderRepository.FindOrderPullConfigsAsync(input.AppKey, input.OrderType, orderNos);
var foundOrderNos = pullConfigs var foundOrderNos = pullConfigs
.Select(x => x.OrderNo) .Select(x => x.OrderNo)
.Distinct() .Distinct()
@ -57,7 +57,7 @@ public partial class OrderService
[HttpPost("Order/UpdateOrderPullConfigs")] [HttpPost("Order/UpdateOrderPullConfigs")]
public async Task UpdateOrderPullConfigsAsync(UpdateOrderPullConfigsInput input) public async Task UpdateOrderPullConfigsAsync(UpdateOrderPullConfigsInput input)
{ {
var orderIds = await orderRepository.GetListAsync( var orderIds = await _orderRepository.GetListAsync(
x => x.AppKey == input.AppKey && x.OrderType == input.OrderType && x => x.AppKey == input.AppKey && x.OrderType == input.OrderType &&
input.OrderNos.Contains(x.OrderNo), input.OrderNos.Contains(x.OrderNo),
x => x.Id); x => x.Id);
@ -69,7 +69,7 @@ public partial class OrderService
var inputVo = ObjectMapper var inputVo = ObjectMapper
.Map<PullConfigInfo, UpdateOrderPullConfigsInputVo>(input.Config); .Map<PullConfigInfo, UpdateOrderPullConfigsInputVo>(input.Config);
inputVo.OrderIds = orderIds.ToArray(); inputVo.OrderIds = orderIds.ToArray();
var cnt = await orderRepository.UpdateOrderPullConfigForServiceProviderAsync(inputVo); var cnt = await _orderRepository.UpdateOrderPullConfigForServiceProviderAsync(inputVo);
Log.Warning( Log.Warning(
$"{CurrentUser.UserName}正在修改服务商拉取信息,本次共计影响了{orderIds.Count}个订单、{cnt}段拉取,请求参数是:{input.ToJson()}", $"{CurrentUser.UserName}正在修改服务商拉取信息,本次共计影响了{orderIds.Count}个订单、{cnt}段拉取,请求参数是:{input.ToJson()}",

View File

@ -19,7 +19,7 @@ public partial class OrderService
[VerifySignature] [VerifySignature]
public async Task<IdBaseDto> UpsertSmallBagAsync(UpsertSmallBagInput input) public async Task<IdBaseDto> UpsertSmallBagAsync(UpsertSmallBagInput input)
{ {
var order = await orderRepository.GetFirstAsync(x => var order = await _orderRepository.GetFirstAsync(x =>
x.AppKey == AppKey && x.OrderType == TrackOrderType.小包 && x.OrderNo == input.OrderNo); x.AppKey == AppKey && x.OrderType == TrackOrderType.小包 && x.OrderNo == input.OrderNo);
if (order != null) if (order != null)
{ {
@ -27,7 +27,7 @@ public partial class OrderService
order = await BuiltOrder(input); order = await BuiltOrder(input);
ApplyUpsertOntoExisting(order, existing, resetTrackInfoWhenTrackNoChanged: true); ApplyUpsertOntoExisting(order, existing, resetTrackInfoWhenTrackNoChanged: true);
await orderRepository.UpdateOrderAsync(order); await _orderRepository.UpdateOrderAsync(order);
var index = new OrderOperationRecordIndex var index = new OrderOperationRecordIndex
{ {
@ -39,13 +39,13 @@ public partial class OrderService
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
}; };
_ = orderOperationRecordEsRepository.AddAsync(index); _ = _orderOperationRecordEsRepository.AddAsync(index);
return new IdBaseDto { Id = order.Id }; return new IdBaseDto { Id = order.Id };
} }
order = await BuiltOrder(input); order = await BuiltOrder(input);
var orderId = await orderRepository.CreateOrderAsync(order); var orderId = await _orderRepository.CreateOrderAsync(order);
var indexForAdd = new OrderOperationRecordIndex var indexForAdd = new OrderOperationRecordIndex
{ {
@ -57,7 +57,7 @@ public partial class OrderService
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
CreateTime = DateTime.Now CreateTime = DateTime.Now
}; };
_ = orderOperationRecordEsRepository.AddAsync(indexForAdd); _ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
return new IdBaseDto { Id = orderId }; return new IdBaseDto { Id = orderId };
} }
@ -69,7 +69,7 @@ public partial class OrderService
[VerifySignature] [VerifySignature]
public async Task<IdBaseDto> UpsertBigBagAsync(UpsertBigBagInput input) public async Task<IdBaseDto> UpsertBigBagAsync(UpsertBigBagInput input)
{ {
var order = await orderRepository.GetFirstAsync(x => var order = await _orderRepository.GetFirstAsync(x =>
x.AppKey == AppKey && x.OrderType == TrackOrderType.大包 && x.OrderNo == input.BagNo); x.AppKey == AppKey && x.OrderType == TrackOrderType.大包 && x.OrderNo == input.BagNo);
if (order != null) if (order != null)
{ {
@ -77,7 +77,7 @@ public partial class OrderService
order = await BuiltOrder(input); order = await BuiltOrder(input);
ApplyUpsertOntoExisting(order, existing, resetTrackInfoWhenTrackNoChanged: false); ApplyUpsertOntoExisting(order, existing, resetTrackInfoWhenTrackNoChanged: false);
await orderRepository.UpdateOrderAsync(order); await _orderRepository.UpdateOrderAsync(order);
var index = new OrderOperationRecordIndex var index = new OrderOperationRecordIndex
{ {
@ -89,13 +89,13 @@ public partial class OrderService
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
}; };
_ = orderOperationRecordEsRepository.AddAsync(index); _ = _orderOperationRecordEsRepository.AddAsync(index);
return new IdBaseDto { Id = order.Id }; return new IdBaseDto { Id = order.Id };
} }
order = await BuiltOrder(input); order = await BuiltOrder(input);
var orderId = await orderRepository.CreateOrderAsync(order); var orderId = await _orderRepository.CreateOrderAsync(order);
var indexForAdd = new OrderOperationRecordIndex var indexForAdd = new OrderOperationRecordIndex
{ {
@ -107,7 +107,7 @@ public partial class OrderService
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
CreateTime = DateTime.Now CreateTime = DateTime.Now
}; };
_ = orderOperationRecordEsRepository.AddAsync(indexForAdd); _ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
return new IdBaseDto { Id = orderId }; return new IdBaseDto { Id = orderId };
} }
@ -119,7 +119,7 @@ public partial class OrderService
[VerifySignature] [VerifySignature]
public async Task<IdBaseDto> UpsertMasterOrderAsync(UpsertMasterOrderInput input) public async Task<IdBaseDto> UpsertMasterOrderAsync(UpsertMasterOrderInput input)
{ {
var order = await orderRepository.GetFirstAsync(x => var order = await _orderRepository.GetFirstAsync(x =>
x.AppKey == AppKey && x.OrderType == TrackOrderType.主单 && x.OrderNo == input.MasterNo); x.AppKey == AppKey && x.OrderType == TrackOrderType.主单 && x.OrderNo == input.MasterNo);
if (order != null) if (order != null)
{ {
@ -127,7 +127,7 @@ public partial class OrderService
order = await BuiltOrder(input); order = await BuiltOrder(input);
ApplyUpsertOntoExisting(order, existing, resetTrackInfoWhenTrackNoChanged: false); ApplyUpsertOntoExisting(order, existing, resetTrackInfoWhenTrackNoChanged: false);
await orderRepository.UpdateOrderAsync(order); await _orderRepository.UpdateOrderAsync(order);
var index = new OrderOperationRecordIndex var index = new OrderOperationRecordIndex
{ {
@ -139,13 +139,13 @@ public partial class OrderService
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
}; };
_ = orderOperationRecordEsRepository.AddAsync(index); _ = _orderOperationRecordEsRepository.AddAsync(index);
return new IdBaseDto { Id = order.Id }; return new IdBaseDto { Id = order.Id };
} }
order = await BuiltOrder(input); order = await BuiltOrder(input);
var orderId = await orderRepository.CreateOrderAsync(order); var orderId = await _orderRepository.CreateOrderAsync(order);
var indexForAdd = new OrderOperationRecordIndex var indexForAdd = new OrderOperationRecordIndex
{ {
@ -157,7 +157,7 @@ public partial class OrderService
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
CreateTime = DateTime.Now CreateTime = DateTime.Now
}; };
_ = orderOperationRecordEsRepository.AddAsync(indexForAdd); _ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
return new IdBaseDto { Id = orderId }; return new IdBaseDto { Id = orderId };
} }
@ -183,7 +183,7 @@ public partial class OrderService
ArrivalEmail = input.Consignee.Email, ArrivalEmail = input.Consignee.Email,
}; };
var config = await trackPullConfigRepository var config = await _trackPullConfigRepository
.FirstOrDefaultAsync(x => x.AppKey == AppKey .FirstOrDefaultAsync(x => x.AppKey == AppKey
&& x.ServiceProviderCode == input.ApiCode && x.ServiceProviderCode == input.ApiCode
&& x.OrderType == TrackOrderType.小包); && x.OrderType == TrackOrderType.小包);
@ -220,7 +220,7 @@ public partial class OrderService
return order; return order;
} }
var config = await trackPullConfigRepository var config = await _trackPullConfigRepository
.FirstOrDefaultAsync(x => x.AppKey == AppKey .FirstOrDefaultAsync(x => x.AppKey == AppKey
&& x.ServiceProviderCode == input.ApiCode && x.ServiceProviderCode == input.ApiCode
&& x.OrderType == TrackOrderType.大包); && x.OrderType == TrackOrderType.大包);
@ -250,7 +250,7 @@ public partial class OrderService
order.OrderFlights = new List<OrderFlightEntity>(); order.OrderFlights = new List<OrderFlightEntity>();
var config = await trackPullConfigRepository var config = await _trackPullConfigRepository
.FirstOrDefaultAsync(x => x.AppKey == AppKey .FirstOrDefaultAsync(x => x.AppKey == AppKey
&& x.ServiceProviderCode == input.ApiCode && x.ServiceProviderCode == input.ApiCode
&& x.OrderType == TrackOrderType.小包); && x.OrderType == TrackOrderType.小包);

View File

@ -1,4 +1,4 @@
using Fake.Messaging; using DotNetCore.CAP;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.OrderAggregate;
using TrackCenter.Domain.Aggregates.TrackDetailsAggregate; using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
@ -13,15 +13,30 @@ namespace TrackCenter.Application.Services.ProvideServices;
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "OpenApi")] [ApiExplorerSettings(GroupName = "OpenApi")]
[Route("/ProviderService")] [Route("/ProviderService")]
public partial class OrderService( public partial class OrderService : ApplicationServiceBase
{
private readonly IOrderRepository _orderRepository;
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
private readonly ITrackPullConfigRepository _trackPullConfigRepository;
private readonly IServiceProvider _serviceProvider;
public OrderService(
IOrderRepository orderRepository, IOrderRepository orderRepository,
IOrderOperationRecordEsRepository orderOperationRecordEsRepository, IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
ITrackPullConfigRepository trackPullConfigRepository, ITrackPullConfigRepository trackPullConfigRepository,
IMessagePublisher messagePublisher, ICapPublisher capPublisher,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackDetailsEsRepository trackDetailsEsRepository,
IServiceProvider serviceProvider) IServiceProvider serviceProvider
: ApplicationServiceBase )
{ {
_orderRepository = orderRepository;
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
_trackPullConfigRepository = trackPullConfigRepository;
_serviceProvider = serviceProvider;
_trackDetailsEsRepository = trackDetailsEsRepository;
_capPublisher = capPublisher;
}
/// <summary> /// <summary>
/// 删除订单 /// 删除订单
/// </summary> /// </summary>
@ -31,14 +46,14 @@ public partial class OrderService(
[HttpPost("Order/Delete")] [HttpPost("Order/Delete")]
public async Task<IdBaseDto> DeleteOrderAsync(DeleteOrderInput input) public async Task<IdBaseDto> DeleteOrderAsync(DeleteOrderInput input)
{ {
var order = await orderRepository.GetFirstAsync(x => var order = await _orderRepository.GetFirstAsync(x =>
x.AppKey == input.AppKey && x.OrderType == input.OrderType && x.OrderNo == input.OrderNo); x.AppKey == input.AppKey && x.OrderType == input.OrderType && x.OrderNo == input.OrderNo);
if (order == null) if (order == null)
{ {
throw ProviderServiceActionResult.Exception("删除订单失败", $"订单:{input.OrderNo}不存在"); throw ProviderServiceActionResult.Exception("删除订单失败", $"订单:{input.OrderNo}不存在");
} }
await orderRepository.DeleteOrderAsync(order.Id); await _orderRepository.DeleteOrderAsync(order.Id);
// 预警计算 // 预警计算
WarningCalc_OrderDeletedOrCompleted(order); WarningCalc_OrderDeletedOrCompleted(order);
@ -51,7 +66,7 @@ public partial class OrderService(
order.IsDeleted = true; order.IsDeleted = true;
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
var scope = serviceProvider.CreateAsyncScope(); var scope = _serviceProvider.CreateAsyncScope();
try try
{ {
var handler = scope.ServiceProvider.GetRequiredService<IOrderWarningHandler>(); var handler = scope.ServiceProvider.GetRequiredService<IOrderWarningHandler>();

View File

@ -15,12 +15,21 @@ namespace TrackCenter.Application.Services.ProvideServices;
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "OpenApi")] [ApiExplorerSettings(GroupName = "OpenApi")]
[Route("/ProviderService")] [Route("/ProviderService")]
public class TrackPullService( public class TrackPullService : ApplicationServiceBase
ProvideQueries provideQueries, {
private readonly IOptionsMonitor<InterfaceRelayParamsConfigOptions> _options;
private readonly ProvideQueries _provideQueries;
private readonly InterfaceForwardService _interfaceForwardService;
public TrackPullService(ProvideQueries provideQueries,
InterfaceForwardService interfaceForwardService, InterfaceForwardService interfaceForwardService,
IOptionsMonitor<InterfaceRelayParamsConfigOptions> options) IOptionsMonitor<InterfaceRelayParamsConfigOptions> options)
: ApplicationServiceBase
{ {
_provideQueries = provideQueries;
_interfaceForwardService = interfaceForwardService;
_options = options;
}
/// <summary> /// <summary>
/// 手动刷新轨迹 /// 手动刷新轨迹
/// </summary> /// </summary>
@ -37,7 +46,7 @@ public class TrackPullService(
// tips:这里没有区分订单类型,就算的不同类型的单一起刷新了也无所谓 // tips:这里没有区分订单类型,就算的不同类型的单一起刷新了也无所谓
var (orders, skipReasons) = var (orders, skipReasons) =
await provideQueries.DiagnoseManualRefreshAsync(input.AppKey, input.OrderNos); await _provideQueries.DiagnoseManualRefreshAsync(input.AppKey, input.OrderNos);
if (orders.Count == 0) if (orders.Count == 0)
{ {
@ -70,8 +79,8 @@ public class TrackPullService(
using (Track2InterfaceRelayAppContext.Use( using (Track2InterfaceRelayAppContext.Use(
new Track2InterfaceRelayAppContext(group.Key.AppKey))) new Track2InterfaceRelayAppContext(group.Key.AppKey)))
{ {
var res = await interfaceForwardService.BatchForwardThenPushToQueueAsync( var res = await _interfaceForwardService.BatchForwardThenPushToQueueAsync(
options.CurrentValue.GetTrackInterfaceCode, serviceProviderCode, data, _options.CurrentValue.GetTrackInterfaceCode, serviceProviderCode, data,
GlobalConst.TrackRefreshSubscribe, GlobalConst.TrackRefreshSubscribe,
attach); attach);

View File

@ -1,6 +1,4 @@
using System.Data; using DotNetCore.CAP;
using Fake.Messaging;
using Fake.UnitOfWork;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Dtos.TrackOrder; using TrackCenter.Application.Dtos.TrackOrder;
using TrackCenter.Application.Dtos.TrackPush; using TrackCenter.Application.Dtos.TrackPush;
@ -20,20 +18,20 @@ namespace TrackCenter.Application.Services.ProvideServices;
[Route("/ProviderService")] [Route("/ProviderService")]
public class TrackPushService : ApplicationServiceBase public class TrackPushService : ApplicationServiceBase
{ {
private readonly IMessagePublisher _messagePublisher; private readonly ICapPublisher _capPublisher;
private readonly IOrderRepository _orderRepository; private readonly IOrderRepository _orderRepository;
private readonly ITrackPushConfigRepository _trackPushConfigRepository; private readonly ITrackPushConfigRepository _trackPushConfigRepository;
private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository; private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
public TrackPushService(IOrderRepository orderRepository public TrackPushService(IOrderRepository orderRepository
, IMessagePublisher messagePublisher , ICapPublisher capPublisher
, ITrackPushRecordEsRepository trackPushRecordEsRepository , ITrackPushRecordEsRepository trackPushRecordEsRepository
, ITrackPushConfigRepository trackPushConfigRepository, , ITrackPushConfigRepository trackPushConfigRepository,
ITrackDetailsEsRepository trackDetailsEsRepository) ITrackDetailsEsRepository trackDetailsEsRepository)
{ {
_orderRepository = orderRepository; _orderRepository = orderRepository;
_messagePublisher = messagePublisher; _capPublisher = capPublisher;
_trackPushRecordEsRepository = trackPushRecordEsRepository; _trackPushRecordEsRepository = trackPushRecordEsRepository;
_trackPushConfigRepository = trackPushConfigRepository; _trackPushConfigRepository = trackPushConfigRepository;
_trackDetailsEsRepository = trackDetailsEsRepository; _trackDetailsEsRepository = trackDetailsEsRepository;
@ -45,8 +43,7 @@ public class TrackPushService : ApplicationServiceBase
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost("Track/Push")] [HttpPost("Track/Push")]
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] public async Task PushTrackAsync(PushTrackInput input)
public virtual async Task PushTrackAsync(PushTrackInput input)
{ {
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList();
if (input.OrderNos.Count == 0) if (input.OrderNos.Count == 0)
@ -155,7 +152,7 @@ public class TrackPushService : ApplicationServiceBase
} }
}; };
await _messagePublisher.PublishAsync(trackPushEvent); await _capPublisher.PublishAsync(GlobalConst.TrackManualPushSubscribe, trackPushEvent);
pushedOrderNos.Add(order.OrderNo); pushedOrderNos.Add(order.OrderNo);
} }

View File

@ -13,7 +13,7 @@ namespace TrackCenter.Application.Services.ProvideServices;
/// </summary> /// </summary>
public partial class TrackService public partial class TrackService
{ {
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository = trackDetailsEsRepository; private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly IHttpContextAccessor _contextAccessor; private readonly IHttpContextAccessor _contextAccessor;
private IObjectStorage ObjectStorage => LazyServiceProvider.GetRequiredService<IObjectStorage>(); private IObjectStorage ObjectStorage => LazyServiceProvider.GetRequiredService<IObjectStorage>();

View File

@ -1,6 +1,4 @@
using System.Data; using DotNetCore.CAP;
using Fake.Messaging;
using Fake.UnitOfWork;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Events; using TrackCenter.Application.Events;
@ -17,23 +15,34 @@ namespace TrackCenter.Application.Services.ProvideServices;
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "OpenApi")] [ApiExplorerSettings(GroupName = "OpenApi")]
[Route("/ProviderService")] [Route("/ProviderService")]
public partial class TrackService( public partial class TrackService : ApplicationServiceBase
IOrderRepository orderRepository,
IMessagePublisher messagePublisher,
ITrackDetailsEsRepository trackDetailsEsRepository,
ITrackContentRepository trackContentRepository)
: ApplicationServiceBase
{ {
private readonly ICapPublisher _capPublisher;
private readonly IOrderRepository _orderRepository;
private readonly ITrackContentRepository _trackContentRepository;
public TrackService(
IOrderRepository orderRepository,
ICapPublisher capPublisher,
ITrackDetailsEsRepository trackDetailsEsRepository,
ITrackContentRepository trackContentRepository
)
{
_orderRepository = orderRepository;
_capPublisher = capPublisher;
_trackContentRepository = trackContentRepository;
_trackDetailsEsRepository = trackDetailsEsRepository;
}
/// <summary> /// <summary>
/// 删除轨迹 /// 删除轨迹
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
[HttpPost("Track/Delete")] [HttpPost("Track/Delete")]
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] public async Task DeleteTrackAsync(DeleteTrackInput input)
public virtual async Task DeleteTrackAsync(DeleteTrackInput input)
{ {
var orderId = await orderRepository.GetFirstAsync(x => var orderId = await _orderRepository.GetFirstAsync(x =>
x.AppKey == input.AppKey x.AppKey == input.AppKey
&& x.OrderType == input.OrderType && x.OrderType == input.OrderType
&& x.OrderNo == input.OrderNo, && x.OrderNo == input.OrderNo,
@ -90,7 +99,7 @@ public partial class TrackService(
} }
// 删除轨迹(异步) // 删除轨迹(异步)
await messagePublisher.PublishAsync(new TrackDeleteEvent await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe, new TrackDeleteEvent
{ {
EsIds = esIds, EsIds = esIds,
OrderIds = new[] { orderId }, OrderIds = new[] { orderId },
@ -109,11 +118,10 @@ public partial class TrackService(
/// <param name="input"></param> /// <param name="input"></param>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
[HttpPost("Track/BatchDelete")] [HttpPost("Track/BatchDelete")]
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] public async Task BatchDeleteTrackAsync(BatchDeleteTrackInput input)
public virtual async Task BatchDeleteTrackAsync(BatchDeleteTrackInput input)
{ {
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空 input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空
var orders = await orderRepository.GetListAsync(x => var orders = await _orderRepository.GetListAsync(x =>
x.AppKey == input.AppKey x.AppKey == input.AppKey
&& x.OrderType == input.OrderType && x.OrderType == input.OrderType
&& input.OrderNos.Contains(x.OrderNo), && input.OrderNos.Contains(x.OrderNo),
@ -165,7 +173,7 @@ public partial class TrackService(
AppKey = input.AppKey, AppKey = input.AppKey,
OperationMan = CurrentUser.UserName OperationMan = CurrentUser.UserName
}; };
await messagePublisher.PublishAsync(@event); await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe, @event);
} }
/// <summary> /// <summary>
@ -174,12 +182,11 @@ public partial class TrackService(
/// <param name="input"></param> /// <param name="input"></param>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
[HttpPost("Track/Upsert")] [HttpPost("Track/Upsert")]
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] public async Task UpsertTrackAsync(UpsertTrackInput input)
public virtual async Task UpsertTrackAsync(UpsertTrackInput input)
{ {
using var activity = ActivityHelper.CreateSpanActivity("UpsertTrack"); using var activity = ActivityHelper.CreateSpanActivity("UpsertTrack");
activity.SetTag("UpsertTrackInput", input.ToJson()); activity.SetTag("UpsertTrackInput", input.ToJson());
var order = await orderRepository.GetFirstAsync(x => var order = await _orderRepository.GetFirstAsync(x =>
x.AppKey == input.AppKey x.AppKey == input.AppKey
&& x.OrderType == input.OrderType && x.OrderType == input.OrderType
&& x.OrderNo == input.OrderNo, && x.OrderNo == input.OrderNo,
@ -219,7 +226,7 @@ public partial class TrackService(
} }
}; };
@event.OperationMan = CurrentUser.UserName; @event.OperationMan = CurrentUser.UserName;
await messagePublisher.PublishAsync(@event); await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
} }
/// <summary> /// <summary>
@ -228,13 +235,12 @@ public partial class TrackService(
/// <param name="input"></param> /// <param name="input"></param>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
[HttpPost("Track/BatchUpsert")] [HttpPost("Track/BatchUpsert")]
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] public async Task BatchUpsertTrackAsync(BatchUpsertTrackInput input)
public virtual async Task BatchUpsertTrackAsync(BatchUpsertTrackInput input)
{ {
using var activity = ActivityHelper.CreateSpanActivity("BatchUpsert"); using var activity = ActivityHelper.CreateSpanActivity("BatchUpsert");
activity.SetTag("BatchUpsertTrackInput", input.ToJson()); activity.SetTag("BatchUpsertTrackInput", input.ToJson());
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList();
var orders = await orderRepository.GetListAsync(x => var orders = await _orderRepository.GetListAsync(x =>
x.AppKey == input.AppKey x.AppKey == input.AppKey
&& x.OrderType == input.OrderType && x.OrderType == input.OrderType
&& input.OrderNos.Contains(x.OrderNo), && input.OrderNos.Contains(x.OrderNo),
@ -292,7 +298,7 @@ public partial class TrackService(
ForcedPush = input.ForcedPush, ForcedPush = input.ForcedPush,
IsToMq = input.IsToMq, IsToMq = input.IsToMq,
}; };
await messagePublisher.PublishAsync(@event); await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
} }
} }
@ -333,7 +339,7 @@ public partial class TrackService(
#region 轨迹内容中的配置规则检查 #region 轨迹内容中的配置规则检查
var trackContent = await trackContentRepository.GetByCodeAsync(appKey, trackCode); var trackContent = await _trackContentRepository.GetByCodeAsync(appKey, trackCode);
if (trackContent == null) if (trackContent == null)
{ {
throw ProviderServiceActionResult.Exception("校验失败", $"轨迹代码:{trackCode}轨迹内容不存在"); throw ProviderServiceActionResult.Exception("校验失败", $"轨迹代码:{trackCode}轨迹内容不存在");

View File

@ -28,14 +28,14 @@ public class TrackPullTask(
ProvideQueries provideQueries, ProvideQueries provideQueries,
InterfaceForwardService interfaceForwardService, InterfaceForwardService interfaceForwardService,
IOptions<InterfaceRelayParamsConfigOptions> interfaceOptions) IOptions<InterfaceRelayParamsConfigOptions> interfaceOptions)
: ApplicationServiceBase : ApplicationServiceBase, ITransientDependency
{ {
/// <summary> /// <summary>
/// 定时任务刷新轨迹 /// 定时任务刷新轨迹
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[DisableConcurrentExecution(timeoutInSeconds: 1800)] [DisableConcurrentExecution(timeoutInSeconds: 1800)]
[UnitOfWork(IsTransactional = true)] [UnitOfWork]
[HttpPost("/TimerTask/Refresh")] [HttpPost("/TimerTask/Refresh")]
public virtual async Task<bool> RefreshForTaskAsync() public virtual async Task<bool> RefreshForTaskAsync()
{ {
@ -66,7 +66,7 @@ public class TrackPullTask(
return true; return true;
} }
[UnitOfWork(IsTransactional = true)] [UnitOfWork]
public virtual async Task RefreshForConfigAsync(TrackPullConfigVo config) public virtual async Task RefreshForConfigAsync(TrackPullConfigVo config)
{ {
var redisKey = trackPullCacheService.TrackPullKey(config); var redisKey = trackPullCacheService.TrackPullKey(config);
@ -89,7 +89,7 @@ public class TrackPullTask(
} }
} }
protected async Task DoRefreshAsync(TrackPullConfigVo config, ProvideQueries queries, protected async Task DoRefreshAsync(TrackPullConfigVo config, ProvideQueries provideQueries,
InterfaceForwardService interfaceForward, InterfaceForwardService interfaceForward,
InterfaceRelayParamsConfigOptions interfaceOptionsValue) InterfaceRelayParamsConfigOptions interfaceOptionsValue)
{ {
@ -97,7 +97,7 @@ public class TrackPullTask(
{ {
var page = 1; var page = 1;
var orders = var orders =
await queries.FindOrderWithServiceProviderAsync(config, page); await provideQueries.FindOrderWithServiceProviderAsync(config, page);
while (orders.Any()) while (orders.Any())
{ {
var serviceProviderCode = orders.First().ServiceProviderCode; var serviceProviderCode = orders.First().ServiceProviderCode;
@ -122,7 +122,7 @@ public class TrackPullTask(
} }
} }
orders = await queries.FindOrderWithServiceProviderAsync(config, ++page); orders = await provideQueries.FindOrderWithServiceProviderAsync(config, ++page);
} }
} }
} }

View File

@ -21,15 +21,28 @@ namespace TrackCenter.Application.Services.TrackOrders;
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "轨迹订单")] [ApiExplorerSettings(GroupName = "轨迹订单")]
[Route("/Track/Order")] [Route("/Track/Order")]
public class TrackOrderService( public class TrackOrderService : ApplicationServiceBase
{
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
private readonly IOrderRepository _orderRepository;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository;
private readonly IServiceProvider _serviceProvider;
private IObjectStorage ObjectStorage => LazyServiceProvider.GetRequiredService<IObjectStorage>();
public TrackOrderService(
IOrderRepository orderRepository, IOrderRepository orderRepository,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackDetailsEsRepository trackDetailsEsRepository,
ITrackPushRecordEsRepository trackPushRecordEsRepository, ITrackPushRecordEsRepository trackPushRecordEsRepository,
IOrderOperationRecordEsRepository orderOperationRecordEsRepository, IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
: ApplicationServiceBase
{ {
private IObjectStorage ObjectStorage => LazyServiceProvider.GetRequiredService<IObjectStorage>(); _orderRepository = orderRepository;
_trackDetailsEsRepository = trackDetailsEsRepository;
_trackPushRecordEsRepository = trackPushRecordEsRepository;
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
_serviceProvider = serviceProvider;
}
#region 轨迹订单列表 #region 轨迹订单列表
@ -42,7 +55,7 @@ public class TrackOrderService(
public async Task<PagedResult<TrackOrderVO>> GetTrackOrderPagedList(GetPageTrackOrderInput input) public async Task<PagedResult<TrackOrderVO>> GetTrackOrderPagedList(GetPageTrackOrderInput input)
{ {
var vo = ObjectMapper.Map<GetPageTrackOrderInput, GetPageTrackOrderInputVO>(input); var vo = ObjectMapper.Map<GetPageTrackOrderInput, GetPageTrackOrderInputVO>(input);
var pageList = await orderRepository.GetTrackOrderPagedList(vo); var pageList = await _orderRepository.GetTrackOrderPagedList(vo);
return pageList; return pageList;
} }
@ -57,7 +70,7 @@ public class TrackOrderService(
[HttpPost("UpdateOrderStatus")] [HttpPost("UpdateOrderStatus")]
public async Task<bool> UpdateOrderStatus(UpdateOrderStatusInput input) public async Task<bool> UpdateOrderStatus(UpdateOrderStatusInput input)
{ {
var order = await orderRepository.GetFirstAsync(x => x.Id == input.OrderId); var order = await _orderRepository.GetFirstAsync(x => x.Id == input.OrderId);
if (order == null) throw new BusinessException(message: "订单不存在"); if (order == null) throw new BusinessException(message: "订单不存在");
@ -68,7 +81,7 @@ public class TrackOrderService(
} }
order.IsCompleted = input.IsCompleted; order.IsCompleted = input.IsCompleted;
var b = await orderRepository.UpdateColumnAsync(order, x => new var b = await _orderRepository.UpdateColumnAsync(order, x => new
{ {
x.IsCompleted x.IsCompleted
}); });
@ -88,7 +101,7 @@ public class TrackOrderService(
[HttpGet("GetTrackPushRecords")] [HttpGet("GetTrackPushRecords")]
public async Task<List<TrackPushRecordOutput>> GetTrackPushRecordsAsync(int orderId) public async Task<List<TrackPushRecordOutput>> GetTrackPushRecordsAsync(int orderId)
{ {
var records = await trackPushRecordEsRepository.GetTrackPushRecordsAsync(orderId); var records = await _trackPushRecordEsRepository.GetTrackPushRecordsAsync(orderId);
var res = ObjectMapper.Map<List<TrackPushRecordIndex>, List<TrackPushRecordOutput>>(records); var res = ObjectMapper.Map<List<TrackPushRecordIndex>, List<TrackPushRecordOutput>>(records);
@ -126,13 +139,13 @@ public class TrackOrderService(
if (whereExpression == default) if (whereExpression == default)
throw new BusinessException(message: "请求参数异常"); throw new BusinessException(message: "请求参数异常");
var order = await orderRepository.GetFirstAsync(whereExpression); var order = await _orderRepository.GetFirstAsync(whereExpression);
if (order == default) if (order == default)
{ {
throw new BusinessException(message: "订单不存在"); throw new BusinessException(message: "订单不存在");
} }
var trackDetails = await trackDetailsEsRepository.GetListAsync(order.Id); var trackDetails = await _trackDetailsEsRepository.GetListAsync(order.Id);
if (trackDetails.IsNullOrEmpty()) if (trackDetails.IsNullOrEmpty())
{ {
return new List<TrackOrderDetailDto>(); return new List<TrackOrderDetailDto>();
@ -196,11 +209,11 @@ public class TrackOrderService(
{ {
var res = new List<GetDetailTimelineOutput>(); var res = new List<GetDetailTimelineOutput>();
var order = await orderRepository.GetFirstAsync(m => m.OrderNo == input.OrderNumber); var order = await _orderRepository.GetFirstAsync(m => m.OrderNo == input.OrderNumber);
if (order == null) if (order == null)
throw new BusinessException("订单不存在"); throw new BusinessException("订单不存在");
var trackDetails = await trackDetailsEsRepository.GetListAsync(order.Id); var trackDetails = await _trackDetailsEsRepository.GetListAsync(order.Id);
//时间轴不显示轨迹内容为空的轨迹,进行过滤 //时间轴不显示轨迹内容为空的轨迹,进行过滤
trackDetails = trackDetails.Where(x => !x.TrackContent.IsNullOrWhiteSpace() && x.TrackContent != GlobalConst.EmptyString).ToList(); trackDetails = trackDetails.Where(x => !x.TrackContent.IsNullOrWhiteSpace() && x.TrackContent != GlobalConst.EmptyString).ToList();
@ -265,7 +278,7 @@ public class TrackOrderService(
[HttpPost("DeleteTrackById")] [HttpPost("DeleteTrackById")]
public async Task<bool> DeleteTrackByIdAsync(string trackId) public async Task<bool> DeleteTrackByIdAsync(string trackId)
{ {
var track = await trackDetailsEsRepository.GetByIdAsync(trackId); var track = await _trackDetailsEsRepository.GetByIdAsync(trackId);
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (track == null) if (track == null)
@ -273,7 +286,7 @@ public class TrackOrderService(
throw new BusinessException(message: "轨迹不存在!"); throw new BusinessException(message: "轨迹不存在!");
} }
await trackDetailsEsRepository.DeleteTrackByIdAsync(trackId, track.IndexName); await _trackDetailsEsRepository.DeleteTrackByIdAsync(trackId, track.IndexName);
return true; return true;
} }
@ -285,14 +298,14 @@ public class TrackOrderService(
[HttpGet("GetOrderOperationsById")] [HttpGet("GetOrderOperationsById")]
public async Task<List<OrderOperationRecordIndex>> GetOrderOperationsByIdAsync(int orderId) public async Task<List<OrderOperationRecordIndex>> GetOrderOperationsByIdAsync(int orderId)
{ {
return await orderOperationRecordEsRepository.GetOrderOperationsByIdAsync(orderId); return await _orderOperationRecordEsRepository.GetOrderOperationsByIdAsync(orderId);
} }
private void WarningCalc_OrderDeletedOrCompleted(OrderEntity order) private void WarningCalc_OrderDeletedOrCompleted(OrderEntity order)
{ {
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
var scope = serviceProvider.CreateAsyncScope(); var scope = _serviceProvider.CreateAsyncScope();
try try
{ {
var handler = scope.ServiceProvider.GetRequiredService<IOrderWarningHandler>(); var handler = scope.ServiceProvider.GetRequiredService<IOrderWarningHandler>();

View File

@ -1,7 +1,5 @@
using System.Data; using DotNetCore.CAP;
using Fake.Messaging;
using Fake.Domain.Dtos; using Fake.Domain.Dtos;
using Fake.UnitOfWork;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using TrackCenter.Application.Dtos.Warning; using TrackCenter.Application.Dtos.Warning;
using TrackCenter.Application.Dtos.Warning.Return; using TrackCenter.Application.Dtos.Warning.Return;
@ -27,7 +25,7 @@ public class ReturnWarningService : ApplicationServiceBase
private readonly IReturnWarningHandler _returnWarningHandler; private readonly IReturnWarningHandler _returnWarningHandler;
private readonly IWarningRuleRepository _warningRuleRepository; private readonly IWarningRuleRepository _warningRuleRepository;
private readonly IWarningDetailsEsRepository _warningDetailsEsRepository; private readonly IWarningDetailsEsRepository _warningDetailsEsRepository;
private readonly IMessagePublisher _messagePublisher; private readonly ICapPublisher _capPublisher;
private readonly TrackStatusNameDictCache _trackStatusNameDictCache; private readonly TrackStatusNameDictCache _trackStatusNameDictCache;
private readonly AppNameDictCache _appNameDictCache; private readonly AppNameDictCache _appNameDictCache;
@ -36,7 +34,7 @@ public class ReturnWarningService : ApplicationServiceBase
IReturnWarningHandler returnWarningHandler, IReturnWarningHandler returnWarningHandler,
IWarningRuleRepository warningRuleRepository, IWarningRuleRepository warningRuleRepository,
IWarningDetailsEsRepository warningDetailsEsRepository, IWarningDetailsEsRepository warningDetailsEsRepository,
IMessagePublisher messagePublisher, ICapPublisher capPublisher,
TrackStatusNameDictCache trackStatusNameDictCache, TrackStatusNameDictCache trackStatusNameDictCache,
AppNameDictCache appNameDictCache AppNameDictCache appNameDictCache
) )
@ -45,7 +43,7 @@ public class ReturnWarningService : ApplicationServiceBase
_returnWarningHandler = returnWarningHandler; _returnWarningHandler = returnWarningHandler;
_warningRuleRepository = warningRuleRepository; _warningRuleRepository = warningRuleRepository;
_warningDetailsEsRepository = warningDetailsEsRepository; _warningDetailsEsRepository = warningDetailsEsRepository;
_messagePublisher = messagePublisher; _capPublisher = capPublisher;
_trackStatusNameDictCache = trackStatusNameDictCache; _trackStatusNameDictCache = trackStatusNameDictCache;
_appNameDictCache = appNameDictCache; _appNameDictCache = appNameDictCache;
} }
@ -222,8 +220,7 @@ public class ReturnWarningService : ApplicationServiceBase
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[HttpPost("ReturnPush")] [HttpPost("ReturnPush")]
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] public async Task<string> ReturnPush(ReturnPushInput input)
public virtual async Task<string> ReturnPush(ReturnPushInput input)
{ {
if (input.WarningDetailIds.Count == 0) if (input.WarningDetailIds.Count == 0)
{ {
@ -279,7 +276,7 @@ public class ReturnWarningService : ApplicationServiceBase
} }
}; };
await _messagePublisher.PublishAsync(trackPushEvent); await _capPublisher.PublishAsync(GlobalConst.TrackManualPushSubscribe, trackPushEvent);
} }
return $"即将进行{filtered.Count}个回传推送!"; return $"即将进行{filtered.Count}个回传推送!";

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../common.props" /> <Import Project="../../common.props" />
@ -9,7 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Fake.AspNetCore" Version="$(FakePackageVersion)" /> <PackageReference Include="Fake.AspNetCore" Version="$(FakePackageVersion)" />
<PackageReference Include="Fake.Messaging.RabbitMQ" Version="$(FakePackageVersion)" /> <PackageReference Include="Fake.EventBus.RabbitMQ" Version="$(FakePackageVersion)" />
<PackageReference Include="Fake.ObjectMapping.AutoMapper" Version="$(FakePackageVersion)" /> <PackageReference Include="Fake.ObjectMapping.AutoMapper" Version="$(FakePackageVersion)" />
<PackageReference Include="Fake.ObjectStorage.TencentCos" Version="$(FakePackageVersion)" /> <PackageReference Include="Fake.ObjectStorage.TencentCos" Version="$(FakePackageVersion)" />
@ -19,6 +19,12 @@
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.4" /> <PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.4" />
<PackageReference Include="OpenTelemetry.Api" Version="1.12.0" /> <PackageReference Include="OpenTelemetry.Api" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" /> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageReference Include="Savorboard.CAP.InMemoryMessageQueue" Version="8.2.1"/>
<PackageReference Include="DotNetCore.CAP" Version="8.2.0"/>
<PackageReference Include="DotNetCore.CAP.Dashboard" Version="8.2.0"/>
<PackageReference Include="DotNetCore.CAP.RabbitMQ" Version="8.2.0"/>
<PackageReference Include="DotNetCore.CAP.MySql" Version="8.2.0"/>
<PackageReference Include="DotNetCore.CAP.InMemoryStorage" Version="8.2.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,26 +1,33 @@
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
using DotNetCore.CAP;
using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Messages;
using Fake; using Fake;
using Fake.Messaging; using Fake.EventBus.RabbitMQ;
using Fake.Messaging.RabbitMQ;
using Fake.Modularity; using Fake.Modularity;
using Fake.ObjectMapping.AutoMapper; using Fake.ObjectMapping.AutoMapper;
using Fake.ObjectStorage; using Fake.ObjectStorage;
using Fake.RabbitMQ;
using FreeRedis; using FreeRedis;
using Hangfire; using Hangfire;
using Hangfire.Redis.StackExchange; using Hangfire.Redis.StackExchange;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TrackCenter.Application.HostServices; using RabbitMQ.Client;
using Savorboard.CAP.InMemoryMessageQueue;
using TrackCenter.Application.Kafka; using TrackCenter.Application.Kafka;
using TrackCenter.Application.Messaging; using TrackCenter.Application.CAP;
using TrackCenter.Application.HostServices;
using TrackCenter.Application.Options; using TrackCenter.Application.Options;
using TrackCenter.Application.Rabbit;
namespace TrackCenter.Application; namespace TrackCenter.Application;
[DependsOn(typeof(FakeRabbitMqModule))]
[DependsOn(typeof(FakeObjectMappingAutoMapperModule))] [DependsOn(typeof(FakeObjectMappingAutoMapperModule))]
[DependsOn(typeof(FakeMessagingRabbitMqModule))] [DependsOn(typeof(FakeEventBusRabbitMqModule))]
[DependsOn(typeof(FakeObjectStorageTencentCosModule))] [DependsOn(typeof(FakeObjectStorageTencentCosModule))]
[DependsOn(typeof(TrackCenterDomainModule))] [DependsOn(typeof(TrackCenterDomainModule))]
public class TrackCenterApplicationModule : FakeModule public class TrackCenterApplicationModule : FakeModule
@ -34,27 +41,106 @@ public class TrackCenterApplicationModule : FakeModule
options.ScanProfiles<TrackCenterApplicationModule>(); options.ScanProfiles<TrackCenterApplicationModule>();
}); });
// Messaging:独立队列;PrefetchCount=0 → ProcessorCount;接口通可按 TrackApp:TrackRefreshFetchCount 覆盖 var rabbitOptions = configuration.GetSection("RabbitMQ").Get<FakeRabbitMqOptions>()!.Default;
context.Services.Configure<MessagingOptions>(configuration.GetSection(MessagingOptions.SectionName));
context.Services.Configure<RabbitMessageOptions>(o =>
{
configuration.GetSection(RabbitMessageOptions.SectionName).Bind(o);
var refreshFetch = configuration.GetValue<ushort>("TrackApp:TrackRefreshFetchCount"); #region CAP
if (refreshFetch > 0)
context.Services.AddSingleton<ISubscribeExecutor, MySubscribeExecutor>();
if (!Debugger.IsAttached)
{ {
o.Handlers[nameof(TrackRefreshEventHandler)] = context.Services.AddCap(options =>
new RabbitMessageHandlerOptions { PrefetchCount = refreshFetch }; {
o.Handlers[nameof(TrackPushCompletedEventHandler)] = options.UseMySql(configuration["ConnectionStrings:Default"]!);
new RabbitMessageHandlerOptions { PrefetchCount = refreshFetch }; options.UseRabbitMQ(configure =>
} {
configure.HostName = rabbitOptions.HostName;
configure.UserName = rabbitOptions.UserName;
configure.Password = rabbitOptions.Password;
configure.Port = rabbitOptions.Port;
configure.BasicQosOptions =
new RabbitMQOptions.BasicQos(configuration.GetValue<ushort>("CAP:PrefetchCount"));
}); });
options.FailedRetryInterval = configuration.GetValue<int>("CAP:FailedRetryInterval");
options.FailedRetryCount = configuration.GetValue<int>("CAP:FailedRetryCount");
options.SucceedMessageExpiredAfter = configuration.GetValue<int>("CAP:SucceedMessageExpiredAfter");
options.FallbackWindowLookbackSeconds =
configuration.GetValue<int>("CAP:FallbackWindowLookbackSeconds");
options.UseDashboard(dashboardOptions => { dashboardOptions.PathBase = configuration["ASPNETCORE_PATHBASE"]; });
options.FailedThresholdCallback = faileCalback =>
{
try
{
Log.Error($@"
|-CAP失败回调事件:
|-处理类型:{(faileCalback.MessageType == MessageType.Subscribe ? "消费消息达到最大重试次数,需要开发人员介入" : "生产消息推送失败")}
|-头部信息:{faileCalback.Message.Headers.ToJson()}");
}
catch (Exception ex)
{
Log.Error("【严重】CAP失败回调事件发生未处理异常" + ex);
}
};
}).AddSubscribeFilter<CapFilter>();
}
else
{
//是本地调试环境,用内存存储和内存队列
context.Services.AddCap(capOptions =>
{
capOptions.UseInMemoryStorage();
capOptions.UseInMemoryMessageQueue();
capOptions.UseDashboard();
}).AddSubscribeFilter<CapFilter>();
context.Services.GetInitLogger<TrackCenterApplicationModule>().LogInformation("-- 当前为本地环境,用内存存储和内存队列");
}
#endregion
var redisOptions = configuration.GetSection("Redis").Get<ConnectionStringBuilder>(); var redisOptions = configuration.GetSection("Redis").Get<ConnectionStringBuilder>();
if (redisOptions == null) if (redisOptions == null)
throw new FakeException("未找到Redis相关配置,请检查配置文件中是否包含名为Redis的节点"); throw new FakeException("未找到Redis相关配置,请检查配置文件中是否包含名为Redis的节点");
RedisHelper.Init(redisOptions, showLogInfo: Debugger.IsAttached); RedisHelper.Init(redisOptions, showLogInfo: Debugger.IsAttached);
context.Services.AddHostedService<RabbitClient>();
context.Services.Configure<RabbitOptions>(options =>
{
options.Declarations = new List<Action<IDeclaration>>()
{
o => o.ExchangeDeclare(GlobalConst.TrackChangeExchange, ExchangeType.Topic, true),
o => o.ExchangeDeclare(GlobalConst.TrackPushExchange, ExchangeType.Topic, true)
};
options.Consumes = new List<ConsumeOptions>
{
new()
{
Queue = GlobalConst.TrackRefreshSubscribe,
FetchCount = configuration.GetValue<ushort>("TrackApp:TrackRefreshFetchCount"),
FailedRequeue = false,
Declaration = o =>
{
o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true);
o.QueueDeclare(GlobalConst.TrackRefreshSubscribe, true);
o.QueueBind(GlobalConst.TrackRefreshSubscribe, GlobalConst.InterfaceRelayExchange,
GlobalConst.TrackRefreshSubscribe);
}
},
new()
{
Queue = GlobalConst.TrackPushCompletedSubscribe,
FetchCount = configuration.GetValue<ushort>("TrackApp:TrackRefreshFetchCount"),
FailedRequeue = false,
Declaration = o =>
{
o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true);
o.QueueDeclare(GlobalConst.TrackPushCompletedSubscribe, true);
o.QueueBind(GlobalConst.TrackPushCompletedSubscribe, GlobalConst.InterfaceRelayExchange,
GlobalConst.TrackPushCompletedSubscribe);
}
}
};
});
context.Services.Configure<TrackAppOptions>(configuration.GetSection("TrackApp")); context.Services.Configure<TrackAppOptions>(configuration.GetSection("TrackApp"));
context.Services.Configure<InterfaceRelayParamsConfigOptions>( context.Services.Configure<InterfaceRelayParamsConfigOptions>(
configuration.GetSection("InterfaceRelayParamsConfig")); configuration.GetSection("InterfaceRelayParamsConfig"));

View File

@ -41,111 +41,6 @@ public class GetPageTrackOrderInputVO : PageInfoInput
/// 创建时间截止 /// 创建时间截止
/// </summary> /// </summary>
public DateTime? CreateTimeEnd { get; set; } public DateTime? CreateTimeEnd { get; set; }
/// <summary>
/// 尾程单号(支持逗号分隔多值,等值匹配)
/// </summary>
public string? TrackNo { get; set; }
/// <summary>
/// 其他单号(支持逗号分隔多值,等值匹配)
/// </summary>
public string? OtherNo { get; set; }
/// <summary>
/// 销售产品
/// </summary>
public string? Product { get; set; }
/// <summary>
/// 渠道
/// </summary>
public string? Channel { get; set; }
/// <summary>
/// API 编码 / 服务商代码
/// </summary>
public string? ApiCode { get; set; }
/// <summary>
/// 国家编码
/// </summary>
public string? CountryCode { get; set; }
/// <summary>
/// 州省
/// </summary>
public string? Province { get; set; }
/// <summary>
/// 城市
/// </summary>
public string? City { get; set; }
/// <summary>
/// 邮编
/// </summary>
public string? Postcode { get; set; }
/// <summary>
/// 邮箱
/// </summary>
public string? Email { get; set; }
/// <summary>
/// 最新轨迹状态
/// </summary>
public string? NewestTrackStatus { get; set; }
/// <summary>
/// 最新轨迹内容
/// </summary>
public string? NewestTrackContent { get; set; }
/// <summary>
/// 最新轨迹内容英文
/// </summary>
public string? NewestTrackEnContent { get; set; }
/// <summary>
/// 最新轨迹发生地
/// </summary>
public string? NewestTrackPlace { get; set; }
/// <summary>
/// 最新轨迹时间开始
/// </summary>
public DateTime? NewestTrackTimeStart { get; set; }
/// <summary>
/// 最新轨迹时间截止
/// </summary>
public DateTime? NewestTrackTimeEnd { get; set; }
/// <summary>
/// 最新轨迹时区
/// </summary>
public float? NewestTrackTimeZone { get; set; }
/// <summary>
/// 是否完结
/// </summary>
public bool? IsFinish { get; set; }
/// <summary>
/// 预计到达时间开始
/// </summary>
public DateTime? EstimatedDeliveryDateStart { get; set; }
/// <summary>
/// 预计到达时间截止
/// </summary>
public DateTime? EstimatedDeliveryDateEnd { get; set; }
/// <summary>
/// 目的地时区
/// </summary>
public float? DestinationTimeZone { get; set; }
} }
/// <summary> /// <summary>

View File

@ -19,57 +19,25 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
} }
public async Task<PagedResult<TrackOrderVO>> GetTrackOrderPagedList(GetPageTrackOrderInputVO input) public async Task<PagedResult<TrackOrderVO>> GetTrackOrderPagedList(GetPageTrackOrderInputVO input)
{ {
//查询完去重
PagedResult<TrackOrderVO> pageList; PagedResult<TrackOrderVO> pageList;
//没传递单号时
if (input.OrderNos.IsNullOrEmpty()) if (input.OrderNos.IsNullOrEmpty())
{ {
pageList = await ApplyTrackOrderListFilters(Context.Queryable<OrderEntity>(), input) pageList = await Context.Queryable<OrderEntity>()
.WhereIF(!input.AppKey.IsNullOrWhiteSpace(), a => a.AppKey == input.AppKey)
.WhereIF(input.CreateTimeStart.HasValue && input.CreateTimeEnd.HasValue,
a => a.CreateTime >= input.CreateTimeStart && a.CreateTime <= input.CreateTimeEnd)
.WhereIF(input.CreateTimeStart.HasValue && (!input.CreateTimeEnd.HasValue),
a => a.CreateTime >= input.CreateTimeStart)
.WhereIF((!input.CreateTimeStart.HasValue) && input.CreateTimeEnd.HasValue,
a => a.CreateTime <= input.CreateTimeEnd)
.WhereIF(!input.Customer.IsNullOrWhiteSpace(), a => a.Customer.Contains(input.Customer!))
.WhereIF(input.TrackOrderType > 0, a => a.OrderType == input.TrackOrderType)
.WhereIF(input.TabType > 0, a => a.QuestionType == input.TabType)
.WhereIF(input.TabType == TrackProblemType.Warn, a => !a.IsCompleted)
.OrderByDescending(a => a.CreateTime) .OrderByDescending(a => a.CreateTime)
.Select(TrackOrderListSelector) .Select(a => new TrackOrderVO
.ToPagedListAsync(input.PageIndex, input.PageSize);
}
else
{
var orderNos = input.OrderNos!.Distinct().ToList();
//匹配主单
var orderIds = await Context.Queryable<OrderEntity>()
.Where(x => orderNos.Contains(x.OrderNo))
.Select(x => x.Id)
.ToListAsync();
//匹配外部单
var orderIdsForThirdParty = await Context.Queryable<OrderPullStatusEntity>()
.Where(x => !string.IsNullOrWhiteSpace(x.OrderNo) && orderNos.Contains(x.OrderNo))
.Select(x => x.OrderId)
.ToListAsync();
var orderHashset = orderIds.ToHashSet();
orderHashset.UnionWith(orderIdsForThirdParty);
orderIds = orderHashset.ToList();
if (orderIds.Count == 0)
{
return new PagedResult<TrackOrderVO>();
}
pageList = await ApplyTrackOrderListFilters(
Context.Queryable<OrderEntity>().Where(a => orderIds.Contains(a.Id)), input)
.OrderByDescending(a => a.CreateTime)
.Select(TrackOrderListSelector)
.ToPagedListAsync(input.PageIndex, input.PageSize);
}
pageList.Items.ForEach(x =>
{
x.OrderTypeName = x.OrderType.GetDescription();
x.QuestionTypeName = x.QuestionType.GetDescription();
});
return pageList;
}
private static System.Linq.Expressions.Expression<Func<OrderEntity, TrackOrderVO>> TrackOrderListSelector =>
a => new TrackOrderVO
{ {
Id = a.Id, Id = a.Id,
OrderNumber = a.OrderNo, OrderNumber = a.OrderNo,
@ -97,80 +65,90 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
Postcode = a.ArrivalPostCode, Postcode = a.ArrivalPostCode,
City = a.ArrivalCity, City = a.ArrivalCity,
Email = a.ArrivalEmail Email = a.ArrivalEmail
}; })
.ToPagedListAsync(input.PageIndex, input.PageSize);
/// <summary> }
/// 表头筛选:客户/单号/产品/渠道/轨迹内容等 //传递单号时
/// </summary> else
private static ISugarQueryable<OrderEntity> ApplyTrackOrderListFilters(
ISugarQueryable<OrderEntity> query,
GetPageTrackOrderInputVO input)
{ {
var trackNos = SplitMultiValues(input.TrackNo); var orderNos = input.OrderNos!.Distinct().ToList();
var otherNos = SplitMultiValues(input.OtherNo);
query = query //匹配主单
var orderIds = await Context.Queryable<OrderEntity>()
.Where(x => orderNos.Contains(x.OrderNo))
.Select(x => x.Id)
.ToListAsync();
//匹配外部单
var orderIdsForThirdParty = await Context.Queryable<OrderPullStatusEntity>()
.Where(x => !string.IsNullOrWhiteSpace(x.OrderNo) && orderNos.Contains(x.OrderNo))
.Select(x => x.OrderId)
.ToListAsync();
var orderHashset = orderIds.ToHashSet();
orderHashset.UnionWith(orderIdsForThirdParty);
orderIds = orderHashset.ToList();
//没有任何单
if (orderIds.Count == 0)
{
return new PagedResult<TrackOrderVO>();
}
pageList = await Context.Queryable<OrderEntity>()
.Where(a => orderIds.Contains(a.Id))
.WhereIF(!input.AppKey.IsNullOrWhiteSpace(), a => a.AppKey == input.AppKey) .WhereIF(!input.AppKey.IsNullOrWhiteSpace(), a => a.AppKey == input.AppKey)
.WhereIF(input.CreateTimeStart.HasValue && input.CreateTimeEnd.HasValue, .WhereIF(input.CreateTimeStart.HasValue && input.CreateTimeEnd.HasValue,
a => a.CreateTime >= input.CreateTimeStart && a.CreateTime <= input.CreateTimeEnd) a => a.CreateTime >= input.CreateTimeStart && a.CreateTime <= input.CreateTimeEnd)
.WhereIF(input.CreateTimeStart.HasValue && !input.CreateTimeEnd.HasValue, .WhereIF(input.CreateTimeStart.HasValue && (!input.CreateTimeEnd.HasValue),
a => a.CreateTime >= input.CreateTimeStart) a => a.CreateTime >= input.CreateTimeStart)
.WhereIF(!input.CreateTimeStart.HasValue && input.CreateTimeEnd.HasValue, .WhereIF((!input.CreateTimeStart.HasValue) && input.CreateTimeEnd.HasValue,
a => a.CreateTime <= input.CreateTimeEnd) a => a.CreateTime <= input.CreateTimeEnd)
.WhereIF(!input.Customer.IsNullOrWhiteSpace(), a => a.Customer.Contains(input.Customer!)) .WhereIF(!input.Customer.IsNullOrWhiteSpace(), a => a.Customer.Contains(input.Customer!))
.WhereIF(input.TrackOrderType > 0, a => a.OrderType == input.TrackOrderType) .WhereIF(input.TrackOrderType > 0, a => a.OrderType == input.TrackOrderType)
.WhereIF(input.TabType > 0, a => a.QuestionType == input.TabType) .WhereIF(input.TabType > 0, a => a.QuestionType == input.TabType)
.WhereIF(input.TabType == TrackProblemType.Warn, a => !a.IsCompleted) .WhereIF(input.TabType == TrackProblemType.Warn, a => !a.IsCompleted)
.WhereIF(trackNos.Count == 1, a => a.TrackNo.Contains(trackNos[0])) .OrderByDescending(a => a.CreateTime)
.WhereIF(trackNos.Count > 1, a => trackNos.Contains(a.TrackNo)) .Select(a => new TrackOrderVO
.WhereIF(otherNos.Count == 1, a => a.ThirdPartyOrderNumber != null && a.ThirdPartyOrderNumber.Contains(otherNos[0])) {
.WhereIF(otherNos.Count > 1, a => a.ThirdPartyOrderNumber != null && otherNos.Contains(a.ThirdPartyOrderNumber)) Id = a.Id,
.WhereIF(!input.Product.IsNullOrWhiteSpace(), a => a.ProductCode != null && a.ProductCode.Contains(input.Product!)) OrderNumber = a.OrderNo,
.WhereIF(!input.Channel.IsNullOrWhiteSpace(), a => a.Channel != null && a.Channel.Contains(input.Channel!)) OrderType = a.OrderType,
.WhereIF(!input.ApiCode.IsNullOrWhiteSpace(), QuestionType = a.QuestionType,
a => a.ServiceProviderCode != null && a.ServiceProviderCode.Contains(input.ApiCode!)) Customer = a.Customer,
.WhereIF(!input.CountryCode.IsNullOrWhiteSpace(), a => a.ArrivalCountry.Contains(input.CountryCode!)) NewestTrackStatus = a.LatestTrackStatus,
.WhereIF(!input.Province.IsNullOrWhiteSpace(), a => a.ArrivalProvince != null && a.ArrivalProvince.Contains(input.Province!)) NewestTrackContent = a.LatestTrackContent,
.WhereIF(!input.City.IsNullOrWhiteSpace(), a => a.ArrivalCity.Contains(input.City!)) NewestTrackEnContent = a.LatestTrackEnContent,
.WhereIF(!input.Postcode.IsNullOrWhiteSpace(), NewestTrackPlace = a.LatestTrackPlace,
a => a.ArrivalPostCode != null && a.ArrivalPostCode.Contains(input.Postcode!)) NewestTrackTime = a.LatestTrackTime,
.WhereIF(!input.Email.IsNullOrWhiteSpace(), a => a.ArrivalEmail != null && a.ArrivalEmail.Contains(input.Email!)) NewestTrackTimeZone = a.LatestTrackTimeZone,
.WhereIF(!input.NewestTrackStatus.IsNullOrWhiteSpace(), IsFinish = a.IsCompleted,
a => a.LatestTrackStatus != null && a.LatestTrackStatus.Contains(input.NewestTrackStatus!)) CreateTime = a.CreateTime,
.WhereIF(!input.NewestTrackContent.IsNullOrWhiteSpace(), AppKey = a.AppKey,
a => a.LatestTrackContent != null && a.LatestTrackContent.Contains(input.NewestTrackContent!)) DestinationTimeZone = a.DestinationTimeZone,
.WhereIF(!input.NewestTrackEnContent.IsNullOrWhiteSpace(), EstimatedDeliveryDate = a.EstimatedDeliveryDate,
a => a.LatestTrackEnContent != null && a.LatestTrackEnContent.Contains(input.NewestTrackEnContent!)) TrackNo = a.TrackNo,
.WhereIF(!input.NewestTrackPlace.IsNullOrWhiteSpace(), OtherNo = a.ThirdPartyOrderNumber,
a => a.LatestTrackPlace != null && a.LatestTrackPlace.Contains(input.NewestTrackPlace!)) Channel = a.Channel,
.WhereIF(input.NewestTrackTimeStart.HasValue && input.NewestTrackTimeEnd.HasValue, Product = a.ProductCode,
a => a.LatestTrackTime >= input.NewestTrackTimeStart && a.LatestTrackTime <= input.NewestTrackTimeEnd) ApiCode = a.ServiceProviderCode,
.WhereIF(input.NewestTrackTimeStart.HasValue && !input.NewestTrackTimeEnd.HasValue, CountryCode = a.ArrivalCountry,
a => a.LatestTrackTime >= input.NewestTrackTimeStart) Province = a.ArrivalProvince,
.WhereIF(!input.NewestTrackTimeStart.HasValue && input.NewestTrackTimeEnd.HasValue, Postcode = a.ArrivalPostCode,
a => a.LatestTrackTime <= input.NewestTrackTimeEnd) City = a.ArrivalCity,
.WhereIF(input.NewestTrackTimeZone.HasValue, a => a.LatestTrackTimeZone == input.NewestTrackTimeZone) Email = a.ArrivalEmail
.WhereIF(input.IsFinish.HasValue, a => a.IsCompleted == input.IsFinish.Value) })
.WhereIF(input.EstimatedDeliveryDateStart.HasValue && input.EstimatedDeliveryDateEnd.HasValue, .ToPagedListAsync(input.PageIndex, input.PageSize);
a => a.EstimatedDeliveryDate >= input.EstimatedDeliveryDateStart &&
a.EstimatedDeliveryDate <= input.EstimatedDeliveryDateEnd)
.WhereIF(input.EstimatedDeliveryDateStart.HasValue && !input.EstimatedDeliveryDateEnd.HasValue,
a => a.EstimatedDeliveryDate >= input.EstimatedDeliveryDateStart)
.WhereIF(!input.EstimatedDeliveryDateStart.HasValue && input.EstimatedDeliveryDateEnd.HasValue,
a => a.EstimatedDeliveryDate <= input.EstimatedDeliveryDateEnd)
.WhereIF(input.DestinationTimeZone.HasValue, a => a.DestinationTimeZone == input.DestinationTimeZone);
return query;
} }
private static List<string> SplitMultiValues(string? raw) //填充数描述信息
pageList.Items.ForEach(x =>
{ {
if (raw.IsNullOrWhiteSpace()) return new List<string>(); x.OrderTypeName = x.OrderType.GetDescription();
return raw! x.QuestionTypeName = x.QuestionType.GetDescription();
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) });
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct(StringComparer.OrdinalIgnoreCase) return pageList;
.ToList();
} }
public async Task<(int MainOrderId, string OrderNo, string Customer)> GetTrackOrderByProviderAndOrderNo( public async Task<(int MainOrderId, string OrderNo, string Customer)> GetTrackOrderByProviderAndOrderNo(