Merge pull request '移除自定义 CAP / Rabbit 相关代码 事件处理迁到 Messaging 目录 新增 TrackUpsertForRefreshEvent、Directory.Build.targets、Fake.Local.props' (#7) from test into master
All checks were successful
Build and Deploy / deploy (push) Successful in 1m16s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m16s
Reviewed-on: #7
This commit is contained in:
commit
f0c53bc319
@ -1,6 +1,9 @@
|
||||
<Project>
|
||||
<Import Project="Fake.Local.props" Condition="Exists('Fake.Local.props')" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- All Fake packages -->
|
||||
<FakePackageVersion>8.0.5.17</FakePackageVersion>
|
||||
<!-- 清本地nuget缓存:dotnet nuget locals -c http-cache -->
|
||||
<FakePackageVersion>8.0.6.2</FakePackageVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
26
Directory.Build.targets
Normal file
26
Directory.Build.targets
Normal file
@ -0,0 +1,26 @@
|
||||
<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>
|
||||
10
Fake.Local.props
Normal file
10
Fake.Local.props
Normal file
@ -0,0 +1,10 @@
|
||||
<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>
|
||||
@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution
|
||||
.editorconfig = .editorconfig
|
||||
Directory.Build.props = Directory.Build.props
|
||||
.gitea\workflows\deploy.yml = .gitea\workflows\deploy.yml
|
||||
Fake.Local.props = Fake.Local.props
|
||||
Directory.Build.targets = Directory.Build.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<Import Project="../../common.props"/>
|
||||
|
||||
@ -20,6 +20,8 @@
|
||||
<ProjectReference Include="..\TrackCenter.Repository\TrackCenter.Repository.csproj" />
|
||||
<PackageReference Include="Fake.Autofac" 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" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -5,6 +5,8 @@ using Fake.AspNetCore.ExceptionHandling;
|
||||
using Fake.AspNetCore.Newtonsoft;
|
||||
using Fake.AspNetCore.Swagger;
|
||||
using Fake.Autofac;
|
||||
using Fake.Messaging.Dashboard;
|
||||
using Fake.Messaging.EntityFrameworkCore;
|
||||
using Fake.FeiShu;
|
||||
using Fake.Json;
|
||||
using Fake.Modularity;
|
||||
@ -15,6 +17,7 @@ using Hangfire;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
using TrackCenter.Application;
|
||||
@ -30,6 +33,8 @@ namespace TrackCenter.Api;
|
||||
[DependsOn(typeof(FakeAspNetCoreModule))]
|
||||
[DependsOn(typeof(TrackCenterApplicationModule))]
|
||||
[DependsOn(typeof(TrackCenterRepositoryModule))]
|
||||
// Messaging 独立队列(Outbox/Inbox + Dashboard);物理传输由 FakeMessagingRabbitMqModule 提供
|
||||
[DependsOn(typeof(FakeMessagingDashboardModule))]
|
||||
[DependsOn(typeof(FakeSerilogModule))]
|
||||
public class TrackCenterApiModule : FakeModule
|
||||
{
|
||||
@ -53,6 +58,28 @@ public class TrackCenterApiModule : FakeModule
|
||||
// 配置签名验证选项
|
||||
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 =>
|
||||
{
|
||||
@ -186,6 +213,9 @@ public class TrackCenterApiModule : FakeModule
|
||||
Authorization = [new AnonymousHangfireAuthorizationFilter()]
|
||||
});
|
||||
|
||||
// 必须在 UsePathBase 之后:完整对外路径为 {PathBase}/eventbus
|
||||
app.UseFakeMessagingDashboard();
|
||||
|
||||
app.Services.GetRequiredService<IFeiShuNotificationService>().Send("Application started");
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,5 +3,8 @@
|
||||
"Consul": {
|
||||
"Address": "http://test.ideship.com:30850",
|
||||
"Token": "c0aea902-dca0-ac29-299b-a0d47e5402aa"
|
||||
},
|
||||
"DataSeed": {
|
||||
"Enable": false
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
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}'.");
|
||||
}
|
||||
}
|
||||
@ -1,269 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -46,6 +46,111 @@ public class GetPageTrackOrderInput : PageInfoInput
|
||||
/// 创建时间截止
|
||||
/// </summary>
|
||||
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>
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
namespace TrackCenter.Application.Rabbit;
|
||||
using Fake.Messaging;
|
||||
|
||||
public class InterfaceRelayForwardReplayEvent
|
||||
namespace TrackCenter.Application.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 接口通回调报文(外部生产,无 Fake MessageId / DLX)。
|
||||
/// </summary>
|
||||
[MessageRoute(Topic = GlobalConst.InterfaceRelayExchange, TopicType = "direct")]
|
||||
public class InterfaceRelayForwardReplayEvent : Message
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求Id
|
||||
@ -1,6 +1,13 @@
|
||||
using Fake.Messaging;
|
||||
|
||||
namespace TrackCenter.Application.Events;
|
||||
|
||||
public class OrderPodPullEvent
|
||||
/// <summary>
|
||||
/// 订单 POD 拉取(当前仓库无订阅方,仍走 Outbox 投递以兼容后续消费者)。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[MessageRoute(nameof(OrderPodPullEvent))]
|
||||
public class OrderPodPullEvent : Message
|
||||
{
|
||||
// 确定应用
|
||||
public string AppKey { get; set; }
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
using Fake.Messaging;
|
||||
|
||||
namespace TrackCenter.Application.Events;
|
||||
|
||||
public class TrackChangeAfterUpdateOrderEvent
|
||||
[Serializable]
|
||||
[MessageRoute(nameof(TrackChangeAfterUpdateOrderEvent))]
|
||||
public class TrackChangeAfterUpdateOrderEvent : Message
|
||||
{
|
||||
/// <summary>
|
||||
/// 订单Id
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
using Fake.Messaging;
|
||||
using TrackCenter.Application.Dtos.ProvideServices;
|
||||
|
||||
namespace TrackCenter.Application.Events;
|
||||
|
||||
public class TrackDeleteEvent
|
||||
[Serializable]
|
||||
[MessageRoute(nameof(TrackDeleteEvent))]
|
||||
public class TrackDeleteEvent : Message
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据esId定位轨迹,优先级最高
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
using Fake.Messaging;
|
||||
|
||||
namespace TrackCenter.Application.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 轨迹推送事件
|
||||
/// 每次可传递多个订单,但仅能传递同一个轨迹内容
|
||||
/// </summary>
|
||||
public class TrackPushEvent
|
||||
[Serializable]
|
||||
[MessageRoute(nameof(TrackPushEvent))]
|
||||
public class TrackPushEvent : Message
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作类型
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
using Fake.Messaging;
|
||||
using TrackCenter.Application.Dtos.ProvideServices;
|
||||
|
||||
namespace TrackCenter.Application.Events;
|
||||
|
||||
public class TrackUpsertEvent
|
||||
[Serializable]
|
||||
[MessageRoute(nameof(TrackUpsertEvent))]
|
||||
public class TrackUpsertEvent : Message
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
using Fake.Messaging;
|
||||
namespace TrackCenter.Application.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 轨迹刷新引发的新增/更新(独立队列,与 <see cref="TrackUpsertEvent"/> 隔离)。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[MessageRoute(nameof(TrackUpsertForRefreshEvent))]
|
||||
public class TrackUpsertForRefreshEvent : TrackUpsertEvent
|
||||
{
|
||||
}
|
||||
@ -1,17 +1,16 @@
|
||||
using DotNetCore.CAP;
|
||||
using Fake.UnitOfWork;
|
||||
using Fake.Messaging;
|
||||
using TrackCenter.Application.Events;
|
||||
using TrackCenter.Application.Services.Warning.Handlers;
|
||||
using TrackCenter.Domain.Aggregates.OrderAggregate;
|
||||
|
||||
namespace TrackCenter.Application.CAP.EventHandlers;
|
||||
namespace TrackCenter.Application.Messaging;
|
||||
|
||||
public class TrackChangeAfterUpdateOrderEventHandler(IOrderRepository orderRepository, IServiceProvider serviceProvider)
|
||||
: ITransientDependency, ICapSubscribe
|
||||
: IMessageHandle<TrackChangeAfterUpdateOrderEvent>, ITransientDependency
|
||||
{
|
||||
[UnitOfWork]
|
||||
[CapSubscribe(GlobalConst.TrackChangeAfterUpdateOrderSubscribe)]
|
||||
public virtual async Task ConsumerAsync(TrackChangeAfterUpdateOrderEvent @event)
|
||||
public virtual async Task HandleAsync(
|
||||
TrackChangeAfterUpdateOrderEvent @event,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var orders = await GetOrdersAsync(@event.OrderIds);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using DotNetCore.CAP;
|
||||
using Fake.Messaging;
|
||||
using Fake.UnitOfWork;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using TrackCenter.Application.Dtos.ProvideServices;
|
||||
@ -11,53 +11,33 @@ using TrackCenter.Domain.Aggregates.OrderAggregate;
|
||||
using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
|
||||
using TrackCenter.Repository.Aggregates.OrderAggregate;
|
||||
|
||||
namespace TrackCenter.Application.CAP.EventHandlers;
|
||||
namespace TrackCenter.Application.Messaging;
|
||||
|
||||
public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
public class TrackDeleteEventHandler(
|
||||
ProvideQueries provideQueries,
|
||||
IOrderRepository orderRepository,
|
||||
IMessagePublisher messagePublisher,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
BroadcastingService broadcastingService,
|
||||
WarningSignalPublisher warningSignalPublisher)
|
||||
: IMessageHandle<TrackDeleteEvent>, ITransientDependency
|
||||
{
|
||||
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,
|
||||
IOrderRepository orderRepository,
|
||||
ICapPublisher capPublisher,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
BroadcastingService broadcastingService,
|
||||
WarningSignalPublisher warningSignalPublisher
|
||||
)
|
||||
[UnitOfWork(IsTransactional = true)]
|
||||
public virtual async Task HandleAsync(TrackDeleteEvent message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_provideQueries = provideQueries;
|
||||
_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(@event)) return;
|
||||
if (!CanDelete(message)) return;
|
||||
|
||||
// 处理删除轨迹
|
||||
await HandleDeleteTrackAsync(@event);
|
||||
await HandleDeleteTrackAsync(message);
|
||||
|
||||
// 根据订单关联关系异步向下刷(异步)
|
||||
await DownByOrderLink(@event);
|
||||
await DownByOrderLink(message);
|
||||
}
|
||||
|
||||
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 group in linkedOrders.Where(x => x.OwnerOrderId == orderId).GroupBy(x => x.AppKey))
|
||||
@ -70,7 +50,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
// 不同应用需要做内容映射
|
||||
if (@event.AppKey != nextEvent.AppKey)
|
||||
{
|
||||
var codeMaps = await _provideQueries.GetTrackCodeMapsAsync(
|
||||
var codeMaps = await provideQueries.GetTrackCodeMapsAsync(
|
||||
@event.AppKey,
|
||||
nextEvent.AppKey,
|
||||
@event.TrackIdentifiers.Select(x => x.TrackCode).ToArray()
|
||||
@ -95,7 +75,8 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
{
|
||||
var subEvent = nextEvent.DeepCopy();
|
||||
subEvent.OrderIds = subOrderIds;
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, subEvent);
|
||||
// 关联单继续走删除链路(原 CAP 误发到 Upsert topic,此处修正)
|
||||
await messagePublisher.PublishAsync(subEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,7 +106,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
// 优先根据EsId查询轨迹
|
||||
if (@event.EsIds.Length > 0)
|
||||
{
|
||||
deleteTracks = await _trackDetailsEsRepository.GetDataByIds(@event.EsIds);
|
||||
deleteTracks = await trackDetailsEsRepository.GetDataByIds(@event.EsIds);
|
||||
|
||||
// 外部api获取的轨迹不允许删除
|
||||
if (deleteTracks.Any(m => m.Source == (int) TrackSourceEnum.ExternalServiceProvide)) return;
|
||||
@ -143,12 +124,12 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
}).ToList();
|
||||
|
||||
// 查出订单下的所有轨迹,以供后面回退使用
|
||||
oldTracks = await _trackDetailsEsRepository.GetListAsync(@event.OrderIds);
|
||||
oldTracks = await trackDetailsEsRepository.GetListAsync(@event.OrderIds);
|
||||
}
|
||||
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();
|
||||
@ -177,7 +158,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
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);
|
||||
@ -186,13 +167,13 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
await PublicTrackTopicAsync(@event, deleteTracks);
|
||||
|
||||
// 发布预警信号,稍后将进行预警计算
|
||||
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹删除, deleteTracks);
|
||||
warningSignalPublisher.PublishSignal(WarningSignalType.轨迹删除, deleteTracks);
|
||||
}
|
||||
|
||||
private async Task PublicTrackTopicAsync(TrackDeleteEvent @event, List<TrackDetailsIndex> delTracks)
|
||||
{
|
||||
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 needPublicTracks = delTracks
|
||||
@ -255,7 +236,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
};
|
||||
|
||||
var content = contentDict[track.TrackContentId];
|
||||
_broadcastingService.TrackChangePublicTopic(eto, content.AppKey);
|
||||
broadcastingService.TrackChangePublicTopic(eto, content.AppKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +251,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
var deleteTrackIds = deleteTracks.Select(m => m.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
|
||||
{
|
||||
Id = x.Id,
|
||||
@ -315,7 +296,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
}
|
||||
|
||||
//更新订单上冗余轨迹明细的字段
|
||||
await _orderRepository.UpdateRangeAsync(orders,
|
||||
await orderRepository.UpdateRangeAsync(orders,
|
||||
x => new
|
||||
{
|
||||
x.LatestTrackStatus,
|
||||
@ -329,7 +310,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
});
|
||||
|
||||
//批量写入订单操作日志
|
||||
_ = _orderOperationRecordEsRepository.AddManyAsync(orders.Select(order => new OrderOperationRecordIndex
|
||||
_ = orderOperationRecordEsRepository.AddManyAsync(orders.Select(order => new OrderOperationRecordIndex
|
||||
{
|
||||
OrderId = order.Id,
|
||||
OrderNo = order.OrderNo,
|
||||
@ -1,63 +1,44 @@
|
||||
using System.Text;
|
||||
using Fake.Messaging;
|
||||
using Fake.UnitOfWork;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using RabbitMQ.Client.Events;
|
||||
using TrackCenter.Application.Dtos.TrackPush;
|
||||
using TrackCenter.Application.Events;
|
||||
using TrackCenter.Application.Helpers;
|
||||
using TrackCenter.Application.Services.Broadcasting;
|
||||
using TrackCenter.Application.Services.Warning.Handlers;
|
||||
using TrackCenter.Domain.Aggregates.OrderAggregate;
|
||||
using TrackCenter.Domain.Aggregates.TrackPushAggregate;
|
||||
|
||||
namespace TrackCenter.Application.Rabbit;
|
||||
namespace TrackCenter.Application.Messaging;
|
||||
|
||||
[ExposeServices(typeof(IRabbitHandler))]
|
||||
public class TrackPushCompletedEventHandler : ITransientDependency, IRabbitHandler
|
||||
/// <summary>
|
||||
/// 接口通轨迹推送完成回调 → 独立队列 <see cref="GlobalConst.TrackPushCompletedSubscribe"/>。
|
||||
/// </summary>
|
||||
[MessageSubscribe(GlobalConst.TrackPushCompletedSubscribe, ExternalQueue = true)]
|
||||
public class TrackPushCompletedEventHandler(
|
||||
IOrderRepository orderRepository,
|
||||
ITrackPushRecordEsRepository trackPushRecordEsRepository,
|
||||
BroadcastingService broadcastingService,
|
||||
IReturnWarningHandler returnWarningHandler)
|
||||
: IMessageHandle<InterfaceRelayForwardReplayEvent>, ITransientDependency
|
||||
{
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository;
|
||||
private readonly BroadcastingService _broadcastingService;
|
||||
private readonly IReturnWarningHandler _returnWarningHandler;
|
||||
|
||||
public TrackPushCompletedEventHandler(IOrderRepository orderRepository,
|
||||
ITrackPushRecordEsRepository trackPushRecordEsRepository,
|
||||
BroadcastingService broadcastingService, IReturnWarningHandler returnWarningHandler)
|
||||
public virtual async Task HandleAsync(
|
||||
InterfaceRelayForwardReplayEvent message,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_trackPushRecordEsRepository = trackPushRecordEsRepository;
|
||||
_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
|
||||
{
|
||||
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);
|
||||
await ConsumerAsync(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"TrackPushCompletedEventHandler出错,错误信息:{ex}\r\n-----\r\n消息:{msg}");
|
||||
Log.Error($"TrackPushCompletedEventHandler出错,错误信息:{ex}\r\n-----\r\n消息:{message.ToJson()}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ConsumerAsync(InterfaceRelayForwardReplayEvent @event)
|
||||
private async Task ConsumerAsync(InterfaceRelayForwardReplayEvent @event)
|
||||
{
|
||||
var optionType = Base64Helper.DeSerialize<string>(@event.Attach);
|
||||
var pushInputs = new List<TrackPushInput>();
|
||||
@ -91,16 +72,16 @@ public class TrackPushCompletedEventHandler : ITransientDependency, IRabbitHandl
|
||||
|
||||
// 推送广播
|
||||
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 _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)
|
||||
@ -118,7 +99,7 @@ public class TrackPushCompletedEventHandler : ITransientDependency, IRabbitHandl
|
||||
LatestPushError = x.Message
|
||||
}).ToList();
|
||||
|
||||
await _orderRepository.UpdateRangeAsync(orders, x => new
|
||||
await orderRepository.UpdateRangeAsync(orders, x => new
|
||||
{
|
||||
x.QuestionType,
|
||||
x.LatestPushError
|
||||
@ -136,7 +117,7 @@ public class TrackPushCompletedEventHandler : ITransientDependency, IRabbitHandl
|
||||
LatestPushTime = x.CreateTime,
|
||||
}).ToList();
|
||||
|
||||
await _orderRepository.UpdateRangeAsync(orders, x => new
|
||||
await orderRepository.UpdateRangeAsync(orders, x => new
|
||||
{
|
||||
x.QuestionType,
|
||||
x.LatestPushError,
|
||||
@ -1,6 +1,6 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using DotNetCore.CAP;
|
||||
using Fake.Messaging;
|
||||
using Fake.UnitOfWork;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using Microsoft.Extensions.Options;
|
||||
@ -18,48 +18,28 @@ using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
|
||||
using TrackCenter.Domain.Aggregates.TrackPushAggregate;
|
||||
using TrackCenter.Repository.Otel;
|
||||
|
||||
namespace TrackCenter.Application.CAP.EventHandlers;
|
||||
namespace TrackCenter.Application.Messaging;
|
||||
|
||||
public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
||||
public class TrackPushEventHandler(
|
||||
IOrderRepository orderRepository,
|
||||
TrackPushCacheService trackPushCacheService,
|
||||
ITrackPushRecordEsRepository trackPushRecordEsRepository,
|
||||
IOptionsMonitor<InterfaceRelayParamsConfigOptions> options,
|
||||
BroadcastingService broadcastingService,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
InterfaceForwardService interfaceForwardService)
|
||||
: IMessageHandle<TrackPushEvent>, ITransientDependency
|
||||
{
|
||||
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,
|
||||
TrackPushCacheService trackPushCacheService,
|
||||
ITrackPushRecordEsRepository trackPushRecordEsRepository,
|
||||
IOptionsMonitor<InterfaceRelayParamsConfigOptions> options,
|
||||
BroadcastingService broadcastingService,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
InterfaceForwardService interfaceForwardService)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_trackPushCacheService = trackPushCacheService;
|
||||
_trackPushRecordEsRepository = trackPushRecordEsRepository;
|
||||
_options = options;
|
||||
_broadcastingService = broadcastingService;
|
||||
_trackDetailsEsRepository = trackDetailsEsRepository;
|
||||
_interfaceForwardService = interfaceForwardService;
|
||||
}
|
||||
|
||||
[UnitOfWork]
|
||||
[CapSubscribe(GlobalConst.TrackPushSubscribe, Group = GlobalConst.TrackPushSubscribe)]
|
||||
[CapSubscribe(GlobalConst.TrackManualPushSubscribe, Group = GlobalConst.TrackPushSubscribe)]
|
||||
public virtual async Task ConsumerAsync(TrackPushEvent @event)
|
||||
public virtual async Task HandleAsync(TrackPushEvent @event, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 应用+客户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.First().ServiceProviderCode.IsNullOrWhiteSpace())
|
||||
{
|
||||
@ -75,7 +55,7 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
||||
|
||||
// 查找所有订单
|
||||
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
|
||||
{
|
||||
Id = x.Id,
|
||||
@ -134,8 +114,8 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
||||
{
|
||||
var asciiString = Base64Helper.SerializeObject(OptionType.新增.ToString());
|
||||
var serviceProviderCode = _pushConfigs.First().ServiceProviderCode;
|
||||
var r = await _interfaceForwardService.BatchForwardThenPushToQueueAsync(
|
||||
_options.CurrentValue.PushTrackInterfaceCode,
|
||||
var r = await interfaceForwardService.BatchForwardThenPushToQueueAsync(
|
||||
options.CurrentValue.PushTrackInterfaceCode,
|
||||
serviceProviderCode,
|
||||
items.Cast<object>().ToList(),
|
||||
GlobalConst.TrackPushCompletedSubscribe,
|
||||
@ -369,8 +349,8 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
||||
{
|
||||
var asciiString = Base64Helper.SerializeObject(OptionType.修改.ToString());
|
||||
var serviceProviderCode = _pushConfigs.First().ServiceProviderCode;
|
||||
var r = await _interfaceForwardService.BatchForwardThenPushToQueueAsync(
|
||||
_options.CurrentValue.TrackUpdateInterfaceCode,
|
||||
var r = await interfaceForwardService.BatchForwardThenPushToQueueAsync(
|
||||
options.CurrentValue.TrackUpdateInterfaceCode,
|
||||
serviceProviderCode,
|
||||
items.Cast<object>().ToList(),
|
||||
GlobalConst.TrackPushCompletedSubscribe,
|
||||
@ -448,13 +428,13 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
||||
|
||||
if (config.IsAllPush)
|
||||
{
|
||||
var history = await _trackPushRecordEsRepository.GetLatestTrackPushRecordAsync(log.OrderId, true);
|
||||
var history = await trackPushRecordEsRepository.GetLatestTrackPushRecordAsync(log.OrderId, true);
|
||||
if (history != null)
|
||||
{
|
||||
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 =>
|
||||
{
|
||||
@ -619,7 +599,7 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
||||
{
|
||||
using var activity = ActivityHelper.CreateSpanActivity($"TrackPushFailedPublicTopic-{order.OrderNo}");
|
||||
activity.SetTag("failedMessage", failedMessage);
|
||||
_broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto
|
||||
broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto
|
||||
{
|
||||
AppKey = @event.AppKey,
|
||||
IsManualPush = @event.OperationMan != null,
|
||||
@ -656,7 +636,7 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
||||
activity.SetTag("@event", @event.ToJson());
|
||||
foreach (var pushInput in pushOrUpdateInputs)
|
||||
{
|
||||
_broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto
|
||||
broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto
|
||||
{
|
||||
AppKey = @event.AppKey,
|
||||
IsManualPush = @event.OperationMan != null,
|
||||
@ -1,13 +1,11 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using DotNetCore.CAP;
|
||||
using Fake.Messaging;
|
||||
using Fake.UnitOfWork;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using RabbitMQ.Client.Events;
|
||||
using TrackCenter.Application.Dtos.TrackPull;
|
||||
using TrackCenter.Application.Events;
|
||||
using TrackCenter.Application.Helpers;
|
||||
@ -16,47 +14,37 @@ using TrackCenter.Application.Services.TrackPull;
|
||||
using TrackCenter.Domain.Aggregates.OrderAggregate;
|
||||
using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
|
||||
|
||||
namespace TrackCenter.Application.Rabbit;
|
||||
namespace TrackCenter.Application.Messaging;
|
||||
|
||||
[ExposeServices(typeof(IRabbitHandler))]
|
||||
/// <summary>
|
||||
/// 接口通轨迹刷新回调 → 独立队列 <see cref="GlobalConst.TrackRefreshSubscribe"/>。
|
||||
/// </summary>
|
||||
[MessageSubscribe(GlobalConst.TrackRefreshSubscribe, ExternalQueue = true)]
|
||||
public class TrackRefreshEventHandler(
|
||||
IOrderRepository orderRepository,
|
||||
ICapPublisher capPublisher,
|
||||
IMessagePublisher messagePublisher,
|
||||
TrackPullCacheService trackPullCacheService,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
ILogger<TrackRefreshEventHandler> logger,
|
||||
IOptionsMonitor<InterfaceRelayParamsConfigOptions> options)
|
||||
: ITransientDependency, IRabbitHandler
|
||||
: IMessageHandle<InterfaceRelayForwardReplayEvent>, ITransientDependency
|
||||
{
|
||||
public bool Enable(ConsumeOptions coptions)
|
||||
public virtual async Task HandleAsync(
|
||||
InterfaceRelayForwardReplayEvent message,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return coptions.Queue.StartsWith(GlobalConst.TrackRefreshSubscribe);
|
||||
}
|
||||
|
||||
[UnitOfWork]
|
||||
public virtual async Task Handle(BasicDeliverEventArgs args)
|
||||
{
|
||||
var msg = string.Empty;
|
||||
try
|
||||
{
|
||||
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);
|
||||
await ConsumerAsync(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"TrackRefreshEventHandler出错,错误信息:{ex}\r\n-----\r\n消息:{msg}");
|
||||
Log.Error($"TrackRefreshEventHandler出错,错误信息:{ex}\r\n-----\r\n消息:{message.ToJson()}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ConsumerAsync(InterfaceRelayForwardReplayEvent @event)
|
||||
private async Task ConsumerAsync(InterfaceRelayForwardReplayEvent @event)
|
||||
{
|
||||
if (!@event.IsSuccess)
|
||||
{
|
||||
@ -71,15 +59,6 @@ public class TrackRefreshEventHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
// 最优先级,最匹配的 权重 sort
|
||||
|
||||
// 客户、渠道、产品
|
||||
// */*/* 全部
|
||||
// 客户A/渠道A/产品A
|
||||
// 客户A
|
||||
|
||||
// 客户A 渠道B 产品A
|
||||
|
||||
var config = await trackPullCacheService.GetPullParamsWithContentAsync(attach.AppKey,
|
||||
attach.ServiceProviderCode, attach.OrderType);
|
||||
if (config is null)
|
||||
@ -134,7 +113,6 @@ public class TrackRefreshEventHandler(
|
||||
ServiceProviderCode = attach.ServiceProviderCode
|
||||
};
|
||||
|
||||
|
||||
if (trackToken is JArray trackTokens)
|
||||
{
|
||||
foreach (var item in trackTokens)
|
||||
@ -252,7 +230,7 @@ public class TrackRefreshEventHandler(
|
||||
}
|
||||
|
||||
// 新增/更新轨迹
|
||||
var trackUpsertEvent = new TrackUpsertEvent
|
||||
var trackUpsertEvent = new TrackUpsertForRefreshEvent
|
||||
{
|
||||
OrderIdentifiers =
|
||||
new[]
|
||||
@ -284,7 +262,7 @@ public class TrackRefreshEventHandler(
|
||||
Extra = trackExtra
|
||||
};
|
||||
|
||||
await capPublisher.PublishAsync(GlobalConst.TrackUpsertForRefreshSubscribe, trackUpsertEvent);
|
||||
await messagePublisher.PublishAsync(trackUpsertEvent);
|
||||
}
|
||||
|
||||
private async Task InsertNotMappedTrackAsync(JToken trackToken, TrackRefreshContext context, string? trackPlace,
|
||||
@ -1,4 +1,4 @@
|
||||
using DotNetCore.CAP;
|
||||
using Fake.Messaging;
|
||||
using Fake.UnitOfWork;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using TrackCenter.Application.Events;
|
||||
@ -13,41 +13,28 @@ using TrackCenter.Repository.Aggregates.OrderAggregate;
|
||||
using TrackCenter.Repository.Otel;
|
||||
using OrderIdentifier = TrackCenter.Application.Events.OrderIdentifier;
|
||||
|
||||
namespace TrackCenter.Application.CAP.EventHandlers;
|
||||
namespace TrackCenter.Application.Messaging;
|
||||
|
||||
public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
public class TrackUpsertEventHandler(
|
||||
IOrderRepository orderRepository,
|
||||
IMessagePublisher messagePublisher,
|
||||
ProvideQueries provideQueries,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
BroadcastingService broadcastingService,
|
||||
WarningSignalPublisher warningSignalPublisher)
|
||||
:
|
||||
IMessageHandle<TrackUpsertEvent>,
|
||||
IMessageHandle<TrackUpsertForRefreshEvent>,
|
||||
ITransientDependency
|
||||
{
|
||||
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 Task HandleAsync(TrackUpsertEvent message, CancellationToken cancellationToken = default)
|
||||
=> ConsumerAsync(message);
|
||||
|
||||
public TrackUpsertEventHandler(
|
||||
IOrderRepository orderRepository,
|
||||
ICapPublisher capPublisher,
|
||||
ProvideQueries provideQueries,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
BroadcastingService broadcastingService,
|
||||
WarningSignalPublisher warningSignalPublisher
|
||||
)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_capPublisher = capPublisher;
|
||||
_provideQueries = provideQueries;
|
||||
_trackDetailsEsRepository = trackDetailsEsRepository;
|
||||
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
|
||||
_broadcastingService = broadcastingService;
|
||||
_warningSignalPublisher = warningSignalPublisher;
|
||||
}
|
||||
public Task HandleAsync(TrackUpsertForRefreshEvent message, CancellationToken cancellationToken = default)
|
||||
=> ConsumerAsync(message);
|
||||
|
||||
[UnitOfWork]
|
||||
[CapSubscribe(GlobalConst.TrackUpsertSubscribe, Group = GlobalConst.TrackUpsertSubscribe)]
|
||||
[CapSubscribe(GlobalConst.TrackUpsertForRefreshSubscribe, Group = GlobalConst.TrackUpsertForRefreshSubscribe)]
|
||||
public virtual async Task ConsumerAsync(TrackUpsertEvent @event)
|
||||
private async Task ConsumerAsync(TrackUpsertEvent @event)
|
||||
{
|
||||
if (!CanDelete(@event)) return;
|
||||
|
||||
@ -56,22 +43,22 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
await HandleTrackAsync(@event, canNextOrderIds);
|
||||
|
||||
if (canNextOrderIds.Count == 0) return;
|
||||
// ?????????????????
|
||||
// 根据订单关联关系异步向下刷(异步)
|
||||
await DownByOrderLink(@event, 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 group in linkedOrders.Where(x => x.OwnerOrderId == order.OrderId)
|
||||
.GroupBy(x => new
|
||||
{
|
||||
x.AppKey,
|
||||
x.Customer // ?????????????????
|
||||
x.Customer // 这里按客户分组是为了后续的推送方便
|
||||
}))
|
||||
{
|
||||
var nextEvent = @event.DeepCopy();
|
||||
@ -85,15 +72,15 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash,
|
||||
};
|
||||
|
||||
// ???????????
|
||||
// 不同应用需要做内容映射
|
||||
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();
|
||||
|
||||
if (mapCode == null)
|
||||
{
|
||||
// ?????????????
|
||||
// 跨应用没做映射的轨迹不处理
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -111,7 +98,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
{
|
||||
var subEvent = nextEvent.DeepCopy();
|
||||
subEvent.OrderIdentifiers = subOrderIds.ToArray();
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, subEvent);
|
||||
await messagePublisher.PublishAsync(subEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,10 +106,10 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
|
||||
private static bool CanDelete(TrackUpsertEvent @event)
|
||||
{
|
||||
// ????????????
|
||||
// 系统创建的轨迹不允许修改
|
||||
if (@event.Visited.IsNullOrEmpty() && @event is { OperationMan: GlobalConst.SystemUser, IsAdd: false })
|
||||
{
|
||||
Log.Warning($"??????????????{@event}", true);
|
||||
Log.Warning($"系统创建的轨迹不允许被修改:{@event}", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -131,7 +118,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
var dup = @event.Visited.Intersect(orderIds).ToArray();
|
||||
if (dup.Any())
|
||||
{
|
||||
Log.Warning($"{(@event.IsAdd ? "??" : "??")}???????????{dup.JoinAsString(",")}", true);
|
||||
Log.Warning($"{(@event.IsAdd ? "添加" : "修改")}轨迹时,发现循环依赖:{dup.JoinAsString(",")}", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -140,29 +127,29 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
|
||||
private async Task HandleTrackAsync(TrackUpsertEvent @event, List<int> canNextOrderIds)
|
||||
{
|
||||
//#7001:??????????????????????????????????????
|
||||
var trackContent = await _provideQueries.GetTrackContentAsync(@event.AppKey, @event.TrackCode);
|
||||
//#7001:未来潜在需求,当轨迹内容匹配预设,记录一个标记,保证下刷过程都是使用预设内容
|
||||
var trackContent = await provideQueries.GetTrackContentAsync(@event.AppKey, @event.TrackCode);
|
||||
|
||||
if (trackContent == null)
|
||||
{
|
||||
// ????????????
|
||||
Log.Warning($"????????{@event}", true);
|
||||
// 找不到轨迹内容,直接结束
|
||||
Log.Warning($"找不到轨迹内容:{@event}", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// ???????????????
|
||||
// 根据轨迹内容标识,确定操作权限
|
||||
var manualOperation = @event.Source == (int) TrackSourceEnum.Artificial;
|
||||
// ???????
|
||||
// 不允许手动创建
|
||||
if (manualOperation && @event.IsAdd && trackContent.IsAllowManuallyCreate == false)
|
||||
{
|
||||
PublicTopicAllFailed(@event, trackContent, "?????????????");
|
||||
PublicTopicAllFailed(@event, trackContent, "轨迹内容标识不允许手动创建");
|
||||
return;
|
||||
}
|
||||
|
||||
// ???????
|
||||
// 不允许手动更新
|
||||
if (manualOperation && !@event.IsAdd && trackContent.IsAllowManuallyUpdate == false)
|
||||
{
|
||||
PublicTopicAllFailed(@event, trackContent, "?????????????");
|
||||
PublicTopicAllFailed(@event, trackContent, "轨迹标识内容不允许手动更新");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -181,30 +168,30 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
|
||||
if (insertTracks.Any())
|
||||
{
|
||||
// ??add to es tips:??????????
|
||||
await _trackDetailsEsRepository.AddManyAsync(insertTracks);
|
||||
// 批量add to es tips:注意副作用填充版本号
|
||||
await trackDetailsEsRepository.AddManyAsync(insertTracks);
|
||||
|
||||
// es??????????
|
||||
// es新增后填充版本号字典
|
||||
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,
|
||||
OrderId = t.OrderId
|
||||
})).ToList());
|
||||
|
||||
// ??????????????
|
||||
await _orderRepository.UpdatePullStatusToSilenceAsync(insertTracks.Select(x => x.OrderId).ToList(),
|
||||
// 添加轨迹触发订单拉取状态更新
|
||||
await orderRepository.UpdatePullStatusToSilenceAsync(insertTracks.Select(x => x.OrderId).ToList(),
|
||||
@event.TrackCode);
|
||||
|
||||
// ????????????????
|
||||
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹新增, insertTracks);
|
||||
// 发布预警信号,稍后将进行预警计算
|
||||
warningSignalPublisher.PublishSignal(WarningSignalType.轨迹新增, insertTracks);
|
||||
|
||||
// ??????????????pod??
|
||||
// 如果存在轨迹标记,则触发订单pod拉取
|
||||
if (!string.IsNullOrWhiteSpace(trackContent.TrackFlagId))
|
||||
{
|
||||
await _capPublisher.PublishAsync(GlobalConst.OrderPodPullSubscribe, new OrderPodPullEvent
|
||||
await messagePublisher.PublishAsync(new OrderPodPullEvent
|
||||
{
|
||||
AppKey = @event.AppKey,
|
||||
OrderIdEsIdDict = insertTracks.ToDictionary(x => x.OrderId, y => y.Id),
|
||||
@ -213,25 +200,24 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
}
|
||||
}
|
||||
|
||||
// ????????
|
||||
// 触发轨迹变更广播
|
||||
await TrackChangePublicTopicAsync(trackContent, trackChangeEtos, true);
|
||||
|
||||
// ??????????????
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackChangeAfterUpdateOrderSubscribe,
|
||||
new TrackChangeAfterUpdateOrderEvent
|
||||
{
|
||||
OrderIds = canNextOrderIds.ToArray(),
|
||||
IsCompleted = trackContent.IsCompleted,
|
||||
TrackStatus = trackContent.TrackStatus,
|
||||
TrackContent = trackContent.TrackContent,
|
||||
TrackEnContent = trackContent.TrackEnContent,
|
||||
TrackPlace = @event.TrackPlace,
|
||||
TrackTime = @event.TrackTime,
|
||||
TrackTimeZone = @event.TrackTimeZone,
|
||||
});
|
||||
// 触发订单主表更新事件(异步)
|
||||
await messagePublisher.PublishAsync(new TrackChangeAfterUpdateOrderEvent
|
||||
{
|
||||
OrderIds = canNextOrderIds.ToArray(),
|
||||
IsCompleted = trackContent.IsCompleted,
|
||||
TrackStatus = trackContent.TrackStatus,
|
||||
TrackContent = trackContent.TrackContent,
|
||||
TrackEnContent = trackContent.TrackEnContent,
|
||||
TrackPlace = @event.TrackPlace,
|
||||
TrackTime = @event.TrackTime,
|
||||
TrackTimeZone = @event.TrackTimeZone,
|
||||
});
|
||||
|
||||
// 触发轨迹推送事件(异步)
|
||||
// tips:????????????????
|
||||
// tips:同一个轨迹内容,多个订单多条轨迹
|
||||
var trackPushEvent = new TrackPushEvent
|
||||
{
|
||||
OptionType = @event.IsAdd ? OptionType.新增 : OptionType.修改,
|
||||
@ -253,7 +239,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
ForcedPush = @event.ForcedPush,
|
||||
IsToMq = (trackContent.IsToMq || @event.IsToMq)
|
||||
};
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackPushSubscribe, trackPushEvent);
|
||||
await messagePublisher.PublishAsync(trackPushEvent);
|
||||
}
|
||||
|
||||
private async Task<List<TrackDetailsIndex>> UpsertTrackAsync(
|
||||
@ -267,7 +253,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
var orderIds = @event.OrderIdentifiers.Select(x => x.OrderId).ToArray();
|
||||
|
||||
// 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 updateTracks = new List<TrackDetailsIndex>();
|
||||
@ -290,7 +276,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = $"???????EsId:[{@event.EsId}]????????????";
|
||||
var message = $"没有通过传入的EsId:[{@event.EsId}]查询到轨迹信息,更新失败";
|
||||
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
|
||||
}
|
||||
|
||||
@ -299,21 +285,21 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
|
||||
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;
|
||||
|
||||
// ??????
|
||||
// 根据时间过滤
|
||||
existTracks = existTracks.Where(x =>
|
||||
x.GetUtc0TrackTime() == @event.TrackTime.Subtract(TimeSpan.FromHours(timezone))).ToList();
|
||||
|
||||
// ????????
|
||||
// 根据扩展信息过滤
|
||||
existTracks = existTracks.Where(x => x.Extra == @event.Extra).ToList();
|
||||
|
||||
if (@event.IsAdd)
|
||||
{
|
||||
if (existTracks.Count > 0)
|
||||
{
|
||||
const string message = "??????????????????????";
|
||||
const string message = "订单已经存在不允许重复的轨迹,不允许重复添加";
|
||||
traceLog.Add($"OrderNo-{order.OrderNo}", message);
|
||||
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
|
||||
continue;
|
||||
@ -325,10 +311,10 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
}
|
||||
else
|
||||
{
|
||||
// ??????
|
||||
// 不存在不让改
|
||||
if (existTracks.Count == 0)
|
||||
{
|
||||
const string message = "?????????????";
|
||||
const string message = "订单不存在轨迹,不允许修改";
|
||||
traceLog.Add($"OrderNo-{order.OrderNo}", message);
|
||||
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
|
||||
continue;
|
||||
@ -336,7 +322,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
|
||||
if (existTracks.Count > 1)
|
||||
{
|
||||
const string message = "?????????????";
|
||||
const string message = "订单存在多条轨迹不允许修改";
|
||||
traceLog.Add($"OrderNo-{order.OrderNo}", message);
|
||||
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
|
||||
continue;
|
||||
@ -356,22 +342,22 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
|
||||
if (updateTracks.Any())
|
||||
{
|
||||
// ?????????
|
||||
await _trackDetailsEsRepository.BulkUpdateAsync(updateTracks);
|
||||
// ???????? updateTracks.ForEach(x => orderIdTrackMetadataDict.Add(x.OrderId, TrackMetadata.Build(x.Id, x.Version, x.CreateTime)));
|
||||
// ????????
|
||||
_ = _orderOperationRecordEsRepository.AddManyAsync(operationRecords);
|
||||
// ???????????????
|
||||
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹更新, tracks);
|
||||
// 批量差异化更新轨迹
|
||||
await trackDetailsEsRepository.BulkUpdateAsync(updateTracks);
|
||||
// 填充轨迹元数据
|
||||
updateTracks.ForEach(x => orderIdTrackMetadataDict.Add(x.OrderId, TrackMetadata.Build(x.Id, x.Version, x.CreateTime)));
|
||||
// 批量写入操作日志
|
||||
_ = orderOperationRecordEsRepository.AddManyAsync(operationRecords);
|
||||
// 发布预警信号稍后将进行预警计算
|
||||
warningSignalPublisher.PublishSignal(WarningSignalType.轨迹更新, tracks);
|
||||
}
|
||||
|
||||
return insertTracks;
|
||||
}
|
||||
|
||||
|
||||
private static TrackDetailsIndex BuiltUpdateTrack(TrackUpsertEvent @event, TrackDetailsIndex track, OrderIdentifier order, TrackContentVO trackContent)
|
||||
{
|
||||
// ?????????? ?????????????
|
||||
// 非外部轨迹才允许修改 【发生地】【时间】【时区】
|
||||
if (track.Source != (int) TrackSourceEnum.ExternalServiceProvide)
|
||||
{
|
||||
track.TrackPlace = @event.TrackPlace;
|
||||
@ -379,11 +365,11 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
track.TrackTimeZone = @event.TrackTimeZone;
|
||||
}
|
||||
|
||||
//??????????
|
||||
// 允许二次修改轨迹内容
|
||||
track.TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content;
|
||||
track.TrackEnContent = string.IsNullOrWhiteSpace(@event.EnStatus) ? trackContent.TrackEnContent : @event.EnStatus;
|
||||
|
||||
// ??????????
|
||||
// 轨迹发生地的更多信息
|
||||
track.TrackPlaceCity = GlobalConst.EmptyString;
|
||||
track.TrackPlaceCityId = GlobalConst.NotExistId;
|
||||
track.TrackPlaceProvince = GlobalConst.EmptyString;
|
||||
@ -392,16 +378,16 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
track.TrackPlaceNationId = GlobalConst.NotExistId;
|
||||
track.TrackPlacePostcode = GlobalConst.EmptyString;
|
||||
|
||||
// ??????????????????????
|
||||
// 签收地时区,签收地时间
|
||||
track.DestinationTimeZone = null;
|
||||
track.EstimatedDeliveryDate = null;
|
||||
|
||||
// ???utc0???
|
||||
// 转换为utc0的时间
|
||||
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
|
||||
? track.TrackTime
|
||||
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
|
||||
|
||||
// ????ids????????????????
|
||||
// 轨迹标记ids,是否对外,备注,扩展信息,附件
|
||||
track.TrackFlagIds = trackContent.TrackFlagId;
|
||||
track.IsExternal = @event.IsExternal ?? trackContent.IsExternal;
|
||||
track.Remarks = @event.Remarks ?? GlobalConst.EmptyString;
|
||||
@ -412,8 +398,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
FileName = x.FileName
|
||||
}).ToList();
|
||||
|
||||
|
||||
// ????
|
||||
// 审计属性
|
||||
track.UpdateUser = @event.OperationMan ?? GlobalConst.SystemUser;
|
||||
track.UpdateTime = DateTime.Now;
|
||||
|
||||
@ -424,13 +409,13 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
{
|
||||
var track = new TrackDetailsIndex
|
||||
{
|
||||
// ????
|
||||
// 订单信息
|
||||
AppKey = @event.AppKey,
|
||||
OrderId = order.OrderId,
|
||||
OrderNo = order.OrderNo,
|
||||
OrderType = order.OrderType,
|
||||
|
||||
// ????
|
||||
// 轨迹信息
|
||||
TrackCode = @event.TrackCode,
|
||||
TrackContentId = trackContent.TrackContentId,
|
||||
TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content,
|
||||
@ -442,12 +427,12 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
TrackTimeZone = @event.TrackTimeZone,
|
||||
TrackPlace = @event.TrackPlace,
|
||||
|
||||
// ???????
|
||||
// 服务商轨迹信息
|
||||
ServiceProviderCode = @event.TrackRefreshData.ServiceProviderCode ?? GlobalConst.EmptyString,
|
||||
OriginTrackContent = @event.TrackRefreshData.OriginTrackContent ?? GlobalConst.EmptyString,
|
||||
OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash ?? GlobalConst.EmptyString,
|
||||
|
||||
// ??????????
|
||||
// 轨迹发生地的更多描述
|
||||
TrackPlaceCity = GlobalConst.EmptyString,
|
||||
TrackPlaceCityId = GlobalConst.NotExistId,
|
||||
TrackPlaceProvince = GlobalConst.EmptyString,
|
||||
@ -455,7 +440,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
TrackPlaceNation = GlobalConst.EmptyString,
|
||||
TrackPlaceNationId = GlobalConst.NotExistId,
|
||||
TrackPlacePostcode = GlobalConst.EmptyString,
|
||||
// ????ids?????????????????????
|
||||
// 轨迹标记ids,轨迹来源,是否对外,备注,扩展信息,附件
|
||||
TrackFlagIds = trackContent.TrackFlagId,
|
||||
Source = @event.Source,
|
||||
IsExternal = @event.IsExternal ?? trackContent.IsExternal,
|
||||
@ -467,12 +452,12 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
FileName = x.FileName
|
||||
}).ToList(),
|
||||
|
||||
// ????
|
||||
// 审计属性
|
||||
CreateUser = @event.OperationMan ?? GlobalConst.SystemUser,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
|
||||
// ???utc0???
|
||||
// 转换为utc0的时间
|
||||
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
|
||||
? track.TrackTime
|
||||
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
|
||||
@ -497,15 +482,17 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
private async Task TrackChangePublicTopicAsync(TrackContentVO trackContent, List<TrackChangeEto> list, bool isDelay = false)
|
||||
{
|
||||
if (list.Count == 0) return;
|
||||
//??????????s????https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-refresh.html
|
||||
//?????????????????
|
||||
//??????????
|
||||
//??1??!???????????????????????????? //??2?????????es???????? //??3????????????????
|
||||
// 理想情况在写入之后1s会刷盘 https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-refresh.html
|
||||
// 增加一个版本号来确定是否真的变更了
|
||||
// 没有刷盘成功事件回调
|
||||
// 方案1:!!【轮询等待静默刷盘】查出版本号确实发生了变更在发送广播
|
||||
// 方案2:考虑强制刷盘(对es压力大)后发送
|
||||
// 方案3:采用一个较大延时,不查询直接发
|
||||
if (isDelay)
|
||||
await Task.Delay(1000);
|
||||
|
||||
foreach (var eto in list)
|
||||
_broadcastingService.TrackChangePublicTopic(eto, trackContent.AppKey);
|
||||
broadcastingService.TrackChangePublicTopic(eto, trackContent.AppKey);
|
||||
}
|
||||
|
||||
private void PublicTopicAllFailed(TrackUpsertEvent @event, TrackContentVO trackContent, string failedReason)
|
||||
@ -514,7 +501,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
foreach (var orderNo in @event.OrderIdentifiers.Select(x => x.OrderNo))
|
||||
{
|
||||
var eto = BuildEto(@event, trackContent, orderNo, null, failedReason);
|
||||
_broadcastingService.TrackChangePublicTopic(eto, trackContent.AppKey);
|
||||
broadcastingService.TrackChangePublicTopic(eto, trackContent.AppKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
using RabbitMQ.Client.Events;
|
||||
|
||||
namespace TrackCenter.Application.Rabbit;
|
||||
|
||||
public interface IRabbitHandler
|
||||
{
|
||||
bool Enable(ConsumeOptions options);
|
||||
|
||||
Task Handle(BasicDeliverEventArgs args);
|
||||
}
|
||||
@ -1,166 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,74 +1,102 @@
|
||||
using System.Text;
|
||||
using Fake.RabbitMQ;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using RabbitMQ.Client;
|
||||
using TrackCenter.Application.Dtos.TrackPush;
|
||||
using TrackCenter.Application.Events;
|
||||
using TrackCenter.Application.Rabbit;
|
||||
|
||||
namespace TrackCenter.Application.Services.Broadcasting;
|
||||
|
||||
/// <summary>
|
||||
/// 广播服务
|
||||
/// 对外 Topic 广播(下游仍按 Newtonsoft PascalCase 契约消费,不经 Messaging 信封)。
|
||||
/// </summary>
|
||||
public class BroadcastingService : ITransientDependency
|
||||
public class BroadcastingService(IRabbitMqChannelPool channelPool) : ITransientDependency
|
||||
{
|
||||
private readonly RabbitClient _rabbitClient;
|
||||
|
||||
public BroadcastingService(RabbitClient rabbitClient)
|
||||
public void TrackChangePublicTopic(TrackChangeEto eto, string AppCode)
|
||||
{
|
||||
_rabbitClient = rabbitClient;
|
||||
}
|
||||
|
||||
public void TrackChangePublicTopic(TrackChangeEto eto, string AppCode)
|
||||
{
|
||||
_rabbitClient.PublishByConfirmSelect(
|
||||
PublishByConfirmSelect(
|
||||
GlobalConst.TrackChangeExchange,
|
||||
ExchangeType.Topic,
|
||||
$"TrackChange.{AppCode}",
|
||||
Encoding.UTF8.GetBytes(eto.ToJson()),
|
||||
prop => { prop.Persistent = true; }
|
||||
);
|
||||
prop => { prop.Persistent = true; });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轨迹推送完成广播(批量)
|
||||
/// </summary>
|
||||
/// <param name="list"></param>
|
||||
/// <returns></returns>
|
||||
public void TrackPushCompletedPublicTopic(List<TrackPushRecordDto> list)
|
||||
{
|
||||
var data = list
|
||||
.Select(TrackPushRecordDtoToEvent)
|
||||
.Select(x => Encoding.UTF8.GetBytes(x.ToJson()));
|
||||
var log = list.First();
|
||||
_rabbitClient.PublishByConfirmSelect(
|
||||
PublishByConfirmSelect(
|
||||
GlobalConst.TrackPushExchange,
|
||||
ExchangeType.Topic,
|
||||
$"TrackPush.{log.AppKey}",
|
||||
data,
|
||||
prop => { prop.Persistent = true; }
|
||||
);
|
||||
prop => { prop.Persistent = true; });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轨迹推送完成广播
|
||||
/// </summary>
|
||||
/// <param name="log"></param>
|
||||
/// <returns></returns>
|
||||
public void TrackPushCompletedPublicTopic(TrackPushRecordDto log)
|
||||
{
|
||||
var data = TrackPushRecordDtoToEvent(log);
|
||||
_rabbitClient.PublishByConfirmSelect(GlobalConst.TrackPushExchange, $"TrackPush.{log.AppKey}",
|
||||
Encoding.UTF8.GetBytes(data.ToJson()), prop => { prop.Persistent = true; });
|
||||
PublishByConfirmSelect(
|
||||
GlobalConst.TrackPushExchange,
|
||||
ExchangeType.Topic,
|
||||
$"TrackPush.{log.AppKey}",
|
||||
Encoding.UTF8.GetBytes(data.ToJson()),
|
||||
prop => { prop.Persistent = true; });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轨迹推送失败广播
|
||||
/// </summary>
|
||||
/// <param name="log"></param>
|
||||
public void TrackPushFailedPubilcTopic(TrackPushRecordDto 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)
|
||||
{
|
||||
return new TrackPushCompletedEvent
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using DotNetCore.CAP;
|
||||
using System.Data;
|
||||
using Fake.Messaging;
|
||||
using Fake.UnitOfWork;
|
||||
using TrackCenter.Application.Dtos.ProvideServices;
|
||||
using TrackCenter.Application.Events;
|
||||
using TrackCenter.Domain.Aggregates.OrderAggregate;
|
||||
@ -11,8 +13,8 @@ namespace TrackCenter.Application.Services.ProvideServices;
|
||||
/// </summary>
|
||||
public partial class OrderService
|
||||
{
|
||||
private readonly ICapPublisher _capPublisher;
|
||||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
||||
private readonly IMessagePublisher _messagePublisher = messagePublisher;
|
||||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository = trackDetailsEsRepository;
|
||||
|
||||
/// <summary>
|
||||
/// 关联、解除关联订单
|
||||
@ -21,9 +23,10 @@ public partial class OrderService
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BusinessException"></exception>
|
||||
[HttpPost("Order/Link")]
|
||||
public async Task LinkOrderAsync(LinkOrderInput input)
|
||||
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)]
|
||||
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 => x.Id);
|
||||
if (orderId == default)
|
||||
@ -60,13 +63,13 @@ public partial class OrderService
|
||||
{
|
||||
// 获取已关联的订单
|
||||
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 existedOrders = await _orderRepository.GetListAsync(x =>
|
||||
var existedOrders = await orderRepository.GetListAsync(x =>
|
||||
x.AppKey == input.LinkAppKey && x.OrderType == input.LinkOrderType &&
|
||||
noLinkOrderNos.Contains(x.OrderNo),
|
||||
x => new
|
||||
@ -90,7 +93,7 @@ public partial class OrderService
|
||||
return 0;
|
||||
}
|
||||
|
||||
await _orderRepository.LinkOrderAsync(existedOrders.Select(x => new OrderLinkEntity
|
||||
await orderRepository.LinkOrderAsync(existedOrders.Select(x => new OrderLinkEntity
|
||||
{
|
||||
OrderId = orderId,
|
||||
LinkOrderId = x.Id,
|
||||
@ -127,7 +130,7 @@ public partial class OrderService
|
||||
IsAdd = true // 存在则不新增
|
||||
};
|
||||
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, fillTrackEvent);
|
||||
await _messagePublisher.PublishAsync(fillTrackEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,11 +139,11 @@ public partial class OrderService
|
||||
|
||||
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);
|
||||
|
||||
// real del
|
||||
await _orderRepository.UnLinkByIdsAsync(unLinkIds);
|
||||
await orderRepository.UnLinkByIdsAsync(unLinkIds);
|
||||
return unLinkIds.Count;
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ public partial class OrderService
|
||||
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
|
||||
.Select(x => x.OrderNo)
|
||||
.Distinct()
|
||||
@ -57,7 +57,7 @@ public partial class OrderService
|
||||
[HttpPost("Order/UpdateOrderPullConfigs")]
|
||||
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 &&
|
||||
input.OrderNos.Contains(x.OrderNo),
|
||||
x => x.Id);
|
||||
@ -69,7 +69,7 @@ public partial class OrderService
|
||||
var inputVo = ObjectMapper
|
||||
.Map<PullConfigInfo, UpdateOrderPullConfigsInputVo>(input.Config);
|
||||
inputVo.OrderIds = orderIds.ToArray();
|
||||
var cnt = await _orderRepository.UpdateOrderPullConfigForServiceProviderAsync(inputVo);
|
||||
var cnt = await orderRepository.UpdateOrderPullConfigForServiceProviderAsync(inputVo);
|
||||
|
||||
Log.Warning(
|
||||
$"{CurrentUser.UserName}正在修改服务商拉取信息,本次共计影响了{orderIds.Count}个订单、{cnt}段拉取,请求参数是:{input.ToJson()}",
|
||||
|
||||
@ -19,7 +19,7 @@ public partial class OrderService
|
||||
[VerifySignature]
|
||||
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);
|
||||
if (order != null)
|
||||
{
|
||||
@ -27,7 +27,7 @@ public partial class OrderService
|
||||
order = await BuiltOrder(input);
|
||||
ApplyUpsertOntoExisting(order, existing, resetTrackInfoWhenTrackNoChanged: true);
|
||||
|
||||
await _orderRepository.UpdateOrderAsync(order);
|
||||
await orderRepository.UpdateOrderAsync(order);
|
||||
|
||||
var index = new OrderOperationRecordIndex
|
||||
{
|
||||
@ -39,13 +39,13 @@ public partial class OrderService
|
||||
CreateUserName = CurrentUser.UserName ?? string.Empty,
|
||||
};
|
||||
|
||||
_ = _orderOperationRecordEsRepository.AddAsync(index);
|
||||
_ = orderOperationRecordEsRepository.AddAsync(index);
|
||||
|
||||
return new IdBaseDto { Id = order.Id };
|
||||
}
|
||||
|
||||
order = await BuiltOrder(input);
|
||||
var orderId = await _orderRepository.CreateOrderAsync(order);
|
||||
var orderId = await orderRepository.CreateOrderAsync(order);
|
||||
|
||||
var indexForAdd = new OrderOperationRecordIndex
|
||||
{
|
||||
@ -57,7 +57,7 @@ public partial class OrderService
|
||||
CreateUserName = CurrentUser.UserName ?? string.Empty,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
_ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
|
||||
_ = orderOperationRecordEsRepository.AddAsync(indexForAdd);
|
||||
|
||||
return new IdBaseDto { Id = orderId };
|
||||
}
|
||||
@ -69,7 +69,7 @@ public partial class OrderService
|
||||
[VerifySignature]
|
||||
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);
|
||||
if (order != null)
|
||||
{
|
||||
@ -77,7 +77,7 @@ public partial class OrderService
|
||||
order = await BuiltOrder(input);
|
||||
ApplyUpsertOntoExisting(order, existing, resetTrackInfoWhenTrackNoChanged: false);
|
||||
|
||||
await _orderRepository.UpdateOrderAsync(order);
|
||||
await orderRepository.UpdateOrderAsync(order);
|
||||
|
||||
var index = new OrderOperationRecordIndex
|
||||
{
|
||||
@ -89,13 +89,13 @@ public partial class OrderService
|
||||
CreateUserName = CurrentUser.UserName ?? string.Empty,
|
||||
};
|
||||
|
||||
_ = _orderOperationRecordEsRepository.AddAsync(index);
|
||||
_ = orderOperationRecordEsRepository.AddAsync(index);
|
||||
|
||||
return new IdBaseDto { Id = order.Id };
|
||||
}
|
||||
|
||||
order = await BuiltOrder(input);
|
||||
var orderId = await _orderRepository.CreateOrderAsync(order);
|
||||
var orderId = await orderRepository.CreateOrderAsync(order);
|
||||
|
||||
var indexForAdd = new OrderOperationRecordIndex
|
||||
{
|
||||
@ -107,7 +107,7 @@ public partial class OrderService
|
||||
CreateUserName = CurrentUser.UserName ?? string.Empty,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
_ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
|
||||
_ = orderOperationRecordEsRepository.AddAsync(indexForAdd);
|
||||
|
||||
return new IdBaseDto { Id = orderId };
|
||||
}
|
||||
@ -119,7 +119,7 @@ public partial class OrderService
|
||||
[VerifySignature]
|
||||
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);
|
||||
if (order != null)
|
||||
{
|
||||
@ -127,7 +127,7 @@ public partial class OrderService
|
||||
order = await BuiltOrder(input);
|
||||
ApplyUpsertOntoExisting(order, existing, resetTrackInfoWhenTrackNoChanged: false);
|
||||
|
||||
await _orderRepository.UpdateOrderAsync(order);
|
||||
await orderRepository.UpdateOrderAsync(order);
|
||||
|
||||
var index = new OrderOperationRecordIndex
|
||||
{
|
||||
@ -139,13 +139,13 @@ public partial class OrderService
|
||||
CreateUserName = CurrentUser.UserName ?? string.Empty,
|
||||
};
|
||||
|
||||
_ = _orderOperationRecordEsRepository.AddAsync(index);
|
||||
_ = orderOperationRecordEsRepository.AddAsync(index);
|
||||
|
||||
return new IdBaseDto { Id = order.Id };
|
||||
}
|
||||
|
||||
order = await BuiltOrder(input);
|
||||
var orderId = await _orderRepository.CreateOrderAsync(order);
|
||||
var orderId = await orderRepository.CreateOrderAsync(order);
|
||||
|
||||
var indexForAdd = new OrderOperationRecordIndex
|
||||
{
|
||||
@ -157,7 +157,7 @@ public partial class OrderService
|
||||
CreateUserName = CurrentUser.UserName ?? string.Empty,
|
||||
CreateTime = DateTime.Now
|
||||
};
|
||||
_ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
|
||||
_ = orderOperationRecordEsRepository.AddAsync(indexForAdd);
|
||||
|
||||
return new IdBaseDto { Id = orderId };
|
||||
}
|
||||
@ -183,7 +183,7 @@ public partial class OrderService
|
||||
ArrivalEmail = input.Consignee.Email,
|
||||
};
|
||||
|
||||
var config = await _trackPullConfigRepository
|
||||
var config = await trackPullConfigRepository
|
||||
.FirstOrDefaultAsync(x => x.AppKey == AppKey
|
||||
&& x.ServiceProviderCode == input.ApiCode
|
||||
&& x.OrderType == TrackOrderType.小包);
|
||||
@ -220,7 +220,7 @@ public partial class OrderService
|
||||
return order;
|
||||
}
|
||||
|
||||
var config = await _trackPullConfigRepository
|
||||
var config = await trackPullConfigRepository
|
||||
.FirstOrDefaultAsync(x => x.AppKey == AppKey
|
||||
&& x.ServiceProviderCode == input.ApiCode
|
||||
&& x.OrderType == TrackOrderType.大包);
|
||||
@ -250,7 +250,7 @@ public partial class OrderService
|
||||
|
||||
order.OrderFlights = new List<OrderFlightEntity>();
|
||||
|
||||
var config = await _trackPullConfigRepository
|
||||
var config = await trackPullConfigRepository
|
||||
.FirstOrDefaultAsync(x => x.AppKey == AppKey
|
||||
&& x.ServiceProviderCode == input.ApiCode
|
||||
&& x.OrderType == TrackOrderType.小包);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using DotNetCore.CAP;
|
||||
using Fake.Messaging;
|
||||
using TrackCenter.Application.Dtos.ProvideServices;
|
||||
using TrackCenter.Domain.Aggregates.OrderAggregate;
|
||||
using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
|
||||
@ -13,30 +13,15 @@ namespace TrackCenter.Application.Services.ProvideServices;
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "OpenApi")]
|
||||
[Route("/ProviderService")]
|
||||
public partial class OrderService : ApplicationServiceBase
|
||||
public partial class OrderService(
|
||||
IOrderRepository orderRepository,
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
ITrackPullConfigRepository trackPullConfigRepository,
|
||||
IMessagePublisher messagePublisher,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
IServiceProvider serviceProvider)
|
||||
: ApplicationServiceBase
|
||||
{
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
|
||||
private readonly ITrackPullConfigRepository _trackPullConfigRepository;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public OrderService(
|
||||
IOrderRepository orderRepository,
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
ITrackPullConfigRepository trackPullConfigRepository,
|
||||
ICapPublisher capPublisher,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
IServiceProvider serviceProvider
|
||||
)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
|
||||
_trackPullConfigRepository = trackPullConfigRepository;
|
||||
_serviceProvider = serviceProvider;
|
||||
_trackDetailsEsRepository = trackDetailsEsRepository;
|
||||
_capPublisher = capPublisher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除订单
|
||||
/// </summary>
|
||||
@ -46,14 +31,14 @@ public partial class OrderService : ApplicationServiceBase
|
||||
[HttpPost("Order/Delete")]
|
||||
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);
|
||||
if (order == null)
|
||||
{
|
||||
throw ProviderServiceActionResult.Exception("删除订单失败", $"订单:{input.OrderNo}不存在");
|
||||
}
|
||||
|
||||
await _orderRepository.DeleteOrderAsync(order.Id);
|
||||
await orderRepository.DeleteOrderAsync(order.Id);
|
||||
|
||||
// 预警计算
|
||||
WarningCalc_OrderDeletedOrCompleted(order);
|
||||
@ -66,7 +51,7 @@ public partial class OrderService : ApplicationServiceBase
|
||||
order.IsDeleted = true;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var scope = _serviceProvider.CreateAsyncScope();
|
||||
var scope = serviceProvider.CreateAsyncScope();
|
||||
try
|
||||
{
|
||||
var handler = scope.ServiceProvider.GetRequiredService<IOrderWarningHandler>();
|
||||
|
||||
@ -15,21 +15,12 @@ namespace TrackCenter.Application.Services.ProvideServices;
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "OpenApi")]
|
||||
[Route("/ProviderService")]
|
||||
public class TrackPullService : ApplicationServiceBase
|
||||
public class TrackPullService(
|
||||
ProvideQueries provideQueries,
|
||||
InterfaceForwardService interfaceForwardService,
|
||||
IOptionsMonitor<InterfaceRelayParamsConfigOptions> options)
|
||||
: ApplicationServiceBase
|
||||
{
|
||||
private readonly IOptionsMonitor<InterfaceRelayParamsConfigOptions> _options;
|
||||
private readonly ProvideQueries _provideQueries;
|
||||
private readonly InterfaceForwardService _interfaceForwardService;
|
||||
|
||||
public TrackPullService(ProvideQueries provideQueries,
|
||||
InterfaceForwardService interfaceForwardService,
|
||||
IOptionsMonitor<InterfaceRelayParamsConfigOptions> options)
|
||||
{
|
||||
_provideQueries = provideQueries;
|
||||
_interfaceForwardService = interfaceForwardService;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动刷新轨迹
|
||||
/// </summary>
|
||||
@ -46,7 +37,7 @@ public class TrackPullService : ApplicationServiceBase
|
||||
|
||||
// tips:这里没有区分订单类型,就算的不同类型的单一起刷新了也无所谓
|
||||
var (orders, skipReasons) =
|
||||
await _provideQueries.DiagnoseManualRefreshAsync(input.AppKey, input.OrderNos);
|
||||
await provideQueries.DiagnoseManualRefreshAsync(input.AppKey, input.OrderNos);
|
||||
|
||||
if (orders.Count == 0)
|
||||
{
|
||||
@ -79,8 +70,8 @@ public class TrackPullService : ApplicationServiceBase
|
||||
using (Track2InterfaceRelayAppContext.Use(
|
||||
new Track2InterfaceRelayAppContext(group.Key.AppKey)))
|
||||
{
|
||||
var res = await _interfaceForwardService.BatchForwardThenPushToQueueAsync(
|
||||
_options.CurrentValue.GetTrackInterfaceCode, serviceProviderCode, data,
|
||||
var res = await interfaceForwardService.BatchForwardThenPushToQueueAsync(
|
||||
options.CurrentValue.GetTrackInterfaceCode, serviceProviderCode, data,
|
||||
GlobalConst.TrackRefreshSubscribe,
|
||||
attach);
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using DotNetCore.CAP;
|
||||
using System.Data;
|
||||
using Fake.Messaging;
|
||||
using Fake.UnitOfWork;
|
||||
using TrackCenter.Application.Dtos.ProvideServices;
|
||||
using TrackCenter.Application.Dtos.TrackOrder;
|
||||
using TrackCenter.Application.Dtos.TrackPush;
|
||||
@ -18,20 +20,20 @@ namespace TrackCenter.Application.Services.ProvideServices;
|
||||
[Route("/ProviderService")]
|
||||
public class TrackPushService : ApplicationServiceBase
|
||||
{
|
||||
private readonly ICapPublisher _capPublisher;
|
||||
private readonly IMessagePublisher _messagePublisher;
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly ITrackPushConfigRepository _trackPushConfigRepository;
|
||||
private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository;
|
||||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
||||
|
||||
public TrackPushService(IOrderRepository orderRepository
|
||||
, ICapPublisher capPublisher
|
||||
, IMessagePublisher messagePublisher
|
||||
, ITrackPushRecordEsRepository trackPushRecordEsRepository
|
||||
, ITrackPushConfigRepository trackPushConfigRepository,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_capPublisher = capPublisher;
|
||||
_messagePublisher = messagePublisher;
|
||||
_trackPushRecordEsRepository = trackPushRecordEsRepository;
|
||||
_trackPushConfigRepository = trackPushConfigRepository;
|
||||
_trackDetailsEsRepository = trackDetailsEsRepository;
|
||||
@ -43,7 +45,8 @@ public class TrackPushService : ApplicationServiceBase
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("Track/Push")]
|
||||
public async Task PushTrackAsync(PushTrackInput input)
|
||||
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)]
|
||||
public virtual async Task PushTrackAsync(PushTrackInput input)
|
||||
{
|
||||
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList();
|
||||
if (input.OrderNos.Count == 0)
|
||||
@ -152,7 +155,7 @@ public class TrackPushService : ApplicationServiceBase
|
||||
}
|
||||
};
|
||||
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackManualPushSubscribe, trackPushEvent);
|
||||
await _messagePublisher.PublishAsync(trackPushEvent);
|
||||
pushedOrderNos.Add(order.OrderNo);
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ namespace TrackCenter.Application.Services.ProvideServices;
|
||||
/// </summary>
|
||||
public partial class TrackService
|
||||
{
|
||||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
||||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository = trackDetailsEsRepository;
|
||||
private readonly IHttpContextAccessor _contextAccessor;
|
||||
private IObjectStorage ObjectStorage => LazyServiceProvider.GetRequiredService<IObjectStorage>();
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using DotNetCore.CAP;
|
||||
using System.Data;
|
||||
using Fake.Messaging;
|
||||
using Fake.UnitOfWork;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using TrackCenter.Application.Dtos.ProvideServices;
|
||||
using TrackCenter.Application.Events;
|
||||
@ -15,34 +17,23 @@ namespace TrackCenter.Application.Services.ProvideServices;
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "OpenApi")]
|
||||
[Route("/ProviderService")]
|
||||
public partial class TrackService : ApplicationServiceBase
|
||||
public partial class TrackService(
|
||||
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>
|
||||
/// <param name="input"></param>
|
||||
/// <exception cref="BusinessException"></exception>
|
||||
[HttpPost("Track/Delete")]
|
||||
public async Task DeleteTrackAsync(DeleteTrackInput input)
|
||||
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)]
|
||||
public virtual async Task DeleteTrackAsync(DeleteTrackInput 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,
|
||||
@ -99,7 +90,7 @@ public partial class TrackService : ApplicationServiceBase
|
||||
}
|
||||
|
||||
// 删除轨迹(异步)
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe, new TrackDeleteEvent
|
||||
await messagePublisher.PublishAsync(new TrackDeleteEvent
|
||||
{
|
||||
EsIds = esIds,
|
||||
OrderIds = new[] { orderId },
|
||||
@ -118,10 +109,11 @@ public partial class TrackService : ApplicationServiceBase
|
||||
/// <param name="input"></param>
|
||||
/// <exception cref="BusinessException"></exception>
|
||||
[HttpPost("Track/BatchDelete")]
|
||||
public async Task BatchDeleteTrackAsync(BatchDeleteTrackInput input)
|
||||
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)]
|
||||
public virtual async Task BatchDeleteTrackAsync(BatchDeleteTrackInput input)
|
||||
{
|
||||
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.OrderType == input.OrderType
|
||||
&& input.OrderNos.Contains(x.OrderNo),
|
||||
@ -173,7 +165,7 @@ public partial class TrackService : ApplicationServiceBase
|
||||
AppKey = input.AppKey,
|
||||
OperationMan = CurrentUser.UserName
|
||||
};
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe, @event);
|
||||
await messagePublisher.PublishAsync(@event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -182,11 +174,12 @@ public partial class TrackService : ApplicationServiceBase
|
||||
/// <param name="input"></param>
|
||||
/// <exception cref="BusinessException"></exception>
|
||||
[HttpPost("Track/Upsert")]
|
||||
public async Task UpsertTrackAsync(UpsertTrackInput input)
|
||||
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)]
|
||||
public virtual async Task UpsertTrackAsync(UpsertTrackInput input)
|
||||
{
|
||||
using var activity = ActivityHelper.CreateSpanActivity("UpsertTrack");
|
||||
activity.SetTag("UpsertTrackInput", input.ToJson());
|
||||
var order = await _orderRepository.GetFirstAsync(x =>
|
||||
var order = await orderRepository.GetFirstAsync(x =>
|
||||
x.AppKey == input.AppKey
|
||||
&& x.OrderType == input.OrderType
|
||||
&& x.OrderNo == input.OrderNo,
|
||||
@ -226,7 +219,7 @@ public partial class TrackService : ApplicationServiceBase
|
||||
}
|
||||
};
|
||||
@event.OperationMan = CurrentUser.UserName;
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
|
||||
await messagePublisher.PublishAsync(@event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -235,12 +228,13 @@ public partial class TrackService : ApplicationServiceBase
|
||||
/// <param name="input"></param>
|
||||
/// <exception cref="BusinessException"></exception>
|
||||
[HttpPost("Track/BatchUpsert")]
|
||||
public async Task BatchUpsertTrackAsync(BatchUpsertTrackInput input)
|
||||
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)]
|
||||
public virtual async Task BatchUpsertTrackAsync(BatchUpsertTrackInput input)
|
||||
{
|
||||
using var activity = ActivityHelper.CreateSpanActivity("BatchUpsert");
|
||||
activity.SetTag("BatchUpsertTrackInput", input.ToJson());
|
||||
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.OrderType == input.OrderType
|
||||
&& input.OrderNos.Contains(x.OrderNo),
|
||||
@ -298,7 +292,7 @@ public partial class TrackService : ApplicationServiceBase
|
||||
ForcedPush = input.ForcedPush,
|
||||
IsToMq = input.IsToMq,
|
||||
};
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
|
||||
await messagePublisher.PublishAsync(@event);
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,7 +333,7 @@ public partial class TrackService : ApplicationServiceBase
|
||||
|
||||
#region 轨迹内容中的配置规则检查
|
||||
|
||||
var trackContent = await _trackContentRepository.GetByCodeAsync(appKey, trackCode);
|
||||
var trackContent = await trackContentRepository.GetByCodeAsync(appKey, trackCode);
|
||||
if (trackContent == null)
|
||||
{
|
||||
throw ProviderServiceActionResult.Exception("校验失败", $"轨迹代码:{trackCode}轨迹内容不存在");
|
||||
|
||||
@ -28,14 +28,14 @@ public class TrackPullTask(
|
||||
ProvideQueries provideQueries,
|
||||
InterfaceForwardService interfaceForwardService,
|
||||
IOptions<InterfaceRelayParamsConfigOptions> interfaceOptions)
|
||||
: ApplicationServiceBase, ITransientDependency
|
||||
: ApplicationServiceBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 定时任务刷新轨迹
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisableConcurrentExecution(timeoutInSeconds: 1800)]
|
||||
[UnitOfWork]
|
||||
[UnitOfWork(IsTransactional = true)]
|
||||
[HttpPost("/TimerTask/Refresh")]
|
||||
public virtual async Task<bool> RefreshForTaskAsync()
|
||||
{
|
||||
@ -66,7 +66,7 @@ public class TrackPullTask(
|
||||
return true;
|
||||
}
|
||||
|
||||
[UnitOfWork]
|
||||
[UnitOfWork(IsTransactional = true)]
|
||||
public virtual async Task RefreshForConfigAsync(TrackPullConfigVo config)
|
||||
{
|
||||
var redisKey = trackPullCacheService.TrackPullKey(config);
|
||||
@ -89,7 +89,7 @@ public class TrackPullTask(
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task DoRefreshAsync(TrackPullConfigVo config, ProvideQueries provideQueries,
|
||||
protected async Task DoRefreshAsync(TrackPullConfigVo config, ProvideQueries queries,
|
||||
InterfaceForwardService interfaceForward,
|
||||
InterfaceRelayParamsConfigOptions interfaceOptionsValue)
|
||||
{
|
||||
@ -97,7 +97,7 @@ public class TrackPullTask(
|
||||
{
|
||||
var page = 1;
|
||||
var orders =
|
||||
await provideQueries.FindOrderWithServiceProviderAsync(config, page);
|
||||
await queries.FindOrderWithServiceProviderAsync(config, page);
|
||||
while (orders.Any())
|
||||
{
|
||||
var serviceProviderCode = orders.First().ServiceProviderCode;
|
||||
@ -122,7 +122,7 @@ public class TrackPullTask(
|
||||
}
|
||||
}
|
||||
|
||||
orders = await provideQueries.FindOrderWithServiceProviderAsync(config, ++page);
|
||||
orders = await queries.FindOrderWithServiceProviderAsync(config, ++page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,29 +21,16 @@ namespace TrackCenter.Application.Services.TrackOrders;
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "轨迹订单")]
|
||||
[Route("/Track/Order")]
|
||||
public class TrackOrderService : ApplicationServiceBase
|
||||
public class TrackOrderService(
|
||||
IOrderRepository orderRepository,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
ITrackPushRecordEsRepository trackPushRecordEsRepository,
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
IServiceProvider serviceProvider)
|
||||
: 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,
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
ITrackPushRecordEsRepository trackPushRecordEsRepository,
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
_trackDetailsEsRepository = trackDetailsEsRepository;
|
||||
_trackPushRecordEsRepository = trackPushRecordEsRepository;
|
||||
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
#region 轨迹订单列表
|
||||
|
||||
/// <summary>
|
||||
@ -55,7 +42,7 @@ public class TrackOrderService : ApplicationServiceBase
|
||||
public async Task<PagedResult<TrackOrderVO>> GetTrackOrderPagedList(GetPageTrackOrderInput input)
|
||||
{
|
||||
var vo = ObjectMapper.Map<GetPageTrackOrderInput, GetPageTrackOrderInputVO>(input);
|
||||
var pageList = await _orderRepository.GetTrackOrderPagedList(vo);
|
||||
var pageList = await orderRepository.GetTrackOrderPagedList(vo);
|
||||
return pageList;
|
||||
}
|
||||
|
||||
@ -70,7 +57,7 @@ public class TrackOrderService : ApplicationServiceBase
|
||||
[HttpPost("UpdateOrderStatus")]
|
||||
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: "订单不存在");
|
||||
|
||||
@ -81,7 +68,7 @@ public class TrackOrderService : ApplicationServiceBase
|
||||
}
|
||||
|
||||
order.IsCompleted = input.IsCompleted;
|
||||
var b = await _orderRepository.UpdateColumnAsync(order, x => new
|
||||
var b = await orderRepository.UpdateColumnAsync(order, x => new
|
||||
{
|
||||
x.IsCompleted
|
||||
});
|
||||
@ -101,7 +88,7 @@ public class TrackOrderService : ApplicationServiceBase
|
||||
[HttpGet("GetTrackPushRecords")]
|
||||
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);
|
||||
|
||||
@ -139,13 +126,13 @@ public class TrackOrderService : ApplicationServiceBase
|
||||
if (whereExpression == default)
|
||||
throw new BusinessException(message: "请求参数异常");
|
||||
|
||||
var order = await _orderRepository.GetFirstAsync(whereExpression);
|
||||
var order = await orderRepository.GetFirstAsync(whereExpression);
|
||||
if (order == default)
|
||||
{
|
||||
throw new BusinessException(message: "订单不存在");
|
||||
}
|
||||
|
||||
var trackDetails = await _trackDetailsEsRepository.GetListAsync(order.Id);
|
||||
var trackDetails = await trackDetailsEsRepository.GetListAsync(order.Id);
|
||||
if (trackDetails.IsNullOrEmpty())
|
||||
{
|
||||
return new List<TrackOrderDetailDto>();
|
||||
@ -209,11 +196,11 @@ public class TrackOrderService : ApplicationServiceBase
|
||||
{
|
||||
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)
|
||||
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();
|
||||
|
||||
@ -278,7 +265,7 @@ public class TrackOrderService : ApplicationServiceBase
|
||||
[HttpPost("DeleteTrackById")]
|
||||
public async Task<bool> DeleteTrackByIdAsync(string trackId)
|
||||
{
|
||||
var track = await _trackDetailsEsRepository.GetByIdAsync(trackId);
|
||||
var track = await trackDetailsEsRepository.GetByIdAsync(trackId);
|
||||
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (track == null)
|
||||
@ -286,7 +273,7 @@ public class TrackOrderService : ApplicationServiceBase
|
||||
throw new BusinessException(message: "轨迹不存在!");
|
||||
}
|
||||
|
||||
await _trackDetailsEsRepository.DeleteTrackByIdAsync(trackId, track.IndexName);
|
||||
await trackDetailsEsRepository.DeleteTrackByIdAsync(trackId, track.IndexName);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -298,14 +285,14 @@ public class TrackOrderService : ApplicationServiceBase
|
||||
[HttpGet("GetOrderOperationsById")]
|
||||
public async Task<List<OrderOperationRecordIndex>> GetOrderOperationsByIdAsync(int orderId)
|
||||
{
|
||||
return await _orderOperationRecordEsRepository.GetOrderOperationsByIdAsync(orderId);
|
||||
return await orderOperationRecordEsRepository.GetOrderOperationsByIdAsync(orderId);
|
||||
}
|
||||
|
||||
private void WarningCalc_OrderDeletedOrCompleted(OrderEntity order)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var scope = _serviceProvider.CreateAsyncScope();
|
||||
var scope = serviceProvider.CreateAsyncScope();
|
||||
try
|
||||
{
|
||||
var handler = scope.ServiceProvider.GetRequiredService<IOrderWarningHandler>();
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
using DotNetCore.CAP;
|
||||
using System.Data;
|
||||
using Fake.Messaging;
|
||||
using Fake.Domain.Dtos;
|
||||
using Fake.UnitOfWork;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using TrackCenter.Application.Dtos.Warning;
|
||||
using TrackCenter.Application.Dtos.Warning.Return;
|
||||
@ -25,7 +27,7 @@ public class ReturnWarningService : ApplicationServiceBase
|
||||
private readonly IReturnWarningHandler _returnWarningHandler;
|
||||
private readonly IWarningRuleRepository _warningRuleRepository;
|
||||
private readonly IWarningDetailsEsRepository _warningDetailsEsRepository;
|
||||
private readonly ICapPublisher _capPublisher;
|
||||
private readonly IMessagePublisher _messagePublisher;
|
||||
private readonly TrackStatusNameDictCache _trackStatusNameDictCache;
|
||||
private readonly AppNameDictCache _appNameDictCache;
|
||||
|
||||
@ -34,7 +36,7 @@ public class ReturnWarningService : ApplicationServiceBase
|
||||
IReturnWarningHandler returnWarningHandler,
|
||||
IWarningRuleRepository warningRuleRepository,
|
||||
IWarningDetailsEsRepository warningDetailsEsRepository,
|
||||
ICapPublisher capPublisher,
|
||||
IMessagePublisher messagePublisher,
|
||||
TrackStatusNameDictCache trackStatusNameDictCache,
|
||||
AppNameDictCache appNameDictCache
|
||||
)
|
||||
@ -43,7 +45,7 @@ public class ReturnWarningService : ApplicationServiceBase
|
||||
_returnWarningHandler = returnWarningHandler;
|
||||
_warningRuleRepository = warningRuleRepository;
|
||||
_warningDetailsEsRepository = warningDetailsEsRepository;
|
||||
_capPublisher = capPublisher;
|
||||
_messagePublisher = messagePublisher;
|
||||
_trackStatusNameDictCache = trackStatusNameDictCache;
|
||||
_appNameDictCache = appNameDictCache;
|
||||
}
|
||||
@ -220,7 +222,8 @@ public class ReturnWarningService : ApplicationServiceBase
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("ReturnPush")]
|
||||
public async Task<string> ReturnPush(ReturnPushInput input)
|
||||
[UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)]
|
||||
public virtual async Task<string> ReturnPush(ReturnPushInput input)
|
||||
{
|
||||
if (input.WarningDetailIds.Count == 0)
|
||||
{
|
||||
@ -276,7 +279,7 @@ public class ReturnWarningService : ApplicationServiceBase
|
||||
}
|
||||
};
|
||||
|
||||
await _capPublisher.PublishAsync(GlobalConst.TrackManualPushSubscribe, trackPushEvent);
|
||||
await _messagePublisher.PublishAsync(trackPushEvent);
|
||||
}
|
||||
|
||||
return $"即将进行{filtered.Count}个回传推送!";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="../../common.props" />
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fake.AspNetCore" Version="$(FakePackageVersion)" />
|
||||
<PackageReference Include="Fake.EventBus.RabbitMQ" Version="$(FakePackageVersion)" />
|
||||
<PackageReference Include="Fake.Messaging.RabbitMQ" Version="$(FakePackageVersion)" />
|
||||
<PackageReference Include="Fake.ObjectMapping.AutoMapper" Version="$(FakePackageVersion)" />
|
||||
<PackageReference Include="Fake.ObjectStorage.TencentCos" Version="$(FakePackageVersion)" />
|
||||
|
||||
@ -19,12 +19,6 @@
|
||||
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.4" />
|
||||
<PackageReference Include="OpenTelemetry.Api" 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>
|
||||
|
||||
@ -1,33 +1,26 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using DotNetCore.CAP;
|
||||
using DotNetCore.CAP.Internal;
|
||||
using DotNetCore.CAP.Messages;
|
||||
using Fake;
|
||||
using Fake.EventBus.RabbitMQ;
|
||||
using Fake.Messaging;
|
||||
using Fake.Messaging.RabbitMQ;
|
||||
using Fake.Modularity;
|
||||
using Fake.ObjectMapping.AutoMapper;
|
||||
using Fake.ObjectStorage;
|
||||
using Fake.RabbitMQ;
|
||||
using FreeRedis;
|
||||
using Hangfire;
|
||||
using Hangfire.Redis.StackExchange;
|
||||
using InterfaceForward.Domain.Shared.Helpers;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RabbitMQ.Client;
|
||||
using Savorboard.CAP.InMemoryMessageQueue;
|
||||
using TrackCenter.Application.Kafka;
|
||||
using TrackCenter.Application.CAP;
|
||||
using TrackCenter.Application.HostServices;
|
||||
using TrackCenter.Application.Kafka;
|
||||
using TrackCenter.Application.Messaging;
|
||||
using TrackCenter.Application.Options;
|
||||
using TrackCenter.Application.Rabbit;
|
||||
|
||||
namespace TrackCenter.Application;
|
||||
|
||||
[DependsOn(typeof(FakeRabbitMqModule))]
|
||||
[DependsOn(typeof(FakeObjectMappingAutoMapperModule))]
|
||||
[DependsOn(typeof(FakeEventBusRabbitMqModule))]
|
||||
[DependsOn(typeof(FakeMessagingRabbitMqModule))]
|
||||
[DependsOn(typeof(FakeObjectStorageTencentCosModule))]
|
||||
[DependsOn(typeof(TrackCenterDomainModule))]
|
||||
public class TrackCenterApplicationModule : FakeModule
|
||||
@ -41,106 +34,27 @@ public class TrackCenterApplicationModule : FakeModule
|
||||
options.ScanProfiles<TrackCenterApplicationModule>();
|
||||
});
|
||||
|
||||
var rabbitOptions = configuration.GetSection("RabbitMQ").Get<FakeRabbitMqOptions>()!.Default;
|
||||
|
||||
#region CAP
|
||||
|
||||
context.Services.AddSingleton<ISubscribeExecutor, MySubscribeExecutor>();
|
||||
if (!Debugger.IsAttached)
|
||||
// Messaging:独立队列;PrefetchCount=0 → ProcessorCount;接口通可按 TrackApp:TrackRefreshFetchCount 覆盖
|
||||
context.Services.Configure<MessagingOptions>(configuration.GetSection(MessagingOptions.SectionName));
|
||||
context.Services.Configure<RabbitMessageOptions>(o =>
|
||||
{
|
||||
context.Services.AddCap(options =>
|
||||
{
|
||||
options.UseMySql(configuration["ConnectionStrings:Default"]!);
|
||||
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");
|
||||
configuration.GetSection(RabbitMessageOptions.SectionName).Bind(o);
|
||||
|
||||
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 =>
|
||||
var refreshFetch = configuration.GetValue<ushort>("TrackApp:TrackRefreshFetchCount");
|
||||
if (refreshFetch > 0)
|
||||
{
|
||||
capOptions.UseInMemoryStorage();
|
||||
capOptions.UseInMemoryMessageQueue();
|
||||
capOptions.UseDashboard();
|
||||
}).AddSubscribeFilter<CapFilter>();
|
||||
context.Services.GetInitLogger<TrackCenterApplicationModule>().LogInformation("-- 当前为本地环境,用内存存储和内存队列");
|
||||
}
|
||||
|
||||
#endregion
|
||||
o.Handlers[nameof(TrackRefreshEventHandler)] =
|
||||
new RabbitMessageHandlerOptions { PrefetchCount = refreshFetch };
|
||||
o.Handlers[nameof(TrackPushCompletedEventHandler)] =
|
||||
new RabbitMessageHandlerOptions { PrefetchCount = refreshFetch };
|
||||
}
|
||||
});
|
||||
|
||||
var redisOptions = configuration.GetSection("Redis").Get<ConnectionStringBuilder>();
|
||||
if (redisOptions == null)
|
||||
throw new FakeException("未找到Redis相关配置,请检查配置文件中是否包含名为Redis的节点");
|
||||
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<InterfaceRelayParamsConfigOptions>(
|
||||
configuration.GetSection("InterfaceRelayParamsConfig"));
|
||||
|
||||
@ -41,6 +41,111 @@ public class GetPageTrackOrderInputVO : PageInfoInput
|
||||
/// 创建时间截止
|
||||
/// </summary>
|
||||
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>
|
||||
|
||||
@ -19,56 +19,14 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
|
||||
}
|
||||
public async Task<PagedResult<TrackOrderVO>> GetTrackOrderPagedList(GetPageTrackOrderInputVO input)
|
||||
{
|
||||
//查询完去重
|
||||
PagedResult<TrackOrderVO> pageList;
|
||||
//没传递单号时
|
||||
if (input.OrderNos.IsNullOrEmpty())
|
||||
{
|
||||
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)
|
||||
pageList = await ApplyTrackOrderListFilters(Context.Queryable<OrderEntity>(), input)
|
||||
.OrderByDescending(a => a.CreateTime)
|
||||
.Select(a => new TrackOrderVO
|
||||
{
|
||||
Id = a.Id,
|
||||
OrderNumber = a.OrderNo,
|
||||
OrderType = a.OrderType,
|
||||
QuestionType = a.QuestionType,
|
||||
Customer = a.Customer,
|
||||
NewestTrackStatus = a.LatestTrackStatus,
|
||||
NewestTrackContent = a.LatestTrackContent,
|
||||
NewestTrackEnContent = a.LatestTrackEnContent,
|
||||
NewestTrackPlace = a.LatestTrackPlace,
|
||||
NewestTrackTime = a.LatestTrackTime,
|
||||
NewestTrackTimeZone = a.LatestTrackTimeZone,
|
||||
IsFinish = a.IsCompleted,
|
||||
CreateTime = a.CreateTime,
|
||||
AppKey = a.AppKey,
|
||||
DestinationTimeZone = a.DestinationTimeZone,
|
||||
EstimatedDeliveryDate = a.EstimatedDeliveryDate,
|
||||
TrackNo = a.TrackNo,
|
||||
OtherNo = a.ThirdPartyOrderNumber,
|
||||
Channel = a.Channel,
|
||||
Product = a.ProductCode,
|
||||
ApiCode = a.ServiceProviderCode,
|
||||
CountryCode = a.ArrivalCountry,
|
||||
Province = a.ArrivalProvince,
|
||||
Postcode = a.ArrivalPostCode,
|
||||
City = a.ArrivalCity,
|
||||
Email = a.ArrivalEmail
|
||||
})
|
||||
.Select(TrackOrderListSelector)
|
||||
.ToPagedListAsync(input.PageIndex, input.PageSize);
|
||||
}
|
||||
//传递单号时
|
||||
else
|
||||
{
|
||||
var orderNos = input.OrderNos!.Distinct().ToList();
|
||||
@ -89,59 +47,18 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
|
||||
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.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)
|
||||
pageList = await ApplyTrackOrderListFilters(
|
||||
Context.Queryable<OrderEntity>().Where(a => orderIds.Contains(a.Id)), input)
|
||||
.OrderByDescending(a => a.CreateTime)
|
||||
.Select(a => new TrackOrderVO
|
||||
{
|
||||
Id = a.Id,
|
||||
OrderNumber = a.OrderNo,
|
||||
OrderType = a.OrderType,
|
||||
QuestionType = a.QuestionType,
|
||||
Customer = a.Customer,
|
||||
NewestTrackStatus = a.LatestTrackStatus,
|
||||
NewestTrackContent = a.LatestTrackContent,
|
||||
NewestTrackEnContent = a.LatestTrackEnContent,
|
||||
NewestTrackPlace = a.LatestTrackPlace,
|
||||
NewestTrackTime = a.LatestTrackTime,
|
||||
NewestTrackTimeZone = a.LatestTrackTimeZone,
|
||||
IsFinish = a.IsCompleted,
|
||||
CreateTime = a.CreateTime,
|
||||
AppKey = a.AppKey,
|
||||
DestinationTimeZone = a.DestinationTimeZone,
|
||||
EstimatedDeliveryDate = a.EstimatedDeliveryDate,
|
||||
TrackNo = a.TrackNo,
|
||||
OtherNo = a.ThirdPartyOrderNumber,
|
||||
Channel = a.Channel,
|
||||
Product = a.ProductCode,
|
||||
ApiCode = a.ServiceProviderCode,
|
||||
CountryCode = a.ArrivalCountry,
|
||||
Province = a.ArrivalProvince,
|
||||
Postcode = a.ArrivalPostCode,
|
||||
City = a.ArrivalCity,
|
||||
Email = a.ArrivalEmail
|
||||
})
|
||||
.Select(TrackOrderListSelector)
|
||||
.ToPagedListAsync(input.PageIndex, input.PageSize);
|
||||
}
|
||||
|
||||
//填充数描述信息
|
||||
pageList.Items.ForEach(x =>
|
||||
{
|
||||
x.OrderTypeName = x.OrderType.GetDescription();
|
||||
@ -151,6 +68,111 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
|
||||
return pageList;
|
||||
}
|
||||
|
||||
private static System.Linq.Expressions.Expression<Func<OrderEntity, TrackOrderVO>> TrackOrderListSelector =>
|
||||
a => new TrackOrderVO
|
||||
{
|
||||
Id = a.Id,
|
||||
OrderNumber = a.OrderNo,
|
||||
OrderType = a.OrderType,
|
||||
QuestionType = a.QuestionType,
|
||||
Customer = a.Customer,
|
||||
NewestTrackStatus = a.LatestTrackStatus,
|
||||
NewestTrackContent = a.LatestTrackContent,
|
||||
NewestTrackEnContent = a.LatestTrackEnContent,
|
||||
NewestTrackPlace = a.LatestTrackPlace,
|
||||
NewestTrackTime = a.LatestTrackTime,
|
||||
NewestTrackTimeZone = a.LatestTrackTimeZone,
|
||||
IsFinish = a.IsCompleted,
|
||||
CreateTime = a.CreateTime,
|
||||
AppKey = a.AppKey,
|
||||
DestinationTimeZone = a.DestinationTimeZone,
|
||||
EstimatedDeliveryDate = a.EstimatedDeliveryDate,
|
||||
TrackNo = a.TrackNo,
|
||||
OtherNo = a.ThirdPartyOrderNumber,
|
||||
Channel = a.Channel,
|
||||
Product = a.ProductCode,
|
||||
ApiCode = a.ServiceProviderCode,
|
||||
CountryCode = a.ArrivalCountry,
|
||||
Province = a.ArrivalProvince,
|
||||
Postcode = a.ArrivalPostCode,
|
||||
City = a.ArrivalCity,
|
||||
Email = a.ArrivalEmail
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 表头筛选:客户/单号/产品/渠道/轨迹内容等
|
||||
/// </summary>
|
||||
private static ISugarQueryable<OrderEntity> ApplyTrackOrderListFilters(
|
||||
ISugarQueryable<OrderEntity> query,
|
||||
GetPageTrackOrderInputVO input)
|
||||
{
|
||||
var trackNos = SplitMultiValues(input.TrackNo);
|
||||
var otherNos = SplitMultiValues(input.OtherNo);
|
||||
|
||||
query = query
|
||||
.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)
|
||||
.WhereIF(trackNos.Count == 1, a => a.TrackNo.Contains(trackNos[0]))
|
||||
.WhereIF(trackNos.Count > 1, a => trackNos.Contains(a.TrackNo))
|
||||
.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))
|
||||
.WhereIF(!input.Product.IsNullOrWhiteSpace(), a => a.ProductCode != null && a.ProductCode.Contains(input.Product!))
|
||||
.WhereIF(!input.Channel.IsNullOrWhiteSpace(), a => a.Channel != null && a.Channel.Contains(input.Channel!))
|
||||
.WhereIF(!input.ApiCode.IsNullOrWhiteSpace(),
|
||||
a => a.ServiceProviderCode != null && a.ServiceProviderCode.Contains(input.ApiCode!))
|
||||
.WhereIF(!input.CountryCode.IsNullOrWhiteSpace(), a => a.ArrivalCountry.Contains(input.CountryCode!))
|
||||
.WhereIF(!input.Province.IsNullOrWhiteSpace(), a => a.ArrivalProvince != null && a.ArrivalProvince.Contains(input.Province!))
|
||||
.WhereIF(!input.City.IsNullOrWhiteSpace(), a => a.ArrivalCity.Contains(input.City!))
|
||||
.WhereIF(!input.Postcode.IsNullOrWhiteSpace(),
|
||||
a => a.ArrivalPostCode != null && a.ArrivalPostCode.Contains(input.Postcode!))
|
||||
.WhereIF(!input.Email.IsNullOrWhiteSpace(), a => a.ArrivalEmail != null && a.ArrivalEmail.Contains(input.Email!))
|
||||
.WhereIF(!input.NewestTrackStatus.IsNullOrWhiteSpace(),
|
||||
a => a.LatestTrackStatus != null && a.LatestTrackStatus.Contains(input.NewestTrackStatus!))
|
||||
.WhereIF(!input.NewestTrackContent.IsNullOrWhiteSpace(),
|
||||
a => a.LatestTrackContent != null && a.LatestTrackContent.Contains(input.NewestTrackContent!))
|
||||
.WhereIF(!input.NewestTrackEnContent.IsNullOrWhiteSpace(),
|
||||
a => a.LatestTrackEnContent != null && a.LatestTrackEnContent.Contains(input.NewestTrackEnContent!))
|
||||
.WhereIF(!input.NewestTrackPlace.IsNullOrWhiteSpace(),
|
||||
a => a.LatestTrackPlace != null && a.LatestTrackPlace.Contains(input.NewestTrackPlace!))
|
||||
.WhereIF(input.NewestTrackTimeStart.HasValue && input.NewestTrackTimeEnd.HasValue,
|
||||
a => a.LatestTrackTime >= input.NewestTrackTimeStart && a.LatestTrackTime <= input.NewestTrackTimeEnd)
|
||||
.WhereIF(input.NewestTrackTimeStart.HasValue && !input.NewestTrackTimeEnd.HasValue,
|
||||
a => a.LatestTrackTime >= input.NewestTrackTimeStart)
|
||||
.WhereIF(!input.NewestTrackTimeStart.HasValue && input.NewestTrackTimeEnd.HasValue,
|
||||
a => a.LatestTrackTime <= input.NewestTrackTimeEnd)
|
||||
.WhereIF(input.NewestTrackTimeZone.HasValue, a => a.LatestTrackTimeZone == input.NewestTrackTimeZone)
|
||||
.WhereIF(input.IsFinish.HasValue, a => a.IsCompleted == input.IsFinish.Value)
|
||||
.WhereIF(input.EstimatedDeliveryDateStart.HasValue && input.EstimatedDeliveryDateEnd.HasValue,
|
||||
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)
|
||||
{
|
||||
if (raw.IsNullOrWhiteSpace()) return new List<string>();
|
||||
return raw!
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<(int MainOrderId, string OrderNo, string Customer)> GetTrackOrderByProviderAndOrderNo(
|
||||
string serviceProviderCode, string orderNo, string? appKey = null, TrackOrderType? orderType = null)
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user