diff --git a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ForewarningRuleCacheEntry.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ForewarningRuleCacheEntry.cs index fd9e30e2..6a00ddb7 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ForewarningRuleCacheEntry.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/ForewarningRuleCacheEntry.cs @@ -46,11 +46,6 @@ public class ForewarningRuleCacheEntry /// 月份 /// public List? Months { get; set; } - - /// - /// 区域 - /// - public AreaCacheEntry? Area { get; set; } /// /// 区域 diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs index 9c642169..85d0ef57 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs @@ -4,7 +4,6 @@ using SJZY.Track.Application.Contract.Events; using SJZY.Track.Application.Contract.Services; using SJZY.Track.Domain.Aggregates.OrderAggregate; using SJZY.Track.Domain.Consts; -using SJZY.Track.Domain.Dtos.Forewarning; using SJZY.Track.Domain.Dtos.Order; using SJZY.Track.Domain.Queries; using Volo.Abp.DependencyInjection; @@ -63,7 +62,7 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa { var config = await _trackPullCacheService.GetPullParamWithContentConfigAsync(provider.SubSystemId, provider.ServiceProviderId); - if (!string.IsNullOrEmpty(config?.ParamConfig?.TrackContentCode) && config.ParamConfig.TrackContentCode!.ToLower() == @event.TrackCode.ToLower()) + if (!string.IsNullOrEmpty(config?.ParamConfig.TrackContentCode) && config.ParamConfig.TrackContentCode!.ToLower() == @event.TrackCode.ToLower()) { // 触发拉取节点 order.IsTriggerPull = 1; diff --git a/src/SJZY.Track.Application/Helpers/ForewarningHelper.cs b/src/SJZY.Track.Application/Helpers/ForewarningHelper.cs index 50ea1424..a1a0e3bc 100644 --- a/src/SJZY.Track.Application/Helpers/ForewarningHelper.cs +++ b/src/SJZY.Track.Application/Helpers/ForewarningHelper.cs @@ -103,7 +103,7 @@ public class ForewarningHelper rules[row].Months = strValue; break; - case RuleFactorType.Areas: + case RuleFactorType.Area: if (value.Type is not JTokenType.Integer) { throw new BusinessException(message: "分区请传id"); @@ -215,7 +215,7 @@ public class ForewarningHelper case RuleFactorType.Months: dynamicRuleDict[nameof(rule.Months)] = rule.Months; break; - case RuleFactorType.Areas: + case RuleFactorType.Area: dynamicRuleDict[nameof(rule.Area)] = rule.Area; break; case RuleFactorType.IsTransit: diff --git a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs index ea453aca..efaaa7d0 100644 --- a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs +++ b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs @@ -1,8 +1,8 @@ +using System.Text; using FreeRedis; using SJZY.Expand.ABP.Core; using SJZY.Track.Application.Contract.Dtos.Forewarning; using SJZY.Track.Application.Contract.Services; -using SJZY.Track.Domain.Aggregates.ForewarningAggregate; using SJZY.Track.Domain.Consts; using SJZY.Track.Domain.Queries; using Volo.Abp.DependencyInjection; @@ -54,18 +54,28 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep ).ForEach(rule => { dels.Add((rule.Id, order.Id)); }); } + var tran = RedisHelper.Client.Multi(); - await RedisHelper.Client.HSetAsync(GlobalConst.ForewarningNotifyCntKey, - adds.ToDictionary(x => $"{x.ruleId}_{x.orderId}", x => x.cnt)); - await RedisHelper.Client.ZAddAsync(GlobalConst.ForewarningNotifyKey, adds.Select(x => + // 维护预警队列 + foreach (var kvp in adds.ToDictionary(x => $"{x.ruleId}_{x.orderId}", x => x.cnt)) + { + await tran.HSetNxAsync(GlobalConst.ForewarningNotifyCntKey, kvp.Key, kvp.Value); // nx: not exist + } + + await tran.ZAddAsync(GlobalConst.ForewarningNotifyKey, adds.Select(x => { var entry = new WarningNotifyCacheEntry(x.ruleId, x.orderId); - return new ZMember(entry.ToString(), x.ts); + return new ZMember(entry.ToString(), x.ts); // update timestamp }).ToArray()); - await RedisHelper.Client.HDelAsync(GlobalConst.ForewarningNotifyCntKey, + + // 移出预警队列 + await tran.HDelAsync(GlobalConst.ForewarningNotifyCntKey, dels.Select(x => $"{x.ruleId}_{x.orderId}").ToArray()); - // await RedisHelper.Client.ZRemAsync(GlobalConst.ForewarningNotifyKey, - // dels.Select(x => $"{x.ruleId}:{x.orderId}").ToArray()); + await tran.ZRemAsync(GlobalConst.ForewarningNotifyKey, + dels.Select(x => $"{x.ruleId}:{x.orderId}").ToArray()); + var res = tran.Exec(); + + if (res == null) throw new BusinessException(message: "轨迹预警计算失败"); } private static List MatchRule(List rules, @@ -99,18 +109,28 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep if (!order.Month.IsIn(rule.Months)) continue; } - if (rule.Area != null) + if (!rule.Areas.IsNullOrEmpty()) { - if (order.ArrivalCountry != rule.Area.Country) continue; - if (!rule.Area.LeftPostcode.IsNullOrEmpty() && !rule.Area.RightPostcode.IsNullOrEmpty()) + var inArea = false; + foreach (var area in rule.Areas!.Where(area => order.ArrivalCountry == area.Country)) { - // todo: 参考小包的邮编匹配 + if (!area.LeftPostcode.IsNullOrEmpty() && !area.RightPostcode.IsNullOrEmpty()) + { + // tips: 参考小包的邮编匹配,字典序 + if (string.CompareOrdinal(order.ArrivalPostCode, area.LeftPostcode) < 0 || + string.CompareOrdinal(order.ArrivalPostCode, area.RightPostcode) > 0) continue; + } + + if (!area.City.IsNullOrEmpty() && order.ArrivalCity != area.City) continue; + if (!area.Province.IsNullOrEmpty() && order.ArrivalProvince != area.Province) continue; + inArea = true; + break; } - if (!rule.Area.City.IsNullOrEmpty() && order.ArrivalCity != rule.Area.City) continue; - if (!rule.Area.Province.IsNullOrEmpty() && order.ArrivalProvince != rule.Area.Province) continue; + if (!inArea) continue; } + // 命中规则 res.Add(rule); } @@ -121,16 +141,43 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep { var max = GetTimestamp(DateTime.Now); var set = await RedisHelper.Client.ZRangeByScoreAsync(GlobalConst.ForewarningNotifyKey, 0, max); - + if (set.IsNullOrEmpty()) return; var entries = set.Select(x => new WarningNotifyCacheEntry(x)).ToList(); var ruleIds = entries.Select(x => x.RuleId).ToList(); var notifyRules = await _warningQueries.GetWarningNotifyRulesAsync(ruleIds); - - foreach (var group in notifyRules) + + /* + * +预警名称:字节全程时效 +预警规则: +{起飞-落地+普货+12345678+美国 时效9,预警时效7,次数3} 命中订单总数:124,详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder +{清关-提货+普货+12345678+美国 时效4,预警时效3,次数1} 命中订单总数:3250,详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder +预警名称:字节全程时效 +预警规则: +{起飞-落地+普货+12345678+美国 时效9,预警时效7,次数3} 命中订单总数:124,详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder +{清关-提货+普货+12345678+美国 时效4,预警时效3,次数1} 命中订单总数:3250,详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder + */ + foreach (var group in notifyRules.GroupBy(x => new { x.NotifyMode, x.NotifyUserId })) { - + var notifyContent = new StringBuilder(); + + foreach (var warningGroup in group.GroupBy(x => x.WarningName)) + { + notifyContent.AppendLine($"预警名称:{warningGroup.Key}"); + notifyContent.AppendLine($"预警规则:"); + + foreach (var rule in warningGroup.ToList()) + { + var cnt = entries.Count(x => x.RuleId == rule.RuleId); + if (cnt > 0) + { + notifyContent.AppendLine( + $"{rule.ToString()} 命中订单总数:{cnt},详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder"); + } + } + } } } diff --git a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningService.cs b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningService.cs index 454692dc..0c7be38f 100644 --- a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningService.cs +++ b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningService.cs @@ -53,6 +53,8 @@ public class ForewarningService : ApplicationService } warning.WarningRules = ForewarningHelper.CalcRuleList(input.TrackStatusSpans, input.WarningFactors); + warning.FactorTypeSort = input.WarningFactors.Select(x => x.FactorType).JoinAsString(","); + warning.WarningFactors = ForewarningHelper.MapFactorEntities(input.WarningFactors); warning.WarningNotifies = ForewarningHelper.MapNotifyEntities(input); await _warningRepository.CreateForewarningAsync(warning); diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/FactorItemModel.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/FactorItemModel.cs deleted file mode 100644 index 7df7f7f2..00000000 --- a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/FactorItemModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SJZY.Track.Domain.Enums; - -namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate; - -public class FactorItemModel -{ - /// - /// Gets or sets the sort order. - /// - public int Sort { get; set; } - - /// - /// Gets or sets the type of the rule factor. - /// - public RuleFactorType FactorType { get; set; } -} \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningEntity.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningEntity.cs index abfc0064..4d8cea7e 100644 --- a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningEntity.cs +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningEntity.cs @@ -27,9 +27,15 @@ public class WarningEntity: IFullAuditedBasicEntitys, IAggregateRoot /// /// 规则模板名称,KPI名称 /// - [SugarColumn(ColumnName = "Name", Length = 64)] + [SugarColumn(ColumnName = "Name")] public string Name { get; set; } = null!; + /// + /// 因素类型排序 + /// + [SugarColumn(ColumnName = "FactorTypeSort")] + public string FactorTypeSort { get; set; } = null!; + /// /// 是否启用,默认否 /// diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRecordIndex.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRecordIndex.cs new file mode 100644 index 00000000..3cd9f954 --- /dev/null +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRecordIndex.cs @@ -0,0 +1,44 @@ +using Nest; +using SJZY.Expand.ElasticSearch; +using SJZY.Track.Domain.Consts; + +namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate; + +[ElasticsearchType(RelationName = GlobalConst.TrackWarningRecordsIndex)] +public class WarningRecordIndex : IdBaseIndex +{ + /// + /// 索引名称 + /// + public string IndexName { get; set; } = null!; + + /// + /// 预警名称 + /// + public string WarningName { get; set; } = null!; + + /// + /// 规则Id + /// + public int RuleId { get; set; } + + /// + /// 规则 + /// + public string Rule { get; set; } = null!; + + /// + /// 订单Id + /// + public int OrderId { get; set; } + + /// + /// 订单号 + /// + public string OrderNo { get; set; } = null!; + + /// + /// + /// + public int CustomId { get; set; } +} \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRuleEntity.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRuleEntity.cs index 56a95930..f6141704 100644 --- a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRuleEntity.cs +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRuleEntity.cs @@ -42,7 +42,7 @@ public class WarningRuleEntity : IFullAuditedBasicEntitys, IEntity /// /// 起始轨迹状态(冗余) /// - [SugarColumn(ColumnName = "StartTrackStatusId")] + [SugarColumn(ColumnName = "StartTrackStatus")] public string StartTrackStatus { get; set; } = null!; /// diff --git a/src/SJZY.Track.Domain/Consts/GlobalConst.ES.cs b/src/SJZY.Track.Domain/Consts/GlobalConst.ES.cs index baf25867..7d279d6d 100644 --- a/src/SJZY.Track.Domain/Consts/GlobalConst.ES.cs +++ b/src/SJZY.Track.Domain/Consts/GlobalConst.ES.cs @@ -13,7 +13,7 @@ public partial class GlobalConst public const string TrackPushRecordsIndex = "sjzy_track_push_records"; /// - /// 预警规则 es索引名称(模板) + /// 预警记录 es索引名称(模板) /// - public const string RuleFactorIndex = "sjzy_rule_factor"; + public const string TrackWarningRecordsIndex = "sjzy_track_warning_records"; } \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/GetWarningNotifiesVo.cs b/src/SJZY.Track.Domain/Dtos/Forewarning/GetWarningNotifiesVo.cs index d39fc838..54ecfdb5 100644 --- a/src/SJZY.Track.Domain/Dtos/Forewarning/GetWarningNotifiesVo.cs +++ b/src/SJZY.Track.Domain/Dtos/Forewarning/GetWarningNotifiesVo.cs @@ -1,17 +1,21 @@ +using System.Text; +using NUglify.Helpers; +using SJZY.Track.Domain.Enums; + namespace SJZY.Track.Domain.Dtos.Forewarning; public class GetWarningNotifiesVo { - /// - /// rule id + /// + /// 规则 id /// - public int Id { get; set; } + public int RuleId { get; set; } /// /// 预警名称 /// public string WarningName { get; set; } = null!; - + /// /// 时效(小时) /// @@ -26,7 +30,7 @@ public class GetWarningNotifiesVo /// 提醒次数 /// public int WarnCount { get; set; } - + /// /// 起始轨迹状态(冗余) /// @@ -66,4 +70,62 @@ public class GetWarningNotifiesVo /// 产品编码 /// public string? ProductCode { get; set; } + + /// + /// 通知方式 + /// + public NotifyMode NotifyMode { get; set; } + + /// + /// 通知模板 + /// + public string NotifyTemplate { get; set; } = null!; + + /// + /// 通知人 + /// + public int NotifyUserId { get; set; } + + /// + /// 因素类型排序 + /// + public string FactorTypeSort { get; set; } = null!; + + + public override string ToString() + { + // {起飞-落地+普货+1/2/3/4+美国 时效9,预警时效7,次数3} + var res = new StringBuilder(); + res.Append('{'); + res.Append($"{StartTrackStatus}-{EndTrackStatus}"); + FactorTypeSort.Split(",").Select(Enum.Parse).ForEach(factorType => + { + switch (factorType) + { + case RuleFactorType.CargoType: + res.Append($"+{CargoType}"); + break; + case RuleFactorType.Months: + res.Append($"+{Months}"); + break; + case RuleFactorType.Area: + res.Append($"+{AreaName}"); + break; + case RuleFactorType.IsTransit: + res.Append($"+{(IsTransit?? false? "中转":"直飞")}"); + break; + case RuleFactorType.LeavePort: + res.Append($"+{LeavePort}"); + break; + case RuleFactorType.ProductCode: + res.Append($"+{ProductCode}"); + break; + default: + throw new ArgumentOutOfRangeException(nameof(factorType), factorType, null); + } + }); + res.Append($" 时效{TotalTimeSpan},预警时效{WarnTimeSpan},次数{WarnCount}}}"); + + return res.ToString(); + } } \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/RuleFactor.cs b/src/SJZY.Track.Domain/Dtos/Forewarning/RuleFactor.cs deleted file mode 100644 index add12997..00000000 --- a/src/SJZY.Track.Domain/Dtos/Forewarning/RuleFactor.cs +++ /dev/null @@ -1,37 +0,0 @@ -using SJZY.Track.Domain.Aggregates.ForewarningAggregate; - -namespace SJZY.Track.Domain.Dtos.Forewarning; - -public class RuleFactor -{ - public int RuleId { get; set; } - - public int SystemId { get; set; } - - public int CustomerId { get; set; } - - /// - /// Gets or sets the start track value. - /// - public int Start { get; set; } - - /// - /// Gets or sets the end track value. - /// - public int End { get; set; } - - /// - /// Gets or sets the user to notify. - /// - public string NotifyUser { get; set; } - - /// - /// Gets or sets the notification template. - /// - public string NotifyTemplate { get; set; } - - /// - /// 获取或设置因素项模型的列表。 - /// - public List FactorItems { get; set; } -} \ No newline at end of file diff --git a/src/SJZY.Track.Domain/Enums/RuleFactorType.cs b/src/SJZY.Track.Domain/Enums/RuleFactorType.cs index 5ff3e3e5..6dd01ace 100644 --- a/src/SJZY.Track.Domain/Enums/RuleFactorType.cs +++ b/src/SJZY.Track.Domain/Enums/RuleFactorType.cs @@ -19,7 +19,7 @@ public enum RuleFactorType /// /// 表示分区或区域。 /// - Areas, + Area, /// /// 指示货物是否中转。 diff --git a/src/SJZY.Track.Repository/Queries/WarningQueries.cs b/src/SJZY.Track.Repository/Queries/WarningQueries.cs index 9601c19d..32aefadf 100644 --- a/src/SJZY.Track.Repository/Queries/WarningQueries.cs +++ b/src/SJZY.Track.Repository/Queries/WarningQueries.cs @@ -1,4 +1,5 @@ using SJZY.Track.Domain.Aggregates.ForewarningAggregate; +using SJZY.Track.Domain.Aggregates.KPIAreaAggregate; using SJZY.Track.Domain.Dtos.Forewarning; using SJZY.Track.Domain.Queries; @@ -17,15 +18,33 @@ public class WarningQueries : ISugarRepository, IWarningQueries, public async Task> GetWarningNotifyRulesAsync(List ruleIds) { return await Context.Queryable() - .LeftJoin((a, b) => a.Id == b.WarningId) - .LeftJoin((a, b, c) => - b.WarningId == c.WarningId && b.StartTrackStatusId == c.StartTrackStatusId && - b.EndTrackStatusId == c.EndTrackStatusId) - .Where((a, b) => ruleIds.Contains(b.Id)) - .Select((a, b, c) => new GetWarningNotifiesVo + .LeftJoin((w, wr) => w.Id == wr.WarningId) + .LeftJoin((w, wr, a) => wr.AreaId == a.Id) + .LeftJoin((w, wr, a, wn) => + wr.WarningId == wn.WarningId && wr.StartTrackStatusId == wn.StartTrackStatusId && + wr.EndTrackStatusId == wn.EndTrackStatusId) + .Filter(null, true) // 支持历史规则 + .Where((w, wr) => ruleIds.Contains(wr.Id)) + .Select((w, wr, a, wn) => new GetWarningNotifiesVo { - WarningName = a.Name - }, true) + RuleId = wr.Id, + WarningName = w.Name, + TotalTimeSpan = wr.TotalTimeSpan, + WarnTimeSpan = wr.WarnTimeSpan, + WarnCount = wr.WarnCount, + StartTrackStatus = wr.StartTrackStatus, + EndTrackStatus = wr.EndTrackStatus, + CargoType = wr.CargoType, + Months = wr.Months, + AreaName = a.Name, + IsTransit = wr.IsTransit, + LeavePort = wr.LeavePort, + ProductCode = wr.ProductCode, + NotifyMode = wn.NotifyMode, + NotifyTemplate = wn.NotifyTemplate, + NotifyUserId = wn.NotifyUserId, + FactorTypeSort = w.FactorTypeSort + }) .ToListAsync(); }