Merge branch 'S07' into test
# Conflicts: # src/SJZY.Track.Application.Contract/SJZY.Track.Application.Contract.csproj
This commit is contained in:
commit
04d7cf1860
@ -2,21 +2,10 @@ using SJZY.Expand.ABP.Core.Configuration;
|
|||||||
using SJZY.Expand.ABP.Core.Configuration.AutoRegistOptions;
|
using SJZY.Expand.ABP.Core.Configuration.AutoRegistOptions;
|
||||||
using SJZY.Expand.ABP.Core.Logger;
|
using SJZY.Expand.ABP.Core.Logger;
|
||||||
using SJZY.Track.Api;
|
using SJZY.Track.Api;
|
||||||
using SJZY.Track.Application;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
if (args.Length > 0)
|
|
||||||
{
|
|
||||||
_BUGOptions.EnablePref = "on".Equals(args[0]);
|
|
||||||
Console.WriteLine($"是否启动优化模式:{_BUGOptions.EnablePref}");
|
|
||||||
if (_BUGOptions.EnablePref)
|
|
||||||
{
|
|
||||||
var loadSuccess = Confluent.Kafka.Library.Load(null);
|
|
||||||
Console.WriteLine($"加载{loadSuccess},版本号:{Confluent.Kafka.Library.VersionString}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder
|
builder
|
||||||
.AddConfigurationContainer()
|
.AddConfigurationContainer()
|
||||||
.AddOptionsFromConfiguration()
|
.AddOptionsFromConfiguration()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<Import Project="../../common.props" />
|
<Import Project="../../common.props"/>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
@ -10,17 +10,18 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.2.2" />
|
<PackageReference Include="FluentValidation.AspNetCore" Version="11.2.2"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.2"/>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0"/>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
|
||||||
<PackageReference Include="Volo.Abp.Autofac" Version="7.0.2" />
|
<PackageReference Include="Volo.Abp.Autofac" Version="7.0.2"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SJZY.Track.Application\SJZY.Track.Application.csproj" />
|
<ProjectReference Include="..\SJZY.Track.Application\SJZY.Track.Application.csproj"/>
|
||||||
<ProjectReference Include="..\SJZY.Track.Repository\SJZY.Track.Repository.csproj" />
|
<ProjectReference Include="..\SJZY.Track.Repository\SJZY.Track.Repository.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="kafkalib\**\*" CopyToPublishDirectory="Always" CopyToOutputDirectory="Always" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
BIN
src/SJZY.Track.Api/kafkalib/librdkafka++.so.1
Normal file
BIN
src/SJZY.Track.Api/kafkalib/librdkafka++.so.1
Normal file
Binary file not shown.
BIN
src/SJZY.Track.Api/kafkalib/librdkafka.so.1
Normal file
BIN
src/SJZY.Track.Api/kafkalib/librdkafka.so.1
Normal file
Binary file not shown.
@ -135,6 +135,11 @@ public class TrackChangeEto
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? EstimatedDeliveryDate { get; set; }
|
public DateTime? EstimatedDeliveryDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目的地时区
|
||||||
|
/// </summary>
|
||||||
|
public float? DestinationTimeZone { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 扩展信息字段,用与轨迹推送的附加字段
|
/// 扩展信息字段,用与轨迹推送的附加字段
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<Version>7.0.20</Version>
|
<Version>7.0.22</Version>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -6,7 +6,6 @@ using SJZY.Track.Application.Contract.Dtos.ProvideServices;
|
|||||||
using SJZY.Track.Application.Contract.Events;
|
using SJZY.Track.Application.Contract.Events;
|
||||||
using SJZY.Track.Application.Contract.Services;
|
using SJZY.Track.Application.Contract.Services;
|
||||||
using SJZY.Track.Application.Kafka;
|
using SJZY.Track.Application.Kafka;
|
||||||
using SJZY.Track.Application.Services.Warning.Handlers;
|
|
||||||
using SJZY.Track.Application.Services.Warning.Signals;
|
using SJZY.Track.Application.Services.Warning.Signals;
|
||||||
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
||||||
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
||||||
@ -25,8 +24,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
private readonly IProvideQueries _provideQueries;
|
private readonly IProvideQueries _provideQueries;
|
||||||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
||||||
private readonly IBroadcastingService _broadcastingService;
|
private readonly IBroadcastingService _broadcastingService;
|
||||||
// private readonly WarningSignalPublisher _warningSignalPublisher;
|
private readonly WarningSignalPublisher _warningSignalPublisher;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
|
|
||||||
public TrackDeleteEventHandler(
|
public TrackDeleteEventHandler(
|
||||||
IProvideQueries provideQueries,
|
IProvideQueries provideQueries,
|
||||||
@ -35,8 +33,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||||
IBroadcastingService broadcastingService,
|
IBroadcastingService broadcastingService,
|
||||||
IServiceProvider serviceProvider
|
WarningSignalPublisher warningSignalPublisher
|
||||||
//WarningSignalPublisher warningSignalPublisher
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_provideQueries = provideQueries;
|
_provideQueries = provideQueries;
|
||||||
@ -45,8 +42,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
_trackDetailsEsRepository = trackDetailsEsRepository;
|
_trackDetailsEsRepository = trackDetailsEsRepository;
|
||||||
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
|
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
|
||||||
_broadcastingService = broadcastingService;
|
_broadcastingService = broadcastingService;
|
||||||
_serviceProvider = serviceProvider;
|
_warningSignalPublisher = warningSignalPublisher;
|
||||||
//_warningSignalPublisher = warningSignalPublisher;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[CapSubscribe(GlobalConst.TrackDeleteSubscribe, Group = GlobalConst.TrackDeleteSubscribe)]
|
[CapSubscribe(GlobalConst.TrackDeleteSubscribe, Group = GlobalConst.TrackDeleteSubscribe)]
|
||||||
@ -327,6 +323,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
_broadcastingService.TrackChangePublicTopic(new TrackChangeEto
|
_broadcastingService.TrackChangePublicTopic(new TrackChangeEto
|
||||||
{
|
{
|
||||||
EstimatedDeliveryDate = track.EstimatedDeliveryDate,
|
EstimatedDeliveryDate = track.EstimatedDeliveryDate,
|
||||||
|
DestinationTimeZone = track.DestinationTimeZone,
|
||||||
TrackPlacePostcode = track.TrackPlacePostcode,
|
TrackPlacePostcode = track.TrackPlacePostcode,
|
||||||
TrackPlaceNation = track.TrackPlaceNation,
|
TrackPlaceNation = track.TrackPlaceNation,
|
||||||
TrackPlaceNationId = track.TrackPlaceNationId,
|
TrackPlaceNationId = track.TrackPlaceNationId,
|
||||||
@ -354,7 +351,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
OrderNo = track.OrderNo,
|
OrderNo = track.OrderNo,
|
||||||
OrderIdentifiers = new[]
|
OrderIdentifiers = new[]
|
||||||
{
|
{
|
||||||
new OrderIdentifier()
|
new OrderIdentifier
|
||||||
{
|
{
|
||||||
OrderId = track.OrderId,
|
OrderId = track.OrderId,
|
||||||
OrderNo = track.OrderNo,
|
OrderNo = track.OrderNo,
|
||||||
@ -368,43 +365,8 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void WarningCalc_DeletedTrack(List<TrackDetailsIndex> delTracks)
|
private void WarningCalc_DeletedTrack(List<TrackDetailsIndex> delTracks)
|
||||||
{
|
{
|
||||||
if (_BUGOptions.EnablePref)
|
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹删除, delTracks);
|
||||||
{
|
|
||||||
var scope = _serviceProvider.CreateScope();
|
|
||||||
var _warningSignalPublisher = scope.ServiceProvider.GetRequiredService<WarningSignalPublisher>();
|
|
||||||
_warningSignalPublisher.PublishWarningSignal(WarningSignalType.轨迹删除, delTracks);
|
|
||||||
scope.Dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//_warningSignalPublisher.PublishWarningSignal(WarningSignalType.轨迹删除, delTracks);
|
|
||||||
var signals = WarningSignal.FromTrack(delTracks, WarningSignalType.轨迹删除);
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var scope = _serviceProvider.CreateAsyncScope();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var handler = scope.ServiceProvider.GetRequiredService<ICompleteWarningHandler>();
|
|
||||||
var handler2 = scope.ServiceProvider.GetRequiredService<ITransportWarningHandler>();
|
|
||||||
var handler3 = scope.ServiceProvider.GetRequiredService<IReturnWarningHandler>();
|
|
||||||
|
|
||||||
await Task.Delay(1500); //确保落盘
|
|
||||||
await handler.TrackDeletedHandleAsync(signals);
|
|
||||||
await handler2.TrackDeletedHandleAsync(signals);
|
|
||||||
await handler3.TrackUpdatedOrDeletedHandleAsync(signals);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.Error(ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await scope.DisposeAsync();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -463,7 +463,7 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
if (string.IsNullOrWhiteSpace(compareFields))
|
if (string.IsNullOrWhiteSpace(compareFields))
|
||||||
return trackIdentifies.Contains($"{code}{timeStr}");
|
return trackIdentifies.Contains($"{code}{timeStr}");
|
||||||
|
|
||||||
var extra = x.GetValueOrDefault("extra");
|
var extra = x.GetValueOrDefault("extra");
|
||||||
if (extra == null) return false;
|
if (extra == null) return false;
|
||||||
|
|
||||||
if (((JsonElement) extra).TryGetProperty(compareFields, out var comparerValueJsonElement))
|
if (((JsonElement) extra).TryGetProperty(compareFields, out var comparerValueJsonElement))
|
||||||
@ -538,15 +538,22 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.Timestamp)
|
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.Timestamp)
|
||||||
return DateTimeHelper.ConvertToUnixTimestamp(dateTime).ToString();
|
return DateTimeHelper.ConvertToUnixTimestamp(dateTime).ToString();
|
||||||
|
|
||||||
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.DateTimeAndTimezone
|
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.DateTimeAndTimezone)
|
||||||
&& !string.IsNullOrEmpty(config.DateTimeFormat)
|
|
||||||
&& !string.IsNullOrEmpty(config.TimezoneFormat))
|
|
||||||
{
|
{
|
||||||
var zoneStr = DateTimeHelper.TimezoneFormat(timezone, config.TimezoneFormat);
|
// 只有时间
|
||||||
var datetimeStr = DateTimeHelper.FormatDateTime(dateTime, config.DateTimeFormat);
|
if (!string.IsNullOrWhiteSpace(config.DateTimeFormat) && string.IsNullOrWhiteSpace(config.TimezoneFormat))
|
||||||
return $"{datetimeStr} {zoneStr}";
|
return DateTimeHelper.FormatDateTime(dateTime, config.DateTimeFormat);
|
||||||
|
|
||||||
|
// 时间和时区
|
||||||
|
if (!string.IsNullOrWhiteSpace(config.DateTimeFormat) && !string.IsNullOrWhiteSpace(config.TimezoneFormat))
|
||||||
|
{
|
||||||
|
var zoneStr = DateTimeHelper.TimezoneFormat(timezone, config.TimezoneFormat);
|
||||||
|
var datetimeStr = DateTimeHelper.FormatDateTime(dateTime, config.DateTimeFormat);
|
||||||
|
return $"{datetimeStr} {zoneStr}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 默认为 时间+时区
|
||||||
return dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffK");
|
return dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ using SJZY.OTel;
|
|||||||
using SJZY.Track.Application.Contract.Events;
|
using SJZY.Track.Application.Contract.Events;
|
||||||
using SJZY.Track.Application.Contract.Services;
|
using SJZY.Track.Application.Contract.Services;
|
||||||
using SJZY.Track.Application.Kafka;
|
using SJZY.Track.Application.Kafka;
|
||||||
using SJZY.Track.Application.Services.Warning.Handlers;
|
|
||||||
using SJZY.Track.Application.Services.Warning.Signals;
|
using SJZY.Track.Application.Services.Warning.Signals;
|
||||||
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
||||||
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
||||||
@ -26,11 +25,8 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
||||||
private readonly IBroadcastingService _broadcastingService;
|
private readonly IBroadcastingService _broadcastingService;
|
||||||
private readonly ILogger<TrackUpsertEventHandler> _logger;
|
private readonly ILogger<TrackUpsertEventHandler> _logger;
|
||||||
|
|
||||||
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
|
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
|
||||||
|
private readonly WarningSignalPublisher _warningSignalPublisher;
|
||||||
//private readonly WarningSignalPublisher _warningSignalPublisher;
|
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
|
|
||||||
public TrackUpsertEventHandler(
|
public TrackUpsertEventHandler(
|
||||||
IOrderRepository orderRepository,
|
IOrderRepository orderRepository,
|
||||||
@ -40,8 +36,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||||
IBroadcastingService broadcastingService,
|
IBroadcastingService broadcastingService,
|
||||||
ILogger<TrackUpsertEventHandler> logger,
|
ILogger<TrackUpsertEventHandler> logger,
|
||||||
IServiceProvider serviceProvider
|
WarningSignalPublisher warningSignalPublisher
|
||||||
//WarningSignalPublisher warningSignalPublisher
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_orderRepository = orderRepository;
|
_orderRepository = orderRepository;
|
||||||
@ -51,8 +46,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
|
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
|
||||||
_broadcastingService = broadcastingService;
|
_broadcastingService = broadcastingService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceProvider = serviceProvider;
|
_warningSignalPublisher = warningSignalPublisher;
|
||||||
//_warningSignalPublisher = warningSignalPublisher;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[CapSubscribe(GlobalConst.TrackUpsertSubscribe, Group = GlobalConst.TrackUpsertSubscribe)]
|
[CapSubscribe(GlobalConst.TrackUpsertSubscribe, Group = GlobalConst.TrackUpsertSubscribe)]
|
||||||
@ -102,11 +96,10 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
// 不同子系统需要做内容映射
|
// 不同子系统需要做内容映射
|
||||||
if (@event.SubSystemId != nextEvent.SubSystemId)
|
if (@event.SubSystemId != nextEvent.SubSystemId)
|
||||||
{
|
{
|
||||||
var mapCode = (await _provideQueries
|
var mapCode = (await _provideQueries.MapTrackCodesAsync(@event.SubSystemId, nextEvent.SubSystemId, @event.TrackCode))
|
||||||
.MapTrackCodesAsync(@event.SubSystemId, nextEvent.SubSystemId, @event.TrackCode))
|
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (mapCode == default)
|
if (mapCode == null)
|
||||||
{
|
{
|
||||||
// 跨子系统没做映射的轨迹不处理
|
// 跨子系统没做映射的轨迹不处理
|
||||||
continue;
|
continue;
|
||||||
@ -518,75 +511,12 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
|||||||
|
|
||||||
private void WarningCalc_AddedTrack(List<TrackDetailsIndex> tracks)
|
private void WarningCalc_AddedTrack(List<TrackDetailsIndex> tracks)
|
||||||
{
|
{
|
||||||
if (_BUGOptions.EnablePref)
|
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹新增, tracks);
|
||||||
{
|
|
||||||
var scope = _serviceProvider.CreateScope();
|
|
||||||
var _warningSignalPublisher = scope.ServiceProvider.GetRequiredService<WarningSignalPublisher>();
|
|
||||||
_warningSignalPublisher.PublishWarningSignal(WarningSignalType.轨迹新增, tracks);
|
|
||||||
scope.Dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//_warningSignalPublisher.PublishWarningSignal(WarningSignalType.轨迹新增, tracks);
|
|
||||||
var signals = WarningSignal.FromTrack(tracks, WarningSignalType.轨迹新增);
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var scope = _serviceProvider.CreateAsyncScope();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var handler = scope.ServiceProvider.GetRequiredService<ICompleteWarningHandler>();
|
|
||||||
var handler2 = scope.ServiceProvider.GetRequiredService<ITransportWarningHandler>();
|
|
||||||
|
|
||||||
await Task.Delay(1500); //确保落盘
|
|
||||||
await handler.TrackAddedHandleAsync(signals);
|
|
||||||
await handler2.TrackAddedOrUpdatedHandleAsync(signals);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.Error(ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await scope.DisposeAsync();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void WarningCalc_UpdatedTrack(List<TrackDetailsIndex> tracks)
|
private void WarningCalc_UpdatedTrack(List<TrackDetailsIndex> tracks)
|
||||||
{
|
{
|
||||||
if (_BUGOptions.EnablePref)
|
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹更新, tracks);
|
||||||
{
|
|
||||||
var scope = _serviceProvider.CreateScope();
|
|
||||||
var _warningSignalPublisher = scope.ServiceProvider.GetRequiredService<WarningSignalPublisher>();
|
|
||||||
_warningSignalPublisher.PublishWarningSignal(WarningSignalType.轨迹更新, tracks);
|
|
||||||
scope.Dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//_warningSignalPublisher.PublishWarningSignal(WarningSignalType.轨迹更新, tracks);
|
|
||||||
var signals = WarningSignal.FromTrack(tracks, WarningSignalType.轨迹更新);
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var scope = _serviceProvider.CreateAsyncScope();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var handler = scope.ServiceProvider.GetRequiredService<ITransportWarningHandler>();
|
|
||||||
var handler2 = scope.ServiceProvider.GetRequiredService<IReturnWarningHandler>();
|
|
||||||
|
|
||||||
await Task.Delay(1500); //确保落盘
|
|
||||||
await handler.TrackAddedOrUpdatedHandleAsync(signals);
|
|
||||||
await handler2.TrackUpdatedOrDeletedHandleAsync(signals);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.Error(ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await scope.DisposeAsync();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
namespace SJZY.Track.Application.Hangfire;
|
|
||||||
|
|
||||||
// 发送消息接口参数
|
|
||||||
// https://centerapi.test.shijizhongyun.com/userApi/index.html?urls.primaryName=%E6%B6%88%E6%81%AF%E8%AE%A2%E9%98%85%E5%AE%9E%E4%BE%8B
|
|
||||||
/// MsgSubscribeInstance/SendMessageByTemplate
|
|
||||||
public class NotifyConfig
|
|
||||||
{
|
|
||||||
private const string ContentKey = "content"; //内容参数
|
|
||||||
|
|
||||||
public bool IsNeedToHandle { get; set; } = true; //是否需要处理(默认为未处理,传false为无需处理,针对站内信是否处理功能)
|
|
||||||
public int MsgSource { get; set; } = 1; // 消息来源:1.消息 2.任务 3.流程 4.消息提醒
|
|
||||||
public int SystemId { get; set; } = 9498; //子系统id 业务中台
|
|
||||||
public string TemplateCode { get; set; } = "TrackWarningNotify"; //模板code
|
|
||||||
public List<int> Channels { get; set; } //1:站内信 2.飞书 4.邮箱 8.短信 16.语音播报
|
|
||||||
public List<Variable> Variables { get; set; } //模板参数
|
|
||||||
public List<Receiver> Receivers { get; set; }
|
|
||||||
|
|
||||||
public sealed record Variable(string Key, string Value);
|
|
||||||
|
|
||||||
public sealed record Receiver(
|
|
||||||
int ReceiverId, //接收人id : 内部用户Id,外部用户Id,或只有邮箱跟短信时不需要接收人Id
|
|
||||||
int RecipientType = 1, //接收人类型 (1:内部用户 2.外部用户)
|
|
||||||
string? ReceiverName = null, //接收人姓名
|
|
||||||
string? Mail = null, //邮箱
|
|
||||||
string? MobilePhone = null //手机号
|
|
||||||
);
|
|
||||||
|
|
||||||
public static NotifyConfig CreateConfig(int userId, string message, List<int> notifyModes)
|
|
||||||
{
|
|
||||||
return new NotifyConfig
|
|
||||||
{
|
|
||||||
Channels = notifyModes,
|
|
||||||
Receivers = new List<Receiver>(1)
|
|
||||||
{
|
|
||||||
new Receiver(userId)
|
|
||||||
},
|
|
||||||
Variables = new List<Variable>(1)
|
|
||||||
{
|
|
||||||
new Variable(ContentKey, message)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NotifyConfig CreateConfig(int userId, string message)
|
|
||||||
{
|
|
||||||
return new NotifyConfig
|
|
||||||
{
|
|
||||||
Channels = new List<int>(2)
|
|
||||||
{
|
|
||||||
1,
|
|
||||||
2
|
|
||||||
},
|
|
||||||
Receivers = new List<Receiver>
|
|
||||||
{
|
|
||||||
new Receiver(userId)
|
|
||||||
},
|
|
||||||
Variables = new List<Variable>
|
|
||||||
{
|
|
||||||
new Variable(ContentKey, message)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using SJZY.Track.Application.Contract.Dtos.Warning;
|
using SJZY.Track.Application.Contract.Dtos.Warning;
|
||||||
using SJZY.Track.Application.Helpers;
|
|
||||||
using SJZY.Track.Application.Options;
|
using SJZY.Track.Application.Options;
|
||||||
using SJZY.Track.Application.Services.Warning.Model;
|
using SJZY.Track.Application.Services.Warning.Model;
|
||||||
using SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
|
using SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
|
||||||
@ -43,7 +42,7 @@ public sealed class WarningNotifyAggregator : ITransientDependency
|
|||||||
// 2.以人的维度聚合通知信息,并生成配置参数NotifyConfig
|
// 2.以人的维度聚合通知信息,并生成配置参数NotifyConfig
|
||||||
// 3.构建并调度一个调用通知接口的立即执行任务
|
// 3.构建并调度一个调用通知接口的立即执行任务
|
||||||
// 4.绑定这个任务id(可能是多个)和jobId用户跟踪任务状态
|
// 4.绑定这个任务id(可能是多个)和jobId用户跟踪任务状态
|
||||||
public async Task<(List<NotifyConfig> NotifyConfigs, HashSet<int> taskIds)> ScanNotifyAndAggregateAsync()
|
public async Task<(List<WarningNotifyParam> NotifyConfigs, HashSet<int> taskIds)> ScanNotifyAndAggregateAsync()
|
||||||
{
|
{
|
||||||
// 构建查询游标 todo:全量查询需要优化,直接存缓存?
|
// 构建查询游标 todo:全量查询需要优化,直接存缓存?
|
||||||
// var options = _optionsMonitor.CurrentValue;
|
// var options = _optionsMonitor.CurrentValue;
|
||||||
@ -210,7 +209,7 @@ public sealed class WarningNotifyAggregator : ITransientDependency
|
|||||||
|
|
||||||
// 2.检查明细中为人工介入的状态
|
// 2.检查明细中为人工介入的状态
|
||||||
var detailsIdSet = filtered.Select(x => x.WarningDetailsId).ToHashSet();
|
var detailsIdSet = filtered.Select(x => x.WarningDetailsId).ToHashSet();
|
||||||
var warningDetails = await _warningDetailsEsRepository.GetReturnWarningDetailsAsync(new GetReturnWarningDetailsVO()
|
var warningDetails = await _warningDetailsEsRepository.GetReturnWarningDetailsAsync(new GetReturnWarningDetailsVO
|
||||||
{
|
{
|
||||||
DetailsIds = detailsIdSet,
|
DetailsIds = detailsIdSet,
|
||||||
Status = new HashSet<int>(1)
|
Status = new HashSet<int>(1)
|
||||||
@ -261,10 +260,9 @@ public sealed class WarningNotifyAggregator : ITransientDependency
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<NotifyConfig> BuildNotifyConfigs(List<WarningNotifyTaskEntity> needNotifyTasks)
|
private List<WarningNotifyParam> BuildNotifyConfigs(List<WarningNotifyTaskEntity> needNotifyTasks)
|
||||||
{
|
{
|
||||||
var systemId = _optionsMonitor.CurrentValue.MiddleSystemId;
|
var result = new List<WarningNotifyParam>();
|
||||||
var result = new List<NotifyConfig>();
|
|
||||||
if (needNotifyTasks.Count == 0) return result;
|
if (needNotifyTasks.Count == 0) return result;
|
||||||
|
|
||||||
// 1.以人的维度对不同类别的预警通知分组聚合
|
// 1.以人的维度对不同类别的预警通知分组聚合
|
||||||
@ -293,109 +291,51 @@ public sealed class WarningNotifyAggregator : ITransientDependency
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.使用消息模板构建通知消息,并发送通知
|
// 2.使用消息模板构建通知参数,稍后排队发送通知
|
||||||
foreach (var (userId, typeNotifyTaskDict) in userIdNotifyTaskDict)
|
foreach (var (userId, typeNotifyTaskDict) in userIdNotifyTaskDict)
|
||||||
{
|
{
|
||||||
var notifyMessage = BuildNotifyMessage(typeNotifyTaskDict);
|
|
||||||
//聚合时出现 用户a
|
//聚合时出现 用户a
|
||||||
//接收回传预警,命中规则1,通知方式(飞书,站内信) ,命中规则2,通知方式(飞书)
|
//接收回传预警,命中规则1,通知方式(飞书,站内信) ,命中规则2,通知方式(飞书)
|
||||||
//接收运输预警,命中规则1,通知方式(飞书,站内信) ,命中规则2,通知方式(飞书)
|
//接收运输预警,命中规则1,通知方式(飞书,站内信) ,命中规则2,通知方式(飞书)
|
||||||
//接收完整预警,命中规则1,通知方式(飞书) ,命中规则2,通知方式(飞书,站内信)
|
//接收完整预警,命中规则1,通知方式(飞书) ,命中规则2,通知方式(飞书,站内信)
|
||||||
//此时聚合后,消息为:总共6条预警消息,其中回传xx条,运输xx条,完整xx条
|
//此时聚合后,消息为:总共6条预警消息,其中回传xx条,运输xx条,完整xx条
|
||||||
//同时使用飞书和站内信两种方式通知
|
//同时使用飞书和站内信两种方式通知
|
||||||
var config = NotifyConfig.CreateConfig(userId, notifyMessage, new List<int>
|
result.Add(BuildNotifyParam(userId, typeNotifyTaskDict));
|
||||||
{
|
|
||||||
1,
|
|
||||||
2
|
|
||||||
});
|
|
||||||
config.SystemId = systemId;
|
|
||||||
result.Add(config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildNotifyMessage(Dictionary<int, List<WarningNotifyTaskEntity>> typeNotifyTaskDict)
|
private WarningNotifyParam BuildNotifyParam(int userId, Dictionary<int, List<WarningNotifyTaskEntity>> typeNotifyTaskDict)
|
||||||
{
|
{
|
||||||
var options = _optionsMonitor.CurrentValue;
|
var options = _optionsMonitor.CurrentValue;
|
||||||
var hours = options.ScanWarningNotifyTaskHourInterval;
|
var hours = options.ScanWarningNotifyTaskHourInterval;
|
||||||
var transportCount = 0;
|
var param = new WarningNotifyParam
|
||||||
var transportLastNHoursCount = 0;
|
{
|
||||||
var returnCount = 0;
|
UserId = userId,
|
||||||
var returnLastNHoursCount = 0;
|
Hours = hours
|
||||||
var completeCount = 0;
|
};
|
||||||
var completeLastNHoursCount = 0;
|
|
||||||
Func<WarningNotifyTaskEntity, bool> hoursNBefore = x => DateTime.Now - x.CreateTime <= TimeSpan.FromHours(hours);
|
|
||||||
|
|
||||||
|
// tips:获取过去x小时的增量
|
||||||
|
Func<WarningNotifyTaskEntity, bool> hoursNBefore = x => DateTime.Now - x.CreateTime <= TimeSpan.FromHours(hours);
|
||||||
if (typeNotifyTaskDict.TryGetValue(WarningRuleType.运输时效预警.GetHashCode(), out var transportTimelinessNotifyTasks))
|
if (typeNotifyTaskDict.TryGetValue(WarningRuleType.运输时效预警.GetHashCode(), out var transportTimelinessNotifyTasks))
|
||||||
{
|
{
|
||||||
transportLastNHoursCount = transportTimelinessNotifyTasks.Count(hoursNBefore);
|
param.TransportLastNHoursCount = transportTimelinessNotifyTasks.Count(hoursNBefore);
|
||||||
transportCount = transportTimelinessNotifyTasks.Count;
|
param.TransportCount = transportTimelinessNotifyTasks.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeNotifyTaskDict.TryGetValue(WarningRuleType.回传时效预警.GetHashCode(), out var returnTimelinessNotifyTasks))
|
if (typeNotifyTaskDict.TryGetValue(WarningRuleType.回传时效预警.GetHashCode(), out var returnTimelinessNotifyTasks))
|
||||||
{
|
{
|
||||||
returnLastNHoursCount = returnTimelinessNotifyTasks.Count(hoursNBefore);
|
param.ReturnLastNHoursCount = returnTimelinessNotifyTasks.Count(hoursNBefore);
|
||||||
returnCount = returnTimelinessNotifyTasks.Count;
|
param.ReturnCount = returnTimelinessNotifyTasks.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeNotifyTaskDict.TryGetValue(WarningRuleType.完整预警.GetHashCode(), out var completeWarningNotifyTasks))
|
if (typeNotifyTaskDict.TryGetValue(WarningRuleType.完整预警.GetHashCode(), out var completeWarningNotifyTasks))
|
||||||
{
|
{
|
||||||
completeLastNHoursCount = completeWarningNotifyTasks.Count(hoursNBefore);
|
param.CompleteLastNHoursCount = completeWarningNotifyTasks.Count(hoursNBefore);
|
||||||
completeCount = completeWarningNotifyTasks.Count;
|
param.CompleteCount = completeWarningNotifyTasks.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotifyMessageBuilder.Build(
|
return param;
|
||||||
hours,
|
|
||||||
transportCount,
|
|
||||||
transportLastNHoursCount,
|
|
||||||
returnCount,
|
|
||||||
returnLastNHoursCount,
|
|
||||||
completeCount,
|
|
||||||
completeLastNHoursCount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标题:预警通知
|
|
||||||
// 内容:尊敬的用户,截止到2024-12-13 10:00:01(UTC +8:00),轨迹中心监测到与您相关的预警风险信息共计299 条,请及时处理,详情如下:
|
|
||||||
// 运输时效预警共计100条,其中过去4小时内新增的预警10条,查看详情
|
|
||||||
// 回传时效预警共计99条,其中过去4小时内新增的预警10条,查看详情
|
|
||||||
// 轨迹完整预警共计100条,其中过去4小时内新增的预警10条,查看详情
|
|
||||||
public static class NotifyMessageBuilder
|
|
||||||
{
|
|
||||||
public const string Title = "预警通知";
|
|
||||||
|
|
||||||
public static string Build(int hours,
|
|
||||||
int transportCount, int transportLastNHoursCount,
|
|
||||||
int returnCount, int returnLastNHoursCount,
|
|
||||||
int completeCount, int completeLastNHoursCount)
|
|
||||||
{
|
|
||||||
var dateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|
||||||
var totalCount = transportCount + returnCount + completeCount;
|
|
||||||
// todo:无法使用a标签填充(可能是为了安全考虑),需要具体给定一个样式
|
|
||||||
var url1 = "";
|
|
||||||
var url2 = "";
|
|
||||||
var url3 = "";
|
|
||||||
|
|
||||||
var message = $"{Title}\n尊敬的用户,截止到{dateTime}({BuildLocalTimezoneStr()}),轨迹中心监测到与您相关的预警风险信息共计{totalCount} 条,请及时处理,详情如下:";
|
|
||||||
|
|
||||||
if (transportCount > 0)
|
|
||||||
message += $"\n运输时效预警共计{transportCount}条,其中过去{hours}小时内新增的预警{transportLastNHoursCount}条";
|
|
||||||
if (returnCount > 0)
|
|
||||||
message += $"\n回传时效预警共计{returnCount}条,其中过去{hours}小时内新增的预警{returnLastNHoursCount}条";
|
|
||||||
if (completeCount > 0)
|
|
||||||
message += $"\n轨迹完整预警共计{completeCount}条,其中过去{hours}小时内新增的预警{completeLastNHoursCount}条";
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建本地时区字符串标识 e.g UTC +8:00
|
|
||||||
private static string BuildLocalTimezoneStr()
|
|
||||||
{
|
|
||||||
var offset = TimeZoneInfo.Local.BaseUtcOffset;
|
|
||||||
var flag = offset.Hours > 0 ? "+" : "";
|
|
||||||
var offsetStr = offset.ToString().RemovePostFix(":00");
|
|
||||||
return $"UTC {flag}{offsetStr}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
122
src/SJZY.Track.Application/Hangfire/WarningNotifyBuilder.cs
Normal file
122
src/SJZY.Track.Application/Hangfire/WarningNotifyBuilder.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
using FeishuNetSdk;
|
||||||
|
using FeishuNetSdk.Im;
|
||||||
|
using FeishuNetSdk.Im.Dtos;
|
||||||
|
using SJZY.UserCenter.SharedModel.Dtos.Message;
|
||||||
|
using SJZY.UserCenter.SharedModel.Enums;
|
||||||
|
|
||||||
|
namespace SJZY.Track.Application.Hangfire;
|
||||||
|
|
||||||
|
// 标题:预警通知
|
||||||
|
// 内容:尊敬的用户,截止到2024-12-13 10:00:01(UTC +8:00),轨迹中心监测到与您相关的预警风险信息共计299 条,请及时处理,详情如下:
|
||||||
|
// 运输时效预警共计100条,其中过去4小时内新增的预警10条,查看详情
|
||||||
|
// 回传时效预警共计99条,其中过去4小时内新增的预警10条,查看详情
|
||||||
|
// 轨迹完整预警共计100条,其中过去4小时内新增的预警10条,查看详情
|
||||||
|
public static class WarningNotifyBuilder
|
||||||
|
{
|
||||||
|
private const string Title = "预警通知";
|
||||||
|
|
||||||
|
// 构建站内信发消息请求体
|
||||||
|
public static SendMessageByTemplateInput BuildZhanNeiXinMessage(WarningNotifyParam param, int systemId)
|
||||||
|
{
|
||||||
|
const string templateCode = "TrackWarningNotify"; // 模板code
|
||||||
|
const string contentKey = "content"; //内容参数
|
||||||
|
var dateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
var totalCount = param.TransportCount + param.ReturnCount + param.CompleteCount;
|
||||||
|
|
||||||
|
// todo:无法使用a标签填充(可能是为了安全考虑),需要具体给定一个样式
|
||||||
|
var url1 = "";
|
||||||
|
var url2 = "";
|
||||||
|
var url3 = "";
|
||||||
|
|
||||||
|
var content = $"{Title}\n尊敬的用户,截止到{dateTime}({BuildLocalTimezoneStr()}),轨迹中心监测到与您相关的预警风险信息共计{totalCount} 条,请及时处理,详情如下:";
|
||||||
|
|
||||||
|
if (param.TransportCount > 0)
|
||||||
|
content += $"\n运输时效预警共计{param.TransportCount}条,其中过去{param.Hours}小时内新增的预警{param.TransportLastNHoursCount}条";
|
||||||
|
if (param.ReturnCount > 0)
|
||||||
|
content += $"\n回传时效预警共计{param.ReturnCount}条,其中过去{param.Hours}小时内新增的预警{param.ReturnLastNHoursCount}条";
|
||||||
|
if (param.CompleteCount > 0)
|
||||||
|
content += $"\n轨迹完整预警共计{param.CompleteCount}条,其中过去{param.Hours}小时内新增的预警{param.CompleteLastNHoursCount}条";
|
||||||
|
|
||||||
|
// 构建用户中心发消息请求体对象
|
||||||
|
var dto = new SendMessageByTemplateInput
|
||||||
|
{
|
||||||
|
IsNeedToHandle = true,
|
||||||
|
MsgSource = MsgSourceEnum.消息,
|
||||||
|
SystemId = systemId,
|
||||||
|
templateCode = templateCode,
|
||||||
|
channels = new List<ChannelEnum>(1)
|
||||||
|
{
|
||||||
|
ChannelEnum.站内信
|
||||||
|
},
|
||||||
|
variables = new List<VariableDto>(1)
|
||||||
|
{
|
||||||
|
new VariableDto
|
||||||
|
{
|
||||||
|
Key = contentKey,
|
||||||
|
Value = content
|
||||||
|
}
|
||||||
|
},
|
||||||
|
receivers = new List<MsgInstanceReceiverInput>(1)
|
||||||
|
{
|
||||||
|
new MsgInstanceReceiverInput
|
||||||
|
{
|
||||||
|
RecipientType = 1, //接收人类型 (1:内部用户 2.外部用户)
|
||||||
|
ReceiverId = param.UserId, //接收人id
|
||||||
|
ReceiverName = null, //接收人名称
|
||||||
|
Mail = null, // 邮箱
|
||||||
|
MobilePhone = null, // 手机号
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建飞书卡片发消息请求体
|
||||||
|
public static PostImV1MessagesBodyDto BuildFeiShuCard(WarningNotifyParam param, string fsUserId)
|
||||||
|
{
|
||||||
|
var totalCount = param.TransportCount + param.ReturnCount + param.CompleteCount;
|
||||||
|
var body = new List<object>
|
||||||
|
{
|
||||||
|
new MarkdownElement(Content: $"尊敬的用户,截止到{DateTime.Now:yyyy-MM-dd HH:mm:ss}({BuildLocalTimezoneStr()}),轨迹中心监测到与您相关的预警风险信息共计{totalCount} 条,请及时处理,详情如下:", TextAlign: "left", TextSize: "normal_v2")
|
||||||
|
};
|
||||||
|
// todo:补充点击链接
|
||||||
|
var url1 = "";
|
||||||
|
var url2 = "";
|
||||||
|
var url3 = "";
|
||||||
|
if (param.TransportCount > 0)
|
||||||
|
body.Add(new MarkdownElement(Content: $"运输时效预警共计{param.TransportCount}条,其中过去{param.Hours}小时内新增的预警{param.TransportLastNHoursCount}条 [查看详情]({url1})", TextAlign: "left", TextSize: "normal_v2"));
|
||||||
|
if (param.ReturnCount > 0)
|
||||||
|
body.Add(new MarkdownElement(Content: $"轨迹回传时效预警共计{param.ReturnCount}条,其中过去{param.Hours}小时内新增的预警{param.ReturnLastNHoursCount}条 [查看详情]({url2})", TextAlign: "left", TextSize: "normal_v2"));
|
||||||
|
if (param.CompleteCount > 0)
|
||||||
|
body.Add(new MarkdownElement(Content: $"轨迹完整预警共计{param.CompleteCount}条,其中过去{param.Hours}小时内新增的预警{param.CompleteLastNHoursCount}条 [查看详情]({url3})", TextAlign: "left", TextSize: "normal_v2"));
|
||||||
|
|
||||||
|
var dto = new PostImV1MessagesBodyDto
|
||||||
|
{
|
||||||
|
MsgType = "interactive",
|
||||||
|
ReceiveId = fsUserId
|
||||||
|
};
|
||||||
|
dto.SetContent(new ElementsCardV2Dto
|
||||||
|
{
|
||||||
|
// 配置卡片全局行为
|
||||||
|
Config = new ElementsCardV2Dto.ConfigSuffix(),
|
||||||
|
Header = new ElementsCardV2Dto.HeaderSuffix()
|
||||||
|
{
|
||||||
|
Title = new HeaderTitleElement(Title),
|
||||||
|
Template = "red"
|
||||||
|
},
|
||||||
|
Body = new ElementsCardV2Dto.BodySuffix(body.ToArray()),
|
||||||
|
});
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建本地时区字符串标识 e.g UTC +8:00
|
||||||
|
private static string BuildLocalTimezoneStr()
|
||||||
|
{
|
||||||
|
var offset = TimeZoneInfo.Local.BaseUtcOffset;
|
||||||
|
var flag = offset.Hours > 0 ? "+" : "";
|
||||||
|
var offsetStr = offset.ToString().RemovePostFix(":00");
|
||||||
|
return $"UTC {flag}{offsetStr}";
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/SJZY.Track.Application/Hangfire/WarningNotifyParam.cs
Normal file
23
src/SJZY.Track.Application/Hangfire/WarningNotifyParam.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace SJZY.Track.Application.Hangfire;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建轨迹预警通知消息所需的参数
|
||||||
|
/// </summary>
|
||||||
|
public class WarningNotifyParam
|
||||||
|
{
|
||||||
|
public int UserId { get; set; }
|
||||||
|
|
||||||
|
public int Hours { get; set; }
|
||||||
|
|
||||||
|
public int TransportCount { get; set; }
|
||||||
|
|
||||||
|
public int TransportLastNHoursCount { get; set; }
|
||||||
|
|
||||||
|
public int ReturnCount { get; set; }
|
||||||
|
|
||||||
|
public int ReturnLastNHoursCount { get; set; }
|
||||||
|
|
||||||
|
public int CompleteCount { get; set; }
|
||||||
|
|
||||||
|
public int CompleteLastNHoursCount { get; set; }
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
using FeishuNetSdk;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using SJZY.Expand.ABP.Core.Logger;
|
using SJZY.Expand.ABP.Core.Logger;
|
||||||
@ -11,9 +12,8 @@ using SJZY.Track.Domain.Aggregates.WarningAggregate.Return;
|
|||||||
using SJZY.Track.Domain.Dtos.Warning;
|
using SJZY.Track.Domain.Dtos.Warning;
|
||||||
using SJZY.Track.Domain.Enums;
|
using SJZY.Track.Domain.Enums;
|
||||||
using SJZY.Track.Domain.Extensions;
|
using SJZY.Track.Domain.Extensions;
|
||||||
|
using SJZY.Track.Domain.Queries;
|
||||||
using SJZY.UserCenter.HttpApi.Services.Message;
|
using SJZY.UserCenter.HttpApi.Services.Message;
|
||||||
using SJZY.UserCenter.SharedModel.Dtos.Message;
|
|
||||||
using SJZY.UserCenter.SharedModel.Enums;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Http.Client.DynamicProxying;
|
using Volo.Abp.Http.Client.DynamicProxying;
|
||||||
|
|
||||||
@ -21,8 +21,8 @@ namespace SJZY.Track.Application.Hangfire;
|
|||||||
|
|
||||||
public interface IWarningTaskService
|
public interface IWarningTaskService
|
||||||
{
|
{
|
||||||
[AutomaticRetry(Attempts = 3)]
|
[AutomaticRetry(Attempts = 1)]
|
||||||
Task CallNotifyAsync(NotifyConfig config, CancellationToken ct);
|
Task CallNotifyAsync(WarningNotifyParam param, CancellationToken ct);
|
||||||
|
|
||||||
[AutomaticRetry(Attempts = 3, ExceptOn = new[] { typeof(BusinessException) })]
|
[AutomaticRetry(Attempts = 3, ExceptOn = new[] { typeof(BusinessException) })]
|
||||||
Task CallTrackRetryPushAsync(ReturnWarningRetryPushTaskEntity entity, CancellationToken ct);
|
Task CallTrackRetryPushAsync(ReturnWarningRetryPushTaskEntity entity, CancellationToken ct);
|
||||||
@ -30,51 +30,57 @@ public interface IWarningTaskService
|
|||||||
|
|
||||||
public class WarningTaskService : IWarningTaskService, ITransientDependency
|
public class WarningTaskService : IWarningTaskService, ITransientDependency
|
||||||
{
|
{
|
||||||
|
private readonly IOptionsMonitor<InterfaceRelayParamsConfigOptions> _interfaceRelayOptions;
|
||||||
|
private readonly IOptionsMonitor<TrackWarningTaskOptions> _warningTaskOptions;
|
||||||
private readonly IHttpClientProxy<IRequestSendMessageService> _requestSendMessageService;
|
private readonly IHttpClientProxy<IRequestSendMessageService> _requestSendMessageService;
|
||||||
private readonly IHttpClientProxy<IInterfaceForwardServiceV2> _interfaceForwardServiceV2;
|
private readonly IHttpClientProxy<IInterfaceForwardServiceV2> _interfaceForwardServiceV2;
|
||||||
private readonly IOptionsMonitor<InterfaceRelayParamsConfigOptions> _options;
|
private readonly IFeishuTenantApi _feishuTenantApi;
|
||||||
private readonly IWarningDetailsEsRepository _warningDetailsEsRepository;
|
private readonly IWarningDetailsEsRepository _warningDetailsEsRepository;
|
||||||
private readonly IWarningTaskRepository _warningTaskRepository;
|
private readonly IWarningTaskRepository _warningTaskRepository;
|
||||||
|
private readonly IVFsUserQueries _vfsUserQueries;
|
||||||
|
|
||||||
public WarningTaskService(IHttpClientProxy<IInterfaceForwardServiceV2> interfaceForwardServiceV2, IOptionsMonitor<InterfaceRelayParamsConfigOptions> options, IHttpClientProxy<IRequestSendMessageService> requestSendMessageService, IWarningDetailsEsRepository warningDetailsEsRepository, IWarningTaskRepository warningTaskRepository)
|
public WarningTaskService(
|
||||||
|
IOptionsMonitor<InterfaceRelayParamsConfigOptions> interfaceRelayOptions,
|
||||||
|
IOptionsMonitor<TrackWarningTaskOptions> warningTaskOptions,
|
||||||
|
IHttpClientProxy<IInterfaceForwardServiceV2> interfaceForwardServiceV2,
|
||||||
|
IHttpClientProxy<IRequestSendMessageService> requestSendMessageService,
|
||||||
|
IFeishuTenantApi feishuTenantApi,
|
||||||
|
IWarningDetailsEsRepository warningDetailsEsRepository,
|
||||||
|
IWarningTaskRepository warningTaskRepository,
|
||||||
|
IVFsUserQueries vfsUserQueries)
|
||||||
{
|
{
|
||||||
|
_interfaceRelayOptions = interfaceRelayOptions;
|
||||||
|
_warningTaskOptions = warningTaskOptions;
|
||||||
_interfaceForwardServiceV2 = interfaceForwardServiceV2;
|
_interfaceForwardServiceV2 = interfaceForwardServiceV2;
|
||||||
_requestSendMessageService = requestSendMessageService;
|
_requestSendMessageService = requestSendMessageService;
|
||||||
|
_feishuTenantApi = feishuTenantApi;
|
||||||
_warningDetailsEsRepository = warningDetailsEsRepository;
|
_warningDetailsEsRepository = warningDetailsEsRepository;
|
||||||
_warningTaskRepository = warningTaskRepository;
|
_warningTaskRepository = warningTaskRepository;
|
||||||
_options = options;
|
_vfsUserQueries = vfsUserQueries;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region 用户中心调用,通知(一次)
|
#region 用户中心调用,通知(一次)
|
||||||
|
|
||||||
[AutomaticRetry(Attempts = 3)]
|
[AutomaticRetry(Attempts = 1)]
|
||||||
public async Task CallNotifyAsync(NotifyConfig config, CancellationToken ct)
|
public async Task CallNotifyAsync(WarningNotifyParam param, CancellationToken ct)
|
||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
var r = await _requestSendMessageService.Service.SendMessageByTemplateAsync(new SendMessageByTemplateInput
|
var options = _warningTaskOptions.CurrentValue;
|
||||||
{
|
|
||||||
IsNeedToHandle = config.IsNeedToHandle,
|
|
||||||
MsgSource = (MsgSourceEnum) config.MsgSource,
|
|
||||||
SystemId = config.SystemId,
|
|
||||||
templateCode = config.TemplateCode,
|
|
||||||
channels = config.Channels.Select(x => (ChannelEnum) x).ToList(),
|
|
||||||
variables = config.Variables.Select(x => new VariableDto()
|
|
||||||
{
|
|
||||||
Key = x.Key,
|
|
||||||
Value = x.Value
|
|
||||||
}).ToList(),
|
|
||||||
receivers = config.Receivers.Select(x => new MsgInstanceReceiverInput()
|
|
||||||
{
|
|
||||||
ReceiverId = x.ReceiverId,
|
|
||||||
RecipientType = x.RecipientType
|
|
||||||
}).ToList(),
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// 发送站内信
|
||||||
|
// https://centerapi.test.shijizhongyun.com/userApi/index.html?urls.primaryName=%E6%B6%88%E6%81%AF%E8%AE%A2%E9%98%85%E5%AE%9E%E4%BE%8B
|
||||||
|
// MsgSubscribeInstance/SendMessageByTemplate
|
||||||
|
var r = await _requestSendMessageService.Service.SendMessageByTemplateAsync(WarningNotifyBuilder.BuildZhanNeiXinMessage(param, options.MiddleSystemId));
|
||||||
if (r.Code != GlobalConst.SuccessCode)
|
if (r.Code != GlobalConst.SuccessCode)
|
||||||
{
|
throw new BusinessException(message: $"用户中心调用失败:SendMessageByTemplateAsync Msg:{r.Msg}");
|
||||||
throw new BusinessException(message: $"用户中心调用失败:SendMessageByTemplateAsync" +
|
|
||||||
$"Msg:{r.Msg}");
|
// 发送FeiShu卡片信息
|
||||||
}
|
var fsUserId = await _vfsUserQueries.GetFirstAsync(x => x.UserCenterId == param.UserId, y => y.FsEmployeeId);
|
||||||
|
if (string.IsNullOrWhiteSpace(fsUserId))
|
||||||
|
throw new BusinessException(message: "查询用户中心失败:SendMessageByTemplateAsync Msg:未找到用户对应的飞书用户Id");
|
||||||
|
var res = await _feishuTenantApi.PostImV1MessagesAsync("user_id", WarningNotifyBuilder.BuildFeiShuCard(param, fsUserId));
|
||||||
|
if (res.Code != 0)
|
||||||
|
throw new BusinessException(message: $"飞书调用失败:SendMessageByTemplateAsync Msg:{res.Msg}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -107,8 +113,8 @@ public class WarningTaskService : IWarningTaskService, ITransientDependency
|
|||||||
using (Track2InterfaceRelaySubSystemContext.Use(new Track2InterfaceRelaySubSystemContext(task.SubSystemCode)))
|
using (Track2InterfaceRelaySubSystemContext.Use(new Track2InterfaceRelaySubSystemContext(task.SubSystemCode)))
|
||||||
{
|
{
|
||||||
var upStreamCode = isAdd
|
var upStreamCode = isAdd
|
||||||
? _options.CurrentValue.PushTrackInterfaceCode
|
? _interfaceRelayOptions.CurrentValue.PushTrackInterfaceCode
|
||||||
: _options.CurrentValue.TrackUpdateInterfaceCode;
|
: _interfaceRelayOptions.CurrentValue.TrackUpdateInterfaceCode;
|
||||||
|
|
||||||
var r = await _interfaceForwardServiceV2.Service.BatchForwardThenPushToQueueAsync(
|
var r = await _interfaceForwardServiceV2.Service.BatchForwardThenPushToQueueAsync(
|
||||||
upStreamCode: upStreamCode,
|
upStreamCode: upStreamCode,
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Confluent.Kafka;
|
using Confluent.Kafka;
|
||||||
using Confluent.Kafka.Core.Consumer;
|
|
||||||
using Confluent.Kafka.Core.Producer;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using SJZY.Expand.ABP.Core.Logger;
|
using SJZY.Expand.ABP.Core.Logger;
|
||||||
@ -20,23 +18,20 @@ namespace SJZY.Track.Application.Kafka;
|
|||||||
public sealed class WarningCalcBackgroundService : BackgroundService
|
public sealed class WarningCalcBackgroundService : BackgroundService
|
||||||
{
|
{
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
private readonly IKafkaConsumer<WarningSignalKey, WarningSignal> _consumer;
|
private readonly IConsumer<WarningSignalKey, WarningSignal> _consumer;
|
||||||
private readonly IKafkaProducer<WarningSignalKey, WarningSignal> _producer;
|
private readonly WarningSignalPublisher _warningSignalPublisher;
|
||||||
private readonly IKafkaProducer<int, WarningSignal> _dlxProducer;
|
|
||||||
private TrackWarningTaskOptions _options;
|
private TrackWarningTaskOptions _options;
|
||||||
|
|
||||||
public WarningCalcBackgroundService(
|
public WarningCalcBackgroundService(
|
||||||
IOptionsMonitor<TrackWarningTaskOptions> optionsMonitor,
|
IOptionsMonitor<TrackWarningTaskOptions> optionsMonitor,
|
||||||
IServiceScopeFactory serviceScopeFactory,
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
IKafkaConsumer<WarningSignalKey, WarningSignal> consumer,
|
IConsumer<WarningSignalKey, WarningSignal> consumer,
|
||||||
IKafkaProducer<WarningSignalKey, WarningSignal> producer,
|
WarningSignalPublisher warningSignalPublisher
|
||||||
IKafkaProducer<int, WarningSignal> dlxProducer
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
_consumer = consumer;
|
_consumer = consumer;
|
||||||
_producer = producer;
|
_warningSignalPublisher = warningSignalPublisher;
|
||||||
_dlxProducer = dlxProducer;
|
|
||||||
_options = optionsMonitor.CurrentValue;
|
_options = optionsMonitor.CurrentValue;
|
||||||
optionsMonitor.OnChange(x => _options = x);
|
optionsMonitor.OnChange(x => _options = x);
|
||||||
}
|
}
|
||||||
@ -175,7 +170,7 @@ public sealed class WarningCalcBackgroundService : BackgroundService
|
|||||||
var tasks = needRetryMetadata.Select(async metadata =>
|
var tasks = needRetryMetadata.Select(async metadata =>
|
||||||
{
|
{
|
||||||
metadata.SetTrackSignalMessageHeader();
|
metadata.SetTrackSignalMessageHeader();
|
||||||
await _producer.ProduceAsync(metadata.OwenMessage, CancellationToken.None);
|
await _warningSignalPublisher.PublishSignalAsync(metadata.OwenMessage, CancellationToken.None);
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
@ -193,7 +188,7 @@ public sealed class WarningCalcBackgroundService : BackgroundService
|
|||||||
{
|
{
|
||||||
metadata.SetTrackSignalMessageHeader();
|
metadata.SetTrackSignalMessageHeader();
|
||||||
var msg = metadata.OwenMessage;
|
var msg = metadata.OwenMessage;
|
||||||
await _dlxProducer.ProduceAsync(new Message<int, WarningSignal>
|
await _warningSignalPublisher.PublishDlxSignalAsync(new Message<int, WarningSignal>
|
||||||
{
|
{
|
||||||
Key = msg.Value.OrderId,
|
Key = msg.Value.OrderId,
|
||||||
Value = msg.Value,
|
Value = msg.Value,
|
||||||
@ -282,6 +277,5 @@ public sealed class WarningCalcBackgroundService : BackgroundService
|
|||||||
base.Dispose();
|
base.Dispose();
|
||||||
_consumer.Close();
|
_consumer.Close();
|
||||||
_consumer.Dispose();
|
_consumer.Dispose();
|
||||||
_producer.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,13 @@
|
|||||||
//#define ProtoBuf
|
|
||||||
#define NewtonsoftJson
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Confluent.Kafka;
|
using Confluent.Kafka;
|
||||||
using Confluent.Kafka.Core.Consumer;
|
|
||||||
using Confluent.Kafka.Core.Producer;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Converters;
|
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
using SJZY.Expand.ABP.Core.Logger;
|
using SJZY.Expand.ABP.Core.Logger;
|
||||||
using SJZY.Track.Application.Helpers;
|
using SJZY.Track.Application.Helpers;
|
||||||
using SJZY.Track.Application.Services.Warning.Signals;
|
using SJZY.Track.Application.Services.Warning.Signals;
|
||||||
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
namespace SJZY.Track.Application.Kafka;
|
namespace SJZY.Track.Application.Kafka;
|
||||||
|
|
||||||
@ -22,6 +15,7 @@ public static class WarningCalcServiceExtension
|
|||||||
{
|
{
|
||||||
public static void AddWarningCalcServices(this IServiceCollection services, IConfiguration conf)
|
public static void AddWarningCalcServices(this IServiceCollection services, IConfiguration conf)
|
||||||
{
|
{
|
||||||
|
PrintKafkaLibVersion();
|
||||||
var env = "-" + conf.GetValue<string>("Environment")!.ToLower();
|
var env = "-" + conf.GetValue<string>("Environment")!.ToLower();
|
||||||
// 预警信号批处理后台任务
|
// 预警信号批处理后台任务
|
||||||
services.AddHostedService<WarningCalcBackgroundService>();
|
services.AddHostedService<WarningCalcBackgroundService>();
|
||||||
@ -37,140 +31,194 @@ public static class WarningCalcServiceExtension
|
|||||||
|
|
||||||
private static void RegisterConsumer<TKey, TValue>(IServiceCollection services, IConfiguration conf, string groupId, string[] subTopic)
|
private static void RegisterConsumer<TKey, TValue>(IServiceCollection services, IConfiguration conf, string groupId, string[] subTopic)
|
||||||
{
|
{
|
||||||
services.AddSingleton<IKafkaConsumer<TKey, TValue>>(sp =>
|
services.AddJsonSerializer<TKey>();
|
||||||
|
services.AddJsonSerializer<TValue>();
|
||||||
|
services.AddSingleton<IConsumer<TKey, TValue>>(sp =>
|
||||||
{
|
{
|
||||||
var bb = new KafkaConsumerBuilder<TKey, TValue>()
|
|
||||||
.WithConsumerConfiguration(o =>
|
|
||||||
{
|
|
||||||
o.WithClientId(Dns.GetHostName())
|
|
||||||
.WithGroupId(groupId)
|
|
||||||
.WithBootstrapServers(conf.GetValue<string>("Kafka:BootstrapServers"))
|
|
||||||
.WithAutoOffsetReset(AutoOffsetReset.Earliest)
|
|
||||||
//偏移提交控制
|
|
||||||
.WithEnablePartitionEof(false)
|
|
||||||
.WithEnableAutoOffsetStore(false)
|
|
||||||
.WithEnableAutoCommit(false)
|
|
||||||
//链接控制
|
|
||||||
.WithSessionTimeoutMs(conf.GetValue<int>("Kafka:Consumer:SessionTimeoutMs"))
|
|
||||||
.WithMaxPollIntervalMs(conf.GetValue<int>("Kafka:Consumer:MaxPollIntervalMs"))
|
|
||||||
.WithHeartbeatIntervalMs(conf.GetValue<int>("Kafka:Consumer:HeartbeatIntervalMs"))
|
|
||||||
//吞吐量
|
|
||||||
.WithFetchMinBytes(conf.GetValue<int>("Kafka:Consumer:FetchMinBytes"))
|
|
||||||
.WithFetchMaxBytes(conf.GetValue<int>("Kafka:Consumer:FetchMaxBytes"))
|
|
||||||
.WithFetchWaitMaxMs(conf.GetValue<int>("Kafka:Consumer:FetchWaitMaxMs"))
|
|
||||||
//日志
|
|
||||||
.WithDebug("consumer,cgrp,protocol")
|
|
||||||
.WithTopicSubscriptions(subTopic);
|
|
||||||
});
|
|
||||||
|
|
||||||
bb.WithLoggerFactory(sp.GetRequiredService<ILoggerFactory>());
|
|
||||||
DebounceHelper.DebounceWrapper? reportFuncWrapper = null;
|
DebounceHelper.DebounceWrapper? reportFuncWrapper = null;
|
||||||
bb.WithErrorHandler((consumer, error) =>
|
var bb = new ConsumerBuilder<TKey, TValue>(
|
||||||
{
|
new ConsumerConfig
|
||||||
if (error.IsFatal)
|
{
|
||||||
|
ClientId = Dns.GetHostName(),
|
||||||
|
GroupId = groupId,
|
||||||
|
BootstrapServers = conf.GetValue<string>("Kafka:BootstrapServers"),
|
||||||
|
AutoOffsetReset = AutoOffsetReset.Earliest,
|
||||||
|
//偏移提交控制
|
||||||
|
EnablePartitionEof = false,
|
||||||
|
EnableAutoOffsetStore = false,
|
||||||
|
EnableAutoCommit = false,
|
||||||
|
//链接控制
|
||||||
|
SessionTimeoutMs = conf.GetValue<int>("Kafka:Consumer:SessionTimeoutMs"),
|
||||||
|
MaxPollIntervalMs = conf.GetValue<int>("Kafka:Consumer:MaxPollIntervalMs"),
|
||||||
|
HeartbeatIntervalMs = conf.GetValue<int>("Kafka:Consumer:HeartbeatIntervalMs"),
|
||||||
|
//吞吐量
|
||||||
|
FetchMinBytes = conf.GetValue<int>("Kafka:Consumer:FetchMinBytes"),
|
||||||
|
FetchMaxBytes = conf.GetValue<int>("Kafka:Consumer:FetchMaxBytes"),
|
||||||
|
FetchWaitMaxMs = conf.GetValue<int>("Kafka:Consumer:FetchWaitMaxMs"),
|
||||||
|
//日志
|
||||||
|
Debug = "consumer,cgrp,protocol",
|
||||||
|
CancellationDelayMaxMs = 100
|
||||||
|
})
|
||||||
|
.SetKeyDeserializer(sp.GetRequiredService<JsonCoreSerializer<TKey>>())
|
||||||
|
.SetValueDeserializer(sp.GetRequiredService<JsonCoreSerializer<TValue>>())
|
||||||
|
.SetErrorHandler((_, error) =>
|
||||||
{
|
{
|
||||||
LogHelper.Error($"Kafka致命错误${error}");
|
if (error.IsFatal)
|
||||||
return;
|
{
|
||||||
}
|
LogHelper.Error($"Kafka致命错误${error}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// tips: 防抖以防止高频上报错误
|
// tips: 防抖以防止高频上报错误
|
||||||
reportFuncWrapper ??= DebounceHelper.WrapMethod(TimeSpan.FromSeconds(5), () =>
|
reportFuncWrapper ??= DebounceHelper.WrapMethod(TimeSpan.FromSeconds(5), () =>
|
||||||
{
|
{
|
||||||
if (error.Code == ErrorCode.Local_Transport)
|
if (error.Code == ErrorCode.Local_Transport)
|
||||||
LogHelper.Error($"Kafka链接错误{error}");
|
LogHelper.Error($"Kafka链接错误{error}");
|
||||||
else if (error.Code == ErrorCode.Local_AllBrokersDown)
|
else if (error.Code == ErrorCode.Local_AllBrokersDown)
|
||||||
LogHelper.Error($"Kafka所有Broker都挂了{error}");
|
LogHelper.Error($"Kafka所有Broker都挂了{error}");
|
||||||
|
});
|
||||||
|
reportFuncWrapper.Debounce();
|
||||||
});
|
});
|
||||||
reportFuncWrapper.Debounce();
|
|
||||||
});
|
|
||||||
|
|
||||||
#if ProtoBuf
|
|
||||||
bb.WithProtobufNetDeserializer();
|
|
||||||
|
|
||||||
#elif NewtonsoftJson
|
var c = bb.Build();
|
||||||
bb.WithNewtonsoftJsonDeserializer(builder =>
|
c.Subscribe(subTopic);
|
||||||
builder.WithNullValueHandling(NullValueHandling.Ignore) // Ignores null values
|
return c;
|
||||||
.WithReferenceLoopHandling(ReferenceLoopHandling.Ignore) // Ignores reference loops
|
|
||||||
.WithContractResolver(new CamelCasePropertyNamesContractResolver()) // Camel case property names
|
|
||||||
);
|
|
||||||
#else
|
|
||||||
bb.WithJsonCoreDeserializer(bc =>
|
|
||||||
bc.WithReferenceHandler(ReferenceHandler.IgnoreCycles) // Handles circular references
|
|
||||||
.WithPropertyNamingPolicy(JsonNamingPolicy.CamelCase) // Uses camelCase naming
|
|
||||||
.WithDefaultIgnoreCondition(JsonIgnoreCondition.WhenWritingNull)
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
return (bb.Build() as IKafkaConsumer<TKey, TValue>)!;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterProducer<TKey, TValue>(IServiceCollection services, IConfiguration conf, string? defaultTopic = null)
|
private static void RegisterProducer<TKey, TValue>(IServiceCollection services, IConfiguration conf, string? defaultTopic = null)
|
||||||
{
|
{
|
||||||
services.AddSingleton<IKafkaProducer<TKey, TValue>>(sp =>
|
services.AddJsonSerializer<TKey>();
|
||||||
|
services.AddJsonSerializer<TValue>();
|
||||||
|
services.AddSingleton<IProducer<TKey, TValue>>(sp =>
|
||||||
{
|
{
|
||||||
var bb = new KafkaProducerBuilder<TKey, TValue>()
|
|
||||||
.WithProducerConfiguration(o =>
|
|
||||||
{
|
|
||||||
o.WithClientId(Dns.GetHostName())
|
|
||||||
.WithBootstrapServers(conf.GetValue<string>("Kafka:BootstrapServers"))
|
|
||||||
// 确保消息发送可靠
|
|
||||||
.WithAcks(Acks.All)
|
|
||||||
.WithEnableIdempotence(true) // 幂等发送
|
|
||||||
.WithMessageSendMaxRetries(conf.GetValue<int>("Kafka:Producer:MessageSendMaxRetries"))
|
|
||||||
.WithRequestTimeoutMs(conf.GetValue<int>("Kafka:Producer:RequestTimeoutMs"))
|
|
||||||
// 消息体大小
|
|
||||||
.WithCompressionType(conf.GetValue<CompressionType>("Kafka:Producer:CompressionType"))
|
|
||||||
.WithCompressionLevel(conf.GetValue<int>("Kafka:Producer:CompressionLevel")) //Gzip 0-9 LZ4 0-12 数值越大压缩效果越好cpu占用越多
|
|
||||||
// 消息发送速率控制(任意一条件先达到就发送)
|
|
||||||
.WithLingerMs(conf.GetValue<int>("Kafka:Producer:LingerMs"))
|
|
||||||
.WithBatchSize(conf.GetValue<int>("Kafka:Producer:BatchSize"))
|
|
||||||
.WithBatchNumMessages(conf.GetValue<int>("Kafka:Producer:BatchNumMessages"))
|
|
||||||
// 发送缓冲内存限制
|
|
||||||
.WithQueueBufferingMaxMessages(conf.GetValue<int>("Kafka:Producer:QueueBufferingMaxMessages"))
|
|
||||||
.WithQueueBufferingMaxKbytes(conf.GetValue<int>("Kafka:Producer:QueueBufferingMaxKbytes"))
|
|
||||||
// 分区器 一致性hash/随机
|
|
||||||
.WithPartitioner(conf.GetValue<Partitioner>("Kafka:Producer:Partitioner"))
|
|
||||||
.WithDefaultTopic(defaultTopic);
|
|
||||||
});
|
|
||||||
|
|
||||||
bb.WithLoggerFactory(sp.GetRequiredService<ILoggerFactory>());
|
|
||||||
DebounceHelper.DebounceWrapper? reportFuncWrapper = null;
|
DebounceHelper.DebounceWrapper? reportFuncWrapper = null;
|
||||||
bb.WithErrorHandler((producer, error) =>
|
var bb = new ProducerBuilder<TKey, TValue>(new ProducerConfig()
|
||||||
{
|
|
||||||
if (error.IsFatal)
|
|
||||||
{
|
{
|
||||||
LogHelper.Error($"Kafka致命错误${error}");
|
ClientId = Dns.GetHostName(),
|
||||||
return;
|
BootstrapServers = conf.GetValue<string>("Kafka:BootstrapServers"),
|
||||||
}
|
// 确保消息发送可靠
|
||||||
|
Acks = Acks.All,
|
||||||
|
EnableIdempotence = true,
|
||||||
|
MessageSendMaxRetries = conf.GetValue<int>("Kafka:Producer:MessageSendMaxRetries"),
|
||||||
|
RequestTimeoutMs = conf.GetValue<int>("Kafka:Producer:RequestTimeoutMs"),
|
||||||
|
// 消息体大小
|
||||||
|
CompressionType = conf.GetValue<CompressionType>("Kafka:Producer:CompressionType"),
|
||||||
|
CompressionLevel = conf.GetValue<int>("Kafka:Producer:CompressionLevel"),
|
||||||
|
// 消息发送速率控制(任意一条件先达到就发送)
|
||||||
|
LingerMs = conf.GetValue<int>("Kafka:Producer:LingerMs"),
|
||||||
|
BatchSize = conf.GetValue<int>("Kafka:Producer:BatchSize"),
|
||||||
|
BatchNumMessages = conf.GetValue<int>("Kafka:Producer:BatchNumMessages"),
|
||||||
|
// 发送缓冲内存限制
|
||||||
|
QueueBufferingMaxMessages = conf.GetValue<int>("Kafka:Producer:QueueBufferingMaxMessages"),
|
||||||
|
QueueBufferingMaxKbytes = conf.GetValue<int>("Kafka:Producer:QueueBufferingMaxKbytes"),
|
||||||
|
// 分区器 一致性hash/随机
|
||||||
|
Partitioner = conf.GetValue<Partitioner>("Kafka:Producer:Partitioner"),
|
||||||
|
})
|
||||||
|
.SetKeySerializer(sp.GetRequiredService<JsonCoreSerializer<TKey>>())
|
||||||
|
.SetValueSerializer(sp.GetRequiredService<JsonCoreSerializer<TValue>>())
|
||||||
|
.SetErrorHandler((_, error) =>
|
||||||
|
{
|
||||||
|
if (error.IsFatal)
|
||||||
|
{
|
||||||
|
LogHelper.Error($"Kafka致命错误${error}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// tips: 防抖以防止高频上报错误
|
// tips: 防抖以防止高频上报错误
|
||||||
reportFuncWrapper ??= DebounceHelper.WrapMethod(TimeSpan.FromSeconds(5), () =>
|
reportFuncWrapper ??= DebounceHelper.WrapMethod(TimeSpan.FromSeconds(5), () =>
|
||||||
{
|
{
|
||||||
if (error.Code == ErrorCode.Local_Transport)
|
if (error.Code == ErrorCode.Local_Transport)
|
||||||
LogHelper.Error($"Kafka链接错误{error}");
|
LogHelper.Error($"Kafka链接错误{error}");
|
||||||
else if (error.Code == ErrorCode.Local_AllBrokersDown)
|
else if (error.Code == ErrorCode.Local_AllBrokersDown)
|
||||||
LogHelper.Error($"Kafka所有Broker都挂了{error}");
|
LogHelper.Error($"Kafka所有Broker都挂了{error}");
|
||||||
|
});
|
||||||
|
reportFuncWrapper.Debounce();
|
||||||
});
|
});
|
||||||
reportFuncWrapper.Debounce();
|
|
||||||
});
|
|
||||||
|
|
||||||
#if ProtoBuf
|
return bb.Build();
|
||||||
// 2000w条消息数据包压缩从 2g -> 1g
|
|
||||||
bb.WithProtobufNetSerializer();
|
|
||||||
#elif NewtonsoftJson
|
|
||||||
bb.WithNewtonsoftJsonSerializer(builder =>
|
|
||||||
builder.WithNullValueHandling(NullValueHandling.Ignore) // Ignores null values
|
|
||||||
.WithReferenceLoopHandling(ReferenceLoopHandling.Ignore) // Ignores reference loops
|
|
||||||
.WithContractResolver(new CamelCasePropertyNamesContractResolver()) // Camel case property names
|
|
||||||
);
|
|
||||||
#else
|
|
||||||
bb.WithJsonCoreSerializer(bc =>
|
|
||||||
bc.WithReferenceHandler(ReferenceHandler.IgnoreCycles) // Handles circular references
|
|
||||||
.WithPropertyNamingPolicy(JsonNamingPolicy.CamelCase) // Uses camelCase naming
|
|
||||||
.WithDefaultIgnoreCondition(JsonIgnoreCondition.WhenWritingNull)
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
return (bb.Build() as IKafkaProducer<TKey, TValue>)!;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddJsonSerializer<T>(this IServiceCollection services, Action<JsonSerializerOptions>? setup = null)
|
||||||
|
{
|
||||||
|
services.TryAddSingleton(_ =>
|
||||||
|
{
|
||||||
|
var o = new JsonSerializerOptions();
|
||||||
|
setup?.Invoke(o);
|
||||||
|
return new JsonCoreSerializer<T>(o);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ConsumeResult<TKey, TValue>> ConsumeBatch<TKey, TValue>(this IConsumer<TKey, TValue> consumer, int batchSize, TimeSpan timeout)
|
||||||
|
{
|
||||||
|
using var cts = new CancellationTokenSource(timeout);
|
||||||
|
var list = new List<ConsumeResult<TKey, TValue>>();
|
||||||
|
while (list.Count < batchSize && !cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var consumeResult = consumer.Consume(100);
|
||||||
|
if (consumeResult != null)
|
||||||
|
list.Add(consumeResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PrintKafkaLibVersion()
|
||||||
|
{
|
||||||
|
var loadSuccess = Library.Load(null);
|
||||||
|
Console.WriteLine($@"
|
||||||
|
+-------------------------------------------------------+
|
||||||
|
| |
|
||||||
|
| LibrdKafka |
|
||||||
|
| 加载:{loadSuccess},版本号:{Library.VersionString} |
|
||||||
|
| |
|
||||||
|
+-------------------------------------------------------+
|
||||||
|
");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class JsonCoreSerializer<T> : ISerializer<T>, IDeserializer<T>
|
||||||
|
{
|
||||||
|
private readonly Encoding _encoding;
|
||||||
|
private readonly JsonSerializerOptions _options;
|
||||||
|
|
||||||
|
public JsonCoreSerializer(JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
_encoding = Encoding.UTF8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Serialize(T data, SerializationContext context)
|
||||||
|
{
|
||||||
|
if (data is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(data, data.GetType(), _options);
|
||||||
|
|
||||||
|
var result = _encoding.GetBytes(json);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context)
|
||||||
|
{
|
||||||
|
if (isNull)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NETSTANDARD2_0
|
||||||
|
var json = _encoding.GetString(data.ToArray());
|
||||||
|
#else
|
||||||
|
var json = _encoding.GetString(data);
|
||||||
|
#endif
|
||||||
|
var result = JsonSerializer.Deserialize<T>(json, _options);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
using Confluent.Kafka;
|
using Confluent.Kafka;
|
||||||
using Confluent.Kafka.Core.Producer;
|
using Microsoft.Extensions.Configuration;
|
||||||
using SJZY.Track.Application.Services.Warning.Signals;
|
using SJZY.Track.Application.Services.Warning.Signals;
|
||||||
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
@ -9,27 +9,48 @@ namespace SJZY.Track.Application.Kafka;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 预警信号发布者
|
/// 预警信号发布者
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class WarningSignalPublisher : ITransientDependency
|
public sealed class WarningSignalPublisher : IDisposable, ISingletonDependency
|
||||||
{
|
{
|
||||||
private readonly IKafkaProducer<WarningSignalKey, WarningSignal> _producer;
|
private readonly IProducer<WarningSignalKey, WarningSignal> _producer;
|
||||||
|
private readonly IProducer<int, WarningSignal> _dlxProducer;
|
||||||
|
private readonly string env;
|
||||||
|
|
||||||
public WarningSignalPublisher(IKafkaProducer<WarningSignalKey, WarningSignal> producer)
|
public WarningSignalPublisher(IProducer<WarningSignalKey, WarningSignal> producer, IProducer<int, WarningSignal> dlxProducer, IConfiguration conf)
|
||||||
{
|
{
|
||||||
_producer = producer;
|
_producer = producer;
|
||||||
|
_dlxProducer = dlxProducer;
|
||||||
|
env = "-" + conf.GetValue<string>("Environment")!.ToLower();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PublishWarningSignal(WarningSignalType type, List<TrackDetailsIndex> tracks)
|
public void PublishSignal(WarningSignalType type, List<TrackDetailsIndex> tracks)
|
||||||
{
|
{
|
||||||
foreach (var track in tracks)
|
foreach (var track in tracks)
|
||||||
PublishWarningSignal(type, track);
|
PublishSignal(type, track);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PublishWarningSignal(WarningSignalType type, TrackDetailsIndex track)
|
public void PublishSignal(WarningSignalType type, TrackDetailsIndex track)
|
||||||
{
|
{
|
||||||
_producer.Produce(new Message<WarningSignalKey, WarningSignal>
|
_producer.Produce(GlobalConst.WarningKafkaTopic + env, new Message<WarningSignalKey, WarningSignal>
|
||||||
{
|
{
|
||||||
Key = new WarningSignalKey(type, track.OrderId),
|
Key = new WarningSignalKey(type, track.OrderId),
|
||||||
Value = WarningSignal.FromTrack(track, type)
|
Value = WarningSignal.FromTrack(track, type)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task PublishSignalAsync(Message<WarningSignalKey, WarningSignal> msg, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return _producer.ProduceAsync(GlobalConst.WarningKafkaTopic + env, msg, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task PublishDlxSignalAsync(Message<int, WarningSignal> message, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return _dlxProducer.ProduceAsync(GlobalConst.WarningDLXKafkaTopic + env, message, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_producer.Dispose();
|
||||||
|
_dlxProducer.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,10 +9,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BSI.Stack.Rabbit" Version="2.1.4" />
|
<PackageReference Include="BSI.Stack.Rabbit" Version="2.1.4" />
|
||||||
<PackageReference Include="Confluent.Kafka.Core" Version="1.0.0" />
|
<PackageReference Include="Confluent.Kafka" Version="2.10.0" />
|
||||||
<PackageReference Include="Confluent.Kafka.Core.Serialization.JsonCore" Version="1.0.0" />
|
<PackageReference Include="FeishuNetSdk" Version="2.4.6" />
|
||||||
<PackageReference Include="Confluent.Kafka.Core.Serialization.NewtonsoftJson" Version="1.0.0" />
|
|
||||||
<PackageReference Include="Confluent.Kafka.Core.Serialization.ProtobufNet" Version="1.0.0" />
|
|
||||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.18" />
|
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.18" />
|
||||||
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.4" />
|
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.4" />
|
||||||
<PackageReference Include="Savorboard.CAP.InMemoryMessageQueue" Version="8.2.1"/>
|
<PackageReference Include="Savorboard.CAP.InMemoryMessageQueue" Version="8.2.1"/>
|
||||||
|
|||||||
@ -32,6 +32,7 @@ public class BroadcastingService : IBroadcastingService
|
|||||||
var data = new TrackChangeEto
|
var data = new TrackChangeEto
|
||||||
{
|
{
|
||||||
EstimatedDeliveryDate = eto.EstimatedDeliveryDate,
|
EstimatedDeliveryDate = eto.EstimatedDeliveryDate,
|
||||||
|
DestinationTimeZone = eto.DestinationTimeZone,
|
||||||
TrackPlacePostcode = eto.TrackPlacePostcode,
|
TrackPlacePostcode = eto.TrackPlacePostcode,
|
||||||
TrackPlaceNation = eto.TrackPlaceNation,
|
TrackPlaceNation = eto.TrackPlaceNation,
|
||||||
TrackPlaceNationId = eto.TrackPlaceNationId,
|
TrackPlaceNationId = eto.TrackPlaceNationId,
|
||||||
|
|||||||
@ -5,12 +5,14 @@ using SJZY.Expand.ABP.Core.Logger;
|
|||||||
using SJZY.Expand.ABP.Core.PublicMethords;
|
using SJZY.Expand.ABP.Core.PublicMethords;
|
||||||
using SJZY.Track.Application.Contract.Dtos.TrackPush;
|
using SJZY.Track.Application.Contract.Dtos.TrackPush;
|
||||||
using SJZY.Track.Application.Contract.Services;
|
using SJZY.Track.Application.Contract.Services;
|
||||||
|
using SJZY.Track.Application.Helpers;
|
||||||
using SJZY.Track.Application.Localization;
|
using SJZY.Track.Application.Localization;
|
||||||
using SJZY.Track.Application.Services.Warning.Handlers;
|
using SJZY.Track.Application.Services.Warning.Handlers;
|
||||||
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
||||||
using SJZY.Track.Domain.Aggregates.TrackPushAggregate;
|
using SJZY.Track.Domain.Aggregates.TrackPushAggregate;
|
||||||
using SJZY.Track.Domain.Dtos.Common;
|
using SJZY.Track.Domain.Dtos.Common;
|
||||||
using SJZY.Track.Domain.Dtos.TrackPush;
|
using SJZY.Track.Domain.Dtos.TrackPush;
|
||||||
|
using SJZY.Track.Domain.Enums;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
|
|
||||||
namespace SJZY.Track.Application.Services.TrackPush;
|
namespace SJZY.Track.Application.Services.TrackPush;
|
||||||
@ -112,7 +114,7 @@ public class TrackPushService : ApplicationService, ISingletonDependency
|
|||||||
/// <param name="input"></param>
|
/// <param name="input"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost("Edit")]
|
[HttpPost("Edit")]
|
||||||
public async Task<bool> EditAsync([FromBody] EditTrackPushConfigInput input)
|
public async Task<string> EditAsync([FromBody] EditTrackPushConfigInput input)
|
||||||
{
|
{
|
||||||
if (input.Options != OptionTypeNoNull.删除)
|
if (input.Options != OptionTypeNoNull.删除)
|
||||||
{
|
{
|
||||||
@ -124,6 +126,14 @@ public class TrackPushService : ApplicationService, ISingletonDependency
|
|||||||
throw new BusinessException(message: _stringLocalizer["IsTrackPushConfigAny"]);
|
throw new BusinessException(message: _stringLocalizer["IsTrackPushConfigAny"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var msg = string.Empty;
|
||||||
|
if (input.Options is OptionTypeNoNull.新增 or OptionTypeNoNull.修改)
|
||||||
|
{
|
||||||
|
var (success, message) = CheckDateTimeFormat(input.DateTimeFormat, input.DateTimeFormatType, input.TimezoneFormat);
|
||||||
|
if (!success) throw new BusinessException(message: message);
|
||||||
|
msg = message;
|
||||||
|
}
|
||||||
|
|
||||||
using var ts = TransactionScopeHelper.Create();
|
using var ts = TransactionScopeHelper.Create();
|
||||||
|
|
||||||
switch (input.Options)
|
switch (input.Options)
|
||||||
@ -161,7 +171,7 @@ public class TrackPushService : ApplicationService, ISingletonDependency
|
|||||||
}
|
}
|
||||||
|
|
||||||
ts.Complete();
|
ts.Complete();
|
||||||
return true;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -346,4 +356,52 @@ public class TrackPushService : ApplicationService, ISingletonDependency
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (bool Success, string Message) CheckDateTimeFormat(string? dateTimeFormat, int? dateTimeFormatType, string? timezoneFormat)
|
||||||
|
{
|
||||||
|
const int defaultTimeZone = 8;
|
||||||
|
var dateTime = DateTime.Now;
|
||||||
|
string dateStr;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (dateTimeFormatType == (int) DateTimeFormatEnum.Timestamp)
|
||||||
|
{
|
||||||
|
dateStr = DateTimeHelper.ConvertToUnixTimestamp(dateTime).ToString();
|
||||||
|
return (true, $"格式化样例:{dateStr}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateTimeFormatType == (int) DateTimeFormatEnum.DateTimeAndTimezone)
|
||||||
|
{
|
||||||
|
// 没时间有时区,出错
|
||||||
|
if (string.IsNullOrWhiteSpace(dateTimeFormat) && !string.IsNullOrWhiteSpace(timezoneFormat))
|
||||||
|
return (false, "如果填写了时区格式,那么时间格式是必须的");
|
||||||
|
|
||||||
|
// 只有时间
|
||||||
|
if (!string.IsNullOrWhiteSpace(dateTimeFormat) && string.IsNullOrWhiteSpace(timezoneFormat))
|
||||||
|
{
|
||||||
|
var datetimeStr = DateTimeHelper.FormatDateTime(dateTime, dateTimeFormat);
|
||||||
|
return (true, $"格式化样例:{datetimeStr}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间和时区同时存在
|
||||||
|
if (!string.IsNullOrWhiteSpace(dateTimeFormat) && !string.IsNullOrWhiteSpace(timezoneFormat))
|
||||||
|
{
|
||||||
|
var zoneStr = DateTimeHelper.TimezoneFormat(defaultTimeZone, timezoneFormat);
|
||||||
|
var datetimeStr = DateTimeHelper.FormatDateTime(dateTime, dateTimeFormat);
|
||||||
|
dateStr = $"{datetimeStr} {zoneStr}";
|
||||||
|
return (true, $"格式化样例:{dateStr}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return (false, $"时间格式错误请检查:{e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认情况
|
||||||
|
dateTimeFormat ??= "yyyy-MM-ddTHH:mm:ss.fffK";
|
||||||
|
dateStr = dateTime.ToString(dateTimeFormat);
|
||||||
|
return (true, $"格式化样例:{dateStr}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -139,7 +139,7 @@ public class SjzyTrackApplicationModule : AbpModule
|
|||||||
Console.WriteLine("-- 当前为VS本地环境,用内存存储和内存队列");
|
Console.WriteLine("-- 当前为VS本地环境,用内存存储和内存队列");
|
||||||
}
|
}
|
||||||
|
|
||||||
Action<RabbitOptions> action = options =>
|
Action<RabbitOptions> action = options =>
|
||||||
{
|
{
|
||||||
options.ConnectionString =
|
options.ConnectionString =
|
||||||
$"amqp://{configuration["RabbitMQ:UserName"]}:{configuration["RabbitMQ:Password"]}@{configuration["RabbitMQ:HostName"]}:{configuration["RabbitMQ:Port"]}";
|
$"amqp://{configuration["RabbitMQ:UserName"]}:{configuration["RabbitMQ:Password"]}@{configuration["RabbitMQ:HostName"]}:{configuration["RabbitMQ:Port"]}";
|
||||||
@ -203,12 +203,19 @@ public class SjzyTrackApplicationModule : AbpModule
|
|||||||
$",abortConnect=false";
|
$",abortConnect=false";
|
||||||
|
|
||||||
// 注册预警计算服务
|
// 注册预警计算服务
|
||||||
if (_BUGOptions.EnablePref)
|
context.Services.AddWarningCalcServices(configuration);
|
||||||
context.Services.AddWarningCalcServices(configuration);
|
|
||||||
|
|
||||||
// 注册预警任务后台服务
|
// 注册预警任务后台服务
|
||||||
context.Services.AddHostedService<WarningTaskComponentBackgroundService>();
|
context.Services.AddHostedService<WarningTaskComponentBackgroundService>();
|
||||||
|
|
||||||
|
// 注册飞书服务(用于预警通知)
|
||||||
|
context.Services.AddFeishuNetSdk(o =>
|
||||||
|
{
|
||||||
|
o.AppId = configuration.GetValue<string>("FeiShu:AppId")!;
|
||||||
|
o.AppSecret = configuration.GetValue<string>("FeiShu:AppSecret")!;
|
||||||
|
o.EnableLogging = configuration.GetValue<bool>("FeiShu:EnableLogging");
|
||||||
|
});
|
||||||
|
|
||||||
// 注册HangFire服务
|
// 注册HangFire服务
|
||||||
context.Services.Configure<TrackWarningTaskOptions>(configuration.GetSection("WarningTask"));
|
context.Services.Configure<TrackWarningTaskOptions>(configuration.GetSection("WarningTask"));
|
||||||
context.Services.AddHangfire(o =>
|
context.Services.AddHangfire(o =>
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
namespace SJZY.Track.Application;
|
|
||||||
|
|
||||||
public static class _BUGOptions
|
|
||||||
{
|
|
||||||
public static bool EnablePref = false;
|
|
||||||
}
|
|
||||||
7
src/SJZY.Track.Domain/Queries/IVFsUserQueries.cs
Normal file
7
src/SJZY.Track.Domain/Queries/IVFsUserQueries.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
using SJZY.Track.Domain.VEntities;
|
||||||
|
|
||||||
|
namespace SJZY.Track.Domain.Queries;
|
||||||
|
|
||||||
|
public interface IVFsUserQueries : IBasicQuery<VFSUserEntity>
|
||||||
|
{
|
||||||
|
}
|
||||||
65
src/SJZY.Track.Domain/VEntities/VFSUserEntity.cs
Normal file
65
src/SJZY.Track.Domain/VEntities/VFSUserEntity.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
namespace SJZY.Track.Domain.VEntities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 飞书用户表,用户中心用户关联飞书用户
|
||||||
|
/// </summary>
|
||||||
|
[SugarTable("v_fs_user")]
|
||||||
|
public class VFSUserEntity : IEntity
|
||||||
|
{
|
||||||
|
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户中心id
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "user_center_id")]
|
||||||
|
public int UserCenterId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户名
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "user_name")]
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户手机号
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "mobile")]
|
||||||
|
public string Mobile { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 飞书 union id
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "union_id")]
|
||||||
|
public string UnionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 飞书 user id
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "fs_employee_id")]
|
||||||
|
public string FsEmployeeId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 飞书头像
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "avatar")]
|
||||||
|
public string Avatar { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自建的飞书登录应用下的OpenId
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "fsloginApp_openid")]
|
||||||
|
public string FsLoginAppOpenId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "create_time")]
|
||||||
|
public DateTime CreateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新时间
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "update_time")]
|
||||||
|
public DateTime UpdateTime { get; set; }
|
||||||
|
}
|
||||||
9
src/SJZY.Track.Repository/Queries/VFsUserQueries.cs
Normal file
9
src/SJZY.Track.Repository/Queries/VFsUserQueries.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using SJZY.Track.Domain.Queries;
|
||||||
|
using SJZY.Track.Domain.VEntities;
|
||||||
|
|
||||||
|
namespace SJZY.Track.Repository.Queries;
|
||||||
|
|
||||||
|
public class VFsUserQueries: BasicQuery<VFSUserEntity>, IVFsUserQueries, ITransientDependency
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user