430 lines
18 KiB
C#
430 lines
18 KiB
C#
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);
|
||
}
|
||
}
|