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.Logger;
|
||||
using SJZY.Track.Api;
|
||||
using SJZY.Track.Application;
|
||||
|
||||
try
|
||||
{
|
||||
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
|
||||
.AddConfigurationContainer()
|
||||
.AddOptionsFromConfiguration()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<Import Project="../../common.props" />
|
||||
<Import Project="../../common.props"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
@ -10,17 +10,18 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.2.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="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Volo.Abp.Autofac" Version="7.0.2" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.2.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="Swashbuckle.AspNetCore" Version="6.5.0"/>
|
||||
<PackageReference Include="Volo.Abp.Autofac" Version="7.0.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SJZY.Track.Application\SJZY.Track.Application.csproj" />
|
||||
<ProjectReference Include="..\SJZY.Track.Repository\SJZY.Track.Repository.csproj" />
|
||||
<ProjectReference Include="..\SJZY.Track.Application\SJZY.Track.Application.csproj"/>
|
||||
<ProjectReference Include="..\SJZY.Track.Repository\SJZY.Track.Repository.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="kafkalib\**\*" CopyToPublishDirectory="Always" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</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>
|
||||
public DateTime? EstimatedDeliveryDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目的地时区
|
||||
/// </summary>
|
||||
public float? DestinationTimeZone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 扩展信息字段,用与轨迹推送的附加字段
|
||||
/// </summary>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<Version>7.0.20</Version>
|
||||
<Version>7.0.22</Version>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@ -6,7 +6,6 @@ using SJZY.Track.Application.Contract.Dtos.ProvideServices;
|
||||
using SJZY.Track.Application.Contract.Events;
|
||||
using SJZY.Track.Application.Contract.Services;
|
||||
using SJZY.Track.Application.Kafka;
|
||||
using SJZY.Track.Application.Services.Warning.Handlers;
|
||||
using SJZY.Track.Application.Services.Warning.Signals;
|
||||
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
||||
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
||||
@ -25,8 +24,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
private readonly IProvideQueries _provideQueries;
|
||||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
||||
private readonly IBroadcastingService _broadcastingService;
|
||||
// private readonly WarningSignalPublisher _warningSignalPublisher;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly WarningSignalPublisher _warningSignalPublisher;
|
||||
|
||||
public TrackDeleteEventHandler(
|
||||
IProvideQueries provideQueries,
|
||||
@ -35,8 +33,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
ITrackDetailsEsRepository trackDetailsEsRepository,
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
IBroadcastingService broadcastingService,
|
||||
IServiceProvider serviceProvider
|
||||
//WarningSignalPublisher warningSignalPublisher
|
||||
WarningSignalPublisher warningSignalPublisher
|
||||
)
|
||||
{
|
||||
_provideQueries = provideQueries;
|
||||
@ -45,8 +42,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
_trackDetailsEsRepository = trackDetailsEsRepository;
|
||||
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
|
||||
_broadcastingService = broadcastingService;
|
||||
_serviceProvider = serviceProvider;
|
||||
//_warningSignalPublisher = warningSignalPublisher;
|
||||
_warningSignalPublisher = warningSignalPublisher;
|
||||
}
|
||||
|
||||
[CapSubscribe(GlobalConst.TrackDeleteSubscribe, Group = GlobalConst.TrackDeleteSubscribe)]
|
||||
@ -327,6 +323,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
_broadcastingService.TrackChangePublicTopic(new TrackChangeEto
|
||||
{
|
||||
EstimatedDeliveryDate = track.EstimatedDeliveryDate,
|
||||
DestinationTimeZone = track.DestinationTimeZone,
|
||||
TrackPlacePostcode = track.TrackPlacePostcode,
|
||||
TrackPlaceNation = track.TrackPlaceNation,
|
||||
TrackPlaceNationId = track.TrackPlaceNationId,
|
||||
@ -354,7 +351,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
OrderNo = track.OrderNo,
|
||||
OrderIdentifiers = new[]
|
||||
{
|
||||
new OrderIdentifier()
|
||||
new OrderIdentifier
|
||||
{
|
||||
OrderId = track.OrderId,
|
||||
OrderNo = track.OrderNo,
|
||||
@ -368,43 +365,8 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void WarningCalc_DeletedTrack(List<TrackDetailsIndex> delTracks)
|
||||
{
|
||||
if (_BUGOptions.EnablePref)
|
||||
{
|
||||
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();
|
||||
}
|
||||
});
|
||||
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹删除, delTracks);
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,7 +463,7 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
||||
if (string.IsNullOrWhiteSpace(compareFields))
|
||||
return trackIdentifies.Contains($"{code}{timeStr}");
|
||||
|
||||
var extra = x.GetValueOrDefault("extra");
|
||||
var extra = x.GetValueOrDefault("extra");
|
||||
if (extra == null) return false;
|
||||
|
||||
if (((JsonElement) extra).TryGetProperty(compareFields, out var comparerValueJsonElement))
|
||||
@ -538,15 +538,22 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
|
||||
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.Timestamp)
|
||||
return DateTimeHelper.ConvertToUnixTimestamp(dateTime).ToString();
|
||||
|
||||
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.DateTimeAndTimezone
|
||||
&& !string.IsNullOrEmpty(config.DateTimeFormat)
|
||||
&& !string.IsNullOrEmpty(config.TimezoneFormat))
|
||||
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.DateTimeAndTimezone)
|
||||
{
|
||||
var zoneStr = DateTimeHelper.TimezoneFormat(timezone, config.TimezoneFormat);
|
||||
var datetimeStr = DateTimeHelper.FormatDateTime(dateTime, config.DateTimeFormat);
|
||||
return $"{datetimeStr} {zoneStr}";
|
||||
// 只有时间
|
||||
if (!string.IsNullOrWhiteSpace(config.DateTimeFormat) && string.IsNullOrWhiteSpace(config.TimezoneFormat))
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ using SJZY.OTel;
|
||||
using SJZY.Track.Application.Contract.Events;
|
||||
using SJZY.Track.Application.Contract.Services;
|
||||
using SJZY.Track.Application.Kafka;
|
||||
using SJZY.Track.Application.Services.Warning.Handlers;
|
||||
using SJZY.Track.Application.Services.Warning.Signals;
|
||||
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
||||
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
||||
@ -26,11 +25,8 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
||||
private readonly IBroadcastingService _broadcastingService;
|
||||
private readonly ILogger<TrackUpsertEventHandler> _logger;
|
||||
|
||||
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
|
||||
|
||||
//private readonly WarningSignalPublisher _warningSignalPublisher;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly WarningSignalPublisher _warningSignalPublisher;
|
||||
|
||||
public TrackUpsertEventHandler(
|
||||
IOrderRepository orderRepository,
|
||||
@ -40,8 +36,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
|
||||
IBroadcastingService broadcastingService,
|
||||
ILogger<TrackUpsertEventHandler> logger,
|
||||
IServiceProvider serviceProvider
|
||||
//WarningSignalPublisher warningSignalPublisher
|
||||
WarningSignalPublisher warningSignalPublisher
|
||||
)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
@ -51,8 +46,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
|
||||
_broadcastingService = broadcastingService;
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
//_warningSignalPublisher = warningSignalPublisher;
|
||||
_warningSignalPublisher = warningSignalPublisher;
|
||||
}
|
||||
|
||||
[CapSubscribe(GlobalConst.TrackUpsertSubscribe, Group = GlobalConst.TrackUpsertSubscribe)]
|
||||
@ -102,11 +96,10 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
// 不同子系统需要做内容映射
|
||||
if (@event.SubSystemId != nextEvent.SubSystemId)
|
||||
{
|
||||
var mapCode = (await _provideQueries
|
||||
.MapTrackCodesAsync(@event.SubSystemId, nextEvent.SubSystemId, @event.TrackCode))
|
||||
var mapCode = (await _provideQueries.MapTrackCodesAsync(@event.SubSystemId, nextEvent.SubSystemId, @event.TrackCode))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (mapCode == default)
|
||||
if (mapCode == null)
|
||||
{
|
||||
// 跨子系统没做映射的轨迹不处理
|
||||
continue;
|
||||
@ -518,75 +511,12 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
|
||||
|
||||
private void WarningCalc_AddedTrack(List<TrackDetailsIndex> tracks)
|
||||
{
|
||||
if (_BUGOptions.EnablePref)
|
||||
{
|
||||
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();
|
||||
}
|
||||
});
|
||||
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹新增, tracks);
|
||||
}
|
||||
|
||||
|
||||
private void WarningCalc_UpdatedTrack(List<TrackDetailsIndex> tracks)
|
||||
{
|
||||
if (_BUGOptions.EnablePref)
|
||||
{
|
||||
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();
|
||||
}
|
||||
});
|
||||
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹更新, tracks);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 SJZY.Track.Application.Contract.Dtos.Warning;
|
||||
using SJZY.Track.Application.Helpers;
|
||||
using SJZY.Track.Application.Options;
|
||||
using SJZY.Track.Application.Services.Warning.Model;
|
||||
using SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
|
||||
@ -43,7 +42,7 @@ public sealed class WarningNotifyAggregator : ITransientDependency
|
||||
// 2.以人的维度聚合通知信息,并生成配置参数NotifyConfig
|
||||
// 3.构建并调度一个调用通知接口的立即执行任务
|
||||
// 4.绑定这个任务id(可能是多个)和jobId用户跟踪任务状态
|
||||
public async Task<(List<NotifyConfig> NotifyConfigs, HashSet<int> taskIds)> ScanNotifyAndAggregateAsync()
|
||||
public async Task<(List<WarningNotifyParam> NotifyConfigs, HashSet<int> taskIds)> ScanNotifyAndAggregateAsync()
|
||||
{
|
||||
// 构建查询游标 todo:全量查询需要优化,直接存缓存?
|
||||
// var options = _optionsMonitor.CurrentValue;
|
||||
@ -210,7 +209,7 @@ public sealed class WarningNotifyAggregator : ITransientDependency
|
||||
|
||||
// 2.检查明细中为人工介入的状态
|
||||
var detailsIdSet = filtered.Select(x => x.WarningDetailsId).ToHashSet();
|
||||
var warningDetails = await _warningDetailsEsRepository.GetReturnWarningDetailsAsync(new GetReturnWarningDetailsVO()
|
||||
var warningDetails = await _warningDetailsEsRepository.GetReturnWarningDetailsAsync(new GetReturnWarningDetailsVO
|
||||
{
|
||||
DetailsIds = detailsIdSet,
|
||||
Status = new HashSet<int>(1)
|
||||
@ -261,10 +260,9 @@ public sealed class WarningNotifyAggregator : ITransientDependency
|
||||
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<NotifyConfig>();
|
||||
var result = new List<WarningNotifyParam>();
|
||||
if (needNotifyTasks.Count == 0) return result;
|
||||
|
||||
// 1.以人的维度对不同类别的预警通知分组聚合
|
||||
@ -293,109 +291,51 @@ public sealed class WarningNotifyAggregator : ITransientDependency
|
||||
}
|
||||
}
|
||||
|
||||
// 2.使用消息模板构建通知消息,并发送通知
|
||||
// 2.使用消息模板构建通知参数,稍后排队发送通知
|
||||
foreach (var (userId, typeNotifyTaskDict) in userIdNotifyTaskDict)
|
||||
{
|
||||
var notifyMessage = BuildNotifyMessage(typeNotifyTaskDict);
|
||||
//聚合时出现 用户a
|
||||
//接收回传预警,命中规则1,通知方式(飞书,站内信) ,命中规则2,通知方式(飞书)
|
||||
//接收运输预警,命中规则1,通知方式(飞书,站内信) ,命中规则2,通知方式(飞书)
|
||||
//接收完整预警,命中规则1,通知方式(飞书) ,命中规则2,通知方式(飞书,站内信)
|
||||
//此时聚合后,消息为:总共6条预警消息,其中回传xx条,运输xx条,完整xx条
|
||||
//同时使用飞书和站内信两种方式通知
|
||||
var config = NotifyConfig.CreateConfig(userId, notifyMessage, new List<int>
|
||||
{
|
||||
1,
|
||||
2
|
||||
});
|
||||
config.SystemId = systemId;
|
||||
result.Add(config);
|
||||
result.Add(BuildNotifyParam(userId, typeNotifyTaskDict));
|
||||
}
|
||||
|
||||
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 hours = options.ScanWarningNotifyTaskHourInterval;
|
||||
var transportCount = 0;
|
||||
var transportLastNHoursCount = 0;
|
||||
var returnCount = 0;
|
||||
var returnLastNHoursCount = 0;
|
||||
var completeCount = 0;
|
||||
var completeLastNHoursCount = 0;
|
||||
Func<WarningNotifyTaskEntity, bool> hoursNBefore = x => DateTime.Now - x.CreateTime <= TimeSpan.FromHours(hours);
|
||||
var param = new WarningNotifyParam
|
||||
{
|
||||
UserId = userId,
|
||||
Hours = hours
|
||||
};
|
||||
|
||||
// tips:获取过去x小时的增量
|
||||
Func<WarningNotifyTaskEntity, bool> hoursNBefore = x => DateTime.Now - x.CreateTime <= TimeSpan.FromHours(hours);
|
||||
if (typeNotifyTaskDict.TryGetValue(WarningRuleType.运输时效预警.GetHashCode(), out var transportTimelinessNotifyTasks))
|
||||
{
|
||||
transportLastNHoursCount = transportTimelinessNotifyTasks.Count(hoursNBefore);
|
||||
transportCount = transportTimelinessNotifyTasks.Count;
|
||||
param.TransportLastNHoursCount = transportTimelinessNotifyTasks.Count(hoursNBefore);
|
||||
param.TransportCount = transportTimelinessNotifyTasks.Count;
|
||||
}
|
||||
|
||||
if (typeNotifyTaskDict.TryGetValue(WarningRuleType.回传时效预警.GetHashCode(), out var returnTimelinessNotifyTasks))
|
||||
{
|
||||
returnLastNHoursCount = returnTimelinessNotifyTasks.Count(hoursNBefore);
|
||||
returnCount = returnTimelinessNotifyTasks.Count;
|
||||
param.ReturnLastNHoursCount = returnTimelinessNotifyTasks.Count(hoursNBefore);
|
||||
param.ReturnCount = returnTimelinessNotifyTasks.Count;
|
||||
}
|
||||
|
||||
if (typeNotifyTaskDict.TryGetValue(WarningRuleType.完整预警.GetHashCode(), out var completeWarningNotifyTasks))
|
||||
{
|
||||
completeLastNHoursCount = completeWarningNotifyTasks.Count(hoursNBefore);
|
||||
completeCount = completeWarningNotifyTasks.Count;
|
||||
param.CompleteLastNHoursCount = completeWarningNotifyTasks.Count(hoursNBefore);
|
||||
param.CompleteCount = completeWarningNotifyTasks.Count;
|
||||
}
|
||||
|
||||
return NotifyMessageBuilder.Build(
|
||||
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}";
|
||||
return param;
|
||||
}
|
||||
}
|
||||
|
||||
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 Microsoft.Extensions.Options;
|
||||
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.Enums;
|
||||
using SJZY.Track.Domain.Extensions;
|
||||
using SJZY.Track.Domain.Queries;
|
||||
using SJZY.UserCenter.HttpApi.Services.Message;
|
||||
using SJZY.UserCenter.SharedModel.Dtos.Message;
|
||||
using SJZY.UserCenter.SharedModel.Enums;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Http.Client.DynamicProxying;
|
||||
|
||||
@ -21,8 +21,8 @@ namespace SJZY.Track.Application.Hangfire;
|
||||
|
||||
public interface IWarningTaskService
|
||||
{
|
||||
[AutomaticRetry(Attempts = 3)]
|
||||
Task CallNotifyAsync(NotifyConfig config, CancellationToken ct);
|
||||
[AutomaticRetry(Attempts = 1)]
|
||||
Task CallNotifyAsync(WarningNotifyParam param, CancellationToken ct);
|
||||
|
||||
[AutomaticRetry(Attempts = 3, ExceptOn = new[] { typeof(BusinessException) })]
|
||||
Task CallTrackRetryPushAsync(ReturnWarningRetryPushTaskEntity entity, CancellationToken ct);
|
||||
@ -30,51 +30,57 @@ public interface IWarningTaskService
|
||||
|
||||
public class WarningTaskService : IWarningTaskService, ITransientDependency
|
||||
{
|
||||
private readonly IOptionsMonitor<InterfaceRelayParamsConfigOptions> _interfaceRelayOptions;
|
||||
private readonly IOptionsMonitor<TrackWarningTaskOptions> _warningTaskOptions;
|
||||
private readonly IHttpClientProxy<IRequestSendMessageService> _requestSendMessageService;
|
||||
private readonly IHttpClientProxy<IInterfaceForwardServiceV2> _interfaceForwardServiceV2;
|
||||
private readonly IOptionsMonitor<InterfaceRelayParamsConfigOptions> _options;
|
||||
private readonly IFeishuTenantApi _feishuTenantApi;
|
||||
private readonly IWarningDetailsEsRepository _warningDetailsEsRepository;
|
||||
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;
|
||||
_requestSendMessageService = requestSendMessageService;
|
||||
_feishuTenantApi = feishuTenantApi;
|
||||
_warningDetailsEsRepository = warningDetailsEsRepository;
|
||||
_warningTaskRepository = warningTaskRepository;
|
||||
_options = options;
|
||||
_vfsUserQueries = vfsUserQueries;
|
||||
}
|
||||
|
||||
#region 用户中心调用,通知(一次)
|
||||
|
||||
[AutomaticRetry(Attempts = 3)]
|
||||
public async Task CallNotifyAsync(NotifyConfig config, CancellationToken ct)
|
||||
[AutomaticRetry(Attempts = 1)]
|
||||
public async Task CallNotifyAsync(WarningNotifyParam param, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var r = await _requestSendMessageService.Service.SendMessageByTemplateAsync(new SendMessageByTemplateInput
|
||||
{
|
||||
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(),
|
||||
});
|
||||
var options = _warningTaskOptions.CurrentValue;
|
||||
|
||||
// 发送站内信
|
||||
// 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)
|
||||
{
|
||||
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
|
||||
@ -107,8 +113,8 @@ public class WarningTaskService : IWarningTaskService, ITransientDependency
|
||||
using (Track2InterfaceRelaySubSystemContext.Use(new Track2InterfaceRelaySubSystemContext(task.SubSystemCode)))
|
||||
{
|
||||
var upStreamCode = isAdd
|
||||
? _options.CurrentValue.PushTrackInterfaceCode
|
||||
: _options.CurrentValue.TrackUpdateInterfaceCode;
|
||||
? _interfaceRelayOptions.CurrentValue.PushTrackInterfaceCode
|
||||
: _interfaceRelayOptions.CurrentValue.TrackUpdateInterfaceCode;
|
||||
|
||||
var r = await _interfaceForwardServiceV2.Service.BatchForwardThenPushToQueueAsync(
|
||||
upStreamCode: upStreamCode,
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using Confluent.Kafka;
|
||||
using Confluent.Kafka.Core.Consumer;
|
||||
using Confluent.Kafka.Core.Producer;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SJZY.Expand.ABP.Core.Logger;
|
||||
@ -20,23 +18,20 @@ namespace SJZY.Track.Application.Kafka;
|
||||
public sealed class WarningCalcBackgroundService : BackgroundService
|
||||
{
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly IKafkaConsumer<WarningSignalKey, WarningSignal> _consumer;
|
||||
private readonly IKafkaProducer<WarningSignalKey, WarningSignal> _producer;
|
||||
private readonly IKafkaProducer<int, WarningSignal> _dlxProducer;
|
||||
private readonly IConsumer<WarningSignalKey, WarningSignal> _consumer;
|
||||
private readonly WarningSignalPublisher _warningSignalPublisher;
|
||||
private TrackWarningTaskOptions _options;
|
||||
|
||||
public WarningCalcBackgroundService(
|
||||
IOptionsMonitor<TrackWarningTaskOptions> optionsMonitor,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
IKafkaConsumer<WarningSignalKey, WarningSignal> consumer,
|
||||
IKafkaProducer<WarningSignalKey, WarningSignal> producer,
|
||||
IKafkaProducer<int, WarningSignal> dlxProducer
|
||||
IConsumer<WarningSignalKey, WarningSignal> consumer,
|
||||
WarningSignalPublisher warningSignalPublisher
|
||||
)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_consumer = consumer;
|
||||
_producer = producer;
|
||||
_dlxProducer = dlxProducer;
|
||||
_warningSignalPublisher = warningSignalPublisher;
|
||||
_options = optionsMonitor.CurrentValue;
|
||||
optionsMonitor.OnChange(x => _options = x);
|
||||
}
|
||||
@ -175,7 +170,7 @@ public sealed class WarningCalcBackgroundService : BackgroundService
|
||||
var tasks = needRetryMetadata.Select(async metadata =>
|
||||
{
|
||||
metadata.SetTrackSignalMessageHeader();
|
||||
await _producer.ProduceAsync(metadata.OwenMessage, CancellationToken.None);
|
||||
await _warningSignalPublisher.PublishSignalAsync(metadata.OwenMessage, CancellationToken.None);
|
||||
}).ToArray();
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
@ -193,7 +188,7 @@ public sealed class WarningCalcBackgroundService : BackgroundService
|
||||
{
|
||||
metadata.SetTrackSignalMessageHeader();
|
||||
var msg = metadata.OwenMessage;
|
||||
await _dlxProducer.ProduceAsync(new Message<int, WarningSignal>
|
||||
await _warningSignalPublisher.PublishDlxSignalAsync(new Message<int, WarningSignal>
|
||||
{
|
||||
Key = msg.Value.OrderId,
|
||||
Value = msg.Value,
|
||||
@ -282,6 +277,5 @@ public sealed class WarningCalcBackgroundService : BackgroundService
|
||||
base.Dispose();
|
||||
_consumer.Close();
|
||||
_consumer.Dispose();
|
||||
_producer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,13 @@
|
||||
//#define ProtoBuf
|
||||
#define NewtonsoftJson
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Confluent.Kafka;
|
||||
using Confluent.Kafka.Core.Consumer;
|
||||
using Confluent.Kafka.Core.Producer;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using SJZY.Expand.ABP.Core.Logger;
|
||||
using SJZY.Track.Application.Helpers;
|
||||
using SJZY.Track.Application.Services.Warning.Signals;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace SJZY.Track.Application.Kafka;
|
||||
|
||||
@ -22,6 +15,7 @@ public static class WarningCalcServiceExtension
|
||||
{
|
||||
public static void AddWarningCalcServices(this IServiceCollection services, IConfiguration conf)
|
||||
{
|
||||
PrintKafkaLibVersion();
|
||||
var env = "-" + conf.GetValue<string>("Environment")!.ToLower();
|
||||
// 预警信号批处理后台任务
|
||||
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)
|
||||
{
|
||||
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;
|
||||
bb.WithErrorHandler((consumer, error) =>
|
||||
{
|
||||
if (error.IsFatal)
|
||||
var bb = new ConsumerBuilder<TKey, TValue>(
|
||||
new ConsumerConfig
|
||||
{
|
||||
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}");
|
||||
return;
|
||||
}
|
||||
if (error.IsFatal)
|
||||
{
|
||||
LogHelper.Error($"Kafka致命错误${error}");
|
||||
return;
|
||||
}
|
||||
|
||||
// tips: 防抖以防止高频上报错误
|
||||
reportFuncWrapper ??= DebounceHelper.WrapMethod(TimeSpan.FromSeconds(5), () =>
|
||||
{
|
||||
if (error.Code == ErrorCode.Local_Transport)
|
||||
LogHelper.Error($"Kafka链接错误{error}");
|
||||
else if (error.Code == ErrorCode.Local_AllBrokersDown)
|
||||
LogHelper.Error($"Kafka所有Broker都挂了{error}");
|
||||
// tips: 防抖以防止高频上报错误
|
||||
reportFuncWrapper ??= DebounceHelper.WrapMethod(TimeSpan.FromSeconds(5), () =>
|
||||
{
|
||||
if (error.Code == ErrorCode.Local_Transport)
|
||||
LogHelper.Error($"Kafka链接错误{error}");
|
||||
else if (error.Code == ErrorCode.Local_AllBrokersDown)
|
||||
LogHelper.Error($"Kafka所有Broker都挂了{error}");
|
||||
});
|
||||
reportFuncWrapper.Debounce();
|
||||
});
|
||||
reportFuncWrapper.Debounce();
|
||||
});
|
||||
|
||||
#if ProtoBuf
|
||||
bb.WithProtobufNetDeserializer();
|
||||
|
||||
#elif NewtonsoftJson
|
||||
bb.WithNewtonsoftJsonDeserializer(builder =>
|
||||
builder.WithNullValueHandling(NullValueHandling.Ignore) // Ignores null values
|
||||
.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>)!;
|
||||
var c = bb.Build();
|
||||
c.Subscribe(subTopic);
|
||||
return c;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
bb.WithErrorHandler((producer, error) =>
|
||||
{
|
||||
if (error.IsFatal)
|
||||
var bb = new ProducerBuilder<TKey, TValue>(new ProducerConfig()
|
||||
{
|
||||
LogHelper.Error($"Kafka致命错误${error}");
|
||||
return;
|
||||
}
|
||||
ClientId = Dns.GetHostName(),
|
||||
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: 防抖以防止高频上报错误
|
||||
reportFuncWrapper ??= DebounceHelper.WrapMethod(TimeSpan.FromSeconds(5), () =>
|
||||
{
|
||||
if (error.Code == ErrorCode.Local_Transport)
|
||||
LogHelper.Error($"Kafka链接错误{error}");
|
||||
else if (error.Code == ErrorCode.Local_AllBrokersDown)
|
||||
LogHelper.Error($"Kafka所有Broker都挂了{error}");
|
||||
// tips: 防抖以防止高频上报错误
|
||||
reportFuncWrapper ??= DebounceHelper.WrapMethod(TimeSpan.FromSeconds(5), () =>
|
||||
{
|
||||
if (error.Code == ErrorCode.Local_Transport)
|
||||
LogHelper.Error($"Kafka链接错误{error}");
|
||||
else if (error.Code == ErrorCode.Local_AllBrokersDown)
|
||||
LogHelper.Error($"Kafka所有Broker都挂了{error}");
|
||||
});
|
||||
reportFuncWrapper.Debounce();
|
||||
});
|
||||
reportFuncWrapper.Debounce();
|
||||
});
|
||||
|
||||
#if ProtoBuf
|
||||
// 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>)!;
|
||||
return bb.Build();
|
||||
});
|
||||
}
|
||||
|
||||
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.Core.Producer;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using SJZY.Track.Application.Services.Warning.Signals;
|
||||
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
@ -9,27 +9,48 @@ namespace SJZY.Track.Application.Kafka;
|
||||
/// <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;
|
||||
_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)
|
||||
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),
|
||||
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>
|
||||
<PackageReference Include="BSI.Stack.Rabbit" Version="2.1.4" />
|
||||
<PackageReference Include="Confluent.Kafka.Core" Version="1.0.0" />
|
||||
<PackageReference Include="Confluent.Kafka.Core.Serialization.JsonCore" Version="1.0.0" />
|
||||
<PackageReference Include="Confluent.Kafka.Core.Serialization.NewtonsoftJson" Version="1.0.0" />
|
||||
<PackageReference Include="Confluent.Kafka.Core.Serialization.ProtobufNet" Version="1.0.0" />
|
||||
<PackageReference Include="Confluent.Kafka" Version="2.10.0" />
|
||||
<PackageReference Include="FeishuNetSdk" Version="2.4.6" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.18" />
|
||||
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.4" />
|
||||
<PackageReference Include="Savorboard.CAP.InMemoryMessageQueue" Version="8.2.1"/>
|
||||
|
||||
@ -32,6 +32,7 @@ public class BroadcastingService : IBroadcastingService
|
||||
var data = new TrackChangeEto
|
||||
{
|
||||
EstimatedDeliveryDate = eto.EstimatedDeliveryDate,
|
||||
DestinationTimeZone = eto.DestinationTimeZone,
|
||||
TrackPlacePostcode = eto.TrackPlacePostcode,
|
||||
TrackPlaceNation = eto.TrackPlaceNation,
|
||||
TrackPlaceNationId = eto.TrackPlaceNationId,
|
||||
|
||||
@ -5,12 +5,14 @@ using SJZY.Expand.ABP.Core.Logger;
|
||||
using SJZY.Expand.ABP.Core.PublicMethords;
|
||||
using SJZY.Track.Application.Contract.Dtos.TrackPush;
|
||||
using SJZY.Track.Application.Contract.Services;
|
||||
using SJZY.Track.Application.Helpers;
|
||||
using SJZY.Track.Application.Localization;
|
||||
using SJZY.Track.Application.Services.Warning.Handlers;
|
||||
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
||||
using SJZY.Track.Domain.Aggregates.TrackPushAggregate;
|
||||
using SJZY.Track.Domain.Dtos.Common;
|
||||
using SJZY.Track.Domain.Dtos.TrackPush;
|
||||
using SJZY.Track.Domain.Enums;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace SJZY.Track.Application.Services.TrackPush;
|
||||
@ -112,7 +114,7 @@ public class TrackPushService : ApplicationService, ISingletonDependency
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("Edit")]
|
||||
public async Task<bool> EditAsync([FromBody] EditTrackPushConfigInput input)
|
||||
public async Task<string> EditAsync([FromBody] EditTrackPushConfigInput input)
|
||||
{
|
||||
if (input.Options != OptionTypeNoNull.删除)
|
||||
{
|
||||
@ -124,6 +126,14 @@ public class TrackPushService : ApplicationService, ISingletonDependency
|
||||
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();
|
||||
|
||||
switch (input.Options)
|
||||
@ -161,7 +171,7 @@ public class TrackPushService : ApplicationService, ISingletonDependency
|
||||
}
|
||||
|
||||
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本地环境,用内存存储和内存队列");
|
||||
}
|
||||
|
||||
Action<RabbitOptions> action = options =>
|
||||
Action<RabbitOptions> action = options =>
|
||||
{
|
||||
options.ConnectionString =
|
||||
$"amqp://{configuration["RabbitMQ:UserName"]}:{configuration["RabbitMQ:Password"]}@{configuration["RabbitMQ:HostName"]}:{configuration["RabbitMQ:Port"]}";
|
||||
@ -203,12 +203,19 @@ public class SjzyTrackApplicationModule : AbpModule
|
||||
$",abortConnect=false";
|
||||
|
||||
// 注册预警计算服务
|
||||
if (_BUGOptions.EnablePref)
|
||||
context.Services.AddWarningCalcServices(configuration);
|
||||
context.Services.AddWarningCalcServices(configuration);
|
||||
|
||||
// 注册预警任务后台服务
|
||||
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服务
|
||||
context.Services.Configure<TrackWarningTaskOptions>(configuration.GetSection("WarningTask"));
|
||||
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