diff --git a/SJZY.Track.Api.sln.DotSettings b/SJZY.Track.Api.sln.DotSettings index 2363a683..55bb2ed6 100644 --- a/SJZY.Track.Api.sln.DotSettings +++ b/SJZY.Track.Api.sln.DotSettings @@ -1,2 +1,3 @@ - + + True True \ No newline at end of file diff --git a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/EditWarningOutput.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/EditWarningOutput.cs index 644b78c6..9bd85f2e 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/EditWarningOutput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/EditWarningOutput.cs @@ -20,7 +20,7 @@ public class EditWarningOutput public int CustomerId { get; set; } /// - /// 通知列表 + /// 轨迹状态列表 /// public List TrackStatusSpans { get; set; } = new(); diff --git a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNitifyDto.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNitifyDto.cs index e743ae68..35133a59 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNitifyDto.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNitifyDto.cs @@ -29,6 +29,11 @@ public class WarningNotifyDto /// public List NotifyUserIds { get; set; } + /// + /// 通知人 + /// + public List NotifyUsers { get; set; } + /// /// 通知方式 /// diff --git a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs index 80cdbe7f..9d07c54e 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/Forewarning/WarningNotifyCacheEntry.cs @@ -10,7 +10,7 @@ public class WarningNotifyCacheEntry public WarningNotifyCacheEntry(string notifyValue) { - var values = notifyValue.Split(':'); + var values = notifyValue.Split('_'); RuleId = int.Parse(values[0]); OrderId = int.Parse(values[1]); } @@ -27,6 +27,6 @@ public class WarningNotifyCacheEntry public override string ToString() { - return $"{RuleId}:{OrderId}"; + return $"{RuleId}_{OrderId}"; } } diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs index 87589d20..4b5d6cd9 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs @@ -1,9 +1,9 @@ using DotNetCore.CAP; +using SJZY.Expand.ABP.Core.Logger; 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; -using SJZY.Track.Domain.Queries; using Volo.Abp.DependencyInjection; namespace SJZY.Track.Application.CAP.EventHandlers; @@ -12,17 +12,12 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa { private readonly IForewarningNotifyService _forewarningNotifyService; private readonly IOrderRepository _orderRepository; - private readonly IProvideQueries _provideQueries; - private readonly ITrackPullCacheService _trackPullCacheService; public TrackChangeAfterUpdateOrderEventHandler(IOrderRepository orderRepository, - IForewarningNotifyService forewarningNotifyService, ITrackPullCacheService trackPullCacheService, - IProvideQueries provideQueries) + IForewarningNotifyService forewarningNotifyService) { _orderRepository = orderRepository; _forewarningNotifyService = forewarningNotifyService; - _trackPullCacheService = trackPullCacheService; - _provideQueries = provideQueries; } [CapSubscribe(GlobalConst.TrackChangeAfterUpdateOrderSubscribe)] @@ -30,7 +25,11 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa { var orders = await GetOrdersAsync(@event.OrderIds); - if (orders.Count == 0) return; + if (orders.Count == 0) + { + LogHelper.Warn("没有找到订单信息,无法更新轨迹信息"); + return; + } var updateOrders = new List(); foreach (var order in orders) @@ -46,7 +45,7 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa } // 执行轨迹兜底时 时区缺失和发生地缺失标记,1表示时区,2表示发生地,3表示两种 - if (@event.DeficiencyTagValue > 0 && @event.DeficiencyTagValue <= 3) + if (@event.DeficiencyTagValue is > 0 and <= 3) { order.DeficiencyTag = @event.DeficiencyTagValue; } diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackDeleteEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackDeleteEventHandler.cs index 396aa1d1..403e275d 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackDeleteEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackDeleteEventHandler.cs @@ -14,15 +14,16 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe private readonly ICapPublisher _capPublisher; private readonly IOrderRepository _orderRepository; private readonly IProvideQueries _provideQueries; - private readonly ITrackDetailsRepository _trackDetailsRepository; + private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; public TrackDeleteEventHandler(IProvideQueries provideQueries, - IOrderRepository orderRepository, ICapPublisher capPublisher, ITrackDetailsRepository trackDetailsRepository) + IOrderRepository orderRepository, ICapPublisher capPublisher, + ITrackDetailsEsRepository trackDetailsEsRepository) { _provideQueries = provideQueries; _orderRepository = orderRepository; _capPublisher = capPublisher; - _trackDetailsRepository = trackDetailsRepository; + _trackDetailsEsRepository = trackDetailsEsRepository; } [CapSubscribe(GlobalConst.TrackDeleteSubscribe, Group = GlobalConst.TrackDeleteSubscribe)] @@ -104,9 +105,9 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe private async Task DeleteTrackAsync(TrackDeleteEvent @event) { // 删除轨迹(删的是同子系统下的单,所以code是统一的) - var tracks = await _trackDetailsRepository.GetLatestListAsync(@event.OrderIds, @event.TrackCodes); + var tracks = await _trackDetailsEsRepository.GetLatestListAsync(@event.OrderIds, @event.TrackCodes); - await _trackDetailsRepository.DeleteTrackAsync(@event.OrderIds, @event.TrackCodes); + await _trackDetailsEsRepository.DeleteTrackAsync(@event.OrderIds, @event.TrackCodes); var orders = await _orderRepository.GetListAsync(x => @event.OrderIds.Contains(x.Id), x => new OrderEntity diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushCompletedEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushCompletedEventHandler.cs index 9a0df561..e10d40fd 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushCompletedEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushCompletedEventHandler.cs @@ -13,12 +13,12 @@ namespace SJZY.Track.Application.CAP.EventHandlers; public class TrackPushCompletedEventHandler : ITransientDependency, ICapSubscribe { private readonly IOrderRepository _orderRepository; - private readonly ITrackPushRecordsRepository _trackPushRecordsRepository; + private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository; - public TrackPushCompletedEventHandler(ITrackPushRecordsRepository trackPushRecordsRepository, + public TrackPushCompletedEventHandler(ITrackPushRecordEsRepository trackPushRecordEsRepository, IOrderRepository orderRepository) { - _trackPushRecordsRepository = trackPushRecordsRepository; + _trackPushRecordEsRepository = trackPushRecordEsRepository; _orderRepository = orderRepository; } @@ -45,7 +45,7 @@ public class TrackPushCompletedEventHandler : ITransientDependency, ICapSubscrib log.RequestId = @event.RequestId; // 往es 写推送日志 - await _trackPushRecordsRepository.AddAsync(log); + await _trackPushRecordEsRepository.AddAsync(log); // if (!log.IsSuccess) // { diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushEventHandler.cs index f63dd7e2..9cae0d44 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushEventHandler.cs @@ -20,21 +20,21 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe { private readonly IHttpClientProxy _httpClientProxy; private readonly IOrderRepository _orderRepository; - private readonly ITrackDetailsRepository _trackDetailsRepository; + private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; private readonly ITrackPushCacheService _trackPushCacheService; - private readonly ITrackPushRecordsRepository _trackPushRecordsRepository; + private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository; private List _orders = null!; private List _pushConfigs = null!; public TrackPushEventHandler(IHttpClientProxy httpClientProxy, IOrderRepository orderRepository, ITrackPushCacheService trackPushCacheService, - ITrackDetailsRepository trackDetailsRepository, ITrackPushRecordsRepository trackPushRecordsRepository) + ITrackDetailsEsRepository trackDetailsEsRepository, ITrackPushRecordEsRepository trackPushRecordEsRepository) { _httpClientProxy = httpClientProxy; _orderRepository = orderRepository; _trackPushCacheService = trackPushCacheService; - _trackDetailsRepository = trackDetailsRepository; - _trackPushRecordsRepository = trackPushRecordsRepository; + _trackDetailsEsRepository = trackDetailsEsRepository; + _trackPushRecordEsRepository = trackPushRecordEsRepository; } [CapSubscribe(GlobalConst.TrackPushSubscribe, Group = GlobalConst.TrackPushSubscribe)] @@ -113,7 +113,7 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode); if (config == null) return; - var tracks = await _trackDetailsRepository.GetListAsync(@event.OrderIds, @event.TrackCode); + var tracks = await _trackDetailsEsRepository.GetListAsync(@event.OrderIds, @event.TrackCode); tracks = tracks.DistinctBy(x => x.OrderId).ToList(); // 防止重复推送 if (tracks.Count == 0) return; // 一个能推的轨迹都不存在 @@ -256,7 +256,7 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe if (config.IsAllPush) { - var history = await _trackPushRecordsRepository.GetLatestTrackPushRecordAsync(log.OrderId); + var history = await _trackPushRecordEsRepository.GetLatestTrackPushRecordAsync(log.OrderId); if (history != default) { body = JsonConvert.DeserializeObject(history.PushContent); diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackUpsertEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackUpsertEventHandler.cs index 857f1ee1..5d5583e3 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackUpsertEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackUpsertEventHandler.cs @@ -15,33 +15,33 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe private readonly ICapPublisher _capPublisher; private readonly IOrderRepository _orderRepository; private readonly IProvideQueries _provideQueries; - private readonly ITrackDetailsRepository _trackDetailsRepository; + private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; public TrackUpsertEventHandler(IOrderRepository orderRepository, ICapPublisher capPublisher, - IProvideQueries provideQueries, ITrackDetailsRepository trackDetailsRepository) + IProvideQueries provideQueries, ITrackDetailsEsRepository trackDetailsEsRepository) { _orderRepository = orderRepository; _capPublisher = capPublisher; _provideQueries = provideQueries; - _trackDetailsRepository = trackDetailsRepository; + _trackDetailsEsRepository = trackDetailsEsRepository; } [CapSubscribe(GlobalConst.TrackUpsertSubscribe, Group = GlobalConst.TrackUpsertSubscribe)] [CapSubscribe(GlobalConst.TrackUpsertForRefreshSubscribe, Group = GlobalConst.TrackUpsertForRefreshSubscribe)] public async Task ConsumerAsync(TrackUpsertEvent @event) { - var orderIds = @event.OrderIdentifiers.Select(x => x.OrderId).ToArray(); - - var dup = @event.Visited.Intersect(orderIds).ToArray(); - if (dup.Any()) - { - LogHelper.Warn($"{(@event.IsAdd ? "添加" : "修改")}轨迹时,发现循环依赖:{dup.JoinAsString(",")}", true); - return; - } + if (!CanDelete(@event)) return; var canNextOrderIds = new List(); await HandleTrackAsync(@event, canNextOrderIds); + if (canNextOrderIds.Count == 0) return; + // 根据订单关联关系异步向下刷(异步) + await DownByOrderLink(@event, canNextOrderIds); + } + + private async Task DownByOrderLink(TrackUpsertEvent @event, List canNextOrderIds) + { // 根据订单关联关系异步向下刷(异步) var linkedOrders = await _orderRepository.GetLinkedOrdersAsync(canNextOrderIds.ToArray()); foreach (var order in @event.OrderIdentifiers) @@ -55,7 +55,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe })) { var nextEvent = @event.DeepCopy(); - nextEvent.Visited.AddRange(orderIds); + nextEvent.Visited.AddRange(canNextOrderIds); nextEvent.SubSystemId = group.Key.SubSystemId; nextEvent.CustomId = group.Key.CustomId; nextEvent.TrackRefreshData = new TrackRefreshStruct @@ -103,6 +103,27 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe } } + private bool CanDelete(TrackUpsertEvent @event) + { + // 系统创建的轨迹不允许修改 + if (@event.Visited.IsNullOrEmpty() && @event is { OperationMan: GlobalConst.SystemUser, IsAdd: false }) + { + LogHelper.Warn($"系统创建的轨迹不允许被修改:{@event}", true); + return false; + } + + var orderIds = @event.OrderIdentifiers.Select(x => x.OrderId).ToArray(); + + var dup = @event.Visited.Intersect(orderIds).ToArray(); + if (dup.Any()) + { + LogHelper.Warn($"{(@event.IsAdd ? "添加" : "修改")}轨迹时,发现循环依赖:{dup.JoinAsString(",")}", true); + return false; + } + + return true; + } + private async Task HandleTrackAsync(TrackUpsertEvent @event, List canNextOrderIds) { var trackContent = await _provideQueries.GetTrackContentAsync(@event.SubSystemId, @event.TrackCode); @@ -117,7 +138,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe var orderIds = @event.OrderIdentifiers.Select(x => x.OrderId).ToArray(); // important:轨迹code相同,时间、发生地不同的,可能存在多条!! - var tracks = await _trackDetailsRepository.GetListAsync(orderIds, @event.TrackCode, true); + var tracks = await _trackDetailsEsRepository.GetListAsync(orderIds, @event.TrackCode, true); var insertTracks = new List(); @@ -167,7 +188,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe track.UpdateUser = @event.OperationMan ?? GlobalConst.SystemUser; track.UpdateTime = DateTime.Now; - await _trackDetailsRepository.UpdateAsync(track); + await _trackDetailsEsRepository.UpdateAsync(track); } canNextOrderIds.Add(order.OrderId); @@ -176,7 +197,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe if (canNextOrderIds.Count == 0) return; // 批量add to es - await _trackDetailsRepository.AddManyAsync(insertTracks); + await _trackDetailsEsRepository.AddManyAsync(insertTracks); // 触发订单主表更新事件(异步) await _capPublisher.PublishAsync(GlobalConst.TrackChangeAfterUpdateOrderSubscribe, diff --git a/src/SJZY.Track.Application/Rabbit/TrackRefreshEventHandler.cs b/src/SJZY.Track.Application/Rabbit/TrackRefreshEventHandler.cs index 083aca06..b02d2b14 100644 --- a/src/SJZY.Track.Application/Rabbit/TrackRefreshEventHandler.cs +++ b/src/SJZY.Track.Application/Rabbit/TrackRefreshEventHandler.cs @@ -28,18 +28,18 @@ public class TrackRefreshEventHandler : ITransientDependency, IRabbitHandler private readonly ILogger _logger; private readonly IOrderRepository _orderRepository; private readonly ITimezoneQueries _timezoneQueries; - private readonly ITrackDetailsRepository _trackDetailsRepository; + private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; private readonly ITrackPullCacheService _trackPullCacheService; public TrackRefreshEventHandler(IOrderRepository orderRepository, ICapPublisher capPublisher, ITrackPullCacheService trackPullCacheService, - ITrackDetailsRepository trackDetailsRepository, ITimezoneQueries timezoneQueries, + ITrackDetailsEsRepository trackDetailsEsRepository, ITimezoneQueries timezoneQueries, ILogger logger) { _orderRepository = orderRepository; _capPublisher = capPublisher; _trackPullCacheService = trackPullCacheService; - _trackDetailsRepository = trackDetailsRepository; + _trackDetailsEsRepository = trackDetailsEsRepository; _timezoneQueries = timezoneQueries; _logger = logger; } @@ -152,7 +152,7 @@ public class TrackRefreshEventHandler : ITransientDependency, IRabbitHandler { var originTrackDataHash = StringHelper.GetHash(JsonConvert.SerializeObject(trackToken)); // 如果已经存在一模一样的报文了说明处理过了,直接return - if (await _trackDetailsRepository.AnyAsync(context.OrderId, originTrackDataHash)) + if (await _trackDetailsEsRepository.AnyAsync(context.OrderId, originTrackDataHash)) { return; } @@ -327,7 +327,7 @@ public class TrackRefreshEventHandler : ITransientDependency, IRabbitHandler ? track.TrackTime : track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone)); - await _trackDetailsRepository.AddAsync(track); + await _trackDetailsEsRepository.AddAsync(track); } /// diff --git a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningCacheService.cs b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningCacheService.cs index 1a530095..c89475f9 100644 --- a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningCacheService.cs +++ b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningCacheService.cs @@ -36,18 +36,12 @@ public class ForewarningCacheService : IForewarningCacheService, ISingletonDepen return await GetRules(subSystemId, customerId); } - public Task DeleteRulesAsync(int subSystemId, int customerId) - { - _rules.Remove(WarningKey(subSystemId, customerId), out _); - - return Task.CompletedTask; - } - public Task Clear(ClearRulCacheEvent @event) { foreach (var item in @event.Items) { - DeleteRulesAsync(item.SystemId, item.CustomerId); + var redisKey = WarningKey(item.SystemId, item.CustomerId); + _rules.Remove(redisKey, out _); } return Task.CompletedTask; @@ -91,7 +85,7 @@ public class ForewarningCacheService : IForewarningCacheService, ISingletonDepen private List WriteRules(List rules) { - List result = new List(); + var result = new List(); foreach (var item in rules) { List? areaList = null; @@ -153,11 +147,11 @@ public class ForewarningCacheService : IForewarningCacheService, ISingletonDepen var warnings = await _warningQueries.GetWarningRule(systemId, customerId); if (warnings == null || warnings.Count == 0) { - RedisHelper.Client.Set(key, new List(), TimeSpan.FromSeconds(20)); + await RedisHelper.Client.SetAsync(key, new List(), TimeSpan.FromSeconds(20)); return new List(); } - RedisHelper.Client.Set(key, warnings); + await RedisHelper.Client.SetAsync(key, warnings); ruleResult = WriteRules(warnings); _rules.TryAdd(key, ruleResult); return ruleResult; @@ -169,7 +163,7 @@ public class ForewarningCacheService : IForewarningCacheService, ISingletonDepen if (_areas.TryGetValue(areaId, out var areaInfos)) return areaInfos; var areas = GetAreas(areaId); - if (areas == null || areas.Count == 0) return null; + if (areas.Count == 0) return null; areaInfos = areas.Select(KpiAreaInfoToEntry).ToList(); _areas.TryAdd(areaId, areaInfos); @@ -192,7 +186,7 @@ public class ForewarningCacheService : IForewarningCacheService, ISingletonDepen /// 获取区域 /// /// - private List? GetAreas(int areaId) + private List GetAreas(int areaId) { var key = AreaKey(areaId); var result = RedisHelper.Client.Get>(key); diff --git a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs index 14e9d9d5..59e99f1f 100644 --- a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs +++ b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningNotifyService.cs @@ -1,11 +1,13 @@ using System.Text; using FreeRedis; using SJZY.Expand.ABP.Core; +using SJZY.Expand.ABP.Core.Logger; using SJZY.Track.Application.Contract.Dtos.Forewarning; using SJZY.Track.Application.Contract.Services; using SJZY.Track.Domain.Aggregates.ForewarningAggregate; using SJZY.Track.Domain.Dtos.Forewarning; using SJZY.Track.Domain.Queries; +using SJZY.Track.Share.Enums; using Volo.Abp.DependencyInjection; namespace SJZY.Track.Application.Services.ForewarningServices; @@ -17,18 +19,20 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep { private readonly IForewarningCacheService _cacheService; private readonly IWarningQueries _warningQueries; + private readonly IWarningRecordEsRepository _warningRecordEsRepository; - public ForewarningNotifyService(IForewarningCacheService cacheService, IWarningQueries warningQueries) + public ForewarningNotifyService(IForewarningCacheService cacheService, IWarningQueries warningQueries, + IWarningRecordEsRepository warningRecordEsRepository) { _cacheService = cacheService; _warningQueries = warningQueries; + _warningRecordEsRepository = warningRecordEsRepository; } /// /// 创建预警通知 /// /// 创建预警通知需要的订单信息 - /// 创建的通知总量,0表示没有规则 public async Task WarningCalculateAsync(WarningDto dto) { if (dto.Orders.Count == 0) return; @@ -36,19 +40,30 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep 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 adds = new List<(int ruleId, int orderId, 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)); + var interval = (rule.TotalTimeSpan - rule.WarnTimeSpan) / rule.WarnCount; + var startTime = order.TrackTime.AddMinutes(rule.WarnTimeSpan); + + for (var i = 0; i < rule.WarnCount; i++) + { + var timestamp = GetTimestamp(startTime); + startTime = startTime.AddMinutes(interval); + + // 计算预警时间戳,作为排序依据 + adds.Add((rule.Id, order.Id, timestamp)); + } }); + // 删右区间 MatchRule( rules.Where(rule => rule.EndTrackStatusId == dto.TrackStatusId).ToList(), order @@ -57,74 +72,123 @@ public class ForewarningNotifyService : IForewarningNotifyService, ITransientDep 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 + return new ZMember(entry.ToString(), x.ts); }).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(); + // 右区间移出预警队列 + var right = dels.Select(x => new WarningNotifyCacheEntry(x.ruleId, x.orderId)).ToList(); + if (!right.IsNullOrEmpty()) + { + await tran.ZRemAsync(GlobalConst.ForewarningNotifyKey, right.Select(x => x.ToString()).ToArray()); + await _warningRecordEsRepository.BulkUpdateAsync(dels); // 标记处理完成 + } - if (res == null) throw new BusinessException(message: "轨迹预警计算失败"); + tran.Exec(); } public async Task SendNotificationAsync() { - var max = GetTimestamp(DateTime.Now); - var set = await RedisHelper.Client.ZRangeByScoreAsync(GlobalConst.ForewarningNotifyKey, 0, max); + var timestamp = GetTimestamp(DateTime.Now); + var set = await RedisHelper.Client.ZRangeByScoreAsync(GlobalConst.ForewarningNotifyKey, 0, timestamp); - if (set.IsNullOrEmpty()) return; + if (set.IsNullOrEmpty()) + { + LogHelper.Info("没有运输时效需要预警"); + return; + } var entries = set.Select(x => new WarningNotifyCacheEntry(x)).ToList(); - var ruleIds = entries.Select(x => x.RuleId).ToList(); + var ruleIds = entries.Select(x => x.RuleId).Distinct().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 + var userRules = new Dictionary>(); + foreach (var rule in notifyRules) { - x.NotifyMode, - x.NotifyUserId - })) - { - var notifyContent = new StringBuilder(); - - foreach (var warningGroup in group.GroupBy(x => x.WarningName)) + foreach (var userId in rule.NotifyUserIds) { - notifyContent.AppendLine($"预警名称:{warningGroup.Key}"); - notifyContent.AppendLine($"预警规则:"); - - foreach (var rule in warningGroup.ToList()) + if (!userRules.ContainsKey(userId)) { - var cnt = entries.Count(x => x.RuleId == rule.RuleId); - if (cnt > 0) - { - notifyContent.AppendLine( - $"{rule.ToString()} 命中订单总数:{cnt},详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder"); - } + userRules[userId] = new List(); } + + userRules[userId].Add(rule); } } + + var warningRecords = new List(); + foreach (var entry in entries) + { + var rule = notifyRules.FirstOrDefault(x => x.RuleId == entry.RuleId); + if (rule == null) continue; + warningRecords.Add(new WarningRecordIndex + { + SubSystemId = rule.SubSystemId, + WarningName = rule.WarningName, + CustomName = rule.CustomName, + RuleId = entry.RuleId, + Rule = rule.ToString(), + OrderId = entry.OrderId, + WarningStatus = WarningStatus.UnProcessed, + NotifyUser = rule.NotifyUser, + CreateTime = DateTime.Now, + }); + } + + await _warningRecordEsRepository.BulkInsertAsync(warningRecords); + + + foreach (var userRule in userRules) + { + var siteInternalContent = BuildNotifyContent(userRule.Value, entries, NotifyMode.SiteInternal); + var feiShuContent = BuildNotifyContent(userRule.Value, entries, NotifyMode.FeiShu); + + if (!string.IsNullOrEmpty(siteInternalContent)) + { + // todo:发送站内通知 + } + + if (!string.IsNullOrEmpty(feiShuContent)) + { + // todo:发送飞书通知 + } + } + } + + private string BuildNotifyContent(List rules, List entries, + NotifyMode mode) + { + /* 以人的维度发送通知 + * + 预警名称:字节全程时效 + 预警规则: + {起飞-落地+普货+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 + */ + var notifyContent = new StringBuilder(); + + foreach (var warningGroup in rules.Where(x => x.NotifyModes.Contains(mode)) + .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); + notifyContent.AppendLine( + $"{rule.ToString()} 命中订单总数:{cnt},详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder"); + } + } + + return notifyContent.ToString(); } private static List MatchRule(List rules, diff --git a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningService.cs b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningService.cs index a0131e04..1ef4fa2a 100644 --- a/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningService.cs +++ b/src/SJZY.Track.Application/Services/ForewarningServices/ForewarningService.cs @@ -22,6 +22,7 @@ public class ForewarningService : ApplicationService _forewarningCacheService = forewarningCacheService; } + /// /// 获取预警分页列表 /// @@ -150,7 +151,7 @@ public class ForewarningService : ApplicationService await _warningRepository.TurnWarningAsync(warning); // 清除缓存 - await _forewarningCacheService.DeleteRulesAsync(warning.SubSystemId, warning.CustomerId); + await _forewarningCacheService.ClearRedisBySystemIdAndCustomer(warning.SubSystemId, warning.CustomerId); return true; } @@ -194,7 +195,7 @@ public class ForewarningService : ApplicationService await _warningRepository.ModifyTimeSpansAsync(warningRules); // 清除缓存 - await _forewarningCacheService.DeleteRulesAsync(warning.SubSystemId, warning.CustomerId); + await _forewarningCacheService.ClearRedisBySystemIdAndCustomer(warning.SubSystemId, warning.CustomerId); return true; } } diff --git a/src/SJZY.Track.Application/Services/ProvideServices/OrderService.Link.cs b/src/SJZY.Track.Application/Services/ProvideServices/OrderService.Link.cs index 29712d98..eb999633 100644 --- a/src/SJZY.Track.Application/Services/ProvideServices/OrderService.Link.cs +++ b/src/SJZY.Track.Application/Services/ProvideServices/OrderService.Link.cs @@ -14,7 +14,7 @@ public partial class OrderService { private readonly ICapPublisher _capPublisher; private readonly IBasicQuery _orderLinkQuery; - private readonly ITrackDetailsRepository _trackDetailsRepository; + private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; /// /// 关联、解除关联订单 @@ -99,7 +99,7 @@ public partial class OrderService LinkOrderType = input.LinkOrderType }).ToList()); - var tracks = await _trackDetailsRepository.GetListAsync(orderId); + var tracks = await _trackDetailsEsRepository.GetListAsync(orderId); // 补轨迹(按子系统、按客户分,同样是为了方便轨迹upsert和push) foreach (var group in existedOrders.GroupBy(x => new diff --git a/src/SJZY.Track.Application/Services/ProvideServices/OrderService.cs b/src/SJZY.Track.Application/Services/ProvideServices/OrderService.cs index 0f5bb56e..0d04747c 100644 --- a/src/SJZY.Track.Application/Services/ProvideServices/OrderService.cs +++ b/src/SJZY.Track.Application/Services/ProvideServices/OrderService.cs @@ -16,16 +16,16 @@ namespace SJZY.Track.Application.Services.ProvideServices; [Route("/ProviderService")] public partial class OrderService : ApplicationService, IOrderService { - private readonly IOrderOperationRecordRepository _orderOperationRecordRepository; + private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository; private readonly IOrderRepository _orderRepository; public OrderService(IOrderRepository orderRepository, IBasicQuery orderLinkQuery, - ICapPublisher capPublisher, ITrackDetailsRepository trackDetailsRepository, - IOrderOperationRecordRepository orderOperationRecordRepository) + ICapPublisher capPublisher, ITrackDetailsEsRepository trackDetailsEsRepository, + IOrderOperationRecordEsRepository orderOperationRecordEsRepository) { _orderRepository = orderRepository; - _orderOperationRecordRepository = orderOperationRecordRepository; - _trackDetailsRepository = trackDetailsRepository; + _orderOperationRecordEsRepository = orderOperationRecordEsRepository; + _trackDetailsEsRepository = trackDetailsEsRepository; _capPublisher = capPublisher; _orderLinkQuery = orderLinkQuery; } @@ -54,7 +54,7 @@ public partial class OrderService : ApplicationService, IOrderService Content = input.ToJson(), }; - _ = _orderOperationRecordRepository.AddAsync(index); + _ = _orderOperationRecordEsRepository.AddAsync(index); return new UnifyResultDto(true, new IdBaseDto { @@ -79,7 +79,7 @@ public partial class OrderService : ApplicationService, IOrderService CreateUserId = CurrentUserRequest.CurrentUser.UserId, CreateTime = DateTime.Now }; - _ = _orderOperationRecordRepository.AddAsync(indexForAdd); + _ = _orderOperationRecordEsRepository.AddAsync(indexForAdd); return new UnifyResultDto(true, new IdBaseDto diff --git a/src/SJZY.Track.Application/Services/ProvideServices/TrackPushService.cs b/src/SJZY.Track.Application/Services/ProvideServices/TrackPushService.cs index 1f90f2ce..c6d52f7f 100644 --- a/src/SJZY.Track.Application/Services/ProvideServices/TrackPushService.cs +++ b/src/SJZY.Track.Application/Services/ProvideServices/TrackPushService.cs @@ -19,16 +19,16 @@ public class TrackPushService : ApplicationService private readonly ICapPublisher _capPublisher; private readonly IOrderRepository _orderRepository; private readonly IHttpClientProxy _requestCurrentUserService; - private readonly ITrackPushRecordsRepository _trackPushRecordsRepository; + private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository; public TrackPushService(IOrderRepository orderRepository , ICapPublisher capPublisher - , ITrackPushRecordsRepository trackPushRecordsRepository, + , ITrackPushRecordEsRepository trackPushRecordEsRepository, IHttpClientProxy requestCurrentUserService) { _orderRepository = orderRepository; _capPublisher = capPublisher; - _trackPushRecordsRepository = trackPushRecordsRepository; + _trackPushRecordEsRepository = trackPushRecordEsRepository; _requestCurrentUserService = requestCurrentUserService; } @@ -115,6 +115,6 @@ public class TrackPushService : ApplicationService throw new BusinessException(message: "订单不存在"); } - return await _trackPushRecordsRepository.GetTrackPushRecordsAsync(orderId); + return await _trackPushRecordEsRepository.GetTrackPushRecordsAsync(orderId); } } diff --git a/src/SJZY.Track.Application/Services/ProvideServices/TrackService.Query.cs b/src/SJZY.Track.Application/Services/ProvideServices/TrackService.Query.cs index 8ceb3b51..6efd4304 100644 --- a/src/SJZY.Track.Application/Services/ProvideServices/TrackService.Query.cs +++ b/src/SJZY.Track.Application/Services/ProvideServices/TrackService.Query.cs @@ -8,7 +8,7 @@ namespace SJZY.Track.Application.Services.ProvideServices; /// public partial class TrackService { - private readonly ITrackDetailsRepository _trackDetailsRepository; + private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; /// /// 查询轨迹 @@ -22,7 +22,7 @@ public partial class TrackService var res = new List(); input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空 - var details = await _trackDetailsRepository.GetListAsync(input.SubSystemId, input.OrderType, input.OrderNos); + var details = await _trackDetailsEsRepository.GetListAsync(input.SubSystemId, input.OrderType, input.OrderNos); // 是否对外 var trackCodes = details.Select(x => x.TrackCode) diff --git a/src/SJZY.Track.Application/Services/ProvideServices/TrackService.cs b/src/SJZY.Track.Application/Services/ProvideServices/TrackService.cs index 1208365a..faa4a04d 100644 --- a/src/SJZY.Track.Application/Services/ProvideServices/TrackService.cs +++ b/src/SJZY.Track.Application/Services/ProvideServices/TrackService.cs @@ -23,14 +23,14 @@ public partial class TrackService : ApplicationService, ITrackService private readonly ITrackContentRepository _trackContentRepository; public TrackService(IOrderRepository orderRepository, ICapPublisher capPublisher, - ITrackDetailsRepository trackDetailsRepository, ITrackContentRepository trackContentRepository, + ITrackDetailsEsRepository trackDetailsEsRepository, ITrackContentRepository trackContentRepository, IHttpClientProxy requestCurrentUserService) { _orderRepository = orderRepository; _capPublisher = capPublisher; _trackContentRepository = trackContentRepository; _requestCurrentUserService = requestCurrentUserService; - _trackDetailsRepository = trackDetailsRepository; + _trackDetailsEsRepository = trackDetailsEsRepository; } /// diff --git a/src/SJZY.Track.Application/Services/TrackOrders/OrderWarningService.cs b/src/SJZY.Track.Application/Services/TrackOrders/OrderWarningService.cs new file mode 100644 index 00000000..cd2f862d --- /dev/null +++ b/src/SJZY.Track.Application/Services/TrackOrders/OrderWarningService.cs @@ -0,0 +1,64 @@ +using SJZY.Track.Domain.Aggregates.ForewarningAggregate; +using SJZY.Track.Domain.Aggregates.OrderAggregate; +using SJZY.Track.Domain.Dtos.Forewarning; + +namespace SJZY.Track.Application.Services.TrackOrders; + +public class OrderWarningService +{ + private readonly IOrderRepository _orderRepository; + private readonly IWarningRecordEsRepository _warningRecordEsRepository; + + public OrderWarningService(IWarningRecordEsRepository warningRecordEsRepository, IOrderRepository orderRepository) + { + _warningRecordEsRepository = warningRecordEsRepository; + _orderRepository = orderRepository; + } + + /// + /// 获取订单运输时效预警分页列表 + /// + /// + /// + /// + [HttpPost("GetOrderWarningPageList")] + public async Task> GetOrderWarningPageListAsync( + GetOrderWarningPageListVo inputVo) + { + var result = await _warningRecordEsRepository.GetOrderWarningPageListAsync(inputVo); + var orderIds = result.list.Select(x => x.OrderId).ToList(); + var orders = await _orderRepository.GetListAsync(x => orderIds.Contains(x.Id), x => new + { + x.Id, + x.LatestTrackContent, + x.LatestTrackTime, + x.LatestTrackPlace, + x.OrderNo + }); + + var list = new List(); + + + result.list.ForEach(x => + { + var item = new GetOrderWarningPageOutput + { + Rule = x.Rule, + WarningName = x.WarningName, + WarningStatus = x.WarningStatus, + CreateTime = x.CreateTime, + NotifyUser = x.NotifyUser, + CustomName = x.CustomName, + }; + var order = orders.FirstOrDefault(o => o.Id == x.OrderId); + if (order == default) return; + item.OrderNo = order.OrderNo; + item.LatestTrackTime = order.LatestTrackTime; + item.LatestTrackPlace = order.LatestTrackPlace; + item.LatestTrackContent = order.LatestTrackContent; + }); + + return new UnifyPageResultDto(list, inputVo.PageIndex, inputVo.PageSize, + (int) result.total); + } +} diff --git a/src/SJZY.Track.Application/Services/TrackOrders/TrackOrderService.cs b/src/SJZY.Track.Application/Services/TrackOrders/TrackOrderService.cs index 7b921aaa..cc94f76d 100644 --- a/src/SJZY.Track.Application/Services/TrackOrders/TrackOrderService.cs +++ b/src/SJZY.Track.Application/Services/TrackOrders/TrackOrderService.cs @@ -12,24 +12,24 @@ namespace SJZY.Track.Application.Services.TrackOrders; [Route("/Track/Order")] public class TrackOrderService : ApplicationService { - private readonly IOrderOperationRecordRepository _orderOperationRecordRepository; + private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository; private readonly IOrderRepository _orderRepository; private readonly ITrackContentRepository _trackContentRepository; - private readonly ITrackDetailsRepository _trackDetailsRepository; - private readonly ITrackPushRecordsRepository _trackPushRecordsRepository; + private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; + private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository; public TrackOrderService( IOrderRepository orderRepository, - ITrackDetailsRepository trackDetailsRepository, + ITrackDetailsEsRepository trackDetailsEsRepository, ITrackContentRepository trackContentRepository, - ITrackPushRecordsRepository trackPushRecordsRepository, - IOrderOperationRecordRepository orderOperationRecordRepository) + ITrackPushRecordEsRepository trackPushRecordEsRepository, + IOrderOperationRecordEsRepository orderOperationRecordEsRepository) { _orderRepository = orderRepository; - _trackDetailsRepository = trackDetailsRepository; + _trackDetailsEsRepository = trackDetailsEsRepository; _trackContentRepository = trackContentRepository; - _trackPushRecordsRepository = trackPushRecordsRepository; - _orderOperationRecordRepository = orderOperationRecordRepository; + _trackPushRecordEsRepository = trackPushRecordEsRepository; + _orderOperationRecordEsRepository = orderOperationRecordEsRepository; } #region 轨迹订单列表 @@ -74,7 +74,7 @@ public class TrackOrderService : ApplicationService [HttpGet("GetTrackPushRecords")] public async Task> GetTrackPushRecordsAsync(int orderId) { - var records = await _trackPushRecordsRepository.GetTrackPushRecordsAsync(orderId); + var records = await _trackPushRecordEsRepository.GetTrackPushRecordsAsync(orderId); var res = ObjectMapper.Map, List>(records); @@ -103,7 +103,7 @@ public class TrackOrderService : ApplicationService throw new BusinessException(message: "订单不存在"); } - var trackDetails = await _trackDetailsRepository.GetListAsync(orderId); + var trackDetails = await _trackDetailsEsRepository.GetListAsync(orderId); if (trackDetails.IsNullOrEmpty()) { return new List(); @@ -162,7 +162,7 @@ public class TrackOrderService : ApplicationService [HttpPost("DeleteTrackById")] public async Task DeleteTrackByIdAsync(string trackId) { - var track = await _trackDetailsRepository.GetByIdAsync(trackId); + var track = await _trackDetailsEsRepository.GetByIdAsync(trackId); // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract if (track == null) @@ -170,7 +170,7 @@ public class TrackOrderService : ApplicationService throw new BusinessException(message: "轨迹不存在!"); } - await _trackDetailsRepository.DeleteTrackByIdAsync(trackId, track.IndexName); + await _trackDetailsEsRepository.DeleteTrackByIdAsync(trackId, track.IndexName); return true; } @@ -182,6 +182,6 @@ public class TrackOrderService : ApplicationService [HttpGet("GetOrderOperationsById")] public async Task> GetOrderOperationsByIdAsync(int orderId) { - return await _orderOperationRecordRepository.GetOrderOperationsByIdAsync(orderId); + return await _orderOperationRecordEsRepository.GetOrderOperationsByIdAsync(orderId); } } diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IForewarningCacheService.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IForewarningCacheService.cs index c80b108f..2c82e8d0 100644 --- a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IForewarningCacheService.cs +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IForewarningCacheService.cs @@ -12,14 +12,6 @@ public interface IForewarningCacheService /// Task?> GetRulesAsync(int subSystemId, int customerId); - /// - /// 删除规则 - /// - /// - /// - /// - Task DeleteRulesAsync(int subSystemId, int customerId); - /// /// 重置本地缓存 /// diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRecordEsRepository.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRecordEsRepository.cs new file mode 100644 index 00000000..eb52ca54 --- /dev/null +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/IWarningRecordEsRepository.cs @@ -0,0 +1,34 @@ +using SJZY.Track.Domain.Dtos.Forewarning; + +namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate; + +public interface IWarningRecordEsRepository +{ + /// + /// 添加预警记录 + /// + /// + /// + Task AddAsync(WarningRecordIndex index); + + /// + /// 批量添加预警记录 + /// + /// + /// + Task BulkInsertAsync(List warningRecords); + + /// + /// 批量更新预警记录 + /// + /// + /// + Task BulkUpdateAsync(List<(int ruleId, int orderId)> warningRecords); + + /// + /// 获取订单运输时效预警分页列表 + /// + /// + /// + Task<(List list, long total)> GetOrderWarningPageListAsync(GetOrderWarningPageListVo inputVo); +} diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningNotifyEntity.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningNotifyEntity.cs index 8d3bb8ac..b26722fe 100644 --- a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningNotifyEntity.cs +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningNotifyEntity.cs @@ -34,8 +34,14 @@ public class WarningNotifyEntity : IFullAuditedBasicEntitys, IEntity public string NotifyMode { get; set; } /// - /// 通知人,多个逗号分隔 + /// 通知人id,多个逗号分隔 /// [SugarColumn(ColumnName = "NotifyUserId")] public string NotifyUserId { get; set; } + + /// + /// 通知人,多个逗号分隔 + /// + [SugarColumn(ColumnName = "NotifyUser")] + public string NotifyUser { get; set; } } diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRecordIndex.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRecordIndex.cs index 308684ab..52a6ffbd 100644 --- a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRecordIndex.cs +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningRecordIndex.cs @@ -7,6 +7,11 @@ namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate; [ElasticsearchType(RelationName = GlobalConst.TrackWarningRecordIndex)] public class WarningRecordIndex : IdBaseIndex { + /// + /// 子系统 + /// + public int SubSystemId { get; set; } + /// /// 索引名称 /// @@ -18,14 +23,14 @@ public class WarningRecordIndex : IdBaseIndex public string WarningName { get; set; } = null!; /// - /// 规则Id + /// 客户简称 /// - public int RuleId { get; set; } + public string CustomName { get; set; } /// - /// 规则 + /// 预警规则id /// - public string Rule { get; set; } = null!; + public int RuleId { get; set; } /// /// 订单Id @@ -33,12 +38,22 @@ public class WarningRecordIndex : IdBaseIndex public int OrderId { get; set; } /// - /// 订单号 + /// 预警规则 /// - public string OrderNo { get; set; } = null!; + public string Rule { get; set; } = null!; /// - /// + /// 预警状态 /// - public int CustomId { get; set; } + public WarningStatus WarningStatus { get; set; } + + /// + /// 通知用户 + /// + public string NotifyUser { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } } diff --git a/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningStatus.cs b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningStatus.cs new file mode 100644 index 00000000..f5a76c9e --- /dev/null +++ b/src/SJZY.Track.Domain/Aggregates/ForewarningAggregate/WarningStatus.cs @@ -0,0 +1,14 @@ +namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate; + +public enum WarningStatus +{ + /// + /// 待处理 + /// + UnProcessed = 1, + + /// + /// 已处理 + /// + Processed = 2, +} diff --git a/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordRepository.cs b/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordEsRepository.cs similarity index 90% rename from src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordRepository.cs rename to src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordEsRepository.cs index 0dfe62f4..9f3af683 100644 --- a/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordEsRepository.cs @@ -1,6 +1,6 @@ namespace SJZY.Track.Domain.Aggregates.OrderAggregate; -public interface IOrderOperationRecordRepository +public interface IOrderOperationRecordEsRepository { /// /// 添加订单操作记录 diff --git a/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsRepository.cs b/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsEsRepository.cs similarity index 90% rename from src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsRepository.cs rename to src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsEsRepository.cs index e30046df..e7ba4c60 100644 --- a/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsEsRepository.cs @@ -2,7 +2,7 @@ using SJZY.Expand.ElasticSearch.Services; namespace SJZY.Track.Domain.Aggregates.TrackDetailsAggregate; -public interface ITrackDetailsRepository : IElasticsearchClientService +public interface ITrackDetailsEsRepository : IElasticsearchClientService { Task> GetListAsync(int[] orderIds, string trackCode, bool forUpsert = false); diff --git a/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordsRepository.cs b/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordEsRepository.cs similarity index 94% rename from src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordsRepository.cs rename to src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordEsRepository.cs index 2345c265..ee5840f5 100644 --- a/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordsRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordEsRepository.cs @@ -1,6 +1,6 @@ namespace SJZY.Track.Domain.Aggregates.TrackPushAggregate; -public interface ITrackPushRecordsRepository +public interface ITrackPushRecordEsRepository { /// /// 添加推送记录 diff --git a/src/SJZY.Track.Domain/Consts/GlobalConst.ES.cs b/src/SJZY.Track.Domain/Consts/GlobalConst.ES.cs index bdc73ae4..04d82607 100644 --- a/src/SJZY.Track.Domain/Consts/GlobalConst.ES.cs +++ b/src/SJZY.Track.Domain/Consts/GlobalConst.ES.cs @@ -3,22 +3,22 @@ namespace SJZY.Track.Domain.Consts; public partial class GlobalConst { /// - /// 轨迹明细 es索引名称(模板) + /// 轨迹明细 es索引名称(模板按天) /// public const string TrackIndex = "sjzy_track_details"; /// - /// 轨迹推送记录 es索引名称(模板) + /// 轨迹推送记录 es索引名称(模板按天) /// public const string TrackPushRecordsIndex = "sjzy_track_push_records"; /// - /// 预警记录 es索引名称(模板) + /// 运输时效预警记录 es索引名称(模板按天) /// public const string TrackWarningRecordIndex = "sjzy_track_warning_records"; /// - /// 订单操作记录 es索引名称(模板) + /// 订单操作记录 es索引名称(模板按天) /// public const string OrderOperationRecordIndex = "sjzy_track_order_operation_records"; } diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/GetOrderWarningPageListVo.cs b/src/SJZY.Track.Domain/Dtos/Forewarning/GetOrderWarningPageListVo.cs new file mode 100644 index 00000000..0378857f --- /dev/null +++ b/src/SJZY.Track.Domain/Dtos/Forewarning/GetOrderWarningPageListVo.cs @@ -0,0 +1,48 @@ +using System.ComponentModel.DataAnnotations; +using SJZY.Track.Domain.Aggregates.ForewarningAggregate; + +namespace SJZY.Track.Domain.Dtos.Forewarning; + +public class GetOrderWarningPageListVo : PageInfoInput +{ + /// + /// 子系统id + /// + public int? SubSystemId { get; set; } + + /// + /// 订单号 + /// + [MaxLength(500)] + public List? OrderNos { get; set; } + + /// + /// 预警状态 + /// + public WarningStatus? WarningStatus { get; set; } + + /// + /// 客户简称 + /// + public string? CustomName { get; set; } + + /// + /// 预警名称 + /// + public string? WarningName { get; set; } + + /// + /// 通知用户 + /// + public string? NotifyUser { get; set; } + + /// + /// 创建时间开始 + /// + public DateTime? StartTime { get; set; } + + /// + /// 创建时间结束 + /// + public DateTime? EndTime { get; set; } +} diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/GetOrderWarningPageOutput.cs b/src/SJZY.Track.Domain/Dtos/Forewarning/GetOrderWarningPageOutput.cs new file mode 100644 index 00000000..e1fede89 --- /dev/null +++ b/src/SJZY.Track.Domain/Dtos/Forewarning/GetOrderWarningPageOutput.cs @@ -0,0 +1,53 @@ +using SJZY.Track.Domain.Aggregates.ForewarningAggregate; + +namespace SJZY.Track.Domain.Dtos.Forewarning; + +public class GetOrderWarningPageOutput +{ + /// + /// 预警名称 + /// + public string WarningName { get; set; } = null!; + + /// + /// 客户简称 + /// + public string CustomName { get; set; } + + /// + /// 预警规则 + /// + public string Rule { get; set; } = null!; + + /// + /// 预警状态 + /// + public WarningStatus WarningStatus { get; set; } + + /// + /// 通知用户 + /// + public string NotifyUser { get; set; } + + /// + /// 最新轨迹内容(异步维护) + /// + public string? LatestTrackContent { get; set; } + + /// + /// 最新轨迹时间(异步维护) + /// + public DateTime? LatestTrackTime { get; set; } + + /// + /// 最新轨迹发生地(异步维护) + /// + public string? LatestTrackPlace { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } + + public string OrderNo { get; set; } +} diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/GetWarningNotifiesVo.cs b/src/SJZY.Track.Domain/Dtos/Forewarning/GetWarningNotifiesVo.cs index 12014a8b..d56ec748 100644 --- a/src/SJZY.Track.Domain/Dtos/Forewarning/GetWarningNotifiesVo.cs +++ b/src/SJZY.Track.Domain/Dtos/Forewarning/GetWarningNotifiesVo.cs @@ -6,6 +6,11 @@ namespace SJZY.Track.Domain.Dtos.Forewarning; public class GetWarningNotifiesVo { + /// + /// 子系统 + /// + public int SubSystemId { get; set; } + /// /// 规则 id /// @@ -16,6 +21,11 @@ public class GetWarningNotifiesVo /// public string WarningName { get; set; } = null!; + /// + /// 客户简称 + /// + public string CustomName { get; set; } + /// /// 时效(小时) /// @@ -85,6 +95,8 @@ public class GetWarningNotifiesVo public List NotifyUserIds { get; set; } + public string NotifyUser { get; set; } + /// /// 因素类型排序 /// diff --git a/src/SJZY.Track.Domain/Dtos/Forewarning/WarningRuleCacheDto.cs b/src/SJZY.Track.Domain/Dtos/Forewarning/WarningRuleCacheDto.cs index e0218636..bbdbd03d 100644 --- a/src/SJZY.Track.Domain/Dtos/Forewarning/WarningRuleCacheDto.cs +++ b/src/SJZY.Track.Domain/Dtos/Forewarning/WarningRuleCacheDto.cs @@ -5,6 +5,12 @@ public class WarningRuleCacheDto public int Id { get; set; } public int SystemId { get; set; } public int CustomerId { get; set; } + + /// + /// 客户简称 + /// + public string CustomName { get; set; } + public int WarningId { get; set; } public float TotalTimeSpan { get; set; } public float WarnTimeSpan { get; set; } diff --git a/src/SJZY.Track.Repository/Queries/WarningQueries.cs b/src/SJZY.Track.Repository/Queries/WarningQueries.cs index f561e785..4d970615 100644 --- a/src/SJZY.Track.Repository/Queries/WarningQueries.cs +++ b/src/SJZY.Track.Repository/Queries/WarningQueries.cs @@ -2,6 +2,7 @@ using SJZY.Track.Domain.Aggregates.ForewarningAggregate; using SJZY.Track.Domain.Aggregates.KPIAreaAggregate; using SJZY.Track.Domain.Dtos.Forewarning; using SJZY.Track.Domain.Queries; +using SJZY.Track.Domain.VEntities; using SJZY.Track.Share.Enums; namespace SJZY.Track.Repository.Queries; @@ -19,12 +20,15 @@ public class WarningQueries : ISugarRepository, IWarningQueries, .LeftJoin((w, wr, a, wn) => wr.WarningId == wn.WarningId && wr.StartTrackStatusId == wn.StartTrackStatusId && wr.EndTrackStatusId == wn.EndTrackStatusId) + .LeftJoin((w, wr, a, wn, cus) => w.CustomerId == cus.Id) .Filter(null, true) // 支持历史规则 .Where((w, wr) => ruleIds.Contains(wr.Id)) - .Select((w, wr, a, wn) => new GetWarningNotifiesVo + .Select((w, wr, a, wn, cus) => new GetWarningNotifiesVo { + SubSystemId = w.SubSystemId, RuleId = wr.Id, WarningName = w.Name, + CustomName = cus.Name, TotalTimeSpan = wr.TotalTimeSpan, WarnTimeSpan = wr.WarnTimeSpan, WarnCount = wr.WarnCount, @@ -37,6 +41,7 @@ public class WarningQueries : ISugarRepository, IWarningQueries, LeavePort = wr.LeavePort, ProductCode = wr.ProductCode, NotifyMode = wn.NotifyMode, + NotifyUser = wn.NotifyUser, NotifyUserId = wn.NotifyUserId, FactorTypeSort = w.FactorTypeSort }) diff --git a/src/SJZY.Track.Repository/Repositories/OrderOperationRecordRepository.cs b/src/SJZY.Track.Repository/Repositories/OrderOperationRecordRepository.cs index 773636f0..49babdaa 100644 --- a/src/SJZY.Track.Repository/Repositories/OrderOperationRecordRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/OrderOperationRecordRepository.cs @@ -7,11 +7,11 @@ using SJZY.Track.Domain.Consts; namespace SJZY.Track.Repository.Repositories; -public class OrderOperationRecordRepository : ElasticSearchContextAbstract, - IOrderOperationRecordRepository, +public class OrderOperationRecordEsRepository : ElasticSearchContextAbstract, + IOrderOperationRecordEsRepository, ITransientDependency { - public OrderOperationRecordRepository(ConnectionFactory connectionFactory) : base(connectionFactory) + public OrderOperationRecordEsRepository(ConnectionFactory connectionFactory) : base(connectionFactory) { } @@ -26,7 +26,7 @@ public class OrderOperationRecordRepository : ElasticSearchContextAbstract, ITrackDetailsRepository, +internal class DetailsEsRepository : ElasticSearchContextAbstract, ITrackDetailsEsRepository, ITransientDependency { const int SafelyLimit = GlobalConst.SafelyLimit; - public TrackDetailsRepository(ConnectionFactory connectionFactory) : base(connectionFactory) + public DetailsEsRepository(ConnectionFactory connectionFactory) : base(connectionFactory) { } diff --git a/src/SJZY.Track.Repository/Repositories/TrackPushRecordsRepository.cs b/src/SJZY.Track.Repository/Repositories/TrackPushRecordsRepository.cs index 71ed5faa..0d4c0246 100644 --- a/src/SJZY.Track.Repository/Repositories/TrackPushRecordsRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/TrackPushRecordsRepository.cs @@ -7,10 +7,10 @@ using SJZY.Track.Domain.Consts; namespace SJZY.Track.Repository.Repositories; -public class TrackPushRecordsRepository : ElasticSearchContextAbstract, ITrackPushRecordsRepository, +public class PushRecordEsRepository : ElasticSearchContextAbstract, ITrackPushRecordEsRepository, ITransientDependency { - public TrackPushRecordsRepository(ConnectionFactory connectionFactory) : base(connectionFactory) + public PushRecordEsRepository(ConnectionFactory connectionFactory) : base(connectionFactory) { } diff --git a/src/SJZY.Track.Repository/Repositories/WarningRecordEsRepository.cs b/src/SJZY.Track.Repository/Repositories/WarningRecordEsRepository.cs new file mode 100644 index 00000000..490570f0 --- /dev/null +++ b/src/SJZY.Track.Repository/Repositories/WarningRecordEsRepository.cs @@ -0,0 +1,106 @@ +using Nest; +using SJZY.Expand.ABP.Core.Logger; +using SJZY.Expand.ElasticSearch.Services; +using SJZY.Track.Domain.Aggregates.ForewarningAggregate; +using SJZY.Track.Domain.Consts; +using SJZY.Track.Domain.Dtos.Forewarning; + +namespace SJZY.Track.Repository.Repositories; + +public class WarningRecordEsRepository : ElasticSearchContextAbstract, IWarningRecordEsRepository, + ITransientDependency +{ + public WarningRecordEsRepository(ConnectionFactory connectionFactory) : base(connectionFactory) + { + } + + public async Task AddAsync(WarningRecordIndex index) + { + index.IndexName = $"{GlobalConst.TrackWarningRecordIndex}-{DateTime.Now:yyyy.MM.dd}"; + index.CreateTime = DateTime.Now; + // 按天写入es + var r = await Context.IndexAsync(index, request => request + .Index(index.IndexName) + ); + + if (!r.IsValid) + { + LogHelper.Error($"运输时效预警记录写入es失败,原因:{r.DebugInformation}。"); + } + } + + public async Task BulkInsertAsync(List warningRecords) + { + if (!warningRecords.Any()) return; + // 按天写入es + var indexName = $"{GlobalConst.TrackIndex}-{DateTime.Now:yyyy.MM.dd}"; + warningRecords.ForEach(x => x.IndexName = indexName); + + var r = await Context.IndexManyAsync(warningRecords, indexName); + + if (!r.IsValid) + { + throw new InvalidOperationException($"{warningRecords.Count}条运输时效预警记录新增失败,原因:{r.DebugInformation}。"); + } + } + + public async Task BulkUpdateAsync(List<(int ruleId, int orderId)> rules) + { + foreach (var rule in rules) + { + var r = await Context.UpdateByQueryAsync(u => + u.Query(q => + q.Term(f => f.RuleId, rule.ruleId) + && q.Term(f => f.OrderId, rule.orderId) + ) + .Script(s => s.Source("ctx._source.warningStatus = params.warningStatus") + .Params(p => p.Add("warningStatus", WarningStatus.Processed)) + ) + ); + + if (!r.IsValid) + { + throw new InvalidOperationException( + $"运输时效预警记录更新失败,ruleId:{rule.ruleId},orderId:{rule.orderId},原因:{r.DebugInformation}。"); + } + } + } + + public async Task<(List list, long total)> GetOrderWarningPageListAsync( + GetOrderWarningPageListVo input) + { + Func, QueryContainer> query = q => + (input.SubSystemId.HasValue ? q.Term(f => f.SubSystemId, input.SubSystemId) : null) + && (!input.OrderNos.IsNullOrEmpty() + ? q.Terms(t => t + .Field(f => f.OrderId.Suffix("keyword")) + .Terms(input.OrderNos)) + : null) + && (input.WarningStatus.HasValue ? q.Term(f => f.WarningStatus, input.WarningStatus) : null) + && (!input.CustomName.IsNullOrWhiteSpace() + ? q.Wildcard(w => w.CustomName, $"*{input.CustomName}*") + : null) + && (!input.WarningName.IsNullOrWhiteSpace() + ? q.Wildcard(w => w.WarningName, $"*{input.WarningName}*") + : null) + && (!input.NotifyUser.IsNullOrWhiteSpace() + ? q.Wildcard(w => w.NotifyUser, $"*{input.NotifyUser}*") + : null) + && (input.StartTime.HasValue + ? q.DateRange(r => r.Field(f => f.CreateTime).GreaterThanOrEquals(input.StartTime)) + : null) + && (input.EndTime.HasValue + ? q.DateRange(r => r.Field(f => f.CreateTime).LessThanOrEquals(input.EndTime)) + : null); + + var data = await Context.SearchAsync(x => x + .Source() + .Sort(s => s.Field(f => f.CreateTime, SortOrder.Descending)) + .From((input.PageIndex - 1) * input.PageSize) + .Size(input.PageSize) + .TrackTotalHits() + ); + + return (data.Documents.ToList(), data.Total); + } +}