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.Dtos.Forewarning; using SJZY.Track.Domain.Queries; using Volo.Abp.DependencyInjection; namespace SJZY.Track.Application.Services.ForewarningServices; /// /// 用于处理预警通知 /// public class ForewarningNotifyService : IForewarningNotifyService, ITransientDependency { private readonly IForewarningCacheService _cacheService; private readonly IWarningQueries _warningQueries; public ForewarningNotifyService(IForewarningCacheService cacheService, IWarningQueries warningQueries) { _cacheService = cacheService; _warningQueries = warningQueries; } /// /// 创建预警通知 /// /// 创建预警通知需要的订单信息 /// 创建的通知总量,0表示没有规则 public async Task WarningCalculateAsync(WarningDto dto) { if (dto.Orders.Count == 0) return; var rules = await _cacheService.GetRulesAsync(dto.SubSystemId, dto.CustomerId); if (rules == null || rules.Count == 0) return; var adds = new List<(int ruleId, int orderId, int cnt, long ts)>(); var dels = new List<(int ruleId, int orderId)>(); foreach (var order in dto.Orders) { MatchRule( rules.Where(rule => rule.StartTrackStatusId == dto.TrackStatusId).ToList(), order ).ForEach(rule => { var timestamp = GetTimestamp(order.TrackTime.AddMinutes(rule.WarnTimeSpan)); adds.Add((rule.Id, order.Id, rule.WarnCount, timestamp)); }); MatchRule( rules.Where(rule => rule.EndTrackStatusId == dto.TrackStatusId).ToList(), order ).ForEach(rule => { dels.Add((rule.Id, order.Id)); }); } var tran = RedisHelper.Client.Multi(); // 维护预警队列 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); // update timestamp }).ToArray()); // 移出预警队列 await tran.HDelAsync(GlobalConst.ForewarningNotifyCntKey, 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: "轨迹预警计算失败"); } public async Task SendNotificationAsync() { 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); /* * 预警名称:字节全程时效 预警规则: {起飞-落地+普货+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"); } } } } } private static List MatchRule(List rules, WarningOrderDto order) { 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.Areas.IsNullOrEmpty()) { var inArea = false; foreach (var area in rule.Areas!.Where(area => order.ArrivalCountry == area.Country)) { 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 (!inArea) continue; } // 命中规则 res.Add(rule); } return res; } private static long GetTimestamp(DateTime dateTime) { return new DateTimeOffset(dateTime).ToUnixTimeSeconds(); } }