add:预警计算

This commit is contained in:
xiaolipro 2024-07-29 18:10:48 +08:00
parent 305847e3eb
commit 94778ef228
10 changed files with 181 additions and 174 deletions

View File

@ -50,7 +50,7 @@ public class ForewarningRuleCacheEntry
/// <summary>
/// 区域
/// </summary>
public List<AreaCacheEntry>? AreaId { get; set; }
public AreaCacheEntry? Area { get; set; }
/// <summary>
/// 是否中转

View File

@ -0,0 +1,17 @@
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class WarningDto
{
/// <summary>
/// 当前轨迹状态
/// </summary>
public int TrackStatusId { get; set; }
public int SubSystemId { get; set; }
public int CustomerId { get; set; }
public List<WarningOrderDto> Orders { get; set; } = new();
}

View File

@ -0,0 +1,32 @@
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class WarningNotifyCacheEntry
{
/// <summary>
/// 订单id
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// 规则id
/// </summary>
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}";
}
}

View File

@ -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
/// </summary>
public int Id { get; set; }
/// <summary>
/// 当前轨迹状态
/// </summary>
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; }

View File

@ -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
/// </summary>
/// <param name="order">预警订单模型。</param>
/// <returns>创建操作的结果,以整数形式返回。</returns>
Task WarningCalculateAsync(List<WarningOrderDto> order);
/// <summary>
/// 删除预警通知。
/// </summary>
/// <param name="order">预警订单模型。</param>
/// <returns>删除操作的结果,以整数形式返回。</returns>
Task<int> DeleteForewarningNotify(WarningOrderDto order);
Task WarningCalculateAsync(WarningDto order);
/// <summary>
/// 发送通知。

View File

@ -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<OrderEntity>();
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<OrderEntity> 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,
};
}
}

View File

@ -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;
}
/// <summary>
/// 创建预警通知
/// </summary>
/// <param name="order">创建预警通知需要的订单信息</param>
/// <param name="dto">创建预警通知需要的订单信息</param>
/// <returns>创建的通知总量,0表示没有规则</returns>
public async Task WarningCalculateAsync(List<WarningOrderDto> 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<ZMember>();
var members = new List<string>();
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<int> DeleteForewarningNotify(WarningOrderDto order)
private static List<ForewarningRuleCacheEntry> MatchRule(List<ForewarningRuleCacheEntry> rules,
WarningOrderDto order)
{
throw new NotImplementedException();
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.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<string> DeleteNotifyValue(ForewarningRuleItem ruleItem, int orderId)
{
var members = new List<string>();
for (var index = 0; index < ruleItem.Count; index++)
{
var notifyValue = NotifyValue(ruleItem, orderId, index);
members.Add(notifyValue);
}
return members;
}
private async ValueTask<int> ExecuteRule(List<ForewarningRuleItem> rules, int orderId)
{
var members = new List<ZMember>();
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<ZMember> ExecuteRule(ForewarningRuleItem ruleItem, int orderId)
{
var members = new List<ZMember>();
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();
}
}

View File

@ -22,11 +22,10 @@ public partial class GlobalConst
/// </summary>
public const string TrackPullContentKeyPrefix = "TrackPullContent";
/// <summary>
/// 预警通知redis key前缀
/// 预警通知redis key
/// </summary>
public const string ForewarningNotifyKeyPrefix = "ForewarningNotity";
public const string ForewarningNotifyKey = "ForewarningNotify";
/// <summary>
/// kpi区域redis key前缀

View File

@ -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; }
}

View File

@ -1,40 +0,0 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class RuleNotifyValue
{
public int RuleId { get; set; }
public int OrderId { get; set; }
/// <summary>
/// 第几次通知
/// </summary>
public int Count { get; set; }
/// <summary>
/// 结束轨迹状态
/// </summary>
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}";
}
}