Merge remote-tracking branch 'origin/S04轨迹预警' into S04轨迹预警

This commit is contained in:
钟喜旺 2024-08-02 15:46:35 +08:00
commit d0eda88a2f
14 changed files with 219 additions and 98 deletions

View File

@ -46,11 +46,6 @@ public class ForewarningRuleCacheEntry
/// 月份
/// </summary>
public List<int>? Months { get; set; }
/// <summary>
/// 区域
/// </summary>
public AreaCacheEntry? Area { get; set; }
/// <summary>
/// 区域

View File

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

View File

@ -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:

View File

@ -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<ForewarningRuleCacheEntry> MatchRule(List<ForewarningRuleCacheEntry> 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");
}
}
}
}
}

View File

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

View File

@ -1,16 +0,0 @@
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate;
public class FactorItemModel
{
/// <summary>
/// Gets or sets the sort order.
/// </summary>
public int Sort { get; set; }
/// <summary>
/// Gets or sets the type of the rule factor.
/// </summary>
public RuleFactorType FactorType { get; set; }
}

View File

@ -27,9 +27,15 @@ public class WarningEntity: IFullAuditedBasicEntitys, IAggregateRoot
/// <summary>
/// 规则模板名称,KPI名称
/// </summary>
[SugarColumn(ColumnName = "Name", Length = 64)]
[SugarColumn(ColumnName = "Name")]
public string Name { get; set; } = null!;
/// <summary>
/// 因素类型排序
/// </summary>
[SugarColumn(ColumnName = "FactorTypeSort")]
public string FactorTypeSort { get; set; } = null!;
/// <summary>
/// 是否启用,默认否
/// </summary>

View File

@ -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
{
/// <summary>
/// 索引名称
/// </summary>
public string IndexName { get; set; } = null!;
/// <summary>
/// 预警名称
/// </summary>
public string WarningName { get; set; } = null!;
/// <summary>
/// 规则Id
/// </summary>
public int RuleId { get; set; }
/// <summary>
/// 规则
/// </summary>
public string Rule { get; set; } = null!;
/// <summary>
/// 订单Id
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// 订单号
/// </summary>
public string OrderNo { get; set; } = null!;
/// <summary>
///
/// </summary>
public int CustomId { get; set; }
}

View File

@ -42,7 +42,7 @@ public class WarningRuleEntity : IFullAuditedBasicEntitys, IEntity
/// <summary>
/// 起始轨迹状态(冗余)
/// </summary>
[SugarColumn(ColumnName = "StartTrackStatusId")]
[SugarColumn(ColumnName = "StartTrackStatus")]
public string StartTrackStatus { get; set; } = null!;
/// <summary>

View File

@ -13,7 +13,7 @@ public partial class GlobalConst
public const string TrackPushRecordsIndex = "sjzy_track_push_records";
/// <summary>
/// 预警规则 es索引名称(模板)
/// 预警记录 es索引名称(模板)
/// </summary>
public const string RuleFactorIndex = "sjzy_rule_factor";
public const string TrackWarningRecordsIndex = "sjzy_track_warning_records";
}

View File

@ -1,17 +1,21 @@
using System.Text;
using NUglify.Helpers;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class GetWarningNotifiesVo
{
/// <summary>
/// rule id
/// <summary>
/// 规则 id
/// </summary>
public int Id { get; set; }
public int RuleId { get; set; }
/// <summary>
/// 预警名称
/// </summary>
public string WarningName { get; set; } = null!;
/// <summary>
/// 时效(小时)
/// </summary>
@ -26,7 +30,7 @@ public class GetWarningNotifiesVo
/// 提醒次数
/// </summary>
public int WarnCount { get; set; }
/// <summary>
/// 起始轨迹状态(冗余)
/// </summary>
@ -66,4 +70,62 @@ public class GetWarningNotifiesVo
/// 产品编码
/// </summary>
public string? ProductCode { get; set; }
/// <summary>
/// 通知方式
/// </summary>
public NotifyMode NotifyMode { get; set; }
/// <summary>
/// 通知模板
/// </summary>
public string NotifyTemplate { get; set; } = null!;
/// <summary>
/// 通知人
/// </summary>
public int NotifyUserId { get; set; }
/// <summary>
/// 因素类型排序
/// </summary>
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<RuleFactorType>).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();
}
}

View File

@ -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; }
/// <summary>
/// Gets or sets the start track value.
/// </summary>
public int Start { get; set; }
/// <summary>
/// Gets or sets the end track value.
/// </summary>
public int End { get; set; }
/// <summary>
/// Gets or sets the user to notify.
/// </summary>
public string NotifyUser { get; set; }
/// <summary>
/// Gets or sets the notification template.
/// </summary>
public string NotifyTemplate { get; set; }
/// <summary>
/// 获取或设置因素项模型的列表。
/// </summary>
public List<FactorItemModel> FactorItems { get; set; }
}

View File

@ -19,7 +19,7 @@ public enum RuleFactorType
/// <summary>
/// 表示分区或区域。
/// </summary>
Areas,
Area,
/// <summary>
/// 指示货物是否中转。

View File

@ -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<List<GetWarningNotifiesVo>> GetWarningNotifyRulesAsync(List<int> ruleIds)
{
return await Context.Queryable<WarningEntity>()
.LeftJoin<WarningRuleEntity>((a, b) => a.Id == b.WarningId)
.LeftJoin<WarningNotifyEntity>((a, b, c) =>
b.WarningId == c.WarningId && b.StartTrackStatusId == c.StartTrackStatusId &&
b.EndTrackStatusId == c.EndTrackStatusId)
.Where((a, b) => ruleIds.Contains(b.Id))
.Select<GetWarningNotifiesVo>((a, b, c) => new GetWarningNotifiesVo
.LeftJoin<WarningRuleEntity>((w, wr) => w.Id == wr.WarningId)
.LeftJoin<RuleAreaEntity>((w, wr, a) => wr.AreaId == a.Id)
.LeftJoin<WarningNotifyEntity>((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<GetWarningNotifiesVo>((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();
}