add:预警计算
This commit is contained in:
parent
305847e3eb
commit
94778ef228
@ -50,7 +50,7 @@ public class ForewarningRuleCacheEntry
|
||||
/// <summary>
|
||||
/// 区域
|
||||
/// </summary>
|
||||
public List<AreaCacheEntry>? AreaId { get; set; }
|
||||
public AreaCacheEntry? Area { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否中转
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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}";
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
|
||||
@ -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>
|
||||
/// 发送通知。
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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前缀
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -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}";
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user