track/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushEventHandler.cs

430 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using DotNetCore.CAP;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.InterfaceRelay.Application.Contracts;
using SJZY.Track.Application.Contract.Dtos.TrackPush;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Application.Helpers;
using SJZY.Track.Application.Options;
using SJZY.Track.Application.Track2InterfaceRelay;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Aggregates.TrackPushAggregate;
using SJZY.Track.Domain.Enums;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.DynamicProxying;
namespace SJZY.Track.Application.CAP.EventHandlers;
public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
{
private readonly IHttpClientProxy<IInterfaceForwardServiceV2> _httpClientProxy;
private readonly IOptionsMonitor<InterfaceRelayParamsConfigOptions> _options;
private readonly IOrderRepository _orderRepository;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly ITrackPushCacheService _trackPushCacheService;
private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository;
private List<OrderEntity> _orders = null!;
private List<TrackPushContentConfigDto> _pushConfigs = null!;
public TrackPushEventHandler(IHttpClientProxy<IInterfaceForwardServiceV2> httpClientProxy,
IOrderRepository orderRepository, ITrackPushCacheService trackPushCacheService,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackPushRecordEsRepository trackPushRecordEsRepository,
IOptionsMonitor<InterfaceRelayParamsConfigOptions> options)
{
_httpClientProxy = httpClientProxy;
_orderRepository = orderRepository;
_trackPushCacheService = trackPushCacheService;
_trackDetailsEsRepository = trackDetailsEsRepository;
_trackPushRecordEsRepository = trackPushRecordEsRepository;
_options = options;
}
[CapSubscribe(GlobalConst.TrackPushSubscribe, Group = GlobalConst.TrackPushSubscribe)]
[CapSubscribe(GlobalConst.TrackManualPushSubscribe, Group = GlobalConst.TrackPushSubscribe)]
public async Task ConsumerAsync(TrackPushEvent @event)
{
var pushConfigs = await _trackPushCacheService.GetPushContentConfigsAsync(@event.SubSystemId, @event.CustomId);
if (pushConfigs == null || pushConfigs.Count == 0) return;
if (pushConfigs.First().ServiceProviderCode.IsNullOrWhiteSpace())
{
throw new BusinessException(GlobalConst.CapNoRetryErrorCode, $"该客户{@event.CustomId}未关联服务商,无法推送");
}
_pushConfigs = pushConfigs;
var orders = await _orderRepository.GetListAsync(x =>
@event.OrderIds.Contains(x.Id),
x => new OrderEntity
{
Id = x.Id,
OrderNo = x.OrderNo,
LatestPushTrackCode = x.LatestPushTrackCode,
OrderType = x.OrderType
});
if (orders.Count == 0) return;
_orders = orders;
if (@event.ForcedPush)
{
//当为强制推送,会走手动推送逻辑
await DoPushAsync(@event);
return;
}
switch (@event.OptionType)
{
case OptionType.无:
return;
case OptionType.新增:
await DoPushAsync(@event);
break;
case OptionType.删除:
break;
case OptionType.修改:
await DoUpdateAsync(@event);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private async Task DoPushAsync(TrackPushEvent @event)
{
var items = new List<TrackPushInput>();
TrackPushContentConfigDto? config;
var isSystemPush = @event.OperationMan == null;
if (isSystemPush && !@event.ForcedPush)
{
foreach (var order in _orders)
{
if (order.QuestionType == (int) TrackProblemType.Error) continue; // 系统不推异常单,只能人工操作
// 强制推送只需直接查出配置
if (@event.ForcedPush)
config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode);
// 推送新增,需要根据上一次的推送 推断 本次推送 是否可行,为了约束顺序
else
config = GetValidPushConfigAsync(_pushConfigs, order.LatestPushTrackCode, @event.TrackCode);
if (config == null) continue;
// 是否自动推送
if (!config.IsAutoPush) continue;
// 配置的类型如何和订单类型不一致则不允许推送
if (config.OrderType != order.OrderType) continue;
var log = new TrackPushRecordIndex
{
OptionType = @event.OptionType.ToString(),
OrderId = order.Id,
OrderNo = order.OrderNo,
TrackStatus = @event.SystemPushInfo.TrackStatus,
TrackCode = @event.TrackCode,
TrackContent = @event.SystemPushInfo.TrackContent,
TrackPlace = @event.SystemPushInfo.TrackPlace,
TrackTime = @event.SystemPushInfo.TrackTime,
TrackTimeZone = @event.SystemPushInfo.TrackTimeZone,
CreateUser = GlobalConst.SystemUser,
SubSystemCode = @event.SubSystemCode,
IsToMq = @event.IsToMq,
Extra = @event.SystemPushInfo.Extra
};
var item = await BuiltPushItem(config, log, @event.SystemPushInfo.Extra);
item.Log = JsonConvert.SerializeObject(log);
items.Add(item);
}
}
else //手工推送
{
config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode);
if (config == null) return;
//使用事件参数传递要推送的轨迹
await ManualPushV2Async(@event, items, config);
goto Push;
var tracks = await _trackDetailsEsRepository.GetListAsync(@event.OrderIds, @event.TrackCode);
// tips:人工推送只要能推就推了,不会校验已推和顺序问题,因为可能是更新
foreach (var order in _orders)
{
//取订单的下某个code的最新创建时间的轨迹 防止重复推送
var track = tracks
.Where(x => x.OrderId == order.Id && x.TrackCode == @event.TrackCode)
.OrderByDescending(x => x.CreateTime)
.FirstOrDefault();
if (track == null) return; // 一个能推的轨迹都不存在
if (config is { IsAllPush: true })
{
// 强制推送只需直接查出配置
if (@event.ForcedPush)
config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode);
// 推断 本次推送 是否可行
else
config = GetValidPushConfigAsync(_pushConfigs, order.LatestPushTrackCode, @event.TrackCode);
}
if (config == null) continue;
//配置的类型如何和订单类型不一致则不允许推送
if (config.OrderType != order.OrderType) continue;
var log = new TrackPushRecordIndex
{
OptionType = @event.OptionType.ToString(),
OrderId = track.OrderId,
OrderNo = order.OrderNo,
TrackStatus = track.TrackStatus,
TrackCode = @event.TrackCode,
TrackContent = track.TrackContent,
TrackPlace = track.TrackPlace,
TrackTime = track.TrackTime,
TrackTimeZone = track.TrackTimeZone,
CreateUser = @event.OperationMan,
SubSystemCode = @event.SubSystemCode,
IsToMq = @event.IsToMq,
Extra = track.Extra
};
var item = await BuiltPushItem(config, log, track.Extra);
item.Log = JsonConvert.SerializeObject(log);
items.Add(item);
}
}
Push:
if (items.Count == 0) return;
using (Track2InterfaceRelaySubSystemContext.Use(
new Track2InterfaceRelaySubSystemContext(@event.SubSystemCode)))
{
var asciiString = Base64Helper.SerializeObject(OptionType.新增.ToString());
var r = await _httpClientProxy.Service.BatchForwardThenPushToQueueAsync(
_options.CurrentValue.PushTrackInterfaceCode,
_pushConfigs.First().ServiceProviderCode, items.Cast<object>().ToList(), GlobalConst.TrackPushCompletedSubscribe,
attach: asciiString);
if (r.Code != GlobalConst.SuccessCode)
{
throw new BusinessException(message: $"requestId: {r.RequestId}; {r.Msg}");
}
}
}
private async Task ManualPushV2Async(TrackPushEvent @event, List<TrackPushInput> items, TrackPushContentConfigDto? config)
{
// tips:人工推送只要能推就推了,不会校验已推和顺序问题,因为可能是更新
foreach (var order in _orders)
{
if (config is { IsAllPush: true })
{
// 强制推送只需直接查出配置
if (@event.ForcedPush)
config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode);
// 推断 本次推送 是否可行
else
config = GetValidPushConfigAsync(_pushConfigs, order.LatestPushTrackCode, @event.TrackCode);
}
if (config == null) continue;
//配置的类型如何和订单类型不一致则不允许推送
if (config.OrderType != order.OrderType) continue;
var log = new TrackPushRecordIndex
{
OptionType = @event.OptionType.ToString(),
OrderId = order.Id,
OrderNo = order.OrderNo,
TrackStatus = @event.SystemPushInfo.TrackStatus,
TrackCode = @event.TrackCode,
TrackContent = @event.SystemPushInfo.TrackContent,
TrackPlace = @event.SystemPushInfo.TrackPlace,
TrackTime = @event.SystemPushInfo.TrackTime,
TrackTimeZone = @event.SystemPushInfo.TrackTimeZone,
CreateUser = @event.OperationMan,
SubSystemCode = @event.SubSystemCode,
IsToMq = @event.IsToMq,
Extra = @event.SystemPushInfo.Extra
};
var item = await BuiltPushItem(config, log, @event.SystemPushInfo.Extra);
item.Log = JsonConvert.SerializeObject(log);
items.Add(item);
}
}
private async Task DoUpdateAsync(TrackPushEvent @event)
{
var config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode);
if (config == null || !config.IsCanUpdate) return;
var items = new List<TrackUpdateInput>();
foreach (var order in _orders)
{
//配置的类型如何和订单类型不一致则不允许推送
if (config.OrderType != order.OrderType) continue;
var log = new TrackPushRecordIndex
{
OptionType = @event.OptionType.ToString(),
OrderId = order.Id,
OrderNo = order.OrderNo,
TrackStatus = @event.SystemPushInfo.TrackStatus,
TrackCode = @event.TrackCode,
TrackContent = @event.SystemPushInfo.TrackContent,
TrackPlace = @event.SystemPushInfo.TrackPlace,
TrackTime = @event.SystemPushInfo.TrackTime,
TrackTimeZone = @event.SystemPushInfo.TrackTimeZone,
CreateUser = GlobalConst.SystemUser,
SubSystemCode = @event.SubSystemCode,
IsToMq = @event.IsToMq,
Extra = @event.SystemPushInfo.Extra
};
var item = BuiltUpdateItem(config, log, @event.SystemPushInfo.Extra);
item.Log = JsonConvert.SerializeObject(log);
items.Add(item);
}
if (items.Count == 0) return;
using (Track2InterfaceRelaySubSystemContext.Use(
new Track2InterfaceRelaySubSystemContext(@event.SubSystemCode)))
{
var asciiString = Base64Helper.SerializeObject(OptionType.修改.ToString());
var r = await _httpClientProxy.Service.BatchForwardThenPushToQueueAsync(
_options.CurrentValue.TrackUpdateInterfaceCode,
_pushConfigs.First().ServiceProviderCode, items.Cast<object>().ToList(), GlobalConst.TrackPushCompletedSubscribe,
attach: asciiString);
if (r.Code != GlobalConst.SuccessCode)
{
throw new BusinessException(message: $"requestId: {r.RequestId}; {r.Msg}");
}
}
}
private TrackUpdateInput BuiltUpdateItem(TrackPushContentConfigDto config, TrackPushRecordIndex log, string? extraJson)
{
var trackDatetime = log.TrackTime.AddHours(config.TrackTimeZone ?? log.TrackTimeZone - log.TrackTimeZone);
var trackDateStr = DateTimeFormat(trackDatetime, config.TrackTimeZone ?? log.TrackTimeZone, config);
var body = new TrackUpdateInput
{
OrderNo = log.OrderNo
};
var dict = new Dictionary<string, object?>()
{
//推真实的发生地和发生时间(时间是计算后的)
{ "location", log.TrackPlace },
{ "date", trackDateStr },
{ "timeZone", config.TrackTimeZone ?? log.TrackTimeZone },
{ "description", config.TrackEnContentValue },
{ "descriptionCn", config.TrackContentValue },
{ "code", config.TrackContentCodeValue },
{ "status", config.TrackStatusValue },
{ "extra", DeserializeForTextJson<object>(extraJson) }
};
body.Data.Add(dict);
log.PushContent = body.ToJson();
return body;
}
private async Task<TrackPushInput> BuiltPushItem(TrackPushContentConfigDto config, TrackPushRecordIndex log, string? extraJson)
{
var trackDatetime = log.TrackTime.AddHours(config.TrackTimeZone ?? log.TrackTimeZone - log.TrackTimeZone);
var trackDateStr = DateTimeFormat(trackDatetime, config.TrackTimeZone ?? log.TrackTimeZone, config);
var body = new TrackPushInput
{
OrderNo = log.OrderNo,
};
var dict = new Dictionary<string, object?>()
{
// 推真实的发生地和发生时间(时间是计算后的)
{ "location", log.TrackPlace },
{ "date", trackDateStr },
{ "timeZone", config.TrackTimeZone ?? log.TrackTimeZone },
{ "description", config.TrackEnContentValue },
{ "descriptionCn", config.TrackContentValue },
{ "code", config.TrackContentCodeValue },
{ "status", config.TrackStatusValue },
{ "extra", DeserializeForTextJson<object>(extraJson) }
};
body.Data.Add(dict);
if (config.IsAllPush)
{
var history = await _trackPushRecordEsRepository.GetLatestTrackPushRecordAsync(log.OrderId);
if (history != default)
{
body = DeserializeForTextJson<TrackPushInput>(history.PushContent);
body!.Data.Add(dict);
}
}
log.PushContent = SerializeForTextJson(body);
return body;
}
/// <summary>
/// 根据轨迹代码推断下一个(升序)要推送的内容,如果找不到或者已经是最后一个了
/// 则查询有没有可交换的推送,没有就返回null
/// </summary>
/// <param name="cache"></param>
/// <param name="latestPushTrackCode"></param>
/// <param name="trackCode"></param>
/// <returns></returns>
TrackPushContentConfigDto? GetValidPushConfigAsync(List<TrackPushContentConfigDto> cache,
string? latestPushTrackCode, string? trackCode)
{
var idx = cache.FindIndex(x => x.TrackCode == latestPushTrackCode);
if (idx + 1 >= cache.Count) return default; // 推完了
// find next
var matched = cache[idx + 1].TrackCode == trackCode
? cache[idx + 1]
:
// find swap
cache.FirstOrDefault(x => x.TrackCode == cache[idx + 1].SwapTrackCode);
return matched != null && matched.TrackCode == trackCode ? matched : default;
}
/// <summary>
/// 转换推送时间格式
/// </summary>
/// <param name="dateTime"></param>
/// <param name="timezone"></param>
/// <param name="config"></param>
/// <returns></returns>
private string DateTimeFormat(DateTime dateTime, double timezone, TrackPushContentConfigDto config)
{
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.Timestamp)
return DateTimeHelper.ConvertToUnixTimestamp(dateTime).ToString();
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.DateTimeAndTimezone
&& !string.IsNullOrEmpty(config.DateTimeFormat)
&& !string.IsNullOrEmpty(config.TimezoneFormat))
{
var zoneStr = DateTimeHelper.TimezoneFormat(timezone, config.TimezoneFormat);
var datetimeStr = DateTimeHelper.FormatDateTime(dateTime, config.DateTimeFormat);
return $"{datetimeStr} {zoneStr}";
}
return dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffK");
}
private string SerializeForTextJson(object? obj)
{
return obj == null ? string.Empty : System.Text.Json.JsonSerializer.Serialize(obj);
}
private T? DeserializeForTextJson<T>(string? json)
{
return string.IsNullOrWhiteSpace(json) ? default : System.Text.Json.JsonSerializer.Deserialize<T>(json);
}
}