Merge pull request 'S06' (#77) from S06 into uat

Reviewed-on: https://git.shijizhongyun.com/Dotnet/SJZY.Track.Api/pulls/77
This commit is contained in:
wangyunlong 2025-01-20 15:54:07 +08:00
commit 75dfc93101
65 changed files with 2793 additions and 853 deletions

View File

@ -1,16 +1,16 @@
{
"Configuration": {
"UseLocalConfiguration": true,
"UseLocalConfiguration": false,
//是否使用本地配置文件
"ConfiguraFileName": "appsettings.Development.json",
//指定配置文件名称,开启UseLocalConfiguration时生效
"Apollo": {
"AppId": "SJZY.Track.Configuration",
"Env": "DEV",
"Namespace": "dev",
"MetaServer": "http://apollo-service-test-apollo-configservice.apollo.svc.cluster.local:8080",
"Namespace": "test",
"MetaServer": "https://apollo.test.shijizhongyun.com",
"ConfigServer": [
"http://apollo-service-test-apollo-configservice.apollo.svc.cluster.local:8080"
"https://apollo.test.shijizhongyun.com"
]
}
},

View File

@ -10,4 +10,14 @@ public class BatchDeleteTrackInput : OrderNosInfo
[Required]
[MaxLength(500)]
public List<TrackIdentifierDto> TrackIdentifiers { get; set; } = new();
/// <summary>
/// 重复的情况下,用什么字段比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 重复的情况下,比较值
/// </summary>
public object? CompareValue { get; set; }
}

View File

@ -120,8 +120,23 @@ public class BatchUpsertTrackInput : OrderNosInfo
[Range(-12, 12)]
public float? DestinationTimeZone { get; set; }
/// <summary>
/// 重复的情况下,用什么比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary>
public Dictionary<string,object>? Extra { get; set; }
public string? Extra { get; set; }
/// <summary>
/// 是否强制推送
/// </summary>
public bool ForcedPush { get; set; }
/// <summary>
/// 指定是否推送
/// </summary>
public bool IsToMq { get; set; }
}

View File

@ -9,8 +9,22 @@ public class DeleteOrderInput : OrderNoInfo
public class DeleteTrackInput : OrderNoInfo
{
/// <summary>
/// 轨迹标识符集合,必填
/// 轨迹标识符集合,为支持esid删除,所以非必填
/// </summary>
[Required]
public List<TrackIdentifierDto> TrackIdentifiers { get; set; } = new();
public List<TrackIdentifierDto>? TrackIdentifiers { get; set; } = new();
/// <summary>
/// 轨迹ESId集合,非必填,如果有优先级最高
/// </summary>
public List<string>? EsIds { get; set; }
/// <summary>
/// 重复的情况下,用什么字段比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 重复的情况下,比较值
/// </summary>
public object? CompareValue { get; set; }
}

View File

@ -1,5 +1,8 @@
namespace SJZY.Track.Application.Contract.Dtos.ProvideServices;
/// <summary>
/// 支持模板,传入时区补正,按轨迹发生时间升序降序
/// </summary>
public class GetTrackInput : OrderNosInfo
{
private float? _timeZone;
@ -8,11 +11,7 @@ public class GetTrackInput : OrderNosInfo
/// 时区 +8 +8.25 -8.5 -9
/// 不传默认+8
/// </summary>
public float? TimeZone
{
get => _timeZone ?? 8;
set => _timeZone = value;
}
public float? TimeZone { get => _timeZone ?? 8; set => _timeZone = value; }
/// <summary>
/// 排序方式,默认按轨迹发生时间降序
@ -20,15 +19,36 @@ public class GetTrackInput : OrderNosInfo
public SortMethod? SortMethod { get; set; }
}
/// <summary>
/// 不支持模板,无时区补正,按轨迹发生时间升序降序
/// </summary>
public class GetTrackInputV2 : OrderNosInfo
{
/// <summary>
/// 排序方式,默认按轨迹发生时间降序
/// </summary>
public SortMethod? SortMethod { get; set; }
}
public enum SortMethod
{
/// <summary>
/// 按轨迹发生时间排序
/// </summary>
ByTrackTime,
ByTrackTime = 0,
/// <summary>
/// 按轨迹发生时间降序
/// </summary>
ByTrackTimeDesc,
ByTrackTimeDesc = 1,
/// <summary>
/// 模板排序+轨迹发生时间
/// </summary>
ByTemplateAndTrackTime = 2,
/// <summary>
/// 模板排序+轨迹发生时间
/// </summary>
ByTemplateAndTrackTimeDesc = 3
}

View File

@ -15,6 +15,7 @@ public class GetTrackOutput
public class TrackDetail
{
public string EsId { get; set; } = null!;
public string Code { get; set; } = null!;
public string Status { get; set; } = null!;
public string? Content { get; set; }
@ -91,7 +92,6 @@ public class TrackDetail
/// </summary>
public string? TrackPlacePostcode { get; set; }
/// <summary>
/// 预计到达时间
/// </summary>
@ -105,5 +105,5 @@ public class TrackDetail
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary>
public Dictionary<string, object>? Extra { get; set; }
public string? Extra { get; set; }
}

View File

@ -9,4 +9,14 @@ public class PushTrackInput : OrderNosInfo
/// </summary>
[Required]
public string TrackCode { get; set; } = null!;
/// <summary>
/// 重复的情况下,用什么字段比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 重复的情况下,比较值
/// </summary>
public object? CompareValue { get; set; }
}

View File

@ -44,4 +44,9 @@ public class TemplateContentDto
/// 轨迹节点
/// </summary>
public string Node { get; set; }
/// <summary>
/// 排序键
/// </summary>
public int Sort { get; set; }
}

View File

@ -81,7 +81,7 @@ public class UpdateOrderInput : OrderNoInfo
/// <summary>
/// 模板code
/// </summary>
public List<string> TemplateCodes { get; set; }
public List<string>? TemplateCodes { get; set; }
}
public class PullConfigInfo

View File

@ -9,6 +9,11 @@ public class UpsertTrackInput : OrderNoInfo
/// </summary>
public bool IsAdd { get; set; }
/// <summary>
/// 轨迹唯一标识(可选)
/// </summary>
public string? EsId { get; set; }
/// <summary>
/// 轨迹内容代码
/// </summary>
@ -27,7 +32,7 @@ public class UpsertTrackInput : OrderNoInfo
/// 轨迹发生时间
/// </summary>
[Required]
public DateTime? TrackTime { get; set; }
public DateTime TrackTime { get; set; }
/// <summary>
/// 更改后的轨迹发生时间,无需更改保持为空
@ -124,8 +129,23 @@ public class UpsertTrackInput : OrderNoInfo
/// </summary>
public int OptionUserId { get; set; }
/// <summary>
/// 重复的情况下,用什么比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary>
public Dictionary<string, object>? Extra { get; set; }
public string? Extra { get; set; }
/// <summary>
/// 是否强制推送
/// </summary>
public bool ForcedPush { get; set; }
/// <summary>
/// 指定是否推送
/// </summary>
public bool IsToMq { get; set; }
}

View File

@ -13,7 +13,7 @@ public class GetAvailableContentInput
public string? OrderNo { get; set; }
/// <summary>
/// 操作类型 1=创建 2=修改 3=删除
/// 操作类型 1=创建 2=修改 3=删除 4=推送
/// </summary>
public TrackOptionType TrackOptionType { get; set; }
}
@ -22,5 +22,6 @@ public enum TrackOptionType
{
创建 = 1,
修改 = 2,
删除 = 3
删除 = 3,
推送 = 4,
}

View File

@ -44,10 +44,15 @@ public class EditTrackPushConfigInput
/// </summary>
public bool? IsAllPush { get; set; }
/// <summary>
/// 是否全量推送
/// </summary>
public bool? IsAllPushWithLatest { get; set; }
/// <summary>
/// 时间格式枚举
/// </summary>
public int DateTimeFormatType { get; set; }
public int? DateTimeFormatType { get; set; } = 0;
/// <summary>
/// 日期时间格式模板

View File

@ -17,6 +17,11 @@ public class TrackPushContentConfigDto
/// </summary>
public bool IsAllPush { get; set; }
/// <summary>
/// 是否全量推送(最新轨迹)
/// </summary>
public bool IsAllPushWithLatest { get; set; }
/// <summary>
/// 是否支持删除
/// </summary>
@ -70,6 +75,7 @@ public class TrackPushContentConfigDto
/// <summary>
/// 扩展字段参数值
/// </summary>
[Obsolete("废弃属性,调整为从轨迹明细扩展中获取扩展值填入")]
public string? ExpandFieldsValue { get; set; }
/// <summary>

View File

@ -4,7 +4,8 @@ public class TrackPushInput
{
public string OrderNo { get; set; } = null!;
public List<TrackItem> Data { get; set; } = new();
//原TrackItem,为了扩展字段入参变更为字典
public List<Dictionary<string,object?>> Data { get; set; } = new();
public string Log { get; set; }
}
@ -13,19 +14,8 @@ public class TrackUpdateInput
{
public string OrderNo { get; set; } = null!;
public List<TrackItem> Data { get; set; } = new();
//原TrackItem,为了扩展字段入参变更为字典
public List<Dictionary<string,object?>> Data { get; set; } = new();
public string Log { get; set; }
}
public class TrackItem
{
public string Location { get; set; } = null!;
public string Date { get; set; }
public double TimeZone { get; set; }
public string? Description { get; set; }
public string? DescriptionCn { get; set; }
public string? Code { get; set; }
public string? Status { get; set; }
public string? Extra { get; set; }
}

View File

@ -0,0 +1,90 @@
namespace SJZY.Track.Application.Contract.Dtos.TrackPush;
public class TrackPushRecordDto
{
/// <summary>
/// 子系统代码
/// </summary>
public string SubSystemCode { get; set; }
/// <summary>
/// 是否为手动推送
/// </summary>
public bool IsManualPush { get; set; }
/// <summary>
/// 推送时间
/// </summary>
public DateTime PushTime { get; set; }
/// <summary>
/// 操作人
/// </summary>
public string? OperationMan { get; set; }
/// <summary>
/// 订单号
/// </summary>
public string OrderNo { get; set; } = null!;
/// <summary>
/// 接口通请求Id
/// 在请求接口通前被拦截,就不会有请求Id
/// </summary>
public Guid? RequestId { get; set; }
/// <summary>
/// 信息
/// </summary>
public string Message { get; set; } = null!;
/// <summary>
/// 操作类型
/// </summary>
public string OptionType { get; set; } = null!;
/// <summary>
/// 轨迹状态
/// </summary>
public string TrackStatus { get; set; } = null!;
/// <summary>
/// 轨迹内容代码
/// </summary>
public string TrackCode { get; set; } = null!;
/// <summary>
/// 轨迹内容
/// </summary>
public string TrackContent { get; set; } = null!;
/// <summary>
/// 轨迹发生地
/// </summary>
public string TrackPlace { get; set; } = null!;
/// <summary>
/// 轨迹发生时间
/// </summary>
public DateTime TrackTime { get; set; }
/// <summary>
/// 轨迹发生时区
/// </summary>
public float TrackTimeZone { get; set; }
/// <summary>
/// 是否成功
/// </summary>
public bool IsSuccess { get; set; }
/// <summary>
/// 推送内容(json)
/// </summary>
public string PushContent { get; set; } = null!;
/// <summary>
/// 轨迹的额外新
/// </summary>
public string? Extra { get; set; }
}

View File

@ -128,4 +128,9 @@ public class TrackChangeEto
/// 预计到达时间
/// </summary>
public DateTime? EstimatedDeliveryDate { get; set; }
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary>
public string? Extra { get; set; }
}

View File

@ -12,17 +12,27 @@ public class TrackDeleteEvent
/// <summary>
/// es中的id
/// </summary>
public string[] EsIds { get; set; }
public string[]? EsIds { get; set; }
/// <summary>
/// 订单ids
/// </summary>
public int[] OrderIds { get; set; } = null!;
/// <summary>
/// 重复的情况下,用什么字段比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 重复的情况下,比较值
/// </summary>
public object? CompareValue { get; set; }
/// <summary>
/// 轨迹标识符
/// </summary>
public List<TrackIdentifierDto> TrackIdentifiers { get; set; } = null!;
public List<TrackIdentifierDto>? TrackIdentifiers { get; set; }
/// <summary>
/// 操作人

View File

@ -0,0 +1,88 @@
namespace SJZY.Track.Application.Contract.Events;
/// <summary>
/// 轨迹推送广播模型
/// </summary>
public class TrackPushCompletedEvent
{
/// <summary>
/// 是否为手动推送
/// </summary>
public bool IsManualPush { get; set; }
/// <summary>
/// 推送时间
/// </summary>
public DateTime PushTime { get; set; }
/// <summary>
/// 操作人
/// </summary>
public string? OperationMan { get; set; }
/// <summary>
/// 订单号
/// </summary>
public string OrderNo { get; set; } = null!;
/// <summary>
/// 接口通请求Id
/// 在请求接口通前被拦截,就不会有请求Id
/// </summary>
public Guid? RequestId { get; set; }
/// <summary>
/// 信息
/// </summary>
public string Message { get; set; } = null!;
/// <summary>
/// 操作类型
/// </summary>
public string OptionType { get; set; } = null!;
/// <summary>
/// 轨迹状态
/// </summary>
public string TrackStatus { get; set; } = null!;
/// <summary>
/// 轨迹内容代码
/// </summary>
public string TrackCode { get; set; } = null!;
/// <summary>
/// 轨迹内容
/// </summary>
public string TrackContent { get; set; } = null!;
/// <summary>
/// 轨迹发生地
/// </summary>
public string TrackPlace { get; set; } = null!;
/// <summary>
/// 轨迹发生时间
/// </summary>
public DateTime TrackTime { get; set; }
/// <summary>
/// 轨迹发生时区
/// </summary>
public float TrackTimeZone { get; set; }
/// <summary>
/// 是否成功
/// </summary>
public bool IsSuccess { get; set; }
/// <summary>
/// 推送内容(json)
/// </summary>
public string PushContent { get; set; } = null!;
/// <summary>
/// 轨迹的额外新
/// </summary>
public string? Extra { get; set; }
}

View File

@ -41,6 +41,16 @@ public class TrackPushEvent
/// 系统推送信息
/// </summary>
public SystemPushStruct SystemPushInfo { get; set; }
/// <summary>
/// 是否强制推送
/// </summary>
public bool ForcedPush { get; set; }
/// <summary>
/// 是否发送到mq
/// </summary>
public bool IsToMq { get; set; }
}
public struct SystemPushStruct
@ -69,4 +79,14 @@ public struct SystemPushStruct
/// 轨迹发生时区
/// </summary>
public float TrackTimeZone { get; set; }
/// <summary>
/// 重复的情况下,用什么比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary>
public string? Extra { get; set; }
}

View File

@ -19,6 +19,11 @@ public class TrackUpsertEvent
/// </summary>
public OrderIdentifier[] OrderIdentifiers { get; set; } = null!;
/// <summary>
/// 轨迹唯一标识(用于单个订单更新单条轨迹,如果有的话优先级最高)
/// </summary>
public string? EsId { get; set; }
/// <summary>
/// 操作人(null则默认系统)
/// </summary>
@ -141,10 +146,25 @@ public class TrackUpsertEvent
/// </summary>
public float? DestinationTimeZone { get; set; }
/// <summary>
/// 重复的情况下,用什么比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary>
public Dictionary<string, object>? Extra { get; set; }
public string? Extra { get; set; }
/// <summary>
/// 是否强制推送
/// </summary>
public bool ForcedPush { get; set; }
/// <summary>
/// 指定是否推送
/// </summary>
public bool IsToMq { get; set; }
public override string ToString()
{

View File

@ -5,7 +5,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Version>7.0.3</Version>
<Version>7.0.14</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>

View File

@ -0,0 +1,44 @@
using SJZY.Track.Application.Contract.Dtos.TrackPush;
using SJZY.Track.Application.Contract.Events;
using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.Contract.Services;
public interface IBroadcastingService : ITransientDependency
{
/// <summary>
/// 轨迹变更广播(新增/修改)
/// </summary>
/// <param name="eto"></param>
/// <param name="subSystemCode"></param>
/// <param name="trackStatusCode"></param>
void TrackChangePublicTopic(TrackUpsertEvent eto, string subSystemCode, string trackStatusCode);
/// <summary>
/// 轨迹变更广播(删除)
/// </summary>
/// <param name="eto"></param>
/// <param name="subSystemCode"></param>
/// <param name="trackStatusCode"></param>
void TrackChangePublicTopic(TrackDeleteEvent eto, OrderIdentifier[] orderIdentifiers, string trackCode, string trackStatusCode, string subSystemCode);
/// <summary>
/// 轨迹变更广播(删除)
/// </summary>
/// <param name="eto"></param>
/// <param name="subSystemCode"></param>
/// <param name="trackStatusCode"></param>
void TrackChangePublicTopic(TrackChangeEto eto, string subSystemCode);
/// <summary>
/// 轨迹推送完成广播(推送完成回调)
/// </summary>
/// <param name="log"></param>
void TrackPushCompletedPublicTopic(TrackPushRecordDto log);
/// <summary>
/// 轨迹推送失败广播(推送前置被拦截,或调用接口通失败)
/// </summary>
/// <param name="log"></param>
void TrackPushFailedPubilcTopic(TrackPushRecordDto log);
}

View File

@ -12,7 +12,7 @@ public interface ITrackPushService : IRemoteService
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<string> PushTrackAsync(PushTrackInput input);
public Task<UnifyResultDto> PushTrackAsync(PushTrackInput input);
/// <summary>
/// 获取轨迹推送内容
@ -20,12 +20,12 @@ public interface ITrackPushService : IRemoteService
/// <returns></returns>
/// <param name="subSystemId">子系统id</param>
/// <param name="customerId">客户id</param>
public Task<List<TrackPushContentDto>> GetTrackPushContentListAsync(int subSystemId, int customerId);
public Task<UnifyResultDto<List<TrackPushContentDto>>> GetTrackPushContentListAsync(int subSystemId, int customerId);
/// <summary>
/// 获取轨迹推送记录
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<List<TrackPushRecordOutput>> GetTrackPushRecordsAsync(OrderNoInfo input);
public Task<UnifyResultDto<List<TrackPushRecordOutput>>> GetTrackPushRecordsAsync(OrderNoInfo input);
}

View File

@ -12,6 +12,13 @@ public interface ITrackService : IRemoteService
/// <returns></returns>
Task<UnifyResultDto> UpsertTrackAsync(UpsertTrackInput input);
/// <summary>
/// 批量新增/更新订单轨迹接口
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<UnifyResultDto> BatchUpsertTrackAsync(BatchUpsertTrackInput input);
/// <summary>
/// 删除轨迹
/// </summary>
@ -28,8 +35,17 @@ public interface ITrackService : IRemoteService
/// <summary>
/// 根据订单号获取轨迹
/// 支持模板,传入时区补正,按轨迹发生时间升序降序
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<UnifyResultDto<List<GetTrackOutput>>> QueryAsync(GetTrackInput input);
/// <summary>
/// 根据订单号获取轨迹
/// 不支持模板,无时区补正,可按轨迹发生时间升序降序
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<UnifyResultDto<List<GetTrackOutput>>> QueryV2Async(GetTrackInputV2 input);
}

View File

@ -26,8 +26,6 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa
return;
}
//指示是否需要更新【预计签收时间】【签收地时区】
var isNeedUpdate = false;
var updateOrders = new List<OrderEntity>();
foreach (var order in orders)
{
@ -62,37 +60,23 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa
{
order.EstimatedDeliveryDate = @event.EstimatedDeliveryDate.Value;
order.DestinationTimeZone = @event.DestinationTimeZone.Value;
isNeedUpdate = true;
}
updateOrders.Add(order);
}
await _orderRepository.UpdateRangeAsync(updateOrders,
isNeedUpdate
? x => new
{
x.IsCompleted,
x.LatestTrackContent,
x.LatestTrackEnContent,
x.LatestTrackPlace,
x.LatestTrackTime,
x.LatestTrackTimeZone,
x.DeficiencyTag,
//当标识指示需要更新时添加更新列
x.EstimatedDeliveryDate,
x.DestinationTimeZone
}
: x => new
{
x.IsCompleted,
x.LatestTrackContent,
x.LatestTrackEnContent,
x.LatestTrackPlace,
x.LatestTrackTime,
x.LatestTrackTimeZone,
x.DeficiencyTag,
}
await _orderRepository.UpdateRangeAsync(updateOrders, x => new
{
x.IsCompleted,
x.LatestTrackContent,
x.LatestTrackEnContent,
x.LatestTrackPlace,
x.LatestTrackTime,
x.LatestTrackTimeZone,
x.DeficiencyTag,
x.EstimatedDeliveryDate,
x.DestinationTimeZone
}
);
}
@ -109,6 +93,8 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa
LatestTrackPlace = order.LatestTrackPlace,
LatestTrackTime = order.LatestTrackTime,
LatestTrackTimeZone = order.LatestTrackTimeZone,
DestinationTimeZone = order.DestinationTimeZone,
EstimatedDeliveryDate = order.EstimatedDeliveryDate,
});
}
}

View File

@ -1,14 +1,14 @@
using System.Text;
using BSI.Stack.Rabbit;
using System.Collections.Immutable;
using System.Text.Json;
using DotNetCore.CAP;
using SJZY.Expand.ABP.Core.Logger;
using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.Track.Application.Contract.Dtos.ProvideServices;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Application.Helpers;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackContentAggregate;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Aggregates.TrackStatusAggregate;
using SJZY.Track.Domain.Enums;
using SJZY.Track.Domain.Queries;
using Volo.Abp.DependencyInjection;
@ -18,30 +18,24 @@ namespace SJZY.Track.Application.CAP.EventHandlers;
public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
{
private readonly ICapPublisher _capPublisher;
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
private readonly IOrderRepository _orderRepository;
private readonly ITrackStatusRepository _trackStatusRepository;
private readonly ITrackContentRepository _trackContentRepository;
private readonly IProvideQueries _provideQueries;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly RabbitClientBus _rabbitClientBus;
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
private readonly IBroadcastingService _broadcastingService;
public TrackDeleteEventHandler(IProvideQueries provideQueries,
IOrderRepository orderRepository, ICapPublisher capPublisher,
ITrackDetailsEsRepository trackDetailsEsRepository,
RabbitClientBus rabbitClientBus,
ITrackStatusRepository trackStatusRepository,
ITrackContentRepository trackContentRepository,
IOrderOperationRecordEsRepository orderOperationRecordEsRepository)
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
IBroadcastingService broadcastingService)
{
_provideQueries = provideQueries;
_orderRepository = orderRepository;
_capPublisher = capPublisher;
_trackDetailsEsRepository = trackDetailsEsRepository;
_rabbitClientBus = rabbitClientBus;
_trackStatusRepository = trackStatusRepository;
_trackContentRepository = trackContentRepository;
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
_broadcastingService = broadcastingService;
}
[CapSubscribe(GlobalConst.TrackDeleteSubscribe, Group = GlobalConst.TrackDeleteSubscribe)]
@ -51,8 +45,9 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
var trackList = await DeleteTrackAsync(@event);
//发布事件
await PublicTrackTopicAsync(@event, trackList);
//当轨迹删除,根据轨迹标记/状态上IsToMq选择进行广播
if (trackList.Count != 0)
_ = PublicTrackTopicAsync(@event, trackList);
// 根据订单关联关系异步向下刷(异步)
await DownByOrderLink(@event);
@ -126,51 +121,6 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
return true;
}
private async Task PublicTrackTopicAsync(TrackDeleteEvent eto, List<TrackDetailsIndex> trackList)
{
if (trackList == null)
return;
var statusList = await _trackStatusRepository.GetListAsync(m => m.SubSystemId == eto.SubSystemId);
if (statusList == null)
return;
var contentList = await _trackContentRepository.GetTrackContentBySyetemIdAsync(eto.SubSystemId);
if (contentList == null)
return;
foreach (var dataItem in trackList.GroupBy(m => new
{
m.TrackCode,
m.OrderNo
}))
{
var item = dataItem.First();
var status = statusList.FirstOrDefault(m => m.Code == item.TrackStatusCode);
if (status == null || !status.IsToMq)
continue;
var content = contentList.FirstOrDefault(m => m.TrackContentCode == item.TrackCode);
var data = new TrackChangeEto()
{
OrderIdentifiers = dataItem.Select(m => new OrderIdentifier
{
OrderId = m.OrderId,
OrderNo = m.OrderNo,
OrderType = m.OrderType
}).ToArray(),
TrackStatusCode = item.TrackStatusCode,
TrackCode = item.TrackCode,
OperationMan = eto.OperationMan,
SubSystemId = eto.SubSystemId,
Option = 2
};
_rabbitClientBus.Publish(GlobalConst.TrackChangeExchange, $"TrackChange.{content.SubSystemCode}", Encoding.UTF8.GetBytes(data.ToJson()));
}
}
private async Task<List<TrackDetailsIndex>> DeleteTrackAsync(TrackDeleteEvent @event)
{
List<int> orderIds;
@ -208,10 +158,40 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
//根据订单查询出所有的轨迹,查所有是因为主单上需要进行回退更新
tracks = await _trackDetailsEsRepository.GetListAsync(@event.OrderIds);
//构建标识符集合
var ids = @event.TrackIdentifiers.Select(x => $"{x.TrackCode}{x.TrackTime:G}").ToList();
//根据标识符选出要删除的轨迹明细
deleteTracks = tracks.Where(x => ids.Contains($"{x.TrackCode}{x.TrackTime:G}")).ToList();
//比较字段为空则使用时间来作为额外的标识key
if (string.IsNullOrWhiteSpace(@event.CompareFields))
{
//构建标识符集合
var ids = @event.TrackIdentifiers.Select(x => $"{x.TrackCode}{x.TrackTime:G}").ToHashSet();
//根据标识符选出要删除的轨迹明细
deleteTracks = tracks.Where(x => ids.Contains($"{x.TrackCode}{x.TrackTime:G}")).ToList();
}
//否则则使用比较字段,使用扩展字段中的内容进行比较
else
{
var codes = @event.TrackIdentifiers.Select(x => x.TrackCode).ToImmutableHashSet();
deleteTracks = tracks
.Where(x =>
{
if (!codes.Contains(x.TrackCode))
return false;
if (string.IsNullOrWhiteSpace(x.Extra))
{
throw new BusinessException(message: $"当前轨迹不存在扩展信息,无法利用扩展字段:{@event.CompareFields}");
}
var extraDict = TextJsonHelper.DeserializeToDictionary(x.Extra);
if (extraDict == null || !extraDict.TryGetValue(@event.CompareFields, out var compareValue))
{
throw new BusinessException(message: $"请检查拓展字段,为什么找不到比较字段:{@event.CompareFields}");
}
return TextJsonHelper.EqJsonElement(compareValue, @event.CompareValue);
})
.ToList();
}
if (deleteTracks.Count == 0) return deleteTracks;
@ -225,7 +205,8 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
}
//排除掉已经删除的轨迹
tracks = tracks.Except(deleteTracks).ToList();
var deleteTrackIds = deleteTracks.Select(m => m.Id).ToList();
tracks = tracks.Where(x => !deleteTrackIds.Contains(x.Id)).ToList();
var orders = await _orderRepository.GetListAsync(x => orderIds.Contains(x.Id),
x => new OrderEntity
@ -267,21 +248,9 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
order.EstimatedDeliveryDate = track.EstimatedDeliveryDate;
order.DestinationTimeZone = track.DestinationTimeZone;
}
// 写入操作日志
_ = _orderOperationRecordEsRepository.AddAsync(new OrderOperationRecordIndex
{
OrderId = order.Id,
OrderNo = order.OrderNo,
OptionType = OrderOperationType.删除轨迹.ToString(),
Content = deleteTracks?.Where(m => m.OrderId == order.Id).ToList().ToJson(),
CreateUserId = @event.OperationManId,
CreateUserName = @event.OperationMan ?? "",
CreateTime = DateTime.Now
});
}
//更新订单上冗余轨迹明细的字段
await _orderRepository.UpdateRangeAsync(orders,
x => new
{
@ -294,6 +263,104 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
x.DestinationTimeZone
});
//批量写入订单操作日志
_ = _orderOperationRecordEsRepository.AddManyAsync(orders.Select(order => new OrderOperationRecordIndex
{
OrderId = order.Id,
OrderNo = order.OrderNo,
OptionType = OrderOperationType.删除轨迹.ToString(),
Content = deleteTracks.Where(m => m.OrderId == order.Id).ToList().ToJson(),
CreateUserId = @event.OperationManId,
CreateUserName = @event.OperationMan ?? "",
CreateTime = DateTime.Now
}).ToList());
return deleteTracks;
}
private async Task PublicTrackTopicAsync(TrackDeleteEvent @event, List<TrackDetailsIndex> trackList)
{
var systemIds = trackList.Select(x => x.SubSystemId).Distinct().ToArray();
var trackCodes = trackList.Select(x => x.TrackCode).Distinct().ToArray();
//从轨迹内容中查询出标记是否需要mq广播
var contentList = await _provideQueries.GetTrackContentsAsync(systemIds, trackCodes);
var toMqDict = contentList
.ToDictionary(x => x.SubSystemId + x.TrackContentCode, y => y.IsToMq);
//选出需要广播的轨迹,并按code和订单号分组
var trackGroup = trackList
.Where(x => toMqDict[x.SubSystemId + x.TrackCode])
.GroupBy(m => new
{
m.TrackCode,
m.OrderNo
})
.ToList();
if (trackGroup.Count == 0) return;
//增加延时确保写盘成功
await Task.Delay(1000);
foreach (var dataItem in trackGroup)
{
var item = dataItem.First();
var content = contentList.First(m => m.TrackContentCode == item.TrackCode);
// _broadcastingService.TrackChangePublicTopic(track,
// dataItem.Select(m => new OrderIdentifier
// {
// OrderId = m.OrderId,
// OrderNo = m.OrderNo,
// OrderType = m.OrderType
// }).ToArray(),
// item.TrackCode,
// item.TrackStatusCode,
// content.SubSystemCode
// );
//将每个订单的每个轨迹的删除变更发布
foreach (var track in dataItem)
{
_broadcastingService.TrackChangePublicTopic(new TrackChangeEto
{
EstimatedDeliveryDate = track.EstimatedDeliveryDate,
TrackPlacePostcode = track.TrackPlacePostcode,
TrackPlaceNation = track.TrackPlaceNation,
TrackPlaceNationId = track.TrackPlaceNationId,
TrackPlaceProvince = track.TrackPlaceProvince,
TrackPlaceProvinceId = track.TrackPlaceProvinceId,
TrackPlaceCity = track.TrackPlaceCity,
TrackPlaceCityId = track.TrackPlaceCityId,
Files = track.Files?.Select(x => new FileItemDto()
{
FileName = x.FileName,
OssKey = x.OssKey
}).ToList(),
Remarks = track.Remarks,
Source = track.Source,
IsVisibility = track.IsVisibility,
TrackTimeZone = track.TrackTimeZone,
TrackTime = track.TrackTime,
TrackPlace = track.TrackPlace,
TrackStatusCode = track.TrackStatusCode,
EnStatus = track.TrackEnContent,
Content = track.TrackContent,
TrackCode = track.TrackCode,
OperationMan = @event.OperationMan,
OrderNo = track.OrderNo,
OrderIdentifiers = new[]
{
new OrderIdentifier()
{
OrderId = track.OrderId,
OrderNo = track.OrderNo,
OrderType = track.OrderType
}
},
SubSystemId = @event.SubSystemId,
Extra = track.Extra
}, content.SubSystemCode);
}
}
}
}

View File

@ -1,3 +1,5 @@
using System.Collections.Immutable;
using System.Text.Json;
using DotNetCore.CAP;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
@ -23,23 +25,28 @@ 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 readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly IBroadcastingService _broadcastingService;
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)
IOrderRepository orderRepository,
ITrackPushCacheService trackPushCacheService,
ITrackPushRecordEsRepository trackPushRecordEsRepository,
IOptionsMonitor<InterfaceRelayParamsConfigOptions> options,
IBroadcastingService broadcastingService,
ITrackDetailsEsRepository trackDetailsEsRepository)
{
_httpClientProxy = httpClientProxy;
_orderRepository = orderRepository;
_trackPushCacheService = trackPushCacheService;
_trackDetailsEsRepository = trackDetailsEsRepository;
_trackPushRecordEsRepository = trackPushRecordEsRepository;
_options = options;
_broadcastingService = broadcastingService;
_trackDetailsEsRepository = trackDetailsEsRepository;
}
[CapSubscribe(GlobalConst.TrackPushSubscribe, Group = GlobalConst.TrackPushSubscribe)]
@ -67,6 +74,13 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
if (orders.Count == 0) return;
_orders = orders;
if (@event.ForcedPush)
{
//当为强制推送,会走手动推送逻辑
await DoPushAsync(@event);
return;
}
switch (@event.OptionType)
{
case OptionType.无:
@ -86,76 +100,22 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
private async Task DoPushAsync(TrackPushEvent @event)
{
var items = new List<object>();
TrackPushContentConfigDto? config;
var items = new List<TrackPushInput>();
if (@event.OperationMan == null)
{
foreach (var order in _orders)
if (@event.OptionType == OptionType.新增)
{
if (order.QuestionType == (int) TrackProblemType.Error) continue; // 系统不推异常单,只能人工操作
// 推送新增,需要根据上一次的推送 推断 本次推送 是否可行
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,
};
var item = await BuiltPushItem(config, log);
item.Log = JsonConvert.SerializeObject(log);
items.Add(item);
await BuiltSystemPushItemsAsync(@event, items);
}
else if (@event.OptionType == OptionType.修改)
{
await BuiltManualPushItemsAsync(@event, items);
}
}
else //手工推送
else
{
config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode);
if (config == null) return;
var tracks = await _trackDetailsEsRepository.GetListAsync(@event.OrderIds, @event.TrackCode);
tracks = tracks.DistinctBy(x => x.OrderId).ToList(); // 防止重复推送
if (tracks.Count == 0) return; // 一个能推的轨迹都不存在
// tips:人工推送只要能推就推了,不会校验已推和顺序问题,因为可能是更新
foreach (var track in tracks)
{
var order = _orders.First(x => x.Id == track.OrderId);
if (config is { IsAllPush: true })
{
// 推断 本次推送 是否可行
config = GetValidPushConfigAsync(_pushConfigs, order.LatestPushTrackCode, @event.TrackCode);
}
if (config == null) 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,
};
var item = await BuiltPushItem(config, log);
item.Log = JsonConvert.SerializeObject(log);
items.Add(item);
}
// 手工推送,不会校验已推和顺序问题,因为可能是更新
await BuiltManualPushItemsAsync(@event, items);
}
if (items.Count == 0) return;
@ -164,26 +124,119 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
new Track2InterfaceRelaySubSystemContext(@event.SubSystemCode)))
{
var asciiString = Base64Helper.SerializeObject(OptionType.新增.ToString());
var serviceProviderCode = _pushConfigs.First().ServiceProviderCode;
var r = await _httpClientProxy.Service.BatchForwardThenPushToQueueAsync(
_options.CurrentValue.PushTrackInterfaceCode,
_pushConfigs.First().ServiceProviderCode, items, GlobalConst.TrackPushCompletedSubscribe,
serviceProviderCode, items.Cast<object>().ToList(), GlobalConst.TrackPushCompletedSubscribe,
attach: asciiString);
if (r.Code != GlobalConst.SuccessCode)
{
throw new BusinessException(message: $"requestId: {r.RequestId}; {r.Msg}");
TrackPushFailedPublicTopic($"轨迹中台调用接口通失败,RequestId: {r.RequestId}; Msg: {r.Msg}", @event, r.RequestId,
items.Cast<dynamic>().ToList());
throw new BusinessException(
message: $"requestId: {r.RequestId}; serviceProviderCode:{serviceProviderCode} Msg: {r.Msg}");
}
}
}
private async Task DoUpdateAsync(TrackPushEvent @event)
private async Task BuiltManualPushItemsAsync(TrackPushEvent @event, List<TrackPushInput> items)
{
// tips: 手工强制推送直接使用配置
var config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode);
if (config == null || !config.IsCanUpdate) return;
var items = new List<object>();
foreach (var order in _orders)
{
if (config is { IsAllPush: true })
{
// 非强制推送需要 推断 本次推送 是否可行
if (!@event.ForcedPush)
{
config = GetValidPushConfigAsync(_pushConfigs, order.LatestPushTrackCode, @event);
}
}
if (config == null)
{
TrackPushFailedPublicTopic("推送顺序异常,找不到有效推送配置", @event, order);
return;
}
//配置的类型如何和订单类型不一致则不允许推送
if (config.OrderType != order.OrderType)
{
TrackPushFailedPublicTopic($"订单类型{order.OrderType}和配置中的订单类型[{config.OrderType}]不一致,不能推送", @event,
order);
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.CompareFields);
item.Log = JsonConvert.SerializeObject(log);
items.Add(item);
}
}
private async Task BuiltSystemPushItemsAsync(TrackPushEvent @event, List<TrackPushInput> items)
{
var config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode);
foreach (var order in _orders)
{
if (config == null)
{
TrackPushFailedPublicTopic($"没有在推送映射配置中找到关于轨迹代码[{@event.TrackCode}]的配置", @event, order);
return;
}
// 系统不推异常单,只能人工操作
if (order.QuestionType == (int) TrackProblemType.Error)
{
TrackPushFailedPublicTopic("系统不推异常单,只能人工操作", @event, order);
continue;
}
// 推送新增,需要根据上一次的推送 推断 本次推送 是否可行,为了约束顺序
// 如果当前推送code与订单上的最后的code不一致,则考虑推送映射顺序
if (!string.Equals(order.LatestPushTrackCode, @event.TrackCode))
{
config = GetValidPushConfigAsync(_pushConfigs, order.LatestPushTrackCode, @event);
if (config == null)
{
TrackPushFailedPublicTopic("推送顺序异常,找不到有效推送配置", @event, order);
continue;
}
}
// 是否自动推送
if (!config.IsAutoPush)
{
TrackPushFailedPublicTopic("没有设置为自动推送,如果要推送请手动推送", @event, order);
continue;
}
// 配置的类型如何和订单类型不一致则不允许推送
if (config.OrderType != order.OrderType)
{
TrackPushFailedPublicTopic($"订单类型{order.OrderType}和配置中的订单类型[{config.OrderType}]不一致,不能推送", @event,
order);
continue;
}
var log = new TrackPushRecordIndex
{
OptionType = @event.OptionType.ToString(),
@ -196,6 +249,47 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
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.CompareFields);
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)
{
TrackPushFailedPublicTopic($"订单类型{order.OrderType}和配置中的订单类型[{config.OrderType}]不一致,不能推送", @event,
order);
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);
item.Log = JsonConvert.SerializeObject(log);
@ -208,14 +302,18 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
new Track2InterfaceRelaySubSystemContext(@event.SubSystemCode)))
{
var asciiString = Base64Helper.SerializeObject(OptionType.修改.ToString());
var serviceProviderCode = _pushConfigs.First().ServiceProviderCode;
var r = await _httpClientProxy.Service.BatchForwardThenPushToQueueAsync(
_options.CurrentValue.TrackUpdateInterfaceCode,
_pushConfigs.First().ServiceProviderCode, items, GlobalConst.TrackPushCompletedSubscribe,
serviceProviderCode, items.Cast<object>().ToList(), GlobalConst.TrackPushCompletedSubscribe,
attach: asciiString);
if (r.Code != GlobalConst.SuccessCode)
{
throw new BusinessException(message: $"requestId: {r.RequestId}; {r.Msg}");
TrackPushFailedPublicTopic($"轨迹中台调用接口通失败,RequestId: {r.RequestId}; Msg: {r.Msg}", @event, r.RequestId,
items.Cast<dynamic>().ToList());
throw new BusinessException(
message: $"requestId: {r.RequestId}; serviceProviderCode:{serviceProviderCode} Msg: {r.Msg}");
}
}
}
@ -225,64 +323,120 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
var trackDatetime = log.TrackTime.AddHours(config.TrackTimeZone ?? log.TrackTimeZone - log.TrackTimeZone);
var trackDateStr = DateTimeFormat(trackDatetime, config.TrackTimeZone ?? log.TrackTimeZone, config);
var track = new TrackItem
{
// 推真实的发生地和发生时间(时间是计算后的)
Location = log.TrackPlace,
Date = trackDateStr,
TimeZone = config.TrackTimeZone ?? log.TrackTimeZone,
Description = config.TrackEnContentValue,
DescriptionCn = config.TrackContentValue,
Code = config.TrackContentCodeValue,
Status = config.TrackStatusValue,
Extra = config.ExpandFieldsValue
};
var body = new TrackUpdateInput
{
OrderNo = log.OrderNo,
OrderNo = log.OrderNo
};
body.Data.Add(track);
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", TextJsonHelper.Deserialize<object>(log.Extra) }
};
body.Data.Add(dict);
log.PushContent = body.ToJson();
return body;
}
private async Task<TrackPushInput> BuiltPushItem(TrackPushContentConfigDto config, TrackPushRecordIndex log)
private async Task<TrackPushInput> BuiltPushItem(TrackPushContentConfigDto config, TrackPushRecordIndex log,
string? compareFields)
{
var trackDatetime = log.TrackTime.AddHours(config.TrackTimeZone ?? log.TrackTimeZone - log.TrackTimeZone);
var trackDateStr = DateTimeFormat(trackDatetime, config.TrackTimeZone ?? log.TrackTimeZone, config);
var track = new TrackItem
{
// 推真实的发生地和发生时间(时间是计算后的)
Location = log.TrackPlace,
Date = trackDateStr,
TimeZone = config.TrackTimeZone ?? log.TrackTimeZone,
Description = config.TrackEnContentValue,
DescriptionCn = config.TrackContentValue,
Code = config.TrackContentCodeValue,
Status = config.TrackStatusValue,
Extra = config.ExpandFieldsValue
};
var body = new TrackPushInput
{
OrderNo = log.OrderNo,
};
body.Data.Add(track);
var cur = 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", TextJsonHelper.Deserialize<object>(log.Extra) }
};
body.Data.Add(cur);
if (config.IsAllPush)
{
var history = await _trackPushRecordEsRepository.GetLatestTrackPushRecordAsync(log.OrderId);
var history = await _trackPushRecordEsRepository.GetLatestTrackPushRecordAsync(log.OrderId, true);
if (history != default)
{
body = JsonConvert.DeserializeObject<TrackPushInput>(history.PushContent);
body!.Data.Add(track);
body = TextJsonHelper.Deserialize<TrackPushInput>(history.PushContent);
//在推送历史中,选出当前轨迹明细一致的轨迹
var tracks = await _trackDetailsEsRepository.GetListAsync(log.OrderId);
//确定轨迹标识符
var trackIdentifies = tracks.Select(x =>
{
//寻找映射配置(转换后的code)
var code = _pushConfigs.First(y => y.TrackCode == x.TrackCode).TrackContentCodeValue;
var timeStr = DateTimeFormat(x.TrackTime, config.TrackTimeZone ?? log.TrackTimeZone, config);
return string.IsNullOrWhiteSpace(compareFields)
? $"{code}{timeStr}"
: $"{code}{TextJsonHelper.ExtractValue(x.Extra, compareFields)}";
}
).ToImmutableHashSet();
//在历史中选取在订单当前轨迹明细中的记录
body!.Data = body.Data.Where(x =>
{
var code = ((JsonElement) x["code"]!).GetString();
var timeStr = ((JsonElement) x["date"]!).GetString();
if (string.IsNullOrWhiteSpace(compareFields))
return trackIdentifies.Contains($"{code}{timeStr}");
if (((JsonElement) x["extra"]!).TryGetProperty(compareFields, out var comparerValueJsonElement))
return trackIdentifies.Contains($"{code}{TextJsonHelper.GetString(comparerValueJsonElement)}");
return trackIdentifies.Contains($"{code}{null}");
}).ToList();
if (config.IsAllPushWithLatest)
{
//同code同比较字段保证唯一
var old = body.Data
.Where(x =>
{
//反序列化出的JsonElement和字典字符串类型做比较
var a = ((JsonElement) x["code"]!).GetString();
var b = cur["code"]!
.ToString(); //tips: 需要要将object类型的字符串转为string类型,否则使用的是引用比较,反序列化出的string一定和本地string引用不同
return a == b;
})
.WhereIf(!compareFields.IsNullOrWhiteSpace(), x =>
{
if (x["extra"] == null || log.Extra == null) return false;
return TextJsonHelper.EqByJsonElementField(x["extra"]!,
TextJsonHelper.Deserialize<JsonElement>(log.Extra), compareFields!);
})
.FirstOrDefault();
if (old != null)
{
body.Data.Remove(old);
}
}
// 添加当前要推送的轨迹
body.Data.Add(cur);
}
}
log.PushContent = body.ToJson();
log.PushContent = TextJsonHelper.Serialize(body)!;
return body;
}
@ -291,35 +445,34 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
/// 根据轨迹代码推断下一个(升序)要推送的内容,如果找不到或者已经是最后一个了
/// 则查询有没有可交换的推送,没有就返回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)
private static TrackPushContentConfigDto? GetValidPushConfigAsync(List<TrackPushContentConfigDto> cache,
string? latestPushTrackCode, TrackPushEvent @event)
{
var trackCode = @event.TrackCode;
var idx = cache.FindIndex(x => x.TrackCode == latestPushTrackCode);
if (@event.ForcedPush && trackCode == latestPushTrackCode)
{
//强制推送,允许重复推送当前轨迹
return cache[idx];
}
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);
: 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)
private static string DateTimeFormat(DateTime dateTime, double timezone, TrackPushContentConfigDto config)
{
if (config.DateTimeFormatType == (int) DateTimeFormatEnum.Timestamp)
return DateTimeHelper.ConvertToUnixTimestamp(dateTime).ToString();
@ -335,4 +488,61 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
return dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffK");
}
//轨迹推送失败(被拦截)
private void TrackPushFailedPublicTopic(string failedMessage, TrackPushEvent @event, OrderEntity order)
{
_broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto
{
//还没请求接口通
RequestId = null,
IsSuccess = false,
Message = failedMessage,
OperationMan = @event.OperationMan,
PushTime = DateTime.Now,
OptionType = @event.OptionType.ToString(),
IsManualPush = @event.OperationMan != null,
//还没到构建推送内容那一步就被拦截
PushContent = string.Empty,
OrderNo = order.OrderNo,
TrackStatus = @event.SystemPushInfo.TrackStatus,
TrackContent = @event.SystemPushInfo.TrackStatus,
TrackTimeZone = @event.SystemPushInfo.TrackTimeZone,
TrackTime = @event.SystemPushInfo.TrackTime,
TrackPlace = @event.SystemPushInfo.TrackPlace,
TrackCode = @event.TrackCode,
Extra = @event.SystemPushInfo.Extra,
});
}
//轨迹推送失败(请求接口通)
private void TrackPushFailedPublicTopic(
string failedMessage,
TrackPushEvent @event,
Guid requestId,
List<dynamic> pushOrUpdateInputs)
{
foreach (var pushInput in pushOrUpdateInputs)
{
_broadcastingService.TrackPushFailedPubilcTopic(new TrackPushRecordDto
{
RequestId = requestId,
IsSuccess = false,
Message = failedMessage,
OperationMan = @event.OperationMan,
PushTime = DateTime.Now,
OptionType = @event.OptionType.ToString(),
IsManualPush = @event.OperationMan != null,
PushContent = TextJsonHelper.Serialize(pushInput),
OrderNo = pushInput.OrderNo,
TrackStatus = @event.SystemPushInfo.TrackStatus,
TrackContent = @event.SystemPushInfo.TrackStatus,
TrackTimeZone = @event.SystemPushInfo.TrackTimeZone,
TrackTime = @event.SystemPushInfo.TrackTime,
TrackPlace = @event.SystemPushInfo.TrackPlace,
TrackCode = @event.TrackCode,
Extra = @event.SystemPushInfo.Extra,
});
}
}
}

View File

@ -1,12 +1,13 @@
using System.Text;
using BSI.Stack.Rabbit;
using System.Diagnostics;
using DotNetCore.CAP;
using Microsoft.Extensions.Logging;
using SJZY.Expand.ABP.Core.Logger;
using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Application.Helpers;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Aggregates.TrackStatusAggregate;
using SJZY.Track.Domain.Dtos.TrackContents;
using SJZY.Track.Domain.Enums;
using SJZY.Track.Domain.Queries;
@ -21,7 +22,8 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
private readonly IOrderRepository _orderRepository;
private readonly IProvideQueries _provideQueries;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly RabbitClientBus _rabbitClientBus;
private readonly IBroadcastingService _broadcastingService;
private readonly ILogger<TrackUpsertEventHandler> _logger;
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
public TrackUpsertEventHandler(
@ -29,15 +31,17 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
ICapPublisher capPublisher,
IProvideQueries provideQueries,
ITrackDetailsEsRepository trackDetailsEsRepository,
RabbitClientBus rabbitClientBus,
IOrderOperationRecordEsRepository orderOperationRecordEsRepository)
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
IBroadcastingService broadcastingService,
ILogger<TrackUpsertEventHandler> logger)
{
_orderRepository = orderRepository;
_capPublisher = capPublisher;
_provideQueries = provideQueries;
_trackDetailsEsRepository = trackDetailsEsRepository;
_rabbitClientBus = rabbitClientBus;
_orderOperationRecordEsRepository = orderOperationRecordEsRepository;
_broadcastingService = broadcastingService;
_logger = logger;
}
[CapSubscribe(GlobalConst.TrackUpsertSubscribe, Group = GlobalConst.TrackUpsertSubscribe)]
@ -51,59 +55,14 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
var content = await HandleTrackAsync(@event, canNextOrderIds);
// 处理广播事件
PublicTrackTopic(@event, content);
if (content != null && (content.IsToMq || @event.IsToMq))
_ = EnsureWriteAfterPublicTopicAsync(@event, content.SubSystemCode, content.TrackStatusCode);
if (canNextOrderIds.Count == 0) return;
// 根据订单关联关系异步向下刷(异步)
await DownByOrderLink(@event, canNextOrderIds);
}
/// <summary>
/// 发送轨迹广播
/// </summary>
/// <param name="eto"></param>
/// <param name="content"></param>
private void PublicTrackTopic(TrackUpsertEvent eto, TrackContentVO? content)
{
// var trackStatus = await _trackStatusRepository.GetFirstAsync(m => m.Code == content.TrackStatusCode);
if (content == null || !content.IsToMq)
return;
var data = new TrackChangeEto()
{
EstimatedDeliveryDate = eto.EstimatedDeliveryDate,
TrackPlacePostcode = eto.TrackPlacePostcode,
TrackPlaceNation = eto.TrackPlaceNation,
TrackPlaceNationId = eto.TrackPlaceNationId,
TrackPlaceProvince = eto.TrackPlaceProvince,
TrackPlaceProvinceId = eto.TrackPlaceProvinceId,
TrackPlaceCity = eto.TrackPlaceCity,
TrackPlaceCityId = eto.TrackPlaceCityId,
Files = eto.Files,
Remarks = eto.Remarks,
Source = eto.Source,
IsVisibility = eto.IsVisibility,
TrackTimeZone = eto.TrackTimeZone,
TrackTime = eto.TrackTime,
TrackPlace = eto.TrackPlace,
TrackStatusCode = content.TrackStatusCode,
EnStatus = eto.EnStatus,
Content = eto.Content,
TrackCode = eto.TrackCode,
OperationMan = eto.OperationMan,
OrderIdentifiers = eto.OrderIdentifiers,
CustomId = eto.CustomId,
SubSystemId = eto.SubSystemId
};
if (eto.IsAdd)
data.Option = 1;
else
data.Option = 3;
_rabbitClientBus.Publish(GlobalConst.TrackChangeExchange, $"TrackChange.{content.SubSystemCode}", Encoding.UTF8.GetBytes(data.ToJson()));
}
private async Task DownByOrderLink(TrackUpsertEvent @event, List<int> canNextOrderIds)
{
// 根据订单关联关系异步向下刷(异步)
@ -185,6 +144,8 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
return false;
}
// todo: 在这里,判断存在性
return true;
}
@ -217,6 +178,14 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
// 批量add to es
await _trackDetailsEsRepository.AddManyAsync(insertTracks);
//批量写入订单操作日志
_ = _orderOperationRecordEsRepository
.AddManyAsync(insertTracks.Select(t => BuildOrderOperationRecord(@event, t, new OrderIdentifier
{
OrderNo = t.OrderNo,
OrderId = t.OrderId
})).ToList());
// 添加轨迹触发订单拉取状态更新
await _orderRepository.UpdatePullStatusToSilenceAsync(insertTracks.Select(x => x.OrderId).ToList(),
@event.TrackCode);
@ -262,8 +231,12 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
TrackContent = trackContent.TrackContent,
TrackPlace = @event.TrackPlace,
TrackTime = @event.NewTrackTime ?? @event.TrackTime,
TrackTimeZone = @event.TrackTimeZone
}
TrackTimeZone = @event.TrackTimeZone,
CompareFields = @event.CompareFields,
Extra = @event.Extra
},
ForcedPush = @event.ForcedPush,
IsToMq = (trackContent.IsToMq || @event.IsToMq)
};
await _capPublisher.PublishAsync(GlobalConst.TrackPushSubscribe, trackPushEvent);
@ -276,180 +249,76 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
var orderIds = @event.OrderIdentifiers.Select(x => x.OrderId).ToArray();
// important:轨迹code相同,时间、发生地不同的,可能存在多条!!
var tracks = await _trackDetailsEsRepository.GetListAsync(orderIds, @event.TrackCode, false);
var tracks = await _trackDetailsEsRepository.GetListAsync(orderIds, @event.TrackCode, true);
var insertTracks = new List<TrackDetailsIndex>();
var updateTracks = new List<TrackDetailsIndex>();
var operationRecords = new List<OrderOperationRecordIndex>();
foreach (var order in @event.OrderIdentifiers)
{
TrackDetailsIndex? track;
if (!@event.IsAdd)
// fast path: by update with es id case
if (!@event.IsAdd && !string.IsNullOrWhiteSpace(@event.EsId))
{
//同一订单同轨迹code同一轨迹时间如果有多条轨迹明细则不允许修改
var existTracks = tracks.Where(x => x.OrderId == order.OrderId
&& x.TrackTime == @event.TrackTime).ToArray();
if (existTracks.Length > 1)
var trackById = tracks.FirstOrDefault(x => x.Id == @event.EsId);
if (trackById != null)
{
// 修改轨迹时,订单存在多条轨迹不允许修改
continue;
updateTracks.Add(BuiltUpdateTrack(@event, trackById, order, trackContent));
}
track = existTracks.FirstOrDefault();
continue;
}
if (track == null) continue; // 不存在不让改
var existTracks = tracks.Where(x => x.OrderId == order.OrderId).ToList();
if (@event.CompareFields.IsNullOrEmpty())
{
existTracks = existTracks.Where(x => x.TrackTime == @event.TrackTime).ToList();
}
else
{
// 【批量新增】所有可能遇到的场景
// 标识:允许重复,不允许重复,零条,一条,多条;
// 场景A:允许重复,零条 => 新增
// 场景B:允许重复,一条 => 新增(约束受限新增,否则跳过新增)
// 场景C:允许重复,多条 => 新增(约束受限新增,否则跳过新增)
// 场景D:不允许重复,零条 => 新增
// 场景E:不允许重复,一条 => 跳过
// 场景F:不允许重复,(理想不会出现多条) => 跳过
var existTracks = tracks.Where(x => x.OrderId == order.OrderId).ToArray();
track = existTracks.FirstOrDefault();
//覆盖场景:B,C
if (trackContent.IsRepeatable && existTracks.Length >= 1)
Debug.Assert(@event.CompareFields != null, "@event.CompareFields != null");
var extraDict = TextJsonHelper.DeserializeToDictionary(@event.Extra);
if (extraDict == null || !extraDict.TryGetValue(@event.CompareFields, out var compareValue))
{
if (existTracks.Any(x => x.TrackPlace == @event.TrackPlace && x.TrackTime == @event.TrackTime))
throw new BusinessException(message: $"请检查拓展字段,为什么找不到比较字段:{@event.CompareFields}");
}
existTracks = existTracks.Where(x =>
{
var oldExtraDict = TextJsonHelper.DeserializeToDictionary(x.Extra);
// tips:注意兼容性,历史轨迹的字段可能为空
if (oldExtraDict == null || !oldExtraDict.TryGetValue(@event.CompareFields, out var oldValue))
{
//允许重复,出现【轨迹发生地】,【轨迹发生时间】的都重复,则不允许新增
continue;
return false;
}
// 设置null后面执行新增逻辑
track = null;
}
return TextJsonHelper.EqJsonElement(oldValue, compareValue);
}).ToList();
}
//不允许重复,并且记录大于等于一条,跳过
//覆盖场景:E,F
if (!trackContent.IsRepeatable && existTracks.Length >= 1)
var track = existTracks.FirstOrDefault();
if (!@event.IsAdd)
{
if (existTracks.Count > 1)
{
// todo: 所有的event handle 多写log,取缔注释,特别是continue,return,这种跳出的地方,方便排查问题
_logger.LogInformation("订单存在多条轨迹不允许修改:{@event}", @event);
continue;
}
//其余情况:A,D都为新增操作,无需检查数量,track为null执行新增操作
}
// todo:例如这里
if (track == null) continue; // 不存在不让改
if (track == default)
{
track = new TrackDetailsIndex
{
SubSystemId = @event.SubSystemId,
OrderId = order.OrderId,
OrderNo = order.OrderNo,
OrderType = order.OrderType,
TrackCode = @event.TrackCode,
TrackPlace = @event.TrackPlace,
TrackTime = @event.NewTrackTime ?? @event.TrackTime,
TrackTimeZone = @event.TrackTimeZone,
ServiceProviderCode = @event.TrackRefreshData.ServiceProviderCode ?? GlobalConst.EmptyString,
OriginTrackContent = @event.TrackRefreshData.OriginTrackContent ?? GlobalConst.EmptyString,
OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash ?? GlobalConst.EmptyString,
CreateUser = @event.OperationMan ?? GlobalConst.SystemUser,
CreateTime = DateTime.Now,
//来源轨迹内容,第一次新增的时候使用轨迹内容表提供的
TrackStatusId = trackContent.TrackStatusId,
TrackStatus = trackContent.TrackStatus,
TrackStatusCode = trackContent.TrackStatusCode,
//跨子系统?
TrackContentId = trackContent.TrackContentId,
TrackContent = @event.Content ?? trackContent.TrackContent,
TrackEnContent = @event.EnStatus ?? trackContent.TrackEnContent,
//S06新增轨迹明细字段
IsVisibility = @event.IsVisibility,
Source = @event.Source,
Remarks = @event.Remarks ?? GlobalConst.EmptyString,
Files = @event.Files?.Select(x => new FileItem
{
OssKey = x.OssKey,
FileName = x.FileName
}).ToList(),
TrackPlaceCity = @event.TrackPlaceCity ?? GlobalConst.EmptyString,
TrackPlaceCityId = @event.TrackPlaceCityId ?? GlobalConst.NotExistId,
TrackPlaceProvince = @event.TrackPlaceProvince ?? GlobalConst.EmptyString,
TrackPlaceProvinceId = @event.TrackPlaceProvinceId ?? GlobalConst.NotExistId,
TrackPlaceNation = @event.TrackPlaceNation ?? GlobalConst.EmptyString,
TrackPlaceNationId = @event.TrackPlaceNationId ?? GlobalConst.NotExistId,
TrackPlacePostcode = @event.TrackPlacePostcode ?? GlobalConst.EmptyString,
DestinationTimeZone = @event.DestinationTimeZone,
EstimatedDeliveryDate = @event.EstimatedDeliveryDate,
Extra = @event.Extra
};
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.TrackTime
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
insertTracks.Add(track);
// 写入操作日志
_ = _orderOperationRecordEsRepository.AddAsync(new OrderOperationRecordIndex
{
OrderId = order.OrderId,
OrderNo = order.OrderNo,
OptionType = OrderOperationType.创建轨迹.ToString(),
Content = track.ToJson(),
CreateUserId = @event.OperationManId,
CreateUserName = @event.OperationMan ?? "",
CreateTime = DateTime.Now
});
updateTracks.Add(BuiltUpdateTrack(@event, track, order, trackContent));
operationRecords.Add(BuildOrderOperationRecord(@event, track, order));
}
else
{
//外部轨迹,轨迹发生地,轨迹发生时间,轨迹发生时区,不允许更改
if (track.Source != (int) TrackSourceEnum.ExternalServiceProvide)
{
track.TrackPlace = @event.TrackPlace;
track.TrackTime = @event.NewTrackTime ?? @event.TrackTime;
track.TrackTimeZone = @event.TrackTimeZone;
}
// 不允许重复轨迹,跳过
if (!trackContent.IsRepeatable && existTracks.Count > 0) continue;
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.TrackTime
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
track.UpdateUser = @event.OperationMan ?? GlobalConst.SystemUser;
track.UpdateTime = DateTime.Now;
//允许二次修改,在修改了轨迹内容后进行修改轨迹操作,如果输入的轨迹内容则以输入为准
//跨子系统?
track.TrackContent = @event.Content ?? trackContent.TrackContent;
track.TrackEnContent = @event.EnStatus ?? trackContent.TrackEnContent;
//S06新增
track.IsVisibility = @event.IsVisibility;
track.Remarks = @event.Remarks ?? GlobalConst.EmptyString;
track.Files = @event.Files?.Select(x => new FileItem
{
OssKey = x.OssKey,
FileName = x.FileName
}).ToList();
track.TrackPlaceCity = @event.TrackPlaceCity ?? GlobalConst.EmptyString;
track.TrackPlaceCityId = @event.TrackPlaceCityId ?? GlobalConst.NotExistId;
track.TrackPlaceProvince = @event.TrackPlaceProvince ?? GlobalConst.EmptyString;
track.TrackPlaceProvinceId = @event.TrackPlaceProvinceId ?? GlobalConst.NotExistId;
track.TrackPlaceNation = @event.TrackPlaceNation ?? GlobalConst.EmptyString;
track.TrackPlaceNationId = @event.TrackPlaceNationId ?? GlobalConst.NotExistId;
track.TrackPlacePostcode = @event.TrackPlacePostcode ?? GlobalConst.EmptyString;
track.DestinationTimeZone = @event.DestinationTimeZone;
track.EstimatedDeliveryDate = @event.EstimatedDeliveryDate;
track.Extra = @event.Extra;
updateTracks.Add(track);
// 写入操作日志
_ = _orderOperationRecordEsRepository.AddAsync(new OrderOperationRecordIndex
{
OrderId = order.OrderId,
OrderNo = order.OrderNo,
OptionType = OrderOperationType.修改轨迹.ToString(),
Content = track.ToJson(),
CreateUserId = @event.OperationManId,
CreateUserName = @event.OperationMan ?? "",
CreateTime = DateTime.Now
});
insertTracks.Add(BuiltNewTrack(@event, order, trackContent));
}
canNextOrderIds.Add(order.OrderId);
@ -459,8 +328,133 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
{
//批量差异化更新轨迹
await _trackDetailsEsRepository.BulkUpdateAsync(updateTracks);
//批量写入操作日志
_ = _orderOperationRecordEsRepository.AddManyAsync(operationRecords);
}
return insertTracks;
}
private TrackDetailsIndex BuiltUpdateTrack(TrackUpsertEvent @event, TrackDetailsIndex track, OrderIdentifier order,
TrackContentVO trackContent)
{
//外部轨迹,轨迹发生地,轨迹发生时间,轨迹发生时区,不允许更改
if (track.Source != (int) TrackSourceEnum.ExternalServiceProvide)
{
track.TrackPlace = @event.TrackPlace;
track.TrackTime = @event.NewTrackTime ?? @event.TrackTime;
track.TrackTimeZone = @event.TrackTimeZone;
}
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.TrackTime
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
track.UpdateUser = @event.OperationMan ?? GlobalConst.SystemUser;
track.UpdateTime = DateTime.Now;
//允许二次修改,在修改了轨迹内容后进行修改轨迹操作,如果输入的轨迹内容则以输入为准
//跨子系统?
track.TrackContent = @event.Content ?? trackContent.TrackContent;
track.TrackEnContent = @event.EnStatus ?? trackContent.TrackEnContent;
//S06新增
track.IsVisibility = @event.IsVisibility;
track.Remarks = @event.Remarks ?? GlobalConst.EmptyString;
track.Files = @event.Files?.Select(x => new FileItem
{
OssKey = x.OssKey,
FileName = x.FileName
}).ToList();
track.TrackPlaceCity = @event.TrackPlaceCity ?? GlobalConst.EmptyString;
track.TrackPlaceCityId = @event.TrackPlaceCityId ?? GlobalConst.NotExistId;
track.TrackPlaceProvince = @event.TrackPlaceProvince ?? GlobalConst.EmptyString;
track.TrackPlaceProvinceId = @event.TrackPlaceProvinceId ?? GlobalConst.NotExistId;
track.TrackPlaceNation = @event.TrackPlaceNation ?? GlobalConst.EmptyString;
track.TrackPlaceNationId = @event.TrackPlaceNationId ?? GlobalConst.NotExistId;
track.TrackPlacePostcode = @event.TrackPlacePostcode ?? GlobalConst.EmptyString;
track.DestinationTimeZone = @event.DestinationTimeZone;
track.EstimatedDeliveryDate = @event.EstimatedDeliveryDate;
track.Extra = @event.Extra;
return track;
}
private TrackDetailsIndex BuiltNewTrack(TrackUpsertEvent @event, OrderIdentifier order, TrackContentVO trackContent)
{
var track = new TrackDetailsIndex
{
SubSystemId = @event.SubSystemId,
OrderId = order.OrderId,
OrderNo = order.OrderNo,
OrderType = order.OrderType,
TrackCode = @event.TrackCode,
TrackPlace = @event.TrackPlace,
TrackTime = @event.NewTrackTime ?? @event.TrackTime,
TrackTimeZone = @event.TrackTimeZone,
ServiceProviderCode = @event.TrackRefreshData.ServiceProviderCode ?? GlobalConst.EmptyString,
OriginTrackContent = @event.TrackRefreshData.OriginTrackContent ?? GlobalConst.EmptyString,
OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash ?? GlobalConst.EmptyString,
CreateUser = @event.OperationMan ?? GlobalConst.SystemUser,
CreateTime = DateTime.Now,
//来源轨迹内容,第一次新增的时候使用轨迹内容表提供的
TrackStatusId = trackContent.TrackStatusId,
TrackStatus = trackContent.TrackStatus,
TrackStatusCode = trackContent.TrackStatusCode,
//跨子系统?
TrackContentId = trackContent.TrackContentId,
TrackContent = @event.Content ?? trackContent.TrackContent,
TrackEnContent = @event.EnStatus ?? trackContent.TrackEnContent,
//S06新增轨迹明细字段
IsVisibility = @event.IsVisibility,
Source = @event.Source,
Remarks = @event.Remarks ?? GlobalConst.EmptyString,
Files = @event.Files?.Select(x => new FileItem
{
OssKey = x.OssKey,
FileName = x.FileName
}).ToList(),
TrackPlaceCity = @event.TrackPlaceCity ?? GlobalConst.EmptyString,
TrackPlaceCityId = @event.TrackPlaceCityId ?? GlobalConst.NotExistId,
TrackPlaceProvince = @event.TrackPlaceProvince ?? GlobalConst.EmptyString,
TrackPlaceProvinceId = @event.TrackPlaceProvinceId ?? GlobalConst.NotExistId,
TrackPlaceNation = @event.TrackPlaceNation ?? GlobalConst.EmptyString,
TrackPlaceNationId = @event.TrackPlaceNationId ?? GlobalConst.NotExistId,
TrackPlacePostcode = @event.TrackPlacePostcode ?? GlobalConst.EmptyString,
DestinationTimeZone = @event.DestinationTimeZone,
EstimatedDeliveryDate = @event.EstimatedDeliveryDate,
Extra = @event.Extra
};
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.TrackTime
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
return track;
}
private OrderOperationRecordIndex BuildOrderOperationRecord(TrackUpsertEvent @event, TrackDetailsIndex track, OrderIdentifier order)
{
return new OrderOperationRecordIndex
{
OrderId = order.OrderId,
OrderNo = order.OrderNo,
OptionType = @event.IsAdd ? OrderOperationType.创建轨迹.ToString() : OrderOperationType.修改轨迹.ToString(),
Content = track.ToJson(),
CreateUserId = @event.OperationManId,
CreateUserName = @event.OperationMan ?? "",
CreateTime = DateTime.Now
};
}
private async Task EnsureWriteAfterPublicTopicAsync(TrackUpsertEvent @event, string subSystemCode, string statusCode)
{
//理想情况在写入之后1s会刷盘 https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-refresh.html
//增加一个版本号来确定是否真的变更了
//没有刷盘成功事件回调
//方案1:!!【轮询等待静默刷盘】查出版本号确实发生了变更在发送广播
//方案2:考虑强制刷盘(对es压力大)后发送
//方案3:采用一个较大延时,不查询直接发
await Task.Delay(1000);
_broadcastingService.TrackChangePublicTopic(@event, subSystemCode, statusCode);
}
}

View File

@ -0,0 +1,181 @@
using System.Globalization;
using System.Text.Json;
namespace SJZY.Track.Application.Helpers;
/// <summary>
/// 针对 Text.Json 的一些辅助方法
/// </summary>
public static class TextJsonHelper
{
public static string? Serialize(object? obj)
{
return obj == null ? default : System.Text.Json.JsonSerializer.Serialize(obj);
}
public static T? Deserialize<T>(string? json)
{
return string.IsNullOrWhiteSpace(json) ? default : System.Text.Json.JsonSerializer.Deserialize<T>(json);
}
public static Dictionary<string, object?>? DeserializeToDictionary(string? dictJsonString)
{
return string.IsNullOrWhiteSpace(dictJsonString)
? null
: System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object?>>(dictJsonString);
}
public static JsonElement? DeserializeToJsonElement(string? dictJsonString)
{
return string.IsNullOrWhiteSpace(dictJsonString)
? null
: System.Text.Json.JsonSerializer.Deserialize<JsonElement>(dictJsonString);
}
/// <summary>
/// 获取JsonElement字符串表达的值
/// </summary>
/// <param name="el"></param>
/// <returns></returns>
public static string? GetString(object? el)
{
if (el is not JsonElement jsonElement)
return default;
return jsonElement.ValueKind switch
{
JsonValueKind.Undefined or JsonValueKind.Null => default,
JsonValueKind.True or JsonValueKind.False => jsonElement.GetBoolean().ToString(),
JsonValueKind.Object or JsonValueKind.Array or JsonValueKind.Number => jsonElement.GetRawText(),
JsonValueKind.String => jsonElement.GetString(),
_ => default
};
}
/// <summary>
/// 提取字典字符串中某个字段的值
/// </summary>
/// <param name="dictString">字典json字符串</param>
/// <param name="fieldName"></param>
/// <returns></returns>
public static object? ExtractValue(string? dictString, string fieldName)
{
if (string.IsNullOrWhiteSpace(dictString))
{
return null;
}
var dict = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object?>>(dictString);
if (dict == null || !dict.TryGetValue(fieldName, out var value))
{
return null;
}
return value;
}
/// <summary>
/// 两个jsonElement进行比较
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static bool EqJsonElement(object? a, object? b)
{
if (a == null && b == null)
{
return true;
}
if (a is JsonElement aJsonElement && b is JsonElement bJsonElement)
{
return aJsonElement.ToString() == bJsonElement.ToString();
}
return false;
}
/// <summary>
/// jsonElement和object之间的比较
/// </summary>
/// <param name="jsonElement"></param>
/// <param name="obj"></param>
/// <returns></returns>
public static bool EqJsonElementAndObject(object? jsonElement, object? obj)
{
if (jsonElement == null && obj == null)
{
return true;
}
if (jsonElement is JsonElement aJsonElement)
{
switch (aJsonElement.ValueKind)
{
case JsonValueKind.Undefined:
return false;
case JsonValueKind.Object:
return false;
case JsonValueKind.Array:
return false;
case JsonValueKind.String:
return string.Equals(aJsonElement.GetString(), obj as string);
case JsonValueKind.Number:
{
if (obj == null) return false;
if (obj is int bInt)
{
var aInt = aJsonElement.GetInt32();
return aInt == bInt;
}
if (obj is long bLong)
{
var aLong = aJsonElement.GetInt64();
return aLong == bLong;
}
return false;
}
case JsonValueKind.True:
case JsonValueKind.False:
{
if (obj is bool bl)
{
return aJsonElement.GetBoolean() == bl;
}
return false;
}
case JsonValueKind.Null:
{
return false;
}
}
}
return false;
}
/// <summary>
/// 比较两个jsonElement中的的fieldName字段的值
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="fieldName"></param>
/// <returns></returns>
public static bool EqByJsonElementField(object? a, object? b, string fieldName)
{
if (a is not JsonElement aJsonElement || b is not JsonElement bJsonElement)
return false;
if (aJsonElement.TryGetProperty(fieldName, out var aValue)
&& bJsonElement.TryGetProperty(fieldName, out var bValue))
{
return aValue.ToString() == bValue.ToString();
}
return false;
}
}

View File

@ -57,6 +57,16 @@ public class InterfaceRelayParamsConfigOptions
/// </summary>
public string PushTrackInterfaceCode { get; set; }
/// <summary>
/// 推送轨迹接口Id
/// </summary>
public int PushTrackInterfaceId { get; set; }
/// <summary>
/// 推送轨迹接口 内容项 参数别名前缀
/// </summary>
public string PushTrackItemParamAliasPrefix { get; set; }
/// <summary>
/// 更新轨迹接口代码
/// </summary>

View File

@ -6,6 +6,7 @@ using RabbitMQ.Client.Events;
using SJZY.Expand.ABP.Core.Logger;
using SJZY.InterfaceRelay.Application.Contracts;
using SJZY.Track.Application.Contract.Dtos.TrackPush;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Application.Helpers;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackPushAggregate;
@ -18,12 +19,15 @@ public class TrackPushCompletedEventHandler : ITransientDependency, IRabbitHandl
{
private readonly IOrderRepository _orderRepository;
private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository;
private readonly IBroadcastingService _broadcastingService;
public TrackPushCompletedEventHandler(IOrderRepository orderRepository,
ITrackPushRecordEsRepository trackPushRecordEsRepository)
ITrackPushRecordEsRepository trackPushRecordEsRepository,
IBroadcastingService broadcastingService)
{
_orderRepository = orderRepository;
_trackPushRecordEsRepository = trackPushRecordEsRepository;
_broadcastingService = broadcastingService;
}
public ConsumeOptions Options()
@ -49,11 +53,11 @@ public class TrackPushCompletedEventHandler : ITransientDependency, IRabbitHandl
var optionType = Base64Helper.DeSerialize<string>(@event.Attach);
if (optionType == OptionType.新增.ToString())
{
logStr = JsonConvert.DeserializeObject<TrackPushInput>(@event.RequestParams)!.Log;
logStr = JsonConvert.DeserializeObject<List<TrackPushInput>>(@event.RequestParams)!.First().Log;
}
else if (optionType == OptionType.修改.ToString())
{
logStr = JsonConvert.DeserializeObject<TrackUpdateInput>(@event.RequestParams)!.Log;
logStr = JsonConvert.DeserializeObject<List<TrackUpdateInput>>(@event.RequestParams)!.First().Log;
}
if (logStr.IsNullOrEmpty()) throw new InvalidOperationException();
@ -64,6 +68,10 @@ public class TrackPushCompletedEventHandler : ITransientDependency, IRabbitHandl
log.Message = @event.Message;
log.RequestId = @event.RequestId;
// 推送广播
if (log.IsToMq)
_broadcastingService.TrackPushCompletedPublicTopic(BuildDto(log));
// 往es 写推送日志
await _trackPushRecordEsRepository.AddAsync(log);
@ -111,4 +119,28 @@ public class TrackPushCompletedEventHandler : ITransientDependency, IRabbitHandl
});
}
}
private TrackPushRecordDto BuildDto(TrackPushRecordIndex log)
{
return new TrackPushRecordDto
{
SubSystemCode = log.SubSystemCode,
RequestId = log.RequestId,
IsSuccess = log.IsSuccess,
Message = log.Message,
OperationMan = log.CreateUser,
PushTime = DateTime.Now,
OptionType = log.OptionType,
IsManualPush = !string.Equals(log.CreateUser, GlobalConst.SystemUser), //操作人不为系统用户,则认为是仍推送
PushContent = log.PushContent,
OrderNo = log.OrderNo,
TrackStatus = log.TrackStatus,
TrackContent = log.TrackContent,
TrackTimeZone = log.TrackTimeZone,
TrackTime = log.TrackTime,
TrackPlace = log.TrackPlace,
TrackCode = log.TrackCode,
Extra = log.Extra
};
}
}

View File

@ -11,7 +11,7 @@
<PackageReference Include="BSI.Stack.Rabbit" Version="2.1.3" />
<PackageReference Include="Savorboard.CAP.InMemoryMessageQueue" Version="8.2.1"/>
<PackageReference Include="SJZY.Expand.AliyunOSS" Version="7.0.17" />
<PackageReference Include="SJZY.InterfaceRelay.Application.Contracts" Version="7.2.5-alpha" />
<PackageReference Include="SJZY.InterfaceRelay.Application.Contracts" Version="7.2.5-alpha2" />
<PackageReference Include="Volo.Abp.Http.Client" Version="7.0.2" />
<PackageReference Include="Volo.Abp.FluentValidation" Version="7.0.2" />
<PackageReference Include="SJZY.UserCenter.HttpApi" Version="3.1.8.17-alpha" />

View File

@ -0,0 +1,128 @@
using System.Text;
using BSI.Stack.Rabbit;
using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.Track.Application.Contract.Dtos.TrackPush;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services;
namespace SJZY.Track.Application.Services.Broadcasting;
/// <summary>
/// 广播服务
/// </summary>
public class BroadcastingService : IBroadcastingService
{
private readonly RabbitClientBus _rabbitClientBus;
public BroadcastingService(RabbitClientBus rabbitClientBus)
{
_rabbitClientBus = rabbitClientBus;
}
/// <summary>
/// 轨迹变更广播
/// </summary>
/// <param name="eto"></param>
/// <param name="subSystemCode">子系统代码</param>
/// <param name="trackStatusCode">轨迹状态代码</param>
/// <returns></returns>
public void TrackChangePublicTopic(TrackUpsertEvent eto, string subSystemCode, string trackStatusCode)
{
var data = new TrackChangeEto
{
EstimatedDeliveryDate = eto.EstimatedDeliveryDate,
TrackPlacePostcode = eto.TrackPlacePostcode,
TrackPlaceNation = eto.TrackPlaceNation,
TrackPlaceNationId = eto.TrackPlaceNationId,
TrackPlaceProvince = eto.TrackPlaceProvince,
TrackPlaceProvinceId = eto.TrackPlaceProvinceId,
TrackPlaceCity = eto.TrackPlaceCity,
TrackPlaceCityId = eto.TrackPlaceCityId,
Files = eto.Files,
Remarks = eto.Remarks,
Source = eto.Source,
IsVisibility = eto.IsVisibility,
TrackTimeZone = eto.TrackTimeZone,
TrackTime = eto.TrackTime,
TrackPlace = eto.TrackPlace,
TrackStatusCode = trackStatusCode,
OrderNo = eto.OrderIdentifiers.First().OrderNo,
EnStatus = eto.EnStatus,
Content = eto.Content,
TrackCode = eto.TrackCode,
OperationMan = eto.OperationMan,
OrderIdentifiers = eto.OrderIdentifiers,
CustomId = eto.CustomId,
SubSystemId = eto.SubSystemId,
Extra = eto.Extra
};
if (eto.IsAdd)
data.Option = 1;
else
data.Option = 3;
_rabbitClientBus.Publish(GlobalConst.TrackChangeExchange, $"TrackChange.{subSystemCode}",
Encoding.UTF8.GetBytes(data.ToJson()));
}
public void TrackChangePublicTopic(TrackDeleteEvent eto, OrderIdentifier[] orderIdentifiers, string trackCode, string trackStatusCode, string subSystemCode)
{
var data = new TrackChangeEto
{
OrderIdentifiers = orderIdentifiers,
TrackStatusCode = trackStatusCode,
TrackCode = trackCode,
OperationMan = eto.OperationMan,
SubSystemId = eto.SubSystemId,
Option = 2
};
_rabbitClientBus.Publish(GlobalConst.TrackChangeExchange, $"TrackChange.{subSystemCode}",
Encoding.UTF8.GetBytes(data.ToJson()));
}
public void TrackChangePublicTopic(TrackChangeEto eto, string subSystemCode)
{
eto.Option = 2;
_rabbitClientBus.Publish(GlobalConst.TrackChangeExchange, $"TrackChange.{subSystemCode}",
Encoding.UTF8.GetBytes(eto.ToJson()));
}
/// <summary>
/// 轨迹推送完成广播
/// </summary>
/// <param name="log"></param>
/// <returns></returns>
public void TrackPushCompletedPublicTopic(TrackPushRecordDto log)
{
var data = new TrackPushCompletedEvent
{
RequestId = log.RequestId,
IsSuccess = log.IsSuccess,
Message = log.Message,
OperationMan = log.OperationMan,
PushTime = log.PushTime,
OptionType = log.OptionType,
IsManualPush = log.IsManualPush, //操作人不为系统用户,则认为是仍推送
PushContent = log.PushContent,
OrderNo = log.OrderNo,
TrackStatus = log.TrackStatus,
TrackContent = log.TrackContent,
TrackTimeZone = log.TrackTimeZone,
TrackTime = log.TrackTime,
TrackPlace = log.TrackPlace,
TrackCode = log.TrackCode,
Extra = log.Extra
};
_rabbitClientBus.Publish(GlobalConst.TrackPushExchange, $"TrackPush.{log.SubSystemCode}",
Encoding.UTF8.GetBytes(data.ToJson()));
}
public void TrackPushFailedPubilcTopic(TrackPushRecordDto log)
{
//暂时使用推送完成模型,后续可能会出现有区分度的模型
TrackPushCompletedPublicTopic(log);
}
}

View File

@ -76,7 +76,7 @@ public partial class OrderService : ApplicationService, IOrderService
order.OrderFlights = ObjectMapper.Map<List<OrderFlightInfo>, List<OrderFlightEntity>>(input.FlightInfos);
order.OrderPullStatus = BuiltOrderPullStatus(input.PullConfigInfos);
order.OrderPlatformNumbers = BuiltOrderPlatformNumbers(input.PullConfigInfos);
order.TemplateCodes = string.Join(',', input.TemplateCodes);
order.TemplateCodes = input.TemplateCodes == null ? null : string.Join(',', input.TemplateCodes);
var orderId = await _orderRepository.CreateOrderAsync(order);
@ -131,7 +131,7 @@ public partial class OrderService : ApplicationService, IOrderService
order.OrderFlights = ObjectMapper.Map<List<OrderFlightInfo>, List<OrderFlightEntity>>(input.FlightInfos);
order.OrderPullStatus = BuiltOrderPullStatus(input.PullConfigInfos);
order.OrderPlatformNumbers = BuiltOrderPlatformNumbers(input.PullConfigInfos);
order.TemplateCodes = string.Join(',', input.TemplateCodes);
order.TemplateCodes = input.TemplateCodes == null ? null : string.Join(',', input.TemplateCodes);
await _orderRepository.UpdateOrderAsync(order);
}

View File

@ -1,10 +1,13 @@
using DotNetCore.CAP;
using SJZY.Expand.ABP.Core.Common;
using SJZY.Track.Application.Contract.Dtos.ProvideServices;
using SJZY.Track.Application.Contract.Dtos.TrackOrder;
using SJZY.Track.Application.Contract.Dtos.TrackPush;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services.ProvideServices;
using SJZY.Track.Application.Helpers;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Aggregates.TrackPushAggregate;
using SJZY.Track.Domain.Dtos.TrackPush;
using SJZY.Track.Domain.Queries;
@ -26,12 +29,14 @@ public class TrackPushService : ApplicationService, ITrackPushService
private readonly IHttpClientProxy<IUserBaseInfoService> _requestCurrentUserService;
private readonly ITrackPushConfigRepository _trackPushConfigRepository;
private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
public TrackPushService(IOrderRepository orderRepository
, ICapPublisher capPublisher
, ITrackPushRecordEsRepository trackPushRecordEsRepository
, IHttpClientProxy<IUserBaseInfoService> requestCurrentUserService
, ITrackPushConfigRepository trackPushConfigRepository, IProvideQueries provideQueries)
, ITrackPushConfigRepository trackPushConfigRepository, IProvideQueries provideQueries,
ITrackDetailsEsRepository trackDetailsEsRepository)
{
_orderRepository = orderRepository;
_capPublisher = capPublisher;
@ -39,6 +44,7 @@ public class TrackPushService : ApplicationService, ITrackPushService
_requestCurrentUserService = requestCurrentUserService;
_trackPushConfigRepository = trackPushConfigRepository;
_provideQueries = provideQueries;
_trackDetailsEsRepository = trackDetailsEsRepository;
}
/// <summary>
@ -47,7 +53,8 @@ public class TrackPushService : ApplicationService, ITrackPushService
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Track/Push")]
public async Task<string> PushTrackAsync(PushTrackInput input)
[CustomerResult]
public async Task<UnifyResultDto> PushTrackAsync(PushTrackInput input)
{
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空
@ -74,23 +81,92 @@ public class TrackPushService : ApplicationService, ITrackPushService
var name = userInfo.Data.UserName;
foreach (var group in orders.GroupBy(x => x.CustomId))
//查出需要推送的轨迹
var tracks = await _trackDetailsEsRepository
.GetListAsync(input.SubSystemId, input.OrderType, input.OrderNos,
new List<string>
{
input.TrackCode
});
if (tracks.Count == 0)
{
throw new BusinessException(message: "没有需要推送的轨迹");
}
//存在比较字段,需要做过滤处理
if (!string.IsNullOrWhiteSpace(input.CompareFields))
{
//根据输入值来进行过滤,比如选取code1,批次1的轨迹,可能会有多条,下面使用创建时间最新的一条
tracks = tracks.Where(x =>
{
if (string.IsNullOrWhiteSpace(x.Extra))
{
throw new BusinessException(message: $"当前轨迹不存在扩展信息,无法利用扩展字段:{input.CompareFields}");
}
var extraDict = GetExtraDictForTextJson(x.Extra);
if (extraDict == null || !extraDict.TryGetValue(input.CompareFields, out var compareValue))
{
throw new BusinessException(message: $"请检查拓展字段,为什么找不到比较字段:{input.CompareFields}");
}
//补充轨迹内容
return TextJsonHelper.EqJsonElementAndObject(compareValue, input.CompareValue);
}).ToList();
}
//不同单可能会有不同的轨迹内容,先推送只推送最新的一条
foreach (var order in orders)
{
//找出每个单的某个code的最新轨迹内容
var track = tracks.Where(x => x.OrderId == order.Id)
.OrderByDescending(x => x.CreateTime)
.FirstOrDefault();
if (track == null) continue;
var trackPushEvent = new TrackPushEvent
{
OptionType = OptionType.新增,
SubSystemId = input.SubSystemId,
SubSystemCode = subSystemCode,
CustomId = group.Key,
OrderIds = group.Select(x => x.Id).ToArray(),
CustomId = order.CustomId,
OrderIds = new[] { order.Id },
TrackCode = input.TrackCode,
OperationMan = name,
SystemPushInfo = new SystemPushStruct()
{
TrackStatus = track.TrackStatus,
TrackContent = track.TrackContent,
TrackPlace = track.TrackPlace,
TrackTime = track.TrackTime,
TrackTimeZone = track.TrackTimeZone,
CompareFields = input.CompareFields,
Extra = track.Extra
}
};
await _capPublisher.PublishAsync(GlobalConst.TrackManualPushSubscribe, trackPushEvent);
}
return $"即将推送轨迹代码{input.TrackCode}给{orders.Length}个订单";
// foreach (var group in orders.GroupBy(x => x.CustomId))
// {
// var trackPushEvent = new TrackPushEvent
// {
// OptionType = OptionType.新增,
// SubSystemId = input.SubSystemId,
// SubSystemCode = subSystemCode,
// CustomId = group.Key,
// OrderIds = group.Select(x => x.Id).ToArray(),
// TrackCode = input.TrackCode,
// OperationMan = name,
// SystemPushInfo = new SystemPushStruct()
// };
//
// await _capPublisher.PublishAsync(GlobalConst.TrackManualPushSubscribe, trackPushEvent);
// }
return new UnifyResultDto(true, $"即将推送轨迹代码{input.TrackCode}给{orders.Length}个订单");
}
@ -101,13 +177,14 @@ public class TrackPushService : ApplicationService, ITrackPushService
/// <param name="customerId"></param>
/// <returns></returns>
[HttpGet("Track/GetTrackPushContentList")]
public async Task<List<TrackPushContentDto>> GetTrackPushContentListAsync(int subSystemId, int customerId)
[CustomerResult]
public async Task<UnifyResultDto<List<TrackPushContentDto>>> GetTrackPushContentListAsync(int subSystemId, int customerId)
{
var list = await _trackPushConfigRepository.GetTrackPushContentListAsync(subSystemId, customerId);
var result = ObjectMapper.Map<List<GetTrackPushContentListVo>, List<TrackPushContentDto>>(list);
return result;
return new UnifyResultDto<List<TrackPushContentDto>>(true, result, "操作成功");
}
/// <summary>
@ -116,7 +193,8 @@ public class TrackPushService : ApplicationService, ITrackPushService
/// <returns></returns>
/// <param name="input"></param>
[HttpGet("Track/GetTrackPushRecords")]
public async Task<List<TrackPushRecordOutput>> GetTrackPushRecordsAsync([FromQuery] OrderNoInfo input)
[CustomerResult]
public async Task<UnifyResultDto<List<TrackPushRecordOutput>>> GetTrackPushRecordsAsync([FromQuery] OrderNoInfo input)
{
var orderId = await _orderRepository.GetFirstAsync(x =>
x.SubSystemId == input.SubSystemId && x.OrderType == input.OrderType && input.OrderNo == x.OrderNo,
@ -128,6 +206,14 @@ public class TrackPushService : ApplicationService, ITrackPushService
var records = await _trackPushRecordEsRepository.GetTrackPushRecordsAsync(orderId);
return ObjectMapper.Map<List<TrackPushRecordIndex>, List<TrackPushRecordOutput>>(records);
var res = ObjectMapper.Map<List<TrackPushRecordIndex>, List<TrackPushRecordOutput>>(records);
return new UnifyResultDto<List<TrackPushRecordOutput>>(true, res, "操作成功");
}
private Dictionary<string, object?>? GetExtraDictForTextJson(string? extraStr)
{
return string.IsNullOrWhiteSpace(extraStr)
? null
: System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object?>>(extraStr);
}
}

View File

@ -1,5 +1,9 @@
using Microsoft.AspNetCore.Http;
using SJZY.Track.Application.Contract.Dtos.ProvideServices;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Aggregates.TrackTemplateAggregate;
using SJZY.Track.Domain.Dtos.TrackTemplate;
using SJZY.Track.Domain.Queries;
namespace SJZY.Track.Application.Services.ProvideServices;
@ -9,9 +13,12 @@ namespace SJZY.Track.Application.Services.ProvideServices;
public partial class TrackService
{
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly ITrackTemplateRepository _trackTemplateRepository;
private readonly IHttpContextAccessor _contextAccessor;
private readonly IVSystemQueries _vSystemQueries;
/// <summary>
/// 查询轨迹
/// 查询轨迹(时区补正)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
@ -24,9 +31,12 @@ public partial class TrackService
var details = await _trackDetailsEsRepository.GetListAsync(input.SubSystemId, input.OrderType, input.OrderNos);
//内部子系统不过滤外部轨迹
var isFilterExternal = await HasFilterExternalTrack();
// 是否对外
var trackCodes = details.Select(x => x.TrackCode)
.Where(x => x != GlobalConst.EmptyString)
.WhereIf(isFilterExternal, x => x != GlobalConst.EmptyString)
.Distinct()
.ToList();
@ -51,6 +61,7 @@ public partial class TrackService
item.TrackDetails = trackDetails.Select(x => new TrackDetail
{
EsId = x.Id,
Code = x.TrackCode,
Status = x.TrackStatus,
Content = x.TrackContent,
@ -63,13 +74,13 @@ public partial class TrackService
? null
: input.TimeZone ?? 8,
IsDisplay = trackContentDic.TryGetValue(x.TrackCode, out var isExternal) && isExternal,
IsVisibility = x.IsVisibility,
IsVisibility = x.IsVisibility,
Source = x.Source,
Remarks = x.Remarks,
Files = x.Files?.Select(y => new FileItemDto
{
FileName = y.FileName,
OssKey = y.OssKey
FileName = y.FileName,
OssKey = y.OssKey
}).ToList(),
TrackPlaceCity = x.TrackPlaceCity,
TrackPlaceCityId = x.TrackPlaceCityId,
@ -87,25 +98,308 @@ public partial class TrackService
}
// 排序
TracksOrdered(res, input);
var sortedList = await TracksOrdered(res, input, input.SortMethod);
return new UnifyResultDto<List<GetTrackOutput>>(true, res, "操作成功");
return new UnifyResultDto<List<GetTrackOutput>>(true, sortedList, "操作成功");
}
private void TracksOrdered(List<GetTrackOutput> list, GetTrackInput input)
/// <summary>
/// 查询轨迹(原样返回)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Track/QueryV2")]
public async Task<UnifyResultDto<List<GetTrackOutput>>> QueryV2Async(GetTrackInputV2 input)
{
switch (input.SortMethod)
var res = new List<GetTrackOutput>();
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空
var details = await _trackDetailsEsRepository.GetListAsync(input.SubSystemId, input.OrderType, input.OrderNos);
//内部子系统不过滤外部轨迹
var isFilterExternal = await HasFilterExternalTrack();
// 是否对外
var trackCodes = details.Select(x => x.TrackCode)
.WhereIf(isFilterExternal, x => x != GlobalConst.EmptyString)
.Distinct()
.ToList();
var trackContentDic = new Dictionary<string, bool>();
if (!trackCodes.IsNullOrEmpty())
{
var trackContents = await _trackContentRepository.GetListAsync(x =>
x.SubSystemId == input.SubSystemId && trackCodes.Contains(x.Code),
x => new
{
x.Code,
x.IsExternal
});
trackContents.ForEach(x => trackContentDic.Add(x.Code, x.IsExternal));
}
foreach (var group in details.GroupBy(x => x.OrderId))
{
var item = new GetTrackOutput();
var trackDetails = group.ToList();
item.OrderNo = trackDetails.First().OrderNo;
item.TrackDetails = trackDetails.Select(x => new TrackDetail
{
EsId = x.Id,
Code = x.TrackCode,
Status = x.TrackStatus,
Content = x.TrackContent,
OriginContent = x.OriginTrackContent,
Location = x.TrackPlace,
Time = x.TrackTime,
TimeZone = x.TrackTimeZone,
IsDisplay = trackContentDic.TryGetValue(x.TrackCode, out var isExternal) && isExternal,
IsVisibility = x.IsVisibility,
Source = x.Source,
Remarks = x.Remarks,
Files = x.Files?.Select(y => new FileItemDto
{
FileName = y.FileName,
OssKey = y.OssKey
}).ToList(),
TrackPlaceCity = x.TrackPlaceCity,
TrackPlaceCityId = x.TrackPlaceCityId,
TrackPlaceProvince = x.TrackPlaceProvince,
TrackPlaceProvinceId = x.TrackPlaceProvinceId,
TrackPlaceNation = x.TrackPlaceNation,
TrackPlaceNationId = x.TrackPlaceNationId,
TrackPlacePostcode = x.TrackPlacePostcode,
DestinationTimeZone = x.DestinationTimeZone,
EstimatedDeliveryDate = x.EstimatedDeliveryDate,
Extra = x.Extra
}).ToList();
res.Add(item);
}
// 排序
var sortedList = await TracksOrdered(res, input, input.SortMethod);
return new UnifyResultDto<List<GetTrackOutput>>(true, sortedList, "操作成功");
}
//内部系统展示所有轨迹,外部系统则要求过滤外部轨迹
private async Task<bool> HasFilterExternalTrack()
{
var isFilter = true;
if (_contextAccessor.HttpContext!.Request.Headers.TryGetValue(GlobalConst.HTTP_HEADER_SUBSYSTEMID, out var subsystemIdStr))
{
if (int.TryParse(subsystemIdStr, out var subsystemId))
{
var systemEntity = await _vSystemQueries.GetFirstAsync(x => x.Id == subsystemId);
isFilter = systemEntity?.SystemProperty != 1;
}
}
return isFilter;
}
private async Task<List<GetTrackOutput>> TracksOrdered(List<GetTrackOutput> list, OrderNosInfo orderNosInfo, SortMethod? sortMethod)
{
switch (sortMethod)
{
case SortMethod.ByTrackTime:
case SortMethod.ByTemplateAndTrackTime:
// tips:quick-sort 直接修改原list
list.ForEach(item => item.TrackDetails.Sort((a, b) => a.Time.CompareTo(b.Time)));
break;
case null:
case SortMethod.ByTrackTimeDesc:
case SortMethod.ByTemplateAndTrackTimeDesc:
list.ForEach(item => item.TrackDetails.Sort((a, b) => b.Time.CompareTo(a.Time)));
break;
default:
throw new ArgumentOutOfRangeException(nameof(input.SortMethod), input.SortMethod, "不支持的排序方式");
throw new ArgumentOutOfRangeException(nameof(sortMethod), sortMethod, "不支持的排序方式");
}
if (sortMethod != SortMethod.ByTemplateAndTrackTime
&& sortMethod != SortMethod.ByTemplateAndTrackTimeDesc)
{
return list;
}
//查出订单模板
var templates = await _trackTemplateRepository.GetTrackTemplateListAsync(new GetTrackTemplateListInputVo
{
SubSystemId = orderNosInfo.SubSystemId
});
if (templates.Count == 0)
{
return list;
}
//查出订单和模板的关系,构建订单号和模板code的关系
var orderNoTemplateDict = (await _orderRepository
.GetOrderTemplateAsync(orderNosInfo.SubSystemId, orderNosInfo.OrderType, orderNosInfo.OrderNos))
.ToDictionary(x => x.OrderNo, x => x.TemplateCodes?.Split(",").ToHashSet());
//查询出订单和模板的关联关系,保证该订单使用自己的模板排序
foreach (var getTrackOutput in list)
{
//查看该订单具有的模板
var curOrderNoTemplateCodes = orderNoTemplateDict[getTrackOutput.OrderNo];
if (curOrderNoTemplateCodes == null || curOrderNoTemplateCodes.Count == 0) continue;
//选出这个订单号所具有的模板
var tipTemplates = templates.Where(x => curOrderNoTemplateCodes.Contains(x.TemplateCode)).ToList();
if (tipTemplates.Count == 0) continue;
getTrackOutput.TrackDetails = SortByTemplatesAsync(getTrackOutput.TrackDetails, tipTemplates, sortMethod == SortMethod.ByTemplateAndTrackTime);
}
return list;
}
/// <summary>
/// 轨迹发生时间(统一utc)排序,相同时间按模板排序,考虑多个模板的情况
/// </summary>
/// <returns></returns>
private static List<TrackDetail> SortByTemplatesAsync(List<TrackDetail> seq, List<TrackTemplateOutputVo> templates, bool isAsc = true)
{
//转化为统一时间
var trackWraps = seq.Select(x => new TrackWrap(x));
var sortedTracks = (isAsc
? trackWraps.OrderBy(x => x.Time)
: trackWraps.OrderByDescending(x => x.Time)).ToList();
//用于统计时间相同的轨迹
var timeEqTrackDict = new Dictionary<DateTime, List<TrackIdxWarp>>();
for (var i = 0; i < sortedTracks.Count; i++)
{
var trackWrap = sortedTracks[i];
if (timeEqTrackDict.TryGetValue(trackWrap.Time, out var list))
list.Add(new TrackIdxWarp(trackWrap.Track, i));
else
timeEqTrackDict.Add(trackWrap.Time, new List<TrackIdxWarp>
{
new TrackIdxWarp(trackWrap.Track, i)
});
}
//选出需要按模板重排序的轨迹
var timeEqTracks = timeEqTrackDict
.Where(kv => kv.Value.Count > 1)
.ToDictionary(kv => kv.Key, kv => kv.Value);
//初始化为最后一个模板排序
var preSortDict = templates[templates.Count - 1]
.TemplateContents
.OrderBy(x => x.Sort)
.ToDictionary(x => x.Code, x => x.Sort);
for (var i = templates.Count - 2; i >= 0; i--)
{
var curCodeSortDict = templates[i]
.TemplateContents
.ToDictionary(x => x.Code, y => y.Sort);
//没在之前模板出现的code
var noTipPreCodeSortDict = curCodeSortDict
.Where(x => !preSortDict.ContainsKey(x.Key))
.ToDictionary(x => x.Key, y => y.Value);
//插入未命中的模板code
foreach (var (noTipCode, curNoTipCodeSort) in noTipPreCodeSortDict)
{
//初始化插入位置
var insertPos = -1;
foreach (var (preCode, _) in preSortDict)
{
if (curCodeSortDict.TryGetValue(preCode, out var preCodeSort))
{
//未命中的相对于之前模板的偏后,设置插入位置在
if (preCodeSort < curNoTipCodeSort)
{
insertPos = preSortDict[preCode] + 1;
}
}
}
//结束了还没选好位置,未命中的code相对于之前的模板code在当前模板最前面
if (insertPos == -1)
{
foreach (var (c, s) in preSortDict)
preSortDict[c] = s + 1;
preSortDict[noTipCode] = 0;
}
else
{
foreach (var (c, s) in preSortDict.Where(x => x.Value >= insertPos))
preSortDict[c] = s + 1;
preSortDict[noTipCode] = insertPos;
}
}
}
foreach (var trackIdxWarps in timeEqTracks.Values)
{
var curIndex = trackIdxWarps.Min(x => x.Index);
//按模板从小到大排序
var sortByTemplate = (isAsc
? trackIdxWarps
.OrderBy(x => preSortDict.GetValueOrDefault(x.Track.Code, int.MaxValue))
: trackIdxWarps
.OrderByDescending(x => preSortDict.GetValueOrDefault(x.Track.Code, int.MaxValue))
).ToList();
for (var j = 0; j < sortByTemplate.Count; j++)
{
var track = sortByTemplate[j];
//当前索引不对应
if (track.Index != curIndex + j)
{
var temp1 = sortedTracks[track.Index].Track;
var temp2 = sortedTracks[curIndex + j].Track;
sortedTracks[track.Index].Track = temp2;
sortedTracks[curIndex + j].Track = temp1;
trackIdxWarps.First(x => x.Index == curIndex + j).Index = track.Index;
track.Index = curIndex + j;
}
}
}
return sortedTracks.Select(x => x.Track).ToList();
}
private class TrackIdxWarp
{
public TrackDetail Track { get; set; }
public int Index { get; set; }
public TrackIdxWarp(TrackDetail track, int index)
{
Track = track;
Index = index;
}
}
private class TrackWrap
{
public TrackDetail Track { get; set; }
public DateTime Time { get; set; }
public TrackWrap(TrackDetail track)
{
Track = track;
var timeZone = track.TimeZone ?? GlobalConst.NoneTrackTimeZone;
Time = Math.Abs(timeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.Time
: track.Time.Subtract(TimeSpan.FromHours(timeZone));
}
}
}

View File

@ -1,11 +1,15 @@
using DotNetCore.CAP;
using Microsoft.AspNetCore.Http;
using SJZY.Track.Application.Contract.Dtos.ProvideServices;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services.ProvideServices;
using SJZY.Track.Application.Helpers;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackContentAggregate;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Aggregates.TrackTemplateAggregate;
using SJZY.Track.Domain.Enums;
using SJZY.Track.Domain.Queries;
using SJZY.UserCenter.HttpApi.Services;
using SJZY.UserCenter.HttpApi.Services.User;
using Volo.Abp.Http.Client.DynamicProxying;
@ -24,149 +28,25 @@ public partial class TrackService : ApplicationService, ITrackService
private readonly IHttpClientProxy<IUserBaseInfoService> _requestCurrentUserService;
private readonly ITrackContentRepository _trackContentRepository;
public TrackService(IOrderRepository orderRepository, ICapPublisher capPublisher,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackContentRepository trackContentRepository,
IHttpClientProxy<IUserBaseInfoService> requestCurrentUserService)
public TrackService(IOrderRepository orderRepository,
ICapPublisher capPublisher,
ITrackDetailsEsRepository trackDetailsEsRepository,
ITrackContentRepository trackContentRepository,
IHttpClientProxy<IUserBaseInfoService> requestCurrentUserService,
ITrackTemplateRepository trackTemplateRepository,
IHttpContextAccessor contextAccessor,
IVSystemQueries vSystemQueries)
{
_orderRepository = orderRepository;
_capPublisher = capPublisher;
_trackContentRepository = trackContentRepository;
_requestCurrentUserService = requestCurrentUserService;
_vSystemQueries = vSystemQueries;
_contextAccessor = contextAccessor;
_trackTemplateRepository = trackTemplateRepository;
_trackDetailsEsRepository = trackDetailsEsRepository;
}
/// <summary>
/// 新增、更新轨迹(存在就更新)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/Upsert")]
public async Task<UnifyResultDto> UpsertTrackAsync(UpsertTrackInput input)
{
var order = await _orderRepository.GetFirstAsync(x =>
x.SubSystemId == input.SubSystemId && x.OrderType == input.OrderType && x.OrderNo == input.OrderNo,
x => new
{
x.Id,
x.CustomId
});
if (order == default)
{
throw new BusinessException(message: $"订单:{input.OrderNo}不存在");
}
if (!GlobalConst.SupportTrackSources.Contains(input.Source))
{
throw new BusinessException(message: "轨迹来源是必须的");
}
//要求时间在当前时间的前后100年之间内
bool ValidateTrackTime(DateTime? d) => (d != null && d < DateTime.Now.AddYears(-100) || d > DateTime.Now.AddYears(100));
if (ValidateTrackTime(input.TrackTime) || ValidateTrackTime(input.NewTrackTime) || ValidateTrackTime(input.EstimatedDeliveryDate))
{
throw new BusinessException(message: "轨迹时间不合法");
}
if ((input.EstimatedDeliveryDate.HasValue == false && input.DestinationTimeZone.HasValue) ||
(input.EstimatedDeliveryDate.HasValue && input.DestinationTimeZone.HasValue == false))
{
throw new BusinessException(message: "【预计签收时间】和【签收地时区】必须同时输入");
}
//当修改时,如果为来源为外部提供商则不允许修改
if (!input.IsAdd && input.Source == (int) TrackSourceEnum.ExternalServiceProvide)
{
throw new BusinessException(message: "来源为外部服务商轨迹不允许修改");
}
var trackContent = await _trackContentRepository.GetByCodeAsync(input.SubSystemId, input.TrackCode);
if (trackContent == default)
{
throw new BusinessException(message: $"轨迹代码:{input.TrackCode}轨迹内容不存在");
}
//指示是否为手动操作
var manualOperation = input.Source == (int) TrackSourceEnum.Artificial;
//不允许手动创建
if (manualOperation && input.IsAdd && !trackContent.IsAllowManuallyCreate)
{
throw new BusinessException(message: $"轨迹代码:{input.TrackCode}不允许手动创建");
}
//不允许手动更新
if (manualOperation && !input.IsAdd && !trackContent.IsAllowManuallyUpdate)
{
throw new BusinessException(message: $"轨迹代码:{input.TrackCode}不允许手动更新");
}
var tracks = (await _trackDetailsEsRepository.GetListAsync(new[] { order.Id }, input.TrackCode, true))
.Where(x => x.TrackTime == input.TrackTime)
.ToList();
//当为更新操作时发现更新的数目为0进行重试操作
if (!input.IsAdd && tracks.Count == 0)
{
var curRetryCount = 0;
var success = false;
while (!success && curRetryCount < GlobalConst.MaxUpdateTrackRetryCount)
{
try
{
tracks = (await _trackDetailsEsRepository.GetListAsync(new[] { order.Id }, input.TrackCode, true))
.Where(x => x.TrackTime == input.TrackTime)
.ToList();
if (tracks.Count > 0)
success = true;
await Task.Delay(300);
}
finally
{
curRetryCount++;
}
}
if (tracks.Count == 0)
{
throw new BusinessException(message: $"订单:{input.OrderNo},轨迹代码:{input.TrackCode}没有要更新的记录");
}
}
//更新轨迹,同一时间存在多条轨迹记录,理由:不清晰到底要修改哪一条或是全部修改
if (!input.IsAdd && tracks.Count > 1)
{
throw new BusinessException(message: $"订单:{input.OrderNo},轨迹代码:{input.TrackCode}存在多条记录,不允许修改");
}
var userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync(input.OptionUserId);
if (userInfo.Code != GlobalConst.SuccessCode || userInfo.Data is null)
{
//TODO: log || ex
throw new BusinessException(message: $"调用用户中心,获取用户信息失败:{userInfo.Msg}");
}
var name = userInfo.Data.UserName;
// 新增/更新轨迹
var @event = ObjectMapper.Map<UpsertTrackInput, TrackUpsertEvent>(input);
@event.SubSystemId = input.SubSystemId;
@event.CustomId = order.CustomId;
@event.OrderIdentifiers = new[]
{
new OrderIdentifier
{
OrderId = order.Id,
OrderNo = input.OrderNo,
OrderType = input.OrderType,
}
};
@event.OperationMan = name;
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
return new UnifyResultDto(true, true);
}
/// <summary>
/// 删除轨迹
/// </summary>
@ -193,29 +73,69 @@ public partial class TrackService : ApplicationService, ITrackService
var name = userInfo.Data.UserName;
//删除轨迹约束,只要其中有一条来源是外部的就立即返回错误
var tracks = await _trackDetailsEsRepository
.GetListAsync(new[] { orderId },
input.TrackIdentifiers.Select(x => x.TrackCode).ToList(),
TrackSourceEnum.ExternalServiceProvide);
var tracks = new List<TrackDetailsIndex>();
var existsExternal = false;
var esIds = input.EsIds?.Distinct().ToArray();
//优先使用esId
if (esIds != null && esIds.Length > 0)
{
tracks = await _trackDetailsEsRepository.GetDataByIds(esIds);
existsExternal = tracks.Any(x => x.Source == (int) TrackSourceEnum.ExternalServiceProvide);
}
else
{
var trackCodes = input.TrackIdentifiers?.Select(x => x.TrackCode).ToList();
var trackIds = input.TrackIdentifiers?.Select(y => $"{y.TrackCode}{y.TrackTime}").ToHashSet();
//没有任何需要操作的轨迹
if (trackIds == null || trackIds.Count == 0)
{
throw new BusinessException(message: "没有任何轨迹要删除,请检查输入参数");
}
//当要删除的轨迹标识出现交叉则不允许删除
var intersect = tracks.Select(x => $"{x.TrackCode}{x.TrackTime}")
.Intersect(input.TrackIdentifiers.Select(y => $"{y.TrackCode}{y.TrackTime}")).ToList();
if (intersect.Count > 0)
//删除轨迹约束,只要其中有一条来源是外部的就立即返回错误
tracks = await _trackDetailsEsRepository.GetListAsync(new[] { orderId }, trackCodes);
//检查删除的轨迹中是否包含外部轨迹
existsExternal = tracks
.Where(x => x.Source == (int) TrackSourceEnum.ExternalServiceProvide)
.WhereIf(!input.CompareFields.IsNullOrWhiteSpace(), y =>
{
if (string.IsNullOrWhiteSpace(y.Extra))
{
throw new BusinessException(message: "轨迹extra为空无法使用比较字段比较,请检查轨迹");
}
var extraDict = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(y.Extra);
if (extraDict!.TryGetValue(input.CompareFields!, out var compareValueJsonElement))
return TextJsonHelper.EqJsonElementAndObject(compareValueJsonElement, input.CompareValue);
return false;
})
.Select(x => $"{x.TrackCode}{x.TrackTime}")
.Any(x => trackIds.Contains(x));
}
if (existsExternal)
{
throw new BusinessException(message: "存在来源于外部服务商的轨迹,不允许删除");
}
// 删除轨迹
var @event = new TrackDeleteEvent
if (tracks.Count == 0)
{
OrderIds = new[] { orderId },
TrackIdentifiers = input.TrackIdentifiers,
SubSystemId = input.SubSystemId,
OperationMan = name
};
await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe, @event);
throw new BusinessException(message: "没有任何轨迹要删除,请检查输入参数");
}
// 删除轨迹
await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe,
new TrackDeleteEvent
{
EsIds = input.EsIds?.ToArray(),
OrderIds = new[] { orderId },
CompareFields = input.CompareFields,
CompareValue = input.CompareValue,
TrackIdentifiers = input.TrackIdentifiers,
SubSystemId = input.SubSystemId,
OperationMan = name
});
return new UnifyResultDto(true, true);
}
@ -297,7 +217,21 @@ public partial class TrackService : ApplicationService, ITrackService
TrackSourceEnum.ExternalServiceProvide);
//当要删除的轨迹标识出现交叉则不允许删除
var intersect = tracks.Select(x => $"{x.TrackCode}{x.TrackTime}")
var intersect = tracks
.WhereIf(!input.CompareFields.IsNullOrWhiteSpace(), y =>
{
if (string.IsNullOrWhiteSpace(y.Extra))
{
throw new BusinessException(message: "轨迹extra为空无法使用比较字段比较,请检查轨迹");
}
var extraDict = TextJsonHelper.DeserializeToDictionary(y.Extra);
if (extraDict!.TryGetValue(input.CompareFields!, out var compareValueJsonElement))
return TextJsonHelper.EqJsonElementAndObject(compareValueJsonElement, input.CompareValue);
return false;
})
.Select(x => $"{x.TrackCode}{x.TrackTime}")
.Intersect(input.TrackIdentifiers.Select(y => $"{y.TrackCode}{y.TrackTime}")).ToList();
if (intersect.Count > 0)
{
@ -309,6 +243,8 @@ public partial class TrackService : ApplicationService, ITrackService
{
OrderIds = orderIds.ToArray(),
TrackIdentifiers = input.TrackIdentifiers,
CompareFields = input.CompareFields,
CompareValue = input.CompareValue,
SubSystemId = input.SubSystemId,
OperationMan = userInfo.Data.UserName,
};
@ -317,6 +253,76 @@ public partial class TrackService : ApplicationService, ITrackService
return new UnifyResultDto(true, true);
}
/// <summary>
/// 新增、更新轨迹(存在就更新)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/Upsert")]
public async Task<UnifyResultDto> UpsertTrackAsync(UpsertTrackInput input)
{
var order = await _orderRepository.GetFirstAsync(x =>
x.SubSystemId == input.SubSystemId
&& x.OrderType == input.OrderType
&& x.OrderNo == input.OrderNo,
x => new
{
x.Id,
x.OrderNo,
x.CustomId
});
if (order == default)
{
throw new BusinessException(message: $"订单:{input.OrderNo}不存在");
}
// 输入参数的静态规则检查
// 轨迹内容上的约束检查
// 更新轨迹时是否可以匹配到轨迹的检查
await TrackUpsertValidation(input.IsAdd,
input.SubSystemId,
order.Id,
order.OrderNo,
input.TrackCode,
input.Source,
input.TrackTime,
input.NewTrackTime,
input.EstimatedDeliveryDate,
input.DestinationTimeZone,
input.Extra,
input.CompareFields);
var userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync();
if (userInfo.Code != GlobalConst.SuccessCode || userInfo.Data is null)
{
//todo:保留之前的方式(FBA在用) 后续可能去除
userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync(input.OptionUserId);
if (userInfo.Code != GlobalConst.SuccessCode || userInfo.Data is null)
throw new BusinessException(message: $"调用用户中心,获取用户信息失败:{userInfo.Msg}");
}
var name = userInfo.Data.UserName;
// 新增/更新轨迹
var @event = ObjectMapper.Map<UpsertTrackInput, TrackUpsertEvent>(input);
@event.SubSystemId = input.SubSystemId;
@event.CustomId = order.CustomId;
@event.OrderIdentifiers = new[]
{
new OrderIdentifier
{
OrderId = order.Id,
OrderNo = input.OrderNo,
OrderType = input.OrderType,
}
};
@event.OperationMan = name;
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
return new UnifyResultDto(true, true);
}
/// <summary>
/// 批量新增、更新轨迹
/// </summary>
@ -328,8 +334,9 @@ public partial class TrackService : ApplicationService, ITrackService
{
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空
var orders = await _orderRepository.GetListAsync(x =>
x.SubSystemId == input.SubSystemId && x.OrderType == input.OrderType &&
input.OrderNos.Contains(x.OrderNo),
x.SubSystemId == input.SubSystemId
&& x.OrderType == input.OrderType
&& input.OrderNos.Contains(x.OrderNo),
x => new
{
x.Id,
@ -342,93 +349,26 @@ public partial class TrackService : ApplicationService, ITrackService
throw new BusinessException(message: "订单一个都不存在");
}
if (!GlobalConst.SupportTrackSources.Contains(input.Source))
{
throw new BusinessException(message: "轨迹来源是必须的");
}
//当修改时,如果为来源为外部提供商则不允许修改
if (!input.IsAdd && input.Source == (int) TrackSourceEnum.ExternalServiceProvide)
{
throw new BusinessException(message: "来源为外部服务商轨迹不允许修改");
}
// 校验主体:批量新增只选取第一条订单进行校验
//【新增】存在指定轨迹的内容,进行更新覆盖(只处理一条的情况,并且决定权交给用户),不存在直接插入即可
//【更新】存在指定轨迹的内容(匹配到多条则不处理)
var order = orders.First();
//要求时间在当前时间的前后100年之间内
bool ValidateTrackTime(DateTime? d) => (d != null && d < DateTime.Now.AddYears(-100) || d > DateTime.Now.AddYears(100));
if (ValidateTrackTime(input.TrackTime) || ValidateTrackTime(input.NewTrackTime) || ValidateTrackTime(input.EstimatedDeliveryDate))
{
throw new BusinessException(message: "轨迹时间不合法");
}
if ((input.EstimatedDeliveryDate.HasValue == false && input.DestinationTimeZone.HasValue) ||
(input.EstimatedDeliveryDate.HasValue && input.DestinationTimeZone.HasValue == false))
{
throw new BusinessException(message: "【预计签收时间】和【签收地时区】必须同时输入");
}
var trackContent = await _trackContentRepository.GetByCodeAsync(input.SubSystemId, input.TrackCode);
if (trackContent == default)
{
throw new BusinessException(message: $"轨迹代码:{input.TrackCode}轨迹内容不存在");
}
//指示是否为手动操作
var manualOperation = input.Source == (int) TrackSourceEnum.Artificial;
//不允许手动创建
if (manualOperation && input.IsAdd && !trackContent.IsAllowManuallyCreate)
{
throw new BusinessException(message: $"轨迹代码:{input.TrackCode}不允许手动创建");
}
//不允许手动更新
if (manualOperation && !input.IsAdd && !trackContent.IsAllowManuallyUpdate)
{
throw new BusinessException(message: $"轨迹代码:{input.TrackCode}不允许手动更新");
}
var tracks = (await _trackDetailsEsRepository.GetListAsync(new[] { order.Id }, input.TrackCode, true))
.Where(x => x.TrackTime == input.TrackTime)
.ToList();
//当为更新操作时发现更新的数目为0进行重试操作
if (!input.IsAdd && tracks.Count == 0)
{
var curRetryCount = 0;
var success = false;
while (!success && curRetryCount < GlobalConst.MaxUpdateTrackRetryCount)
{
try
{
tracks = (await _trackDetailsEsRepository.GetListAsync(new[] { order.Id }, input.TrackCode, true))
.Where(x => x.TrackTime == input.TrackTime)
.ToList();
if (tracks.Count > 0)
success = true;
await Task.Delay(300);
}
finally
{
curRetryCount++;
}
}
if (tracks.Count == 0)
{
throw new BusinessException(message: $"订单:[{string.Join(",", input.OrderNos)}],轨迹代码:{input.TrackCode}没有要更新的记录");
}
}
//更新轨迹,同一时间存在多条轨迹记录,理由:不清晰到底要修改哪一条或是全部修改
if (!input.IsAdd && tracks.Count > 1)
{
throw new BusinessException(message: $"订单:{order.OrderNo},轨迹代码:{input.TrackCode}存在多条记录,不允许修改");
}
// 输入参数的静态规则检查
// 轨迹内容上的约束检查
// 更新轨迹时是否可以匹配到轨迹的检查
await TrackUpsertValidation(input.IsAdd,
input.SubSystemId,
order.Id,
order.OrderNo,
input.TrackCode,
input.Source,
input.TrackTime,
input.NewTrackTime,
input.EstimatedDeliveryDate,
input.DestinationTimeZone,
input.Extra,
input.CompareFields);
// 获取用户name
var userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync();
@ -473,11 +413,168 @@ public partial class TrackService : ApplicationService, ITrackService
TrackPlacePostcode = input.TrackPlacePostcode,
EstimatedDeliveryDate = input.EstimatedDeliveryDate,
DestinationTimeZone = input.DestinationTimeZone,
Extra = input.Extra
CompareFields = input.CompareFields,
Extra = input.Extra,
ForcedPush = input.ForcedPush,
IsToMq = input.IsToMq,
};
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
}
return new UnifyResultDto(true, true);
}
//在轨迹更新主流程之前的前置检查
private async Task TrackUpsertValidation(
bool isAdd,
int subSystemId,
int orderId,
string orderNo,
string trackCode,
int source,
DateTime trackTime,
DateTime? newTrackTime,
DateTime? estimatedDeliveryDate,
float? destinationTimeZone,
string? extra,
string? compareFields
)
{
#region 静态规则检查
// 约束轨迹来源只能选取 1-外部 2-内部 3-手动
if (!GlobalConst.SupportTrackSources.Contains(source))
{
throw new BusinessException(message: "轨迹来源是必须的,1:外部服务商 2:内部系统 3:人工操作");
}
//当修改时,如果为来源为外部提供商则不允许修改
if (!isAdd && source == (int) TrackSourceEnum.ExternalServiceProvide)
{
throw new BusinessException(message: "来源为外部服务商轨迹不允许修改");
}
//要求时间在当前时间的前后100年之间内
bool ValidateTrackTime(DateTime? d) => (d != null && d < DateTime.Now.AddYears(-100) || d > DateTime.Now.AddYears(100));
if (ValidateTrackTime(trackTime) || ValidateTrackTime(newTrackTime) || ValidateTrackTime(estimatedDeliveryDate))
{
throw new BusinessException(message: "轨迹时间不合法");
}
if ((estimatedDeliveryDate.HasValue == false && destinationTimeZone.HasValue) ||
(estimatedDeliveryDate.HasValue && destinationTimeZone.HasValue == false))
{
throw new BusinessException(message: "【预计签收时间】和【签收地时区】必须同时输入");
}
if (!string.IsNullOrWhiteSpace(compareFields) && string.IsNullOrWhiteSpace(extra))
{
throw new BusinessException(message: "当输入了【轨迹对比字段】则轨迹扩展是必须的的");
}
#endregion
#region 轨迹内容中的配置规则检查
var trackContent = await _trackContentRepository.GetByCodeAsync(subSystemId, trackCode);
if (trackContent == default)
{
throw new BusinessException(message: $"轨迹代码:{trackCode}轨迹内容不存在");
}
//指示是否为手动操作
var manualOperation = source == (int) TrackSourceEnum.Artificial;
//不允许手动创建
if (manualOperation && isAdd && !trackContent.IsAllowManuallyCreate)
{
throw new BusinessException(message: $"轨迹代码:{trackCode}不允许手动创建");
}
//不允许手动更新
if (manualOperation && !isAdd && !trackContent.IsAllowManuallyUpdate)
{
throw new BusinessException(message: $"轨迹代码:{trackCode}不允许手动更新");
}
#endregion
//轨迹新增,不允许重复的轨迹提前拦截
if (isAdd)
{
//允许重复,跳过检查
if (trackContent.IsRepeatable) return;
//找到这个订单的这个轨迹code的所有轨迹
var tracks = await QueryAndFilterAsync(orderId, trackCode, trackTime, extra, compareFields);
if (tracks.Count > 0)
throw new BusinessException(message: $"订单:[{orderNo}],轨迹代码:{trackCode}不允许重复创建轨迹");
}
//针对于轨迹更新检查
else
{
//找到这个订单的这个轨迹code的所有轨迹,
var tracks = await RetryQueryTrackAsync(orderId, trackCode, trackTime, extra, compareFields);
//不存在需要更新的轨迹
if (tracks.Count == 0)
throw new BusinessException(message: $"订单:[{orderNo}],轨迹代码:{trackCode}没有要更新的记录");
//更新轨迹,同一时间存在多条轨迹记录,理由:不清晰到底要修改哪一条或是全部修改
if (tracks.Count > 1)
throw new BusinessException(message: $"订单:{orderNo},轨迹代码:{trackCode}存在多条记录,不允许修改");
}
}
// 重试查询轨迹,直到存在符合要求的轨迹,或者超过最大重试次数
private async Task<List<TrackDetailsIndex>> RetryQueryTrackAsync(int orderId, string trackCode, DateTime trackTime, string? extra, string? compareFields)
{
var tracks = await QueryAndFilterAsync(orderId, trackCode, trackTime, extra, compareFields);
if (tracks.Count > 0)
return tracks;
//重试阶段
var curRetryCount = 0;
while (curRetryCount < GlobalConst.MaxUpdateTrackRetryCount) //tips:后续将最大重试次数提出到配置中
{
try
{
tracks = await QueryAndFilterAsync(orderId, trackCode, trackTime, extra, compareFields);
if (tracks.Count > 0)
return tracks;
//指数退避等待
var delay = TimeSpan.FromMilliseconds(Math.Pow(2, curRetryCount) * 150); // 150ms, 300ms, 600ms, ...
await Task.Delay(delay);
}
finally
{
curRetryCount++;
}
}
//超过最大重试次数还没找到
return tracks;
}
//查询并按比较字段过滤轨迹,当比较字段为空时则使用轨迹发生时间过滤
//tips:后续进行投影缩参优化
private async Task<List<TrackDetailsIndex>> QueryAndFilterAsync(int orderId1, string trackCode1, DateTime trackTime1, string? extra1, string? compareFields1)
{
var tracks1 = await _trackDetailsEsRepository.GetListAsync(new[] { orderId1 }, trackCode1, true);
//无比较字段时使用轨迹时间参与过滤(兼容老版本)
if (string.IsNullOrWhiteSpace(compareFields1))
{
return tracks1.Where(x => x.TrackTime == trackTime1).ToList();
}
return tracks1.Where(x =>
{
if (string.IsNullOrWhiteSpace(x.Extra))
throw new BusinessException(message: "轨迹extra为空无法使用比较字段比较,请检查轨迹");
var extraJsonElement = TextJsonHelper.DeserializeToJsonElement(x.Extra);
var inputExtraJsonElement = TextJsonHelper.DeserializeToJsonElement(extra1);
return TextJsonHelper.EqByJsonElementField(extraJsonElement, inputExtraJsonElement, compareFields1);
})
.ToList();
}
}

View File

@ -5,6 +5,7 @@ using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.Expand.AliyunOSS;
using SJZY.Track.Application.Contract.Dtos.ProvideServices;
using SJZY.Track.Application.Contract.Dtos.TrackContents;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Application.Localization;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackContentAggregate;
@ -13,7 +14,7 @@ using SJZY.Track.Domain.Aggregates.TrackFlagAggregate;
using SJZY.Track.Domain.Aggregates.TrackStatusAggregate;
using SJZY.Track.Domain.Aggregates.TrackTemplateAggregate;
using SJZY.Track.Domain.Dtos.TrackContents;
using SJZY.Track.Domain.Dtos.TrackTemplate;
using SJZY.Track.Domain.Enums;
using SJZY.Track.Domain.Queries;
namespace SJZY.Track.Application.Services.TrackContents;
@ -32,6 +33,7 @@ public class TrackContentService : ApplicationService
private readonly IBasicQuery<TrackStatusEntity> _trackStatusBasicQuery;
private readonly IOrderRepository _orderRepository;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly ITrackPushCacheService _trackPushCacheService;
public TrackContentService(
IStringLocalizer<TrackLocalizationResource> stringLocalizer,
@ -40,7 +42,7 @@ public class TrackContentService : ApplicationService
IBasicQuery<TrackFlagEntity> trackFlagBasicQuery,
IOrderRepository orderRepository,
ITrackDetailsEsRepository trackDetailsEsRepository,
ITrackTemplateRepository trackTemplateRepository)
ITrackTemplateRepository trackTemplateRepository, ITrackPushCacheService trackPushCacheService)
{
_stringLocalizer = stringLocalizer;
_trackContentRepository = trackContentRepository;
@ -49,6 +51,7 @@ public class TrackContentService : ApplicationService
_orderRepository = orderRepository;
_trackDetailsEsRepository = trackDetailsEsRepository;
_trackTemplateRepository = trackTemplateRepository;
_trackPushCacheService = trackPushCacheService;
}
/// <summary>
@ -289,9 +292,40 @@ public class TrackContentService : ApplicationService
if (order == default)
throw new BusinessException(message: "订单不存在!");
var res = new UnifyResultDto<List<GetAvailableContentOutput>>(true)
{
Data = new List<GetAvailableContentOutput>()
};
//获取轨迹详情
var trackDetails = await _trackDetailsEsRepository.GetListAsync(order.Id);
// 删除时去除掉外部服务商轨迹
if (input.TrackOptionType == TrackOptionType.删除)
{
trackDetails = trackDetails.Where(m => m.Source != (int) TrackSourceEnum.ExternalServiceProvide).ToList();
}
//进行推送时,匹配订单类型一致的轨迹
if (input.TrackOptionType == TrackOptionType.推送)
{
var pushConfigs = await _trackPushCacheService.GetPushContentConfigsAsync(order.SubSystemId, order.CustomId);
//没有推送配置也不会展示任何下拉
if (pushConfigs == null || pushConfigs.Count == 0) return res;
//选订单类型和配置订单类型一致的配置的轨迹代码
var trackCodes = pushConfigs.Where(m => m.OrderType == order.OrderType)
.Select(x=>x.TrackCode)
.Distinct()
.ToHashSet();
//根据配置过滤轨迹
trackDetails = trackDetails.Where(m => trackCodes.Contains(m.TrackCode)).ToList();
}
//进行删除和推送行为无任何轨迹内容,直接返回为空即可
if (input.TrackOptionType is TrackOptionType.删除 or TrackOptionType.推送 && trackDetails.Count == 0)
{
return res;
}
//获取当前子系统下所有的轨迹内容
var contentList = await _trackContentRepository.GetListBySystemIdAsync(input.SystemId);
if (contentList == null)
@ -305,11 +339,6 @@ public class TrackContentService : ApplicationService
contentList = contentList.Where(m => template.Contains(m.Code)).ToList();
}
var res = new UnifyResultDto<List<GetAvailableContentOutput>>(true)
{
Data = new List<GetAvailableContentOutput>()
};
foreach (var item in contentList)
{
//新增时跳过已存在轨迹内容
@ -344,7 +373,7 @@ public class TrackContentService : ApplicationService
}
//修改操作,查轨迹内容列表反映明细表数据,重新构造响应数据
//修改/删除/推送 操作,查轨迹内容列表反映明细表数据,重新构造响应数据
var matchContent = new List<GetAvailableContentOutput>();
var contents = res.Data.Select(x => new
{

View File

@ -51,6 +51,7 @@ public class TrackPushCacheService : ITrackPushCacheService, ITransientDependenc
x.Id,
x.IsAutoPush,
x.IsAllPush,
x.IsAllPushWithLatest,
x.IsCanDelete,
x.IsCanUpdate,
x.SubSystemId,
@ -76,6 +77,7 @@ public class TrackPushCacheService : ITrackPushCacheService, ITransientDependenc
IsAutoPush = trackPushConfig.IsAutoPush,
IsCanUpdate = trackPushConfig.IsCanUpdate,
IsAllPush = trackPushConfig.IsAllPush,
IsAllPushWithLatest = trackPushConfig.IsAllPushWithLatest,
IsCanDelete = trackPushConfig.IsCanDelete,
DateTimeFormatType = (int) trackPushConfig.DateTimeFormatType,
DateTimeFormat = trackPushConfig.DateTimeFormat,
@ -89,6 +91,7 @@ public class TrackPushCacheService : ITrackPushCacheService, ITransientDependenc
TrackContentValue = item.TrackContentValue,
TrackEnContentValue = item.TrackEnContentValue,
TrackTimeZone = item.TrackTimeZone,
// todo:废弃字段如果没有外部子系统依赖,可以逐步删除
ExpandFieldsValue = item.ExpandFieldsValue,
}).ToList();

View File

@ -142,7 +142,8 @@ public class SjzyTrackApplicationModule : AbpModule
options.PoolSize = configuration.GetValue<int>("RabbitMQ:PoolSize");
options.Declarations = new List<Action<IDeclaration>>()
{
o=>o.ExchangeDeclare(GlobalConst.TrackChangeExchange, ExchangeType.Topic, true),
o => o.ExchangeDeclare(GlobalConst.TrackChangeExchange, ExchangeType.Topic, true),
o => o.ExchangeDeclare(GlobalConst.TrackPushExchange, ExchangeType.Topic, true)
};
options.Consumes = new List<ConsumeOptions>
{
@ -158,6 +159,19 @@ public class SjzyTrackApplicationModule : AbpModule
o.QueueBind(GlobalConst.TrackRefreshSubscribe, GlobalConst.InterfaceRelayExchange,
GlobalConst.TrackRefreshSubscribe);
}
},
new()
{
Queue = GlobalConst.TrackPushCompletedSubscribe,
FetchCount = configuration.GetValue<ushort>("TrackApp:TrackRefreshFetchCount"),
FailedRequeue = false,
Declaration = o =>
{
o.ExchangeDeclare(GlobalConst.InterfaceRelayExchange, ExchangeType.Direct, true);
o.QueueDeclare(GlobalConst.TrackPushCompletedSubscribe, true);
o.QueueBind(GlobalConst.TrackPushCompletedSubscribe, GlobalConst.InterfaceRelayExchange,
GlobalConst.TrackPushCompletedSubscribe);
}
}
};
}, typeof(SjzyTrackApplicationModule).Assembly);

View File

@ -9,6 +9,21 @@ public interface IOrderOperationRecordEsRepository
/// <returns></returns>
Task AddAsync(OrderOperationRecordIndex index);
/// <summary>
/// 添加订单操作记录(批量)
/// </summary>
/// <param name="indexes"></param>
/// <returns></returns>
Task AddManyAsync(List<OrderOperationRecordIndex> indexes);
/// <summary>
/// 根据id删除订单操作记录
/// </summary>
/// <param name="id"></param>
/// <param name="indexName"></param>
/// <returns></returns>
Task DeleteOperationRecordByIdAsync(string id, string indexName);
/// <summary>
/// 获取订单操作记录
/// </summary>

View File

@ -12,7 +12,7 @@ public interface IOrderRepository : IBasicRepository<OrderEntity>
/// <returns></returns>
Task<UnifyPageResultDto<TrackOrderVO>> GetTrackOrderPagedList(GetPageTrackOrderInputVO input);
/// <summary>
/// <summary>
/// 通过三方平台和三方单号查询订单
/// </summary>
/// <param name="serviceProvidrCode">三方平台</param>
@ -62,6 +62,27 @@ public interface IOrderRepository : IBasicRepository<OrderEntity>
/// <returns></returns>
Task<List<(string code, int id)>> GetSubSystemListByCodesAsync(List<string> codes);
#region 订单模板
/// <summary>
/// 根据订单id,获取订单所属的模板
/// </summary>
/// <param name="orderId"></param>
/// <returns></returns>
Task<List<OrderTemplateVO>> GetOrderTemplateAsync(int[] orderId);
/// <summary>
/// 根据子系统+订单号+类型,获取订单所属的模板
/// </summary>
/// <param name="subSystemId"></param>
/// <param name="orderType"></param>
/// <param name="orderNos"></param>
/// <returns></returns>
Task<List<OrderTemplateVO>> GetOrderTemplateAsync(int subSystemId,int orderType,List<string> orderNos);
#endregion
#region 订单关联
/// <summary>

View File

@ -10,7 +10,7 @@ public interface ITrackDetailsEsRepository : IElasticsearchClientService<TrackDe
Task<List<TrackDetailsIndex>> GetLatestListAsync(int[] eventOrderIds, List<string> trackCodes);
Task<List<TrackDetailsIndex>> GetListAsync(int subSystemId, int orderType, List<string> orderNos);
Task<List<TrackDetailsIndex>> GetListAsync(int subSystemId, int orderType, List<string> orderNos, List<string>? trackCodes = null);
Task<bool> AnyAsync(int orderId, string originTrackDataHash);
@ -31,6 +31,8 @@ public interface ITrackDetailsEsRepository : IElasticsearchClientService<TrackDe
Task<List<int>> DeleteTrackAsync(int[] orderIds, List<string> trackCodes);
Task DeleteTrackByIdAsync(string id, string indexName);
Task<List<TrackDetailsIndex>> GetDataByIds(string[] ids);
Task<TrackDetailsIndex> GetDetailAsync(string orderNo, string trackCode, int orderType, int subSystemId, DateTime trackTime);
}

View File

@ -190,5 +190,5 @@ public class TrackDetailsIndex : IdBaseIndex
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary>
public Dictionary<string, object>? Extra { get; set; }
public string? Extra { get; set; }
}

View File

@ -11,6 +11,14 @@ public interface ITrackPushRecordEsRepository
Task AddManyAsync(List<TrackPushRecordIndex> index);
/// <summary>
/// 根据id删除轨迹推送记录
/// </summary>
/// <param name="id"></param>
/// <param name="indexName"></param>
/// <returns></returns>
Task DeletePushRecordByIdAsync(string id, string indexName);
/// <summary>
/// 获取轨迹推送记录
/// </summary>
@ -22,6 +30,14 @@ public interface ITrackPushRecordEsRepository
/// 获取最新轨迹推送记录
/// </summary>
/// <param name="orderId"></param>
/// <param name="isSuccess">只选择成功的记录</param>
/// <returns></returns>
Task<TrackPushRecordIndex?> GetLatestTrackPushRecordAsync(int orderId);
Task<TrackPushRecordIndex?> GetLatestTrackPushRecordAsync(int orderId, bool isSuccess = false);
/// <summary>
/// 获取历史中推送成功的记录
/// </summary>
/// <param name="orderId"></param>
/// <returns></returns>
Task<List<TrackPushRecordIndex>> GetHistorySuccessTrackPushRecordAsync(int orderId);
}

View File

@ -56,6 +56,12 @@ public class TrackPushConfigEntity : IFullAuditedBasicEntitys, IAggregateRoot
[SugarColumn(ColumnName = "IsAllPush")]
public bool IsAllPush { get; set; }
/// <summary>
/// 是否全量推送(最新轨迹)
/// </summary>
[SugarColumn(ColumnName = "IsAllPushWithLatest")]
public bool IsAllPushWithLatest { get; set; }
/// <summary>
/// 日期时间格式类型
/// </summary>

View File

@ -82,4 +82,19 @@ public class TrackPushRecordIndex : IdBaseIndex
/// <summary>创建时间</summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 子系统代码
/// </summary>
public string SubSystemCode { get; set; }
/// <summary>
/// 是否广播
/// </summary>
public bool IsToMq { get; set; }
/// <summary>
/// 轨迹额外信息
/// </summary>
public string? Extra { get; set; }
}

View File

@ -45,6 +45,8 @@ public interface ITrackTemplateRepository : IBasicRepository<TrackTemplateEntity
Expression<Func<TrackTemplateContentEntity, bool>> whereExpression);
Task<List<TrackTemplateOutputVo>> GetTrackTemplateListAsync(GetTrackTemplateListInputVo input);
Task<UnifyPageResultDto<TemplateContentDtoVo>> GetPageTrackTemplateListAsync(GetPageTrackTemplateListInputVo input);
Task<List<string>> GetContentByTemplateCodes(string[] codes, int systemId);
}

View File

@ -57,5 +57,8 @@ public partial class GlobalConst
/// </summary>
public const string TrackChangeExchange = "sjzy.track.on_track_change_ex";
/// <summary>
/// 轨迹推送通知交换机
/// </summary>
public const string TrackPushExchange = "sjzy.track.on_track_push_ex";
}

View File

@ -4,6 +4,7 @@ public partial class GlobalConst
{
public const string MainDataKey_OrderType = "TrackOrderType";
public const string MainDataKey_TrackQuestionType = "TrackQuestionType";
public const string HTTP_HEADER_SUBSYSTEMID = "Subsystemid";
public const string SystemUser = "系统";
public const string EmptyString = "--";
public const int ChunkSize = 100;

View File

@ -0,0 +1,13 @@
namespace SJZY.Track.Domain.Dtos.Order;
/// <summary>
/// 订单关联的模板模型
/// </summary>
public class OrderTemplateVO
{
public int OrderId { get; set; }
public string OrderNo { get; set; }
public string? TemplateCodes { get; set; }
}

View File

@ -2,6 +2,11 @@ namespace SJZY.Track.Domain.Dtos.TrackContents;
public class TrackContentVO
{
/// <summary>
/// 子系统Id
/// </summary>
public int SubSystemId { get; set; }
/// <summary>
/// 子系统代码
/// </summary>

View File

@ -51,6 +51,11 @@ public class GetTrackPushConfigDetailOutputVO
/// </summary>
public bool IsAllPush { get; set; }
/// <summary>
/// 是否全量推送(最新轨迹)
/// </summary>
public bool IsAllPushWithLatest { get; set; }
/// <summary>
/// 时间格式枚举
/// </summary>

View File

@ -54,4 +54,9 @@ public class TemplateContentDtoVo
/// 轨迹节点
/// </summary>
public string Node { get; set; }
/// <summary>
/// 排序键
/// </summary>
public int Sort { get; set; }
}

View File

@ -8,17 +8,17 @@ namespace SJZY.Track.Domain.Enums;
public enum TrackSourceEnum
{
/// <summary>
/// 外部服务商创建
/// 外部服务商
/// </summary>
[Description("外部服务商创建")] ExternalServiceProvide = 1,
[Description("外部服务商")] ExternalServiceProvide = 1,
/// <summary>
/// 内部系统创建
/// 内部系统
/// </summary>
[Description("内部系统创建")] InternalService = 2,
[Description("内部系统")] InternalService = 2,
/// <summary>
/// 人工创建
/// 人工
/// </summary>
[Description("人工创建")] Artificial = 3,
[Description("人工")] Artificial = 3,
}

View File

@ -52,6 +52,14 @@ public interface IProvideQueries
/// <returns></returns>
Task<TrackContentVO?> GetTrackContentAsync(int subSystemId, string trackCode);
/// <summary>
/// 获取轨迹内容
/// </summary>
/// <param name="subSystemIds">子系统ids</param>
/// <param name="trackCodes">轨迹代码集合</param>
/// <returns></returns>
Task<List<TrackContentVO>> GetTrackContentsAsync(int[] subSystemIds, string[] trackCodes);
/// <summary>
/// 获取所有拉取配置
/// </summary>

View File

@ -0,0 +1,7 @@
using SJZY.Track.Domain.VEntities;
namespace SJZY.Track.Domain.Queries;
public interface IVSystemQueries : IBasicQuery<VSystemEntity>
{
}

View File

@ -4,7 +4,7 @@ namespace SJZY.Track.Domain.VEntities;
///
/// </summary>
[SugarTable("v_system")]
public class VSystemEntity : IFullAuditedBasicEntitys
public class VSystemEntity : IFullAuditedBasicEntitys, IEntity
{
/// <summary>
/// 子系统id

View File

@ -1,5 +1,6 @@
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackContentAggregate;
using SJZY.Track.Domain.Aggregates.TrackFlagAggregate;
using SJZY.Track.Domain.Aggregates.TrackPullAggregate;
using SJZY.Track.Domain.Aggregates.TrackStatusAggregate;
using SJZY.Track.Domain.Dtos.Order;
@ -99,24 +100,52 @@ public class ProvideQueries : ISugarRepository, IProvideQueries, ITransientDepen
return await Context.Queryable<TrackContentEntity>()
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
.LeftJoin<VSystemEntity>((x, y, z) => x.SubSystemId == z.Id)
.LeftJoin<TrackFlagEntity>((x, y, z, k) => x.SubSystemId == k.SubSystemId && k.Code == x.Code)
.Where(x => x.SubSystemId == subSystemId && x.Code == trackCode)
.Select((x, y, z) => new TrackContentVO
.Select((x, y, z, k) => new TrackContentVO
{
SubSystemCode = z.Key,
TrackStatusId = x.TrackStatusId,
TrackStatus = y.Status,
TrackStatusCode = y.Code,
TrackContentCode = x.Code,
TrackContent = x.Content,
TrackEnContent = x.EnContent,
IsCompleted = y.IsCompleted,
IsRepeatable = x.IsRepeatable,
IsAllowManuallyUpdate = x.IsAllowManuallyUpdate,
IsAllowManuallyCreate = x.IsAllowManuallyCreate,
IsToMq = y.IsToMq,
IsToMq = y.IsToMq || k.IsToMq,
})
.FirstAsync();
}
public async Task<List<TrackContentVO>> GetTrackContentsAsync(int[] subSystemIds, string[] trackCodes)
{
return await Context.Queryable<TrackContentEntity>()
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
.LeftJoin<VSystemEntity>((x, y, z) => x.SubSystemId == z.Id)
.LeftJoin<TrackFlagEntity>((x, y, z, k) => x.SubSystemId == k.SubSystemId && k.Code == x.Code)
.Where(x => subSystemIds.Contains(x.SubSystemId) && trackCodes.Contains(x.Code))
.Select((x, y, z, k) => new TrackContentVO
{
SubSystemId = x.SubSystemId,
SubSystemCode = z.Key,
TrackStatusId = x.TrackStatusId,
TrackStatus = y.Status,
TrackStatusCode = y.Code,
TrackContentCode = x.Code,
TrackContent = x.Content,
TrackEnContent = x.EnContent,
IsCompleted = y.IsCompleted,
IsRepeatable = x.IsRepeatable,
IsAllowManuallyUpdate = x.IsAllowManuallyUpdate,
IsAllowManuallyCreate = x.IsAllowManuallyCreate,
IsToMq = y.IsToMq || k.IsToMq,
})
.ToListAsync();
}
public async Task<List<TrackPullConfigVo>> GetAllPullConfigsAsync()
{
return await Context.Queryable<TrackPullConfigEntity>()

View File

@ -0,0 +1,8 @@
using SJZY.Track.Domain.Queries;
using SJZY.Track.Domain.VEntities;
namespace SJZY.Track.Repository.Queries;
public class VSystemQueries : BasicQuery<VSystemEntity>, IVSystemQueries, ITransientDependency
{
}

View File

@ -29,6 +29,36 @@ public class OrderOperationRecordEsRepository : ElasticSearchContextAbstract<Ord
}
}
public async Task AddManyAsync(List<OrderOperationRecordIndex> indexes)
{
// 按天写入es
var indexName = $"{GlobalConst.OrderOperationRecordIndex}-{DateTime.Now:yyyy.MM.dd}";
indexes.ForEach(x =>
{
x.IndexName = indexName;
x.CreateTime = DateTime.Now;
});
var r = await Context.IndexManyAsync(indexes, indexName);
if (!r.IsValid)
{
throw new InvalidOperationException($"{indexes.Count}条订单变更记录写入失败,原因:{r.DebugInformation}。");
}
}
public async Task DeleteOperationRecordByIdAsync(string id, string indexName)
{
var r = await Context.DeleteAsync<OrderOperationRecordIndex>(id, u => u
.Index(indexName)
);
if (!r.IsValid)
{
throw new InvalidOperationException($"轨迹操作记录失败,id:{indexName},原因:{r.DebugInformation}。");
}
}
public async Task<List<OrderOperationRecordIndex>> GetOrderOperationsByIdAsync(int orderId)
{
var data = await Context.SearchAsync<OrderOperationRecordIndex>(x => x
@ -39,6 +69,6 @@ public class OrderOperationRecordEsRepository : ElasticSearchContextAbstract<Ord
.Size(GlobalConst.NormalLimit)
.Sort(s => s.Field(f => f.CreateTime, SortOrder.Descending)));
return data.Documents.ToList();
return data.ToIdList();
}
}

View File

@ -1,4 +1,4 @@
using NUglify.Helpers;
using Microsoft.Extensions.Caching.Memory;
using SJZY.Expand.ABP.Core.Logger;
using SJZY.Track.Domain;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
@ -11,56 +11,64 @@ namespace SJZY.Track.Repository.Repositories;
public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, ITransientDependency
{
public async Task<UnifyPageResultDto<TrackOrderVO>> GetTrackOrderPagedList(GetPageTrackOrderInputVO input)
//MemoryCache本身线程安全简单使用,短暂数据不一致可以容忍,每过固定时间强制清除缓存
private static readonly MemoryCache _memoryCache = new(new MemoryCacheOptions());
private static readonly Dictionary<int, string> _empty = new();
private static readonly Dictionary<string, Dictionary<int, string>> _empty2 = new();
private const int ThirdPartyOrderNumberCacheTimeSpan = 3 * 60;
private const int MainDataEnumCacheTimeSpan = 5 * 60;
private async Task<Dictionary<int, string>> GetThirdPartyOrderNumberForCacheAsync(int[] orderIds)
{
//查询完去重
var pageList = await Context.Queryable<OrderEntity>()
const string key = "ThirdPartyOrderNumber";
if (orderIds.Length == 0) return _empty;
var dict = _memoryCache.Get<Dictionary<int, string>>(key);
orderIds = orderIds.Distinct().ToArray();
if (dict != null && dict.Count != 0 && !orderIds.Except(dict.Keys).Any()) return dict;
var orders = await Context.Queryable<OrderEntity>()
.Includes(a => a.OrderPlatformNumbers)
.LeftJoin<VExternalSubjectEntity>((a, b) => a.CustomId == b.Id && b.IsDeleted == false)
.LeftJoin<VSystemEntity>((a, b, c) => a.SubSystemId == c.Id && c.IsDeleted == false)
.LeftJoin<OrderPlatformNumberEntity>((a, b, c, d) => a.Id == d.OrderId && d.IsDeleted == false)
.WhereIF(input.SubSystemId > 0, (a, b, c) => a.SubSystemId == input.SubSystemId)
.WhereIF(!input.OrderNos.IsNullOrEmpty(), (a, b, c, d) => input.OrderNos!.Contains(a.OrderNo) || input.OrderNos!.Contains(d.OrderNumber))
.WhereIF(input.CreateTimeStart.HasValue && input.CreateTimeEnd.HasValue,
(a, b, c) => a.CreateTime >= input.CreateTimeStart && a.CreateTime <= input.CreateTimeEnd)
.WhereIF(input.CreateTimeStart.HasValue && (!input.CreateTimeEnd.HasValue),
(a, b, c) => a.CreateTime >= input.CreateTimeStart)
.WhereIF((!input.CreateTimeStart.HasValue) && input.CreateTimeEnd.HasValue,
(a, b, c) => a.CreateTime <= input.CreateTimeEnd)
.WhereIF(input.CustomerId > 0, (a, b, c) => a.CustomId == input.CustomerId)
.WhereIF(input.TrackOrderType > 0, (a, b, c) => a.OrderType == input.TrackOrderType)
.WhereIF(input.TabType > 0, (a, b, c) => a.QuestionType == input.TabType)
.WhereIF(input.TabType == (int) TrackProblemType.Warn, (a, b, c) => !a.IsCompleted)
.OrderByDescending((a, b, c) => a.CreateTime)
.Distinct()
.Select((a, b, c) => new TrackOrderVO
.LeftJoin<OrderPlatformNumberEntity>((a, b) => a.Id == b.OrderId && b.IsDeleted == false)
.Where((a, b) => orderIds.Contains(a.Id))
.Select(a => new OrderEntity()
{
Id = a.Id,
OrderNumber = a.OrderNo,
OrderType = a.OrderType,
QuestionType = a.QuestionType,
CustomerShortName = b.Abbreviation,
CustomerCode = b.Code,
CustomerId = a.CustomId,
NewestTrackContent = a.LatestTrackContent,
NewestTrackEnContent = a.LatestTrackEnContent,
NewestTrackPlace = a.LatestTrackPlace,
NewestTrackTime = a.LatestTrackTime,
IsFinish = a.IsCompleted,
CreateTime = a.CreateTime,
SubSystem = c.CnName,
SubSystemId = c.Id,
DestinationTimeZone = a.DestinationTimeZone,
EstimatedDeliveryDate = a.EstimatedDeliveryDate,
ThirdPartyOrderNumber = BuildThirdPartyOrderNumber(a.OrderPlatformNumbers)
OrderPlatformNumbers = a.OrderPlatformNumbers
})
.ToPagedListAsync(input.PageIndex, input.PageSize);
.ToListAsync();
if (orders.Count == 0)
return _empty;
orders = orders.DistinctBy(x => x.Id).ToList();
dict = orders.ToDictionary(x => x.Id, x => BuildThirdPartyOrderNumber(x.OrderPlatformNumbers));
_memoryCache.Set(key, dict, new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(ThirdPartyOrderNumberCacheTimeSpan),
Size = dict.Count
});
return dict;
}
private async Task<Dictionary<string, Dictionary<int, string>>> GetMainDataEnumForCacheAsync(List<int> orderTypes, List<int> questTypes)
{
const string key = "EnumForMainData";
if (orderTypes.Count == 0 && questTypes.Count == 0) return _empty2;
var dict = _memoryCache.Get<Dictionary<string, Dictionary<int, string>>>(key);
if (dict != null && dict.Count != 0
&& !orderTypes.Except(dict[GlobalConst.MainDataKey_OrderType].Keys).Any()
&& !questTypes.Except(dict[GlobalConst.MainDataKey_TrackQuestionType].Keys).Any())
{
return dict;
}
//查询订单类型描述
var orderTypes = pageList.List.Select(x => x.OrderType).Distinct().ToList();
var questTypes = pageList.List.Select(x => x.QuestionType).Distinct().ToList();
//从主数据中心查询类型描述
var typeDispDict = (await Context.Queryable<VEnumValueInfoEntity>()
dict = (await Context.Queryable<VEnumValueInfoEntity>()
.Where(x =>
(x.Property == GlobalConst.MainDataKey_OrderType && orderTypes.Contains(x.Enumkey))
|| (x.Property == GlobalConst.MainDataKey_TrackQuestionType && questTypes.Contains(x.Enumkey))
@ -79,20 +87,161 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
y => y.ToDictionary(x => x.Enumkey, x => x.Disp)
);
if (dict.Count == 0)
return _empty2;
_memoryCache.Set(key, dict, new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(MainDataEnumCacheTimeSpan),
Size = dict.Count
});
return dict;
}
public async Task<UnifyPageResultDto<TrackOrderVO>> GetTrackOrderPagedList(GetPageTrackOrderInputVO input)
{
//查询完去重
UnifyPageResultDto<TrackOrderVO> pageList;
//没传递单号时
if (input.OrderNos.IsNullOrEmpty())
{
pageList = await Context.Queryable<OrderEntity>()
.LeftJoin<VExternalSubjectEntity>((a, b) => a.CustomId == b.Id && b.IsDeleted == false)
.LeftJoin<VSystemEntity>((a, b, c) => a.SubSystemId == c.Id && c.IsDeleted == false)
.WhereIF(input.SubSystemId > 0, (a, b, c) => a.SubSystemId == input.SubSystemId)
.WhereIF(input.CreateTimeStart.HasValue && input.CreateTimeEnd.HasValue,
(a, b, c) => a.CreateTime >= input.CreateTimeStart && a.CreateTime <= input.CreateTimeEnd)
.WhereIF(input.CreateTimeStart.HasValue && (!input.CreateTimeEnd.HasValue),
(a, b, c) => a.CreateTime >= input.CreateTimeStart)
.WhereIF((!input.CreateTimeStart.HasValue) && input.CreateTimeEnd.HasValue,
(a, b, c) => a.CreateTime <= input.CreateTimeEnd)
.WhereIF(input.CustomerId > 0, (a, b, c) => a.CustomId == input.CustomerId)
.WhereIF(input.TrackOrderType > 0, (a, b, c) => a.OrderType == input.TrackOrderType)
.WhereIF(input.TabType > 0, (a, b, c) => a.QuestionType == input.TabType)
.WhereIF(input.TabType == (int) TrackProblemType.Warn, (a, b, c) => !a.IsCompleted)
.OrderByDescending((a, b, c) => a.CreateTime)
.Select((a, b, c) => new TrackOrderVO
{
Id = a.Id,
OrderNumber = a.OrderNo,
OrderType = a.OrderType,
QuestionType = a.QuestionType,
CustomerShortName = b.Abbreviation,
CustomerCode = b.Code,
CustomerId = a.CustomId,
NewestTrackContent = a.LatestTrackContent,
NewestTrackEnContent = a.LatestTrackEnContent,
NewestTrackPlace = a.LatestTrackPlace,
NewestTrackTime = a.LatestTrackTime,
IsFinish = a.IsCompleted,
CreateTime = a.CreateTime,
SubSystem = c.CnName,
SubSystemId = c.Id,
DestinationTimeZone = a.DestinationTimeZone,
EstimatedDeliveryDate = a.EstimatedDeliveryDate
})
.ToPagedListAsync(input.PageIndex, input.PageSize);
}
//传递单号时
else
{
var orderNos = input.OrderNos!.Distinct().ToList();
//匹配主单
var orderIds = await Context.Queryable<OrderEntity>()
.Where(x => orderNos.Contains(x.OrderNo))
.Select(x => x.Id)
.ToListAsync();
//匹配外部单
var orderIdsForThirdParty = await Context.Queryable<OrderPlatformNumberEntity>()
.Where(x => !string.IsNullOrWhiteSpace(x.OrderNumber) && orderNos.Contains(x.OrderNumber))
.Select(x => x.OrderId)
.ToListAsync();
var orderHashset = orderIds.ToHashSet();
orderHashset.UnionWith(orderIdsForThirdParty);
orderIds = orderHashset.ToList();
//没有任何单
if (orderIds.Count == 0)
{
return new UnifyPageResultDto<TrackOrderVO>
{
List = new List<TrackOrderVO>(),
Pagination = new PageInfoOutput()
{
PageIndex = input.PageIndex,
PageSize = input.PageSize,
Total = 0
}
};
}
pageList = await Context.Queryable<OrderEntity>()
.Where(x => orderIds.Contains(x.Id))
.LeftJoin<VExternalSubjectEntity>((a, b) => a.CustomId == b.Id && b.IsDeleted == false)
.LeftJoin<VSystemEntity>((a, b, c) => a.SubSystemId == c.Id && c.IsDeleted == false)
.WhereIF(input.SubSystemId > 0, (a, b, c) => a.SubSystemId == input.SubSystemId)
.WhereIF(input.CreateTimeStart.HasValue && input.CreateTimeEnd.HasValue,
(a, b, c) => a.CreateTime >= input.CreateTimeStart && a.CreateTime <= input.CreateTimeEnd)
.WhereIF(input.CreateTimeStart.HasValue && (!input.CreateTimeEnd.HasValue),
(a, b, c) => a.CreateTime >= input.CreateTimeStart)
.WhereIF((!input.CreateTimeStart.HasValue) && input.CreateTimeEnd.HasValue,
(a, b, c) => a.CreateTime <= input.CreateTimeEnd)
.WhereIF(input.CustomerId > 0, (a, b, c) => a.CustomId == input.CustomerId)
.WhereIF(input.TrackOrderType > 0, (a, b, c) => a.OrderType == input.TrackOrderType)
.WhereIF(input.TabType > 0, (a, b, c) => a.QuestionType == input.TabType)
.WhereIF(input.TabType == (int) TrackProblemType.Warn, (a, b, c) => !a.IsCompleted)
.OrderByDescending((a, b, c) => a.CreateTime)
.Select((a, b, c) => new TrackOrderVO
{
Id = a.Id,
OrderNumber = a.OrderNo,
OrderType = a.OrderType,
QuestionType = a.QuestionType,
CustomerShortName = b.Abbreviation,
CustomerCode = b.Code,
CustomerId = a.CustomId,
NewestTrackContent = a.LatestTrackContent,
NewestTrackEnContent = a.LatestTrackEnContent,
NewestTrackPlace = a.LatestTrackPlace,
NewestTrackTime = a.LatestTrackTime,
IsFinish = a.IsCompleted,
CreateTime = a.CreateTime,
SubSystem = c.CnName,
SubSystemId = c.Id,
DestinationTimeZone = a.DestinationTimeZone,
EstimatedDeliveryDate = a.EstimatedDeliveryDate,
})
.ToPagedListAsync(input.PageIndex, input.PageSize);
}
//补三方单号,从缓存中加速查
var orderIdArr = pageList.List.Select(x => x.Id).ToArray();
var thirdPartyOrderNumbers = await GetThirdPartyOrderNumberForCacheAsync(orderIdArr);
pageList.List.ForEach(x => x.ThirdPartyOrderNumber = thirdPartyOrderNumbers[x.Id]);
//查询订单类型描述,从缓存中加速查
var orderTypes = pageList.List.Select(x => x.OrderType).Distinct().ToList();
var questTypes = pageList.List.Select(x => x.QuestionType).Distinct().ToList();
var typeDisplayDict = await GetMainDataEnumForCacheAsync(orderTypes, questTypes);
//填充数描述信息
pageList.List.ForEach(x =>
{
x.OrderTypeName = typeDispDict.TryGetValue(GlobalConst.MainDataKey_OrderType, out var orderTypeDisplayDict)
x.OrderTypeName = typeDisplayDict.TryGetValue(GlobalConst.MainDataKey_OrderType, out var orderTypeDisplayDict)
? orderTypeDisplayDict.TryGetValue(x.OrderType, out var orderTypeDisplay)
? orderTypeDisplay
: $"未在主数据中心找到Property:[{GlobalConst.MainDataKey_OrderType}] Enumkey:[{x.OrderType}]"
: $"未在主数据中心找到Property:[{GlobalConst.MainDataKey_OrderType}]";
: $"未在主数据中心找到Property:[{GlobalConst.MainDataKey_OrderType}] "
: $"未在主数据中心找到Property:[{GlobalConst.MainDataKey_OrderType}] EnumKey:[{x.OrderType}]";
x.QuestionTypeName = typeDispDict.TryGetValue(GlobalConst.MainDataKey_TrackQuestionType, out var questDisplayDict)
x.QuestionTypeName = typeDisplayDict.TryGetValue(GlobalConst.MainDataKey_TrackQuestionType, out var questDisplayDict)
? questDisplayDict.TryGetValue(x.QuestionType, out var questDisplay)
? questDisplay
: $"未在主数据中心找到Property:[{GlobalConst.MainDataKey_OrderType}] Enumkey:[{x.OrderType}]"
: $"未在主数据中心找到Property:[{GlobalConst.MainDataKey_TrackQuestionType}]";
: $"未在主数据中心找到Property:[{GlobalConst.MainDataKey_OrderType}]"
: $"未在主数据中心找到Property:[{GlobalConst.MainDataKey_TrackQuestionType}] EnumKey:[{x.OrderType}]";
});
return pageList;
@ -247,6 +396,32 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
return res.Select(x => (x.Key, x.Id)).ToList();
}
public Task<List<OrderTemplateVO>> GetOrderTemplateAsync(int[] orderId)
{
return Context.Queryable<OrderEntity>()
.Where(x => orderId.Contains(x.Id))
.Select(a => new OrderTemplateVO
{
OrderId = a.Id,
OrderNo = a.OrderNo,
TemplateCodes = a.TemplateCodes
})
.ToListAsync();
}
public Task<List<OrderTemplateVO>> GetOrderTemplateAsync(int subSystemId, int orderType, List<string> orderNos)
{
return Context.Queryable<OrderEntity>()
.Where(x => orderNos.Contains(x.OrderNo) && x.SubSystemId == subSystemId && x.OrderType == orderType)
.Select(a => new OrderTemplateVO
{
OrderId = a.Id,
OrderNo = a.OrderNo,
TemplateCodes = a.TemplateCodes
})
.ToListAsync();
}
public Task<VSystemEntity> GetSystemInfoById(int id)
{
return Context.Queryable<VSystemEntity>().FirstAsync(m => m.IsDeleted == false && m.Id == id);
@ -274,12 +449,15 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
/// </summary>
/// <param name="orderPlatformNumberEntities"></param>
/// <returns></returns>
private static string BuildThirdPartyOrderNumber(List<OrderPlatformNumberEntity> orderPlatformNumberEntities)
public string BuildThirdPartyOrderNumber(List<OrderPlatformNumberEntity> orderPlatformNumberEntities)
{
if (orderPlatformNumberEntities.Count == 0) return GlobalConst.EmptyString;
const string separator = "、";
var thirdPartyOrderNumbers = orderPlatformNumberEntities
.Where(x => !string.IsNullOrWhiteSpace(x.OrderNumber))
.Select(x => $"{x.OrderNumber}({x.Platform})").ToList();
.Select(x => $"{x.OrderNumber}({x.Platform})")
.ToList();
if (thirdPartyOrderNumbers.Count == 0) return GlobalConst.EmptyString;
return string.Join(separator, thirdPartyOrderNumbers);
}

View File

@ -3,7 +3,6 @@ using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.Expand.ElasticSearch.Services;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Consts;
using SJZY.Track.Domain.Dtos.TrackContents;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Repository.Repositories;
@ -20,7 +19,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
public async Task<List<TrackDetailsIndex>> GetListAsync(int[] orderIds, string trackCode,
bool forUpsert = false)
{
var r = !forUpsert
var r = forUpsert
? await Context.SearchAsync<TrackDetailsIndex>(s => s
.Index(GlobalConst.TrackDetailsIndex)
.Source()
@ -373,36 +372,38 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
}
}
public async Task<List<TrackDetailsIndex>> GetListAsync(int subSystemId, int orderType, List<string> orderNos)
public async Task<List<TrackDetailsIndex>> GetListAsync(int subSystemId, int orderType, List<string> orderNos, List<string>? trackCodes = null)
{
if (orderNos.Count < 1) return new List<TrackDetailsIndex>();
var musts = new List<Func<QueryContainerDescriptor<TrackDetailsIndex>, QueryContainer>>
{
mu => mu.Term(f => f.SubSystemId, subSystemId),
mu => mu
.Terms(t => t
.Field(f => f.OrderNo.Suffix("keyword"))
.Terms(orderNos)
),
mu => mu
.Term(f => f.OrderType, orderType)
};
if (trackCodes != null && trackCodes.Count > 0)
{
musts.Add(mu => mu
.Terms(t => t
.Field(f => f.TrackCode.Suffix("keyword"))
.Terms(trackCodes)
));
}
var r = await Context.SearchAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex)
.Source(s => s.Includes(fd => fd
.Field(f => f.OrderId)
.Field(f => f.OrderNo)
.Field(f => f.TrackStatus)
.Field(f => f.TrackCode)
.Field(f => f.TrackContent)
.Field(f => f.OriginTrackContent)
.Field(f => f.TrackPlace)
.Field(f => f.TrackTime)
.Field(f => f.ZeroTrackTime)
.Field(f => f.TrackTimeZone)
))
.Source()
.Query(q =>
q.Bool(b => b
.Must(
mu => mu.Term(f => f.SubSystemId, subSystemId),
mu => mu
.Terms(t => t
.Field(f => f.OrderNo.Suffix("keyword"))
.Terms(orderNos)
),
mu => mu
.Term(f => f.OrderType, orderType)
)
.Must(musts)
// tips:对外不暴露服务商轨迹
.MustNot(mn => mn
.Term(t => t.TrackCode.Suffix("keyword"), GlobalConst.EmptyString)
@ -418,7 +419,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
$"轨迹查询失败,orderType:{orderType},orderNos:{orderNos},原因:{r.DebugInformation}。");
}
return r.Documents.ToList();
return r.ToIdList();
}
public async Task<List<TrackDetailsIndex>> GetListAsync(int orderId)
@ -429,7 +430,10 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
q.Term(t => t.OrderId, orderId)
)
.Size(SafelyLimit)
.Sort(s => s.Field(f => f.ZeroTrackTime, SortOrder.Descending)));
.Sort(s => s
.Field(f => f.ZeroTrackTime, SortOrder.Descending)
.Field(f => f.CreateTime, SortOrder.Descending)
));
if (!r.IsValid)
{
@ -462,7 +466,10 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
.Index(GlobalConst.TrackDetailsIndex)
.Query(q => query)
.Size(SafelyLimit)
.Sort(s => s.Field(f => f.ZeroTrackTime, SortOrder.Descending)));
.Sort(s => s
.Field(f => f.ZeroTrackTime, SortOrder.Descending)
.Field(f => f.CreateTime, SortOrder.Descending))
);
if (!r.IsValid)
{

View File

@ -45,6 +45,18 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract<TrackPus
}
}
public async Task DeletePushRecordByIdAsync(string id, string indexName)
{
var r = await Context.DeleteAsync<TrackPushRecordIndex>(id, u => u
.Index(indexName)
);
if (!r.IsValid)
{
throw new InvalidOperationException($"轨迹推送记录失败,id:{indexName},原因:{r.DebugInformation}。");
}
}
public async Task<List<TrackPushRecordIndex>> GetTrackPushRecordsAsync(int orderId)
{
@ -56,19 +68,46 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract<TrackPus
.Size(1000)
.Sort(s => s.Field(f => f.CreateTime, SortOrder.Descending)));
return data.Documents.ToList();
return data.ToIdList();
}
public async Task<TrackPushRecordIndex?> GetLatestTrackPushRecordAsync(int orderId)
public async Task<TrackPushRecordIndex?> GetLatestTrackPushRecordAsync(int orderId, bool isSuccess = false)
{
var musts = new List<Func<QueryContainerDescriptor<TrackPushRecordIndex>, QueryContainer>>
{
mu => mu.Term(t => t.OrderId, orderId),
};
if (isSuccess)
musts.Add(mu => mu.Term(t => t.IsSuccess, true));
var data = await Context.SearchAsync<TrackPushRecordIndex>(x => x
.Index(GlobalConst.TrackPushRecordIndex)
.Query(q =>
q.Term(t => t.OrderId, orderId)
q.Bool(b =>
b.Must(musts))
)
.Size(1000)
.Sort(s => s.Field(f => f.CreateTime, SortOrder.Descending)));
return data.Documents.FirstOrDefault();
}
public async Task<List<TrackPushRecordIndex>> GetHistorySuccessTrackPushRecordAsync(int orderId)
{
var data = await Context.SearchAsync<TrackPushRecordIndex>(x => x
.Index(GlobalConst.TrackPushRecordIndex)
.Source(s =>
s.Excludes(fs =>
fs.Field(f => f.PushContent)))
.Query(q => q.Bool(b =>
b.Must(
mu => mu.Term(t => t.IsSuccess, true),
mu => mu.Term(t => t.OrderId, orderId)
))
)
.Size(1000)
.Sort(s => s.Field(f => f.CreateTime, SortOrder.Descending)));
return data.ToIdList();
}
}

View File

@ -214,7 +214,8 @@ public class TrackTemplateRepository : BasicRepository<TrackTemplateEntity>, ITr
ContentCn = b.Content,
ContentEn = b.EnContent,
Status = c.Status,
Node = d.NodeName
Node = d.NodeName,
Sort = a.Sort
})
.ToListAsync();
@ -222,7 +223,8 @@ public class TrackTemplateRepository : BasicRepository<TrackTemplateEntity>, ITr
{
var templateContents = contents.Where(x => x.TemplateId == template.Id).ToList();
template.TemplateContents = templateContents;
//根据排序键排序
template.TemplateContents = templateContents.OrderBy(x => x.Sort).ToList();
}
return templates;
@ -262,10 +264,13 @@ public class TrackTemplateRepository : BasicRepository<TrackTemplateEntity>, ITr
ContentCn = b.Content,
ContentEn = b.EnContent,
Status = c.Status,
Node = d.NodeName
Node = d.NodeName,
Sort = a.Sort
})
.ToPagedListAsync(input.PageIndex, input.PageSize);
//返回轨迹模板内容时按排序键排序
contents.List = contents.List.OrderBy(x => x.Sort).ToList();
return contents;
}