diff --git a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs index 9d07c54e..591be62e 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs @@ -2,10 +2,11 @@ namespace SJZY.Track.Application.Contract.Dtos.Forewarning; public class WarningNotifyCacheEntry { - public WarningNotifyCacheEntry(int ruleId, int orderId) + public WarningNotifyCacheEntry(int ruleId, int orderId, int counter) { RuleId = ruleId; OrderId = orderId; + Counter = counter; } public WarningNotifyCacheEntry(string notifyValue) @@ -25,8 +26,13 @@ public class WarningNotifyCacheEntry /// public int OrderId { get; set; } + /// + /// 预警次数 + /// + public int Counter { get; set; } + public override string ToString() { - return $"{RuleId}_{OrderId}"; + return $"{RuleId}_{OrderId}_{Counter}"; } } diff --git a/src/SJZY.Track.Application.Contract/Events/TrackInsertForNotMappedEvent.cs b/src/SJZY.Track.Application.Contract/Events/TrackInsertForNotMappedEvent.cs deleted file mode 100644 index f67be132..00000000 --- a/src/SJZY.Track.Application.Contract/Events/TrackInsertForNotMappedEvent.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace SJZY.Track.Application.Contract.Events; - -public class TrackInsertForNotMappedEvent -{ - /// - /// 订单id - /// - public int OrderId { get; set; } - - /// - /// 订单类型 - /// - public int OrderType { get; set; } - - /// - /// 订单号 - /// - public string OrderNo { get; set; } = null!; - - /// - /// 服务商 - /// - public string ServiceProviderName { get; set; } = null!; - - /// - /// 原轨迹内容 - /// - public string? OriginTrackContent { get; set; } - - /// - /// 原轨迹报文 - /// - public string? OriginTrackData { get; set; } - - /// - /// 轨迹发生地 - /// - public string TrackPlace { get; set; } = null!; - - /// - /// 轨迹发生时间 - /// - public DateTime TrackTime { get; set; } - - /// - /// 轨迹发生时区 - /// - public float TrackTimeZone { get; set; } -} diff --git a/src/SJZY.Track.Application.Contract/Events/TrackUpsertForRefreshEvent.cs b/src/SJZY.Track.Application.Contract/Events/TrackUpsertForRefreshEvent.cs deleted file mode 100644 index 4ae99f4f..00000000 --- a/src/SJZY.Track.Application.Contract/Events/TrackUpsertForRefreshEvent.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace SJZY.Track.Application.Contract.Events; - -public class TrackUpsertForRefreshEvent -{ - /// - /// 子系统id(操作的订单都是相同子系统下的) - /// - public int SubSystemId { get; set; } - - /// - /// 订单 - /// - public OrderIdentifier OrderIdentifiers { get; set; } - - /// - /// 服务商 - /// - public string? ServiceProviderName { get; set; } - - /// - /// 原轨迹内容 - /// - public string? OriginTrackContent { get; set; } - - /// - /// 原轨迹报文 - /// - public string? OriginTrackData { get; set; } - - /// - /// 表示订单是否完成(只由轨迹刷新配置决定) - /// tips:能够使得IsCompleted为true的点,全局只有一个:轨迹刷新 - /// - public bool IsCompleted { get; set; } - - /// - /// 轨迹内容代码 - /// - public string TrackCode { get; set; } = null!; - - /// - /// 轨迹发生地 - /// - public string TrackPlace { get; set; } = null!; - - /// - /// 轨迹发生时间 - /// - public DateTime TrackTime { get; set; } - - /// - /// 轨迹发生时区 - /// - public float TrackTimeZone { get; set; } -} - -public class TrackHandledDto -{ - /// - /// 原轨迹内容 - /// - public string? OriginTrackContent { get; set; } - - /// - /// 原轨迹报文 - /// - public string? OriginTrackData { get; set; } - - /// - /// 表示订单是否完成(只由轨迹刷新配置决定) - /// tips:能够使得IsCompleted为true的点,全局只有一个:轨迹刷新 - /// - public bool? IsCompleted { get; set; } - - /// - /// 轨迹内容代码 - /// - public string? TrackCode { get; set; } - - /// - /// 轨迹发生地 - /// - public string? TrackPlace { get; set; } - - /// - /// 轨迹发生时间 - /// - public DateTime? TrackTime { get; set; } - - /// - /// 轨迹发生时区 - /// - public float? TrackTimeZone { get; set; } -} diff --git a/src/SJZY.Track.Application.Contract/Services/IForewarningNotifyService.cs b/src/SJZY.Track.Application.Contract/Services/IForewarningNotifyService.cs index df0272c6..14647fcc 100644 --- a/src/SJZY.Track.Application.Contract/Services/IForewarningNotifyService.cs +++ b/src/SJZY.Track.Application.Contract/Services/IForewarningNotifyService.cs @@ -18,5 +18,5 @@ public interface IForewarningNotifyService /// /// 发送通知。 /// - Task SendNotificationAsync(); + Task SendNotificationAsync(int subSystemId); } diff --git a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs index 59e99f1f..4f104e7d 100644 --- a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs +++ b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs @@ -40,8 +40,8 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep var rules = await _cacheService.GetRulesAsync(dto.SubSystemId, dto.CustomerId); if (rules == null || rules.Count == 0) return; - var adds = new List<(int ruleId, int orderId, long ts)>(); - var dels = new List<(int ruleId, int orderId)>(); + var adds = new List<(int ruleId, int orderId, int counter, long ts)>(); + var dels = new List<(int ruleId, int orderId, int counter)>(); foreach (var order in dto.Orders) { // 加左区间 @@ -59,7 +59,7 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep startTime = startTime.AddMinutes(interval); // 计算预警时间戳,作为排序依据 - adds.Add((rule.Id, order.Id, timestamp)); + adds.Add((rule.Id, order.Id, i, timestamp)); } }); @@ -67,33 +67,43 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep MatchRule( rules.Where(rule => rule.EndTrackStatusId == dto.TrackStatusId).ToList(), order - ).ForEach(rule => { dels.Add((rule.Id, order.Id)); }); + ).ForEach(rule => + { + for (var i = 0; i < rule.WarnCount; i++) + { + dels.Add((rule.Id, order.Id, i)); + } + }); } + var redisKey = GetForewarningNotifySetKeyRedisKey(dto.SubSystemId); + var tran = RedisHelper.Client.Multi(); // 左区间加入预警队列 - await tran.ZAddAsync(GlobalConst.ForewarningNotifyKey, adds.Select(x => + await tran.ZAddAsync(redisKey, adds.Select(x => { - var entry = new WarningNotifyCacheEntry(x.ruleId, x.orderId); + var entry = new WarningNotifyCacheEntry(x.ruleId, x.orderId, x.counter); return new ZMember(entry.ToString(), x.ts); }).ToArray()); // 右区间移出预警队列 - var right = dels.Select(x => new WarningNotifyCacheEntry(x.ruleId, x.orderId)).ToList(); + var right = dels.Select(x => new WarningNotifyCacheEntry(x.ruleId, x.orderId, x.counter)).ToList(); if (!right.IsNullOrEmpty()) { - await tran.ZRemAsync(GlobalConst.ForewarningNotifyKey, right.Select(x => x.ToString()).ToArray()); - await _warningRecordEsRepository.BulkUpdateAsync(dels); // 标记处理完成 + await tran.ZRemAsync(redisKey, right.Select(x => x.ToString()).ToArray()); } tran.Exec(); + + await _warningRecordEsRepository.BulkUpdateAsync(dels); // 标记处理完成 } - public async Task SendNotificationAsync() + public async Task SendNotificationAsync(int subSystemId) { + var redisKey = GetForewarningNotifySetKeyRedisKey(subSystemId); var timestamp = GetTimestamp(DateTime.Now); - var set = await RedisHelper.Client.ZRangeByScoreAsync(GlobalConst.ForewarningNotifyKey, 0, timestamp); + var set = await RedisHelper.Client.ZRangeByScoreAsync(redisKey, 0, timestamp); if (set.IsNullOrEmpty()) { @@ -119,6 +129,7 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep } } + // 写入es预警记录 var warningRecords = new List(); foreach (var entry in entries) { @@ -141,6 +152,7 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep await _warningRecordEsRepository.BulkInsertAsync(warningRecords); + // 发通知 foreach (var userRule in userRules) { var siteInternalContent = BuildNotifyContent(userRule.Value, entries, NotifyMode.SiteInternal); @@ -156,8 +168,13 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep // todo:发送飞书通知 } } + + await RedisHelper.Client.ZRemRangeByScoreAsync(redisKey, 0, timestamp); } + private string GetForewarningNotifySetKeyRedisKey(int subSystemId) => + $"{GlobalConst.ForewarningNotifySetKey}:{subSystemId}"; + private string BuildNotifyContent(List rules, List entries, NotifyMode mode) { diff --git a/src/SJZY.Track.Application/Services/TrackOrders/OrderWarningService.cs b/src/SJZY.Track.Application/Services/TrackOrders/OrderWarningService.cs index cd2f862d..86bf42b9 100644 --- a/src/SJZY.Track.Application/Services/TrackOrders/OrderWarningService.cs +++ b/src/SJZY.Track.Application/Services/TrackOrders/OrderWarningService.cs @@ -1,3 +1,8 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; +using SJZY.Expand.ABP.Core; +using SJZY.Expand.ABP.Core.Logger; +using SJZY.Track.Application.Contract.Services; using SJZY.Track.Domain.Aggregates.ForewarningAggregate; using SJZY.Track.Domain.Aggregates.OrderAggregate; using SJZY.Track.Domain.Dtos.Forewarning; @@ -6,25 +11,28 @@ namespace SJZY.Track.Application.Services.TrackOrders; public class OrderWarningService { + private readonly IForewarningNotifyService _forewarningNotifyService; private readonly IOrderRepository _orderRepository; private readonly IWarningRecordEsRepository _warningRecordEsRepository; - public OrderWarningService(IWarningRecordEsRepository warningRecordEsRepository, IOrderRepository orderRepository) + public OrderWarningService(IWarningRecordEsRepository warningRecordEsRepository, IOrderRepository orderRepository, + IForewarningNotifyService forewarningNotifyService) { _warningRecordEsRepository = warningRecordEsRepository; _orderRepository = orderRepository; + _forewarningNotifyService = forewarningNotifyService; } /// /// 获取订单运输时效预警分页列表 /// /// - /// /// [HttpPost("GetOrderWarningPageList")] public async Task> GetOrderWarningPageListAsync( GetOrderWarningPageListVo inputVo) { + // todo:产品要的,排序字段,查询方式需要讨论 var result = await _warningRecordEsRepository.GetOrderWarningPageListAsync(inputVo); var orderIds = result.list.Select(x => x.OrderId).ToList(); var orders = await _orderRepository.GetListAsync(x => orderIds.Contains(x.Id), x => new @@ -61,4 +69,49 @@ public class OrderWarningService return new UnifyPageResultDto(list, inputVo.PageIndex, inputVo.PageSize, (int) result.total); } + + + /// + /// 定时任务轨迹运输时效预警推送 + /// + /// + [AllowAnonymous] + [HttpPost("Track/SendWarningForTask")] + public async Task SendWarningForTaskAsync([FromBody] IConfiguration configuration) + { + var configs = configuration.GetSection("SubSystemWarning").Get>(); + if (configs == null || configs.Keys.Count == 0) + { + throw new BusinessException("未配置预警系统"); + } + + var subSystemIds = await _orderRepository.GetSubSystemListByCodesAsync(configs.Keys.ToList()); + foreach (var item in subSystemIds) + { + // 如果已经发送过通知,不再发送 + var redisKey = $"{GlobalConst.ForewarningNotifyCheckKey}:{subSystemIds}"; + if (await RedisHelper.Client.ExistsAsync(redisKey)) + { + continue; + } + + // 异步发送通知 + _ = Task.Run(async () => + { + try + { + await _forewarningNotifyService.SendNotificationAsync(item.id); + } + catch (Exception e) + { + LogHelper.Error(e.ToString()); + await RedisHelper.Client.DelAsync(redisKey); + } + }); + + await RedisHelper.Client.SetExAsync(redisKey, configs[item.code] * 60, 1); + } + + return true; + } } diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRecordEsRepository.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRecordEsRepository.cs index eb52ca54..5f7d1ad3 100644 --- a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRecordEsRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRecordEsRepository.cs @@ -23,7 +23,7 @@ public interface IWarningRecordEsRepository /// /// /// - Task BulkUpdateAsync(List<(int ruleId, int orderId)> warningRecords); + Task BulkUpdateAsync(List<(int ruleId, int orderId, int counter)> warningRecords); /// /// 获取订单运输时效预警分页列表 diff --git a/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderRepository.cs b/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderRepository.cs index d71e24b6..4839a628 100644 --- a/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderRepository.cs @@ -46,6 +46,13 @@ public interface IOrderRepository : IBasicRepository /// Task UpdateBatchIdsAsync(List ids, int newValue); + /// + /// 根据子系统code获取子系统id + /// + /// + /// + Task> GetSubSystemListByCodesAsync(List codes); + #region 订单关联 /// diff --git a/src/SJZY.Track.Domain/Consts/GlobalConst.MQ.cs b/src/SJZY.Track.Domain/Consts/GlobalConst.MQ.cs index e75305e1..488eced1 100644 --- a/src/SJZY.Track.Domain/Consts/GlobalConst.MQ.cs +++ b/src/SJZY.Track.Domain/Consts/GlobalConst.MQ.cs @@ -51,4 +51,9 @@ public partial class GlobalConst /// 推送预警任务 /// public const string TrackPushWarning = "SJZY.Track.TrackPushWarning"; + + /// + /// 预警计算任务 + /// + public const string TrackWarningCalc = "SJZY.Track.TrackWarningCalc"; } diff --git a/src/SJZY.Track.Domain/Consts/GlobalConst.Redis.cs b/src/SJZY.Track.Domain/Consts/GlobalConst.Redis.cs index 37e054a5..6e2e6791 100644 --- a/src/SJZY.Track.Domain/Consts/GlobalConst.Redis.cs +++ b/src/SJZY.Track.Domain/Consts/GlobalConst.Redis.cs @@ -23,14 +23,14 @@ public partial class GlobalConst public const string TrackPullContentKeyPrefix = "TrackPullContent"; /// - /// 预警通知redis key + /// 预警通知set redis key /// - public const string ForewarningNotifyKey = "ForewarningNotify"; + public const string ForewarningNotifySetKey = "ForewarningNotifySet"; /// - /// 预警通知剩余次数redis key + /// 预警通知定时任务罚时 redis key /// - public const string ForewarningNotifyCntKey = "ForewarningNotifyCnt"; + public const string ForewarningNotifyCheckKey = "ForewarningNotifyCheck"; /// /// kpi区域redis key前缀 diff --git a/src/SJZY.Track.Repository/Repositories/OrderRepository.cs b/src/SJZY.Track.Repository/Repositories/OrderRepository.cs index c6a68df9..e7517f86 100644 --- a/src/SJZY.Track.Repository/Repositories/OrderRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/OrderRepository.cs @@ -148,6 +148,20 @@ public class OrderRepository : BasicRepository, IOrderRepository, I return ordersToUpdate.Count; } + public async Task> GetSubSystemListByCodesAsync(List codes) + { + var res = await Context.Queryable() + .Where(x => codes.Contains(x.Key)) + .Select(x => new + { + x.Key, + x.Id + }) + .ToListAsync(); + + return res.Select(x => (x.Key, x.Id)).ToList(); + } + private int UpdateOrderQuestionType(int questionType, IEnumerable ids) { var idsString = string.Join(",", ids); diff --git a/src/SJZY.Track.Repository/Repositories/WarningRecordEsRepository.cs b/src/SJZY.Track.Repository/Repositories/WarningRecordEsRepository.cs index 490570f0..a3d73b5e 100644 --- a/src/SJZY.Track.Repository/Repositories/WarningRecordEsRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/WarningRecordEsRepository.cs @@ -44,7 +44,7 @@ public class WarningRecordEsRepository : ElasticSearchContextAbstract rules) + public async Task BulkUpdateAsync(List<(int ruleId, int orderId, int counter)> rules) { foreach (var rule in rules) {