From 94778ef228482c74aad6fcd071dfc0da47abfc98 Mon Sep 17 00:00:00 2001 From: xiaolipro <2357729423@qq.com> Date: Mon, 29 Jul 2024 18:10:48 +0800 Subject: [PATCH] =?UTF-8?q?add=EF=BC=9A=E9=A2=84=E8=AD=A6=E8=AE=A1?= =?UTF-8?q?=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Forewarning/ForewarningRuleCacheEntry.cs | 2 +- .../Dtos/Forewarning/WarningDto.cs | 17 ++ .../Forewarning/WarningNotifyCacheEntry.cs | 32 ++++ .../Dtos/Forewarning/WarningOrderDto.cs | 13 +- .../Services/IForewarningNotifyService.cs | 10 +- ...TrackChangeAfterUpdateOrderEventHandler.cs | 60 +++---- .../ForewarningNotifyService.cs | 150 +++++++++++------- .../Consts/GlobalConst.Redis.cs | 5 +- .../Dtos/Forewarning/ForewarningRuleItem.cs | 26 --- .../Dtos/Forewarning/RuleNotifyValue.cs | 40 ----- 10 files changed, 181 insertions(+), 174 deletions(-) create mode 100644 src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningDto.cs create mode 100644 src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs rename src/{SJZY.Track.Domain => SJZY.Track.Application.Contract}/Dtos/Forewarning/WarningOrderDto.cs (74%) delete mode 100644 src/SJZY.Track.Domain/Dtos/Forewarning/ForewarningRuleItem.cs delete mode 100644 src/SJZY.Track.Domain/Dtos/Forewarning/RuleNotifyValue.cs diff --git a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ForewarningRuleCacheEntry.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ForewarningRuleCacheEntry.cs index 09ab3e1d..71d402b6 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ForewarningRuleCacheEntry.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ForewarningRuleCacheEntry.cs @@ -50,7 +50,7 @@ public class ForewarningRuleCacheEntry /// /// 区域 /// - public List? AreaId { get; set; } + public AreaCacheEntry? Area { get; set; } /// /// 是否中转 diff --git a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningDto.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningDto.cs new file mode 100644 index 00000000..f911768c --- /dev/null +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningDto.cs @@ -0,0 +1,17 @@ +using SJZY.Track.Domain.Dtos.Forewarning; + +namespace SJZY.Track.Application.Contract.Dtos.Forewarning; + +public class WarningDto +{ + /// + /// 当前轨迹状态 + /// + public int TrackStatusId { get; set; } + + public int SubSystemId { get; set; } + + public int CustomerId { get; set; } + + public List Orders { get; set; } = new(); +} \ No newline at end of file diff --git a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs new file mode 100644 index 00000000..71112bb8 --- /dev/null +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs @@ -0,0 +1,32 @@ +namespace SJZY.Track.Application.Contract.Dtos.Forewarning; + +public class WarningNotifyCacheEntry +{ + /// + /// 订单id + /// + public int OrderId { get; set; } + + /// + /// 规则id + /// + public int RuleId { get; set; } + + public WarningNotifyCacheEntry(int orderId, int ruleId) + { + RuleId = ruleId; + OrderId = orderId; + } + + public WarningNotifyCacheEntry(string notifyValue) + { + var values = notifyValue.Split(':'); + OrderId = int.Parse(values[0]); + RuleId = int.Parse(values[1]); + } + + public override string ToString() + { + return $"{OrderId}:{RuleId}"; + } +} \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/WarningOrderDto.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningOrderDto.cs similarity index 74% rename from src/SJZY.Track.Domain/Dtos/Forewarning/WarningOrderDto.cs rename to src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningOrderDto.cs index 4cdc5a91..c9a395b9 100644 --- a/src/SJZY.Track.Domain/Dtos/Forewarning/WarningOrderDto.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningOrderDto.cs @@ -1,4 +1,4 @@ -namespace SJZY.Track.Domain.Dtos.Forewarning; +namespace SJZY.Track.Application.Contract.Dtos.Forewarning; public class WarningOrderDto { @@ -7,18 +7,9 @@ public class WarningOrderDto /// public int Id { get; set; } - /// - /// 当前轨迹状态 - /// - public int TrackStatusId { get; set; } - - public int SubSystemId { get; set; } - - public int CustomerId { get; set; } - public string? CargoType { get; set; } - public int? Month { get; set; } + public int Month { get; set; } public bool? IsTransit { get; set; } diff --git a/src/SJZY.Track.Application.Contract/Services/IForewarningNotifyService.cs b/src/SJZY.Track.Application.Contract/Services/IForewarningNotifyService.cs index 8830f610..2709a84c 100644 --- a/src/SJZY.Track.Application.Contract/Services/IForewarningNotifyService.cs +++ b/src/SJZY.Track.Application.Contract/Services/IForewarningNotifyService.cs @@ -1,3 +1,4 @@ +using SJZY.Track.Application.Contract.Dtos.Forewarning; using SJZY.Track.Domain.Dtos.Forewarning; namespace SJZY.Track.Application.Contract.Services; @@ -13,14 +14,7 @@ public interface IForewarningNotifyService /// /// 预警订单模型。 /// 创建操作的结果,以整数形式返回。 - Task WarningCalculateAsync(List order); - - /// - /// 删除预警通知。 - /// - /// 预警订单模型。 - /// 删除操作的结果,以整数形式返回。 - Task DeleteForewarningNotify(WarningOrderDto order); + Task WarningCalculateAsync(WarningDto order); /// /// 发送通知。 diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs index 52f1d535..3e96a3b5 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs @@ -1,4 +1,5 @@ using DotNetCore.CAP; +using SJZY.Track.Application.Contract.Dtos.Forewarning; using SJZY.Track.Application.Contract.Events; using SJZY.Track.Application.Contract.Services; using SJZY.Track.Domain.Aggregates.OrderAggregate; @@ -28,7 +29,7 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa if (orders.Count == 0) return; var updateOrders = new List(); - + foreach (var order in orders) { if (order.LatestTrackContent != @event.TrackContent) @@ -65,11 +66,34 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa x.IsCompleted, x.LatestTrackContent, x.LatestTrackEnContent, x.LatestTrackPlace, x.LatestTrackTime, x.LatestTrackTimeZone }); - + // 预警计算 - var warningOrders = orders.Select(order => MapWarningOrderDto(order, @event)).ToList(); - - _forewarningNotifyService.WarningCalculateAsync(warningOrders); + await _forewarningNotifyService.WarningCalculateAsync( BuiltWarningDto(orders, @event)); + } + + private WarningDto BuiltWarningDto(List orders, TrackChangeAfterUpdateOrderEvent @event) + { + return new WarningDto + { + TrackStatusId = @event.TrackStatusId, + SubSystemId = @event.SubSystemId, + CustomerId = @event.CustomId, + Orders = orders.Select(order => new WarningOrderDto + { + Id = order.Id, + + CargoType = order.CargoType, + Month = order.CreateTime.Month, + IsTransit = order.IsTransit, + LeavePort = order.LeavePort, + ProductCode = order.ProductCode, + + ArrivalCountry = order.ArrivalCountry, + ArrivalProvince = order.ArrivalProvince, + ArrivalCity = order.ArrivalCity, + ArrivalPostCode = order.ArrivalPostCode, + }).ToList() + }; } private OrderEntity MapOrderSummary(OrderEntity order) @@ -77,7 +101,7 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa return new OrderEntity { Id = order.Id, - + // 轨迹 IsCompleted = order.IsCompleted, LatestTrackContent = order.LatestTrackContent, @@ -85,7 +109,7 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa LatestTrackPlace = order.LatestTrackPlace, LatestTrackTime = order.LatestTrackTime, LatestTrackTimeZone = order.LatestTrackTimeZone, - + // 因素 CargoType = order.CargoType, CreateTime = order.CreateTime, @@ -98,26 +122,4 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa ProductCode = order.ProductCode }; } - - private WarningOrderDto MapWarningOrderDto(OrderEntity order, TrackChangeAfterUpdateOrderEvent @event) - { - return new WarningOrderDto - { - Id = order.Id, - TrackStatusId = @event.TrackStatusId, - SubSystemId = @event.SubSystemId, - CustomerId = @event.CustomId, - - CargoType = order.CargoType, - Month = order.CreateTime.Month, - IsTransit = order.IsTransit, - LeavePort = order.LeavePort, - ProductCode = order.ProductCode, - - ArrivalCountry = order.ArrivalCountry, - ArrivalProvince = order.ArrivalProvince, - ArrivalCity = order.ArrivalCity, - ArrivalPostCode = order.ArrivalPostCode, - }; - } } \ No newline at end of file diff --git a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs index f438a9cd..75f44fec 100644 --- a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs +++ b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs @@ -1,8 +1,8 @@ using FreeRedis; using SJZY.Expand.ABP.Core; +using SJZY.Track.Application.Contract.Dtos.Forewarning; using SJZY.Track.Application.Contract.Services; using SJZY.Track.Domain.Consts; -using SJZY.Track.Domain.Dtos.Forewarning; using Volo.Abp.DependencyInjection; namespace SJZY.Track.Application.Services.ForewarningServices; @@ -18,84 +18,122 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep { _cacheService = cacheService; } + /// /// 创建预警通知 /// - /// 创建预警通知需要的订单信息 + /// 创建预警通知需要的订单信息 /// 创建的通知总量,0表示没有规则 - public async Task WarningCalculateAsync(List order) + public async Task WarningCalculateAsync(WarningDto dto) { - var rules = await _cacheService.GetRulesAsync(order[0].SubSystemId, order[0].CustomerId); + if (dto.Orders.Count == 0) return; + + var rules = await _cacheService.GetRulesAsync(dto.SubSystemId, dto.CustomerId); + if (rules == null || rules.Count == 0) return; + + var zMembers = new List(); + var members = new List(); + foreach (var order in dto.Orders) + { + MatchRule( + rules.Where(rule => rule.StartTrackStatusId == dto.TrackStatusId).ToList(), + order + ).ForEach(rule => + { + var notifyTimestamp = GetTimestamp(DateTime.Now.AddMinutes(rule.WarnTimeSpan)); + var entry = new WarningNotifyCacheEntry(rule.Id, order.Id); + var notifyValue = new ZMember(entry.ToString(), notifyTimestamp); + zMembers.Add(notifyValue); + }); + + var endingRules = MatchRule( + rules.Where(rule => rule.EndTrackStatusId == dto.TrackStatusId).ToList(), + order + ); + + foreach (var rule in endingRules) + { + var entry = new WarningNotifyCacheEntry(rule.Id, order.Id); + var member = entry.ToString(); + // todo: pipeline optimize? + var score = await RedisHelper.Client.ZScoreAsync(GlobalConst.ForewarningNotifyKey, member); + // tips: score is null 说明发生ending时,没有匹配到规则,所以没有add到set,不需要删除 + if (score != null && (long)score > GetTimestamp(DateTime.Now)) + { + members.Add(member); + } + } + } + + await RedisHelper.Client.ZAddAsync(GlobalConst.ForewarningNotifyKey, zMembers.ToArray()); + await RedisHelper.Client.ZRemAsync(GlobalConst.ForewarningNotifyKey, members.ToArray()); } - public Task DeleteForewarningNotify(WarningOrderDto order) + private static List MatchRule(List rules, + WarningOrderDto order) { - throw new NotImplementedException(); + var res = new List(); + foreach (var rule in rules) + { + if (rule.IsTransit != null) + { + if (order.IsTransit != rule.IsTransit) continue; + } + + if (!rule.CargoType.IsNullOrEmpty()) + { + if (order.CargoType != rule.CargoType) continue; + } + + if (!rule.LeavePort.IsNullOrEmpty()) + { + if (order.LeavePort != rule.LeavePort) continue; + } + + if (!rule.ProductCode.IsNullOrEmpty()) + { + if (order.ProductCode != rule.ProductCode) continue; + } + + if (!rule.Months.IsNullOrEmpty()) + { + if (!order.Month.IsIn(rule.Months)) continue; + } + + if (rule.Area != null) + { + if (order.ArrivalCountry != rule.Area.Country) continue; + if (!rule.Area.LeftPostcode.IsNullOrEmpty() && !rule.Area.RightPostcode.IsNullOrEmpty()) + { + // todo: 参考小包的邮编匹配 + } + + if (!rule.Area.City.IsNullOrEmpty() && order.ArrivalCity != rule.Area.City) continue; + if (!rule.Area.Province.IsNullOrEmpty() && order.ArrivalProvince != rule.Area.Province) continue; + } + + res.Add(rule); + } + + return res; } public void SendNotification(int intervalMinute) { var currentTimestamp = GetTimestamp(DateTime.Now); var notifyMembers = - RedisHelper.Client.ZRangeByScore(GlobalConst.ForewarningNotifyKeyPrefix, 0, currentTimestamp); + RedisHelper.Client.ZRangeByScore(GlobalConst.ForewarningNotifyKey, 0, currentTimestamp); if (notifyMembers == null || notifyMembers.Length == 0) return; - var notifyValues = notifyMembers.Select(val => new RuleNotifyValue(val)); + var notifyValues = notifyMembers.Select(val => new WarningNotifyCacheEntry(val)); foreach (var notifyValue in notifyValues) { } } - private List DeleteNotifyValue(ForewarningRuleItem ruleItem, int orderId) - { - var members = new List(); - for (var index = 0; index < ruleItem.Count; index++) - { - var notifyValue = NotifyValue(ruleItem, orderId, index); - members.Add(notifyValue); - } - - return members; - } - - private async ValueTask ExecuteRule(List rules, int orderId) - { - var members = new List(); - foreach (var rule in rules) - { - var notifys = ExecuteRule(rule, orderId); - members.AddRange(notifys); - } - - await RedisHelper.Client.ZAddAsync(GlobalConst.ForewarningNotifyKeyPrefix, members.ToArray()); - return members.Count; - } - - private List ExecuteRule(ForewarningRuleItem ruleItem, int orderId) - { - var members = new List(); - var intervalMinute = 60 * ((ruleItem.Timeliness - ruleItem.Remind) / ruleItem.Count); // 间隔多少分钟 - - for (var index = 0; index < ruleItem.Count; index++) - { - var notifyTime = (ruleItem.Remind * 60) + intervalMinute * index; - var notifyTimestamp = GetTimestamp(DateTime.Now.AddMinutes(notifyTime)); - var notifyValue = new ZMember(NotifyValue(ruleItem, orderId, index), notifyTimestamp); - members.Add(notifyValue); - } - - return members; - } - - private long GetTimestamp(DateTime dateTime) + private static long GetTimestamp(DateTime dateTime) { return new DateTimeOffset(dateTime).ToUnixTimeSeconds(); } - - private string NotifyValue(ForewarningRuleItem ruleItem, int orderId, int sort) - { - var result = new RuleNotifyValue(ruleItem.Id, orderId, sort, ruleItem.TrackEnd); - return result.ToString(); - } } \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Consts/GlobalConst.Redis.cs b/src/SJZY.Track.Domain/Consts/GlobalConst.Redis.cs index 376af72f..9b29d958 100644 --- a/src/SJZY.Track.Domain/Consts/GlobalConst.Redis.cs +++ b/src/SJZY.Track.Domain/Consts/GlobalConst.Redis.cs @@ -22,11 +22,10 @@ public partial class GlobalConst /// public const string TrackPullContentKeyPrefix = "TrackPullContent"; - /// - /// 预警通知redis key前缀 + /// 预警通知redis key /// - public const string ForewarningNotifyKeyPrefix = "ForewarningNotity"; + public const string ForewarningNotifyKey = "ForewarningNotify"; /// /// kpi区域redis key前缀 diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/ForewarningRuleItem.cs b/src/SJZY.Track.Domain/Dtos/Forewarning/ForewarningRuleItem.cs deleted file mode 100644 index 9e03f6c2..00000000 --- a/src/SJZY.Track.Domain/Dtos/Forewarning/ForewarningRuleItem.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace SJZY.Track.Domain.Dtos.Forewarning; - -public record struct ForewarningRuleItem:IForewarningFactorValue -{ - public int Id { get; set; } - - public int CustomerId { get; set; } - - public int SystemId { get; set; } - public int RuleId { get; set; } - public float Remind { get; set; } - public float Timeliness { get; set; } - public int Count { get; set; } - public int TrackStart { get; set; } - public int TrackEnd { get; set; } - public string? CargoType { get; set; } - public int? Month { get; set; } - public int? Area { get; set; } - public bool? IsTransit { get; set; } - public string? LeavePort { get; set; } - public string? ProductCode { get; set; } - - public string Conect { get; set; } - - public string Template { get; set; } -} \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/RuleNotifyValue.cs b/src/SJZY.Track.Domain/Dtos/Forewarning/RuleNotifyValue.cs deleted file mode 100644 index eda39cc7..00000000 --- a/src/SJZY.Track.Domain/Dtos/Forewarning/RuleNotifyValue.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace SJZY.Track.Domain.Dtos.Forewarning; - -public class RuleNotifyValue -{ - public int RuleId { get; set; } - - public int OrderId { get; set; } - - /// - /// 第几次通知 - /// - public int Count { get; set; } - - /// - /// 结束轨迹状态 - /// - public int TrackEnd { get; set; } - - public RuleNotifyValue(int ruleId, int orderId, int count, int trackEnd) - { - RuleId = ruleId; - OrderId = orderId; - Count = count; - TrackEnd = trackEnd; - } - - public RuleNotifyValue(string notifyValue) - { - var values = notifyValue.Split(':'); - RuleId = int.Parse(values[0]); - OrderId = int.Parse(values[1]); - Count = int.Parse(values[2]); - TrackEnd = int.Parse(values[3]); - } - - public override string ToString() - { - return $"{RuleId}:{OrderId}:{Count}:{TrackEnd}"; - } -} \ No newline at end of file