Merge remote-tracking branch 'origin/S04轨迹预警' into S04轨迹预警
This commit is contained in:
commit
d0eda88a2f
@ -46,11 +46,6 @@ public class ForewarningRuleCacheEntry
|
||||
/// 月份
|
||||
/// </summary>
|
||||
public List<int>? Months { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 区域
|
||||
/// </summary>
|
||||
public AreaCacheEntry? Area { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 区域
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -42,7 +42,7 @@ public class WarningRuleEntity : IFullAuditedBasicEntitys, IEntity
|
||||
/// <summary>
|
||||
/// 起始轨迹状态(冗余)
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "StartTrackStatusId")]
|
||||
[SugarColumn(ColumnName = "StartTrackStatus")]
|
||||
public string StartTrackStatus { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -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";
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -19,7 +19,7 @@ public enum RuleFactorType
|
||||
/// <summary>
|
||||
/// 表示分区或区域。
|
||||
/// </summary>
|
||||
Areas,
|
||||
Area,
|
||||
|
||||
/// <summary>
|
||||
/// 指示货物是否中转。
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user