190 lines
7.4 KiB
C#
190 lines
7.4 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 用于处理预警通知
|
||
/// </summary>
|
||
public class ForewarningNotifyService : IForewarningNotifyService, ITransientDependency
|
||
{
|
||
private readonly IForewarningCacheService _cacheService;
|
||
private readonly IWarningQueries _warningQueries;
|
||
|
||
public ForewarningNotifyService(IForewarningCacheService cacheService, IWarningQueries warningQueries)
|
||
{
|
||
_cacheService = cacheService;
|
||
_warningQueries = warningQueries;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建预警通知
|
||
/// </summary>
|
||
/// <param name="dto">创建预警通知需要的订单信息</param>
|
||
/// <returns>创建的通知总量,0表示没有规则</returns>
|
||
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<ForewarningRuleCacheEntry> MatchRule(List<ForewarningRuleCacheEntry> rules,
|
||
WarningOrderDto order)
|
||
{
|
||
var res = new List<ForewarningRuleCacheEntry>();
|
||
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();
|
||
}
|
||
} |