diff --git a/src/SJZY.Track.Api/appsettings.json b/src/SJZY.Track.Api/appsettings.json index 0c0044ed..beb55965 100644 --- a/src/SJZY.Track.Api/appsettings.json +++ b/src/SJZY.Track.Api/appsettings.json @@ -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" ] } }, diff --git a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/BatchDeleteTrackInput.cs b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/BatchDeleteTrackInput.cs index 45ccc51c..9d3db0a5 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/BatchDeleteTrackInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/BatchDeleteTrackInput.cs @@ -10,4 +10,14 @@ public class BatchDeleteTrackInput : OrderNosInfo [Required] [MaxLength(500)] public List TrackIdentifiers { get; set; } = new(); + + /// + /// 重复的情况下,用什么字段比较 + /// + public string? CompareFields { get; set; } + + /// + /// 重复的情况下,比较值 + /// + public object? CompareValue { get; set; } } diff --git a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/BatchUpsertTrackInput.cs b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/BatchUpsertTrackInput.cs index a8ced808..276ad83b 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/BatchUpsertTrackInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/BatchUpsertTrackInput.cs @@ -120,8 +120,23 @@ public class BatchUpsertTrackInput : OrderNosInfo [Range(-12, 12)] public float? DestinationTimeZone { get; set; } + /// + /// 重复的情况下,用什么比较 + /// + public string? CompareFields { get; set; } + /// /// 扩展信息字段,用与轨迹推送的附加字段 /// - public Dictionary? Extra { get; set; } + public string? Extra { get; set; } + + /// + /// 是否强制推送 + /// + public bool ForcedPush { get; set; } + + /// + /// 指定是否推送 + /// + public bool IsToMq { get; set; } } diff --git a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/DeleteOrderInput.cs b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/DeleteOrderInput.cs index de305c57..ef4139e4 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/DeleteOrderInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/DeleteOrderInput.cs @@ -9,8 +9,22 @@ public class DeleteOrderInput : OrderNoInfo public class DeleteTrackInput : OrderNoInfo { /// - /// 轨迹标识符集合,必填 + /// 轨迹标识符集合,为支持esid删除,所以非必填 /// - [Required] - public List TrackIdentifiers { get; set; } = new(); + public List? TrackIdentifiers { get; set; } = new(); + + /// + /// 轨迹ESId集合,非必填,如果有优先级最高 + /// + public List? EsIds { get; set; } + + /// + /// 重复的情况下,用什么字段比较 + /// + public string? CompareFields { get; set; } + + /// + /// 重复的情况下,比较值 + /// + public object? CompareValue { get; set; } } diff --git a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/GetTrackInput.cs b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/GetTrackInput.cs index b36217c3..44df6107 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/GetTrackInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/GetTrackInput.cs @@ -1,5 +1,8 @@ namespace SJZY.Track.Application.Contract.Dtos.ProvideServices; +/// +/// 支持模板,传入时区补正,按轨迹发生时间升序降序 +/// public class GetTrackInput : OrderNosInfo { private float? _timeZone; @@ -8,11 +11,7 @@ public class GetTrackInput : OrderNosInfo /// 时区 +8 +8.25 -8.5 -9 /// 不传默认+8 /// - public float? TimeZone - { - get => _timeZone ?? 8; - set => _timeZone = value; - } + public float? TimeZone { get => _timeZone ?? 8; set => _timeZone = value; } /// /// 排序方式,默认按轨迹发生时间降序 @@ -20,15 +19,36 @@ public class GetTrackInput : OrderNosInfo public SortMethod? SortMethod { get; set; } } +/// +/// 不支持模板,无时区补正,按轨迹发生时间升序降序 +/// +public class GetTrackInputV2 : OrderNosInfo +{ + /// + /// 排序方式,默认按轨迹发生时间降序 + /// + public SortMethod? SortMethod { get; set; } +} + public enum SortMethod { /// /// 按轨迹发生时间排序 /// - ByTrackTime, + ByTrackTime = 0, /// /// 按轨迹发生时间降序 /// - ByTrackTimeDesc, + ByTrackTimeDesc = 1, + + /// + /// 模板排序+轨迹发生时间 + /// + ByTemplateAndTrackTime = 2, + + /// + /// 模板排序+轨迹发生时间 + /// + ByTemplateAndTrackTimeDesc = 3 } diff --git a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/GetTrackOutput.cs b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/GetTrackOutput.cs index 7af8aabd..f5313b3b 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/GetTrackOutput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/GetTrackOutput.cs @@ -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 /// public string? TrackPlacePostcode { get; set; } - /// /// 预计到达时间 /// @@ -105,5 +105,5 @@ public class TrackDetail /// /// 扩展信息字段,用与轨迹推送的附加字段 /// - public Dictionary? Extra { get; set; } + public string? Extra { get; set; } } diff --git a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/PushTrackInput.cs b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/PushTrackInput.cs index d58bea41..bc5b6cae 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/PushTrackInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/PushTrackInput.cs @@ -9,4 +9,14 @@ public class PushTrackInput : OrderNosInfo /// [Required] public string TrackCode { get; set; } = null!; + + /// + /// 重复的情况下,用什么字段比较 + /// + public string? CompareFields { get; set; } + + /// + /// 重复的情况下,比较值 + /// + public object? CompareValue { get; set; } } diff --git a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/TrackTemplateContentDto.cs b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/TrackTemplateContentDto.cs index 5e673fa4..ffabeb90 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/TrackTemplateContentDto.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/TrackTemplateContentDto.cs @@ -44,4 +44,9 @@ public class TemplateContentDto /// 轨迹节点 /// public string Node { get; set; } + + /// + /// 排序键 + /// + public int Sort { get; set; } } diff --git a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/UpdateOrderInput.cs b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/UpdateOrderInput.cs index d9d6d235..a37ab135 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/UpdateOrderInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/UpdateOrderInput.cs @@ -81,7 +81,7 @@ public class UpdateOrderInput : OrderNoInfo /// /// 模板code /// - public List TemplateCodes { get; set; } + public List? TemplateCodes { get; set; } } public class PullConfigInfo diff --git a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/UpsertTrackInput.cs b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/UpsertTrackInput.cs index 22762d6c..def0e322 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/UpsertTrackInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/ProvideServices/UpsertTrackInput.cs @@ -9,6 +9,11 @@ public class UpsertTrackInput : OrderNoInfo /// public bool IsAdd { get; set; } + /// + /// 轨迹唯一标识(可选) + /// + public string? EsId { get; set; } + /// /// 轨迹内容代码 /// @@ -27,7 +32,7 @@ public class UpsertTrackInput : OrderNoInfo /// 轨迹发生时间 /// [Required] - public DateTime? TrackTime { get; set; } + public DateTime TrackTime { get; set; } /// /// 更改后的轨迹发生时间,无需更改保持为空 @@ -124,8 +129,23 @@ public class UpsertTrackInput : OrderNoInfo /// public int OptionUserId { get; set; } + /// + /// 重复的情况下,用什么比较 + /// + public string? CompareFields { get; set; } + /// /// 扩展信息字段,用与轨迹推送的附加字段 /// - public Dictionary? Extra { get; set; } + public string? Extra { get; set; } + + /// + /// 是否强制推送 + /// + public bool ForcedPush { get; set; } + + /// + /// 指定是否推送 + /// + public bool IsToMq { get; set; } } diff --git a/src/SJZY.Track.Application.Contract/Dtos/TrackContents/GetAvailableContentInput.cs b/src/SJZY.Track.Application.Contract/Dtos/TrackContents/GetAvailableContentInput.cs index 47425b3c..39ae0073 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/TrackContents/GetAvailableContentInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/TrackContents/GetAvailableContentInput.cs @@ -13,7 +13,7 @@ public class GetAvailableContentInput public string? OrderNo { get; set; } /// - /// 操作类型 1=创建 2=修改 3=删除 + /// 操作类型 1=创建 2=修改 3=删除 4=推送 /// public TrackOptionType TrackOptionType { get; set; } } @@ -22,5 +22,6 @@ public enum TrackOptionType { 创建 = 1, 修改 = 2, - 删除 = 3 + 删除 = 3, + 推送 = 4, } diff --git a/src/SJZY.Track.Application.Contract/Dtos/TrackPush/EditTrackPushConfigInput.cs b/src/SJZY.Track.Application.Contract/Dtos/TrackPush/EditTrackPushConfigInput.cs index 745cdf56..9d7c58d9 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/TrackPush/EditTrackPushConfigInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/TrackPush/EditTrackPushConfigInput.cs @@ -44,10 +44,15 @@ public class EditTrackPushConfigInput /// public bool? IsAllPush { get; set; } + /// + /// 是否全量推送 + /// + public bool? IsAllPushWithLatest { get; set; } + /// /// 时间格式枚举 /// - public int DateTimeFormatType { get; set; } + public int? DateTimeFormatType { get; set; } = 0; /// /// 日期时间格式模板 diff --git a/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushContentConfigDto.cs b/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushContentConfigDto.cs index 7e4464ed..7c1c1568 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushContentConfigDto.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushContentConfigDto.cs @@ -17,6 +17,11 @@ public class TrackPushContentConfigDto /// public bool IsAllPush { get; set; } + /// + /// 是否全量推送(最新轨迹) + /// + public bool IsAllPushWithLatest { get; set; } + /// /// 是否支持删除 /// @@ -70,6 +75,7 @@ public class TrackPushContentConfigDto /// /// 扩展字段参数值 /// + [Obsolete("废弃属性,调整为从轨迹明细扩展中获取扩展值填入")] public string? ExpandFieldsValue { get; set; } /// diff --git a/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushInput.cs b/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushInput.cs index 45047f0a..eae08b81 100644 --- a/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushInput.cs +++ b/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushInput.cs @@ -4,7 +4,8 @@ public class TrackPushInput { public string OrderNo { get; set; } = null!; - public List Data { get; set; } = new(); + //原TrackItem,为了扩展字段入参变更为字典 + public List> Data { get; set; } = new(); public string Log { get; set; } } @@ -13,19 +14,8 @@ public class TrackUpdateInput { public string OrderNo { get; set; } = null!; - public List Data { get; set; } = new(); + //原TrackItem,为了扩展字段入参变更为字典 + public List> 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; } -} diff --git a/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushRecordDto.cs b/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushRecordDto.cs new file mode 100644 index 00000000..a2d88e3b --- /dev/null +++ b/src/SJZY.Track.Application.Contract/Dtos/TrackPush/TrackPushRecordDto.cs @@ -0,0 +1,90 @@ +namespace SJZY.Track.Application.Contract.Dtos.TrackPush; + +public class TrackPushRecordDto +{ + /// + /// 子系统代码 + /// + public string SubSystemCode { get; set; } + + /// + /// 是否为手动推送 + /// + public bool IsManualPush { get; set; } + + /// + /// 推送时间 + /// + public DateTime PushTime { get; set; } + + /// + /// 操作人 + /// + public string? OperationMan { get; set; } + + /// + /// 订单号 + /// + public string OrderNo { get; set; } = null!; + + /// + /// 接口通请求Id + /// 在请求接口通前被拦截,就不会有请求Id + /// + public Guid? RequestId { get; set; } + + /// + /// 信息 + /// + public string Message { get; set; } = null!; + + /// + /// 操作类型 + /// + public string OptionType { get; set; } = null!; + + /// + /// 轨迹状态 + /// + public string TrackStatus { get; set; } = null!; + + /// + /// 轨迹内容代码 + /// + public string TrackCode { get; set; } = null!; + + /// + /// 轨迹内容 + /// + public string TrackContent { get; set; } = null!; + + /// + /// 轨迹发生地 + /// + public string TrackPlace { get; set; } = null!; + + /// + /// 轨迹发生时间 + /// + public DateTime TrackTime { get; set; } + + /// + /// 轨迹发生时区 + /// + public float TrackTimeZone { get; set; } + + /// + /// 是否成功 + /// + public bool IsSuccess { get; set; } + + /// + /// 推送内容(json) + /// + public string PushContent { get; set; } = null!; + + /// + /// 轨迹的额外新 + /// + public string? Extra { get; set; } +} diff --git a/src/SJZY.Track.Application.Contract/Events/TrackChangeEto.cs b/src/SJZY.Track.Application.Contract/Events/TrackChangeEto.cs index f2eb4fe9..6319446e 100644 --- a/src/SJZY.Track.Application.Contract/Events/TrackChangeEto.cs +++ b/src/SJZY.Track.Application.Contract/Events/TrackChangeEto.cs @@ -128,4 +128,9 @@ public class TrackChangeEto /// 预计到达时间 /// public DateTime? EstimatedDeliveryDate { get; set; } + + /// + /// 扩展信息字段,用与轨迹推送的附加字段 + /// + public string? Extra { get; set; } } diff --git a/src/SJZY.Track.Application.Contract/Events/TrackDeleteEvent.cs b/src/SJZY.Track.Application.Contract/Events/TrackDeleteEvent.cs index 0859de6b..6fa3c346 100644 --- a/src/SJZY.Track.Application.Contract/Events/TrackDeleteEvent.cs +++ b/src/SJZY.Track.Application.Contract/Events/TrackDeleteEvent.cs @@ -12,17 +12,27 @@ public class TrackDeleteEvent /// /// es中的id /// - public string[] EsIds { get; set; } + public string[]? EsIds { get; set; } /// /// 订单ids /// public int[] OrderIds { get; set; } = null!; + /// + /// 重复的情况下,用什么字段比较 + /// + public string? CompareFields { get; set; } + + /// + /// 重复的情况下,比较值 + /// + public object? CompareValue { get; set; } + /// /// 轨迹标识符 /// - public List TrackIdentifiers { get; set; } = null!; + public List? TrackIdentifiers { get; set; } /// /// 操作人 diff --git a/src/SJZY.Track.Application.Contract/Events/TrackPushCompletedEvent.cs b/src/SJZY.Track.Application.Contract/Events/TrackPushCompletedEvent.cs new file mode 100644 index 00000000..c7f4801d --- /dev/null +++ b/src/SJZY.Track.Application.Contract/Events/TrackPushCompletedEvent.cs @@ -0,0 +1,88 @@ +namespace SJZY.Track.Application.Contract.Events; + +/// +/// 轨迹推送广播模型 +/// +public class TrackPushCompletedEvent +{ + /// + /// 是否为手动推送 + /// + public bool IsManualPush { get; set; } + + /// + /// 推送时间 + /// + public DateTime PushTime { get; set; } + + /// + /// 操作人 + /// + public string? OperationMan { get; set; } + + /// + /// 订单号 + /// + public string OrderNo { get; set; } = null!; + + /// + /// 接口通请求Id + /// 在请求接口通前被拦截,就不会有请求Id + /// + public Guid? RequestId { get; set; } + + /// + /// 信息 + /// + public string Message { get; set; } = null!; + + /// + /// 操作类型 + /// + public string OptionType { get; set; } = null!; + + /// + /// 轨迹状态 + /// + public string TrackStatus { get; set; } = null!; + + /// + /// 轨迹内容代码 + /// + public string TrackCode { get; set; } = null!; + + /// + /// 轨迹内容 + /// + public string TrackContent { get; set; } = null!; + + /// + /// 轨迹发生地 + /// + public string TrackPlace { get; set; } = null!; + + /// + /// 轨迹发生时间 + /// + public DateTime TrackTime { get; set; } + + /// + /// 轨迹发生时区 + /// + public float TrackTimeZone { get; set; } + + /// + /// 是否成功 + /// + public bool IsSuccess { get; set; } + + /// + /// 推送内容(json) + /// + public string PushContent { get; set; } = null!; + + /// + /// 轨迹的额外新 + /// + public string? Extra { get; set; } +} diff --git a/src/SJZY.Track.Application.Contract/Events/TrackPushEvent.cs b/src/SJZY.Track.Application.Contract/Events/TrackPushEvent.cs index 2bc5760c..9be77fbc 100644 --- a/src/SJZY.Track.Application.Contract/Events/TrackPushEvent.cs +++ b/src/SJZY.Track.Application.Contract/Events/TrackPushEvent.cs @@ -41,6 +41,16 @@ public class TrackPushEvent /// 系统推送信息 /// public SystemPushStruct SystemPushInfo { get; set; } + + /// + /// 是否强制推送 + /// + public bool ForcedPush { get; set; } + + /// + /// 是否发送到mq + /// + public bool IsToMq { get; set; } } public struct SystemPushStruct @@ -69,4 +79,14 @@ public struct SystemPushStruct /// 轨迹发生时区 /// public float TrackTimeZone { get; set; } + + /// + /// 重复的情况下,用什么比较 + /// + public string? CompareFields { get; set; } + + /// + /// 扩展信息字段,用与轨迹推送的附加字段 + /// + public string? Extra { get; set; } } diff --git a/src/SJZY.Track.Application.Contract/Events/TrackUpsertEvent.cs b/src/SJZY.Track.Application.Contract/Events/TrackUpsertEvent.cs index d08a1171..fef7ab3f 100644 --- a/src/SJZY.Track.Application.Contract/Events/TrackUpsertEvent.cs +++ b/src/SJZY.Track.Application.Contract/Events/TrackUpsertEvent.cs @@ -19,6 +19,11 @@ public class TrackUpsertEvent /// public OrderIdentifier[] OrderIdentifiers { get; set; } = null!; + /// + /// 轨迹唯一标识(用于单个订单更新单条轨迹,如果有的话优先级最高) + /// + public string? EsId { get; set; } + /// /// 操作人(null则默认系统) /// @@ -141,10 +146,25 @@ public class TrackUpsertEvent /// public float? DestinationTimeZone { get; set; } + /// + /// 重复的情况下,用什么比较 + /// + public string? CompareFields { get; set; } + /// /// 扩展信息字段,用与轨迹推送的附加字段 /// - public Dictionary? Extra { get; set; } + public string? Extra { get; set; } + + /// + /// 是否强制推送 + /// + public bool ForcedPush { get; set; } + + /// + /// 指定是否推送 + /// + public bool IsToMq { get; set; } public override string ToString() { diff --git a/src/SJZY.Track.Application.Contract/SJZY.Track.Application.Contract.csproj b/src/SJZY.Track.Application.Contract/SJZY.Track.Application.Contract.csproj index b70c8550..f0778d54 100644 --- a/src/SJZY.Track.Application.Contract/SJZY.Track.Application.Contract.csproj +++ b/src/SJZY.Track.Application.Contract/SJZY.Track.Application.Contract.csproj @@ -5,7 +5,7 @@ net7.0 True - 7.0.3 + 7.0.14 True diff --git a/src/SJZY.Track.Application.Contract/Services/IBroadcastingService.cs b/src/SJZY.Track.Application.Contract/Services/IBroadcastingService.cs new file mode 100644 index 00000000..782126bb --- /dev/null +++ b/src/SJZY.Track.Application.Contract/Services/IBroadcastingService.cs @@ -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 +{ + /// + /// 轨迹变更广播(新增/修改) + /// + /// + /// + /// + void TrackChangePublicTopic(TrackUpsertEvent eto, string subSystemCode, string trackStatusCode); + + /// + /// 轨迹变更广播(删除) + /// + /// + /// + /// + void TrackChangePublicTopic(TrackDeleteEvent eto, OrderIdentifier[] orderIdentifiers, string trackCode, string trackStatusCode, string subSystemCode); + + /// + /// 轨迹变更广播(删除) + /// + /// + /// + /// + void TrackChangePublicTopic(TrackChangeEto eto, string subSystemCode); + + /// + /// 轨迹推送完成广播(推送完成回调) + /// + /// + void TrackPushCompletedPublicTopic(TrackPushRecordDto log); + + /// + /// 轨迹推送失败广播(推送前置被拦截,或调用接口通失败) + /// + /// + void TrackPushFailedPubilcTopic(TrackPushRecordDto log); +} diff --git a/src/SJZY.Track.Application.Contract/Services/ProvideServices/ITrackPushService.cs b/src/SJZY.Track.Application.Contract/Services/ProvideServices/ITrackPushService.cs index 4ddeb81d..b7029aa6 100644 --- a/src/SJZY.Track.Application.Contract/Services/ProvideServices/ITrackPushService.cs +++ b/src/SJZY.Track.Application.Contract/Services/ProvideServices/ITrackPushService.cs @@ -12,7 +12,7 @@ public interface ITrackPushService : IRemoteService /// /// /// - public Task PushTrackAsync(PushTrackInput input); + public Task PushTrackAsync(PushTrackInput input); /// /// 获取轨迹推送内容 @@ -20,12 +20,12 @@ public interface ITrackPushService : IRemoteService /// /// 子系统id /// 客户id - public Task> GetTrackPushContentListAsync(int subSystemId, int customerId); + public Task>> GetTrackPushContentListAsync(int subSystemId, int customerId); /// /// 获取轨迹推送记录 /// /// /// - public Task> GetTrackPushRecordsAsync(OrderNoInfo input); + public Task>> GetTrackPushRecordsAsync(OrderNoInfo input); } diff --git a/src/SJZY.Track.Application.Contract/Services/ProvideServices/ITrackService.cs b/src/SJZY.Track.Application.Contract/Services/ProvideServices/ITrackService.cs index 8cafa4b1..0921c05d 100644 --- a/src/SJZY.Track.Application.Contract/Services/ProvideServices/ITrackService.cs +++ b/src/SJZY.Track.Application.Contract/Services/ProvideServices/ITrackService.cs @@ -12,6 +12,13 @@ public interface ITrackService : IRemoteService /// Task UpsertTrackAsync(UpsertTrackInput input); + /// + /// 批量新增/更新订单轨迹接口 + /// + /// + /// + Task BatchUpsertTrackAsync(BatchUpsertTrackInput input); + /// /// 删除轨迹 /// @@ -28,8 +35,17 @@ public interface ITrackService : IRemoteService /// /// 根据订单号获取轨迹 + /// 支持模板,传入时区补正,按轨迹发生时间升序降序 /// /// /// Task>> QueryAsync(GetTrackInput input); + + /// + /// 根据订单号获取轨迹 + /// 不支持模板,无时区补正,可按轨迹发生时间升序降序 + /// + /// + /// + Task>> QueryV2Async(GetTrackInputV2 input); } diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs index 1f32a4e3..e3a98037 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackChangeAfterUpdateOrderEventHandler.cs @@ -26,8 +26,6 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa return; } - //指示是否需要更新【预计签收时间】【签收地时区】 - var isNeedUpdate = false; var updateOrders = new List(); 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, }); } } diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackDeleteEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackDeleteEventHandler.cs index e9db1cfa..e1c416d0 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackDeleteEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackDeleteEventHandler.cs @@ -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 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> DeleteTrackAsync(TrackDeleteEvent @event) { List 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 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); + } + } + } } diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushEventHandler.cs index 0ef48ef3..cd792aa6 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackPushEventHandler.cs @@ -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 _httpClientProxy; private readonly IOptionsMonitor _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 _orders = null!; private List _pushConfigs = null!; public TrackPushEventHandler(IHttpClientProxy httpClientProxy, - IOrderRepository orderRepository, ITrackPushCacheService trackPushCacheService, - ITrackDetailsEsRepository trackDetailsEsRepository, ITrackPushRecordEsRepository trackPushRecordEsRepository, - IOptionsMonitor options) + IOrderRepository orderRepository, + ITrackPushCacheService trackPushCacheService, + ITrackPushRecordEsRepository trackPushRecordEsRepository, + IOptionsMonitor 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(); - TrackPushContentConfigDto? config; - + var items = new List(); 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().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().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 items) { + // tips: 手工强制推送直接使用配置 var config = _pushConfigs.FirstOrDefault(x => x.TrackCode == @event.TrackCode); - if (config == null || !config.IsCanUpdate) return; - - var items = new List(); 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 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(); + 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().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().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() + { + //推真实的发生地和发生时间(时间是计算后的) + { "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(log.Extra) } + }; + body.Data.Add(dict); log.PushContent = body.ToJson(); return body; } - private async Task BuiltPushItem(TrackPushContentConfigDto config, TrackPushRecordIndex log) + private async Task 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 + { + // 推真实的发生地和发生时间(时间是计算后的) + { "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(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(history.PushContent); - body!.Data.Add(track); + body = TextJsonHelper.Deserialize(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(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 /// - /// - /// - /// - /// - TrackPushContentConfigDto? GetValidPushConfigAsync(List cache, - string? latestPushTrackCode, string? trackCode) + private static TrackPushContentConfigDto? GetValidPushConfigAsync(List 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; } /// /// 转换推送时间格式 /// - /// - /// - /// - /// - 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 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, + }); + } + } } diff --git a/src/SJZY.Track.Application/CAP/EventHandlers/TrackUpsertEventHandler.cs b/src/SJZY.Track.Application/CAP/EventHandlers/TrackUpsertEventHandler.cs index 1b6ee76f..f8d2cc5a 100644 --- a/src/SJZY.Track.Application/CAP/EventHandlers/TrackUpsertEventHandler.cs +++ b/src/SJZY.Track.Application/CAP/EventHandlers/TrackUpsertEventHandler.cs @@ -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 _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 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); } - /// - /// 发送轨迹广播 - /// - /// - /// - 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 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(); var updateTracks = new List(); - + var operationRecords = new List(); 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); + } } diff --git a/src/SJZY.Track.Application/Helpers/TextJsonHelper.cs b/src/SJZY.Track.Application/Helpers/TextJsonHelper.cs new file mode 100644 index 00000000..306e22e2 --- /dev/null +++ b/src/SJZY.Track.Application/Helpers/TextJsonHelper.cs @@ -0,0 +1,181 @@ +using System.Globalization; +using System.Text.Json; + +namespace SJZY.Track.Application.Helpers; + +/// +/// 针对 Text.Json 的一些辅助方法 +/// +public static class TextJsonHelper +{ + public static string? Serialize(object? obj) + { + return obj == null ? default : System.Text.Json.JsonSerializer.Serialize(obj); + } + + public static T? Deserialize(string? json) + { + return string.IsNullOrWhiteSpace(json) ? default : System.Text.Json.JsonSerializer.Deserialize(json); + } + + public static Dictionary? DeserializeToDictionary(string? dictJsonString) + { + return string.IsNullOrWhiteSpace(dictJsonString) + ? null + : System.Text.Json.JsonSerializer.Deserialize>(dictJsonString); + } + + public static JsonElement? DeserializeToJsonElement(string? dictJsonString) + { + return string.IsNullOrWhiteSpace(dictJsonString) + ? null + : System.Text.Json.JsonSerializer.Deserialize(dictJsonString); + } + + /// + /// 获取JsonElement字符串表达的值 + /// + /// + /// + 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 + }; + } + + /// + /// 提取字典字符串中某个字段的值 + /// + /// 字典json字符串 + /// + /// + public static object? ExtractValue(string? dictString, string fieldName) + { + if (string.IsNullOrWhiteSpace(dictString)) + { + return null; + } + + var dict = System.Text.Json.JsonSerializer.Deserialize>(dictString); + if (dict == null || !dict.TryGetValue(fieldName, out var value)) + { + return null; + } + + return value; + } + + /// + /// 两个jsonElement进行比较 + /// + /// + /// + /// + 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; + } + + /// + /// jsonElement和object之间的比较 + /// + /// + /// + /// + 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; + } + + + /// + /// 比较两个jsonElement中的的fieldName字段的值 + /// + /// + /// + /// + /// + 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; + } +} diff --git a/src/SJZY.Track.Application/Options/InterfaceRelayParamsConfigOptions.cs b/src/SJZY.Track.Application/Options/InterfaceRelayParamsConfigOptions.cs index 59a6ab9c..e2a3d78e 100644 --- a/src/SJZY.Track.Application/Options/InterfaceRelayParamsConfigOptions.cs +++ b/src/SJZY.Track.Application/Options/InterfaceRelayParamsConfigOptions.cs @@ -57,6 +57,16 @@ public class InterfaceRelayParamsConfigOptions /// public string PushTrackInterfaceCode { get; set; } + /// + /// 推送轨迹接口Id + /// + public int PushTrackInterfaceId { get; set; } + + /// + /// 推送轨迹接口 内容项 参数别名前缀 + /// + public string PushTrackItemParamAliasPrefix { get; set; } + /// /// 更新轨迹接口代码 /// diff --git a/src/SJZY.Track.Application/Rabbit/TrackPushCompletedEventHandler.cs b/src/SJZY.Track.Application/Rabbit/TrackPushCompletedEventHandler.cs index e55485a9..11be63aa 100644 --- a/src/SJZY.Track.Application/Rabbit/TrackPushCompletedEventHandler.cs +++ b/src/SJZY.Track.Application/Rabbit/TrackPushCompletedEventHandler.cs @@ -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(@event.Attach); if (optionType == OptionType.新增.ToString()) { - logStr = JsonConvert.DeserializeObject(@event.RequestParams)!.Log; + logStr = JsonConvert.DeserializeObject>(@event.RequestParams)!.First().Log; } else if (optionType == OptionType.修改.ToString()) { - logStr = JsonConvert.DeserializeObject(@event.RequestParams)!.Log; + logStr = JsonConvert.DeserializeObject>(@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 + }; + } } diff --git a/src/SJZY.Track.Application/SJZY.Track.Application.csproj b/src/SJZY.Track.Application/SJZY.Track.Application.csproj index 8226317a..78816ec1 100644 --- a/src/SJZY.Track.Application/SJZY.Track.Application.csproj +++ b/src/SJZY.Track.Application/SJZY.Track.Application.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/SJZY.Track.Application/Services/Broadcasting/BroadcastingService.cs b/src/SJZY.Track.Application/Services/Broadcasting/BroadcastingService.cs new file mode 100644 index 00000000..48345f81 --- /dev/null +++ b/src/SJZY.Track.Application/Services/Broadcasting/BroadcastingService.cs @@ -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; + +/// +/// 广播服务 +/// +public class BroadcastingService : IBroadcastingService +{ + private readonly RabbitClientBus _rabbitClientBus; + + public BroadcastingService(RabbitClientBus rabbitClientBus) + { + _rabbitClientBus = rabbitClientBus; + } + + /// + /// 轨迹变更广播 + /// + /// + /// 子系统代码 + /// 轨迹状态代码 + /// + 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())); + } + + /// + /// 轨迹推送完成广播 + /// + /// + /// + 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); + } +} diff --git a/src/SJZY.Track.Application/Services/ProvideServices/OrderService.cs b/src/SJZY.Track.Application/Services/ProvideServices/OrderService.cs index d529f83d..ed613ffb 100644 --- a/src/SJZY.Track.Application/Services/ProvideServices/OrderService.cs +++ b/src/SJZY.Track.Application/Services/ProvideServices/OrderService.cs @@ -76,7 +76,7 @@ public partial class OrderService : ApplicationService, IOrderService order.OrderFlights = ObjectMapper.Map, List>(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>(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); } diff --git a/src/SJZY.Track.Application/Services/ProvideServices/TrackPushService.cs b/src/SJZY.Track.Application/Services/ProvideServices/TrackPushService.cs index 3934c950..ec3289a3 100644 --- a/src/SJZY.Track.Application/Services/ProvideServices/TrackPushService.cs +++ b/src/SJZY.Track.Application/Services/ProvideServices/TrackPushService.cs @@ -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 _requestCurrentUserService; private readonly ITrackPushConfigRepository _trackPushConfigRepository; private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository; + private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; public TrackPushService(IOrderRepository orderRepository , ICapPublisher capPublisher , ITrackPushRecordEsRepository trackPushRecordEsRepository , IHttpClientProxy 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; } /// @@ -47,7 +53,8 @@ public class TrackPushService : ApplicationService, ITrackPushService /// /// [HttpPost("Track/Push")] - public async Task PushTrackAsync(PushTrackInput input) + [CustomerResult] + public async Task 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 + { + 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 /// /// [HttpGet("Track/GetTrackPushContentList")] - public async Task> GetTrackPushContentListAsync(int subSystemId, int customerId) + [CustomerResult] + public async Task>> GetTrackPushContentListAsync(int subSystemId, int customerId) { var list = await _trackPushConfigRepository.GetTrackPushContentListAsync(subSystemId, customerId); var result = ObjectMapper.Map, List>(list); - return result; + return new UnifyResultDto>(true, result, "操作成功"); } /// @@ -116,7 +193,8 @@ public class TrackPushService : ApplicationService, ITrackPushService /// /// [HttpGet("Track/GetTrackPushRecords")] - public async Task> GetTrackPushRecordsAsync([FromQuery] OrderNoInfo input) + [CustomerResult] + public async Task>> 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>(records); + var res = ObjectMapper.Map, List>(records); + return new UnifyResultDto>(true, res, "操作成功"); + } + + private Dictionary? GetExtraDictForTextJson(string? extraStr) + { + return string.IsNullOrWhiteSpace(extraStr) + ? null + : System.Text.Json.JsonSerializer.Deserialize>(extraStr); } } diff --git a/src/SJZY.Track.Application/Services/ProvideServices/TrackService.Query.cs b/src/SJZY.Track.Application/Services/ProvideServices/TrackService.Query.cs index 6e36fae2..f6768eee 100644 --- a/src/SJZY.Track.Application/Services/ProvideServices/TrackService.Query.cs +++ b/src/SJZY.Track.Application/Services/ProvideServices/TrackService.Query.cs @@ -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; /// - /// 查询轨迹 + /// 查询轨迹(时区补正) /// /// /// @@ -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>(true, res, "操作成功"); + return new UnifyResultDto>(true, sortedList, "操作成功"); } - private void TracksOrdered(List list, GetTrackInput input) + + /// + /// 查询轨迹(原样返回) + /// + /// + /// + [HttpPost("Track/QueryV2")] + public async Task>> QueryV2Async(GetTrackInputV2 input) { - switch (input.SortMethod) + var res = new List(); + 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(); + 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>(true, sortedList, "操作成功"); + } + + //内部系统展示所有轨迹,外部系统则要求过滤外部轨迹 + private async Task 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> TracksOrdered(List 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; + } + + + /// + /// 轨迹发生时间(统一utc)排序,相同时间按模板排序,考虑多个模板的情况 + /// + /// + private static List SortByTemplatesAsync(List seq, List 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>(); + 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 + { + 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)); } } } diff --git a/src/SJZY.Track.Application/Services/ProvideServices/TrackService.cs b/src/SJZY.Track.Application/Services/ProvideServices/TrackService.cs index a73ffe17..f52e1b17 100644 --- a/src/SJZY.Track.Application/Services/ProvideServices/TrackService.cs +++ b/src/SJZY.Track.Application/Services/ProvideServices/TrackService.cs @@ -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 _requestCurrentUserService; private readonly ITrackContentRepository _trackContentRepository; - public TrackService(IOrderRepository orderRepository, ICapPublisher capPublisher, - ITrackDetailsEsRepository trackDetailsEsRepository, ITrackContentRepository trackContentRepository, - IHttpClientProxy requestCurrentUserService) + public TrackService(IOrderRepository orderRepository, + ICapPublisher capPublisher, + ITrackDetailsEsRepository trackDetailsEsRepository, + ITrackContentRepository trackContentRepository, + IHttpClientProxy requestCurrentUserService, + ITrackTemplateRepository trackTemplateRepository, + IHttpContextAccessor contextAccessor, + IVSystemQueries vSystemQueries) { _orderRepository = orderRepository; _capPublisher = capPublisher; _trackContentRepository = trackContentRepository; _requestCurrentUserService = requestCurrentUserService; + _vSystemQueries = vSystemQueries; + _contextAccessor = contextAccessor; + _trackTemplateRepository = trackTemplateRepository; _trackDetailsEsRepository = trackDetailsEsRepository; } - /// - /// 新增、更新轨迹(存在就更新) - /// - /// - /// - /// - [HttpPost("Track/Upsert")] - public async Task 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(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); - } - /// /// 删除轨迹 /// @@ -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(); + 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>(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); } + /// + /// 新增、更新轨迹(存在就更新) + /// + /// + /// + /// + [HttpPost("Track/Upsert")] + public async Task 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(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); + } + /// /// 批量新增、更新轨迹 /// @@ -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> 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> 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(); + } } diff --git a/src/SJZY.Track.Application/Services/TrackContents/TrackContentService.cs b/src/SJZY.Track.Application/Services/TrackContents/TrackContentService.cs index c5e23f8d..4113d9ff 100644 --- a/src/SJZY.Track.Application/Services/TrackContents/TrackContentService.cs +++ b/src/SJZY.Track.Application/Services/TrackContents/TrackContentService.cs @@ -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 _trackStatusBasicQuery; private readonly IOrderRepository _orderRepository; private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; + private readonly ITrackPushCacheService _trackPushCacheService; public TrackContentService( IStringLocalizer stringLocalizer, @@ -40,7 +42,7 @@ public class TrackContentService : ApplicationService IBasicQuery 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; } /// @@ -289,9 +292,40 @@ public class TrackContentService : ApplicationService if (order == default) throw new BusinessException(message: "订单不存在!"); + var res = new UnifyResultDto>(true) + { + Data = new List() + }; + //获取轨迹详情 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>(true) - { - Data = new List() - }; - foreach (var item in contentList) { //新增时跳过已存在轨迹内容 @@ -344,7 +373,7 @@ public class TrackContentService : ApplicationService } - //修改操作,查轨迹内容列表反映明细表数据,重新构造响应数据 + //修改/删除/推送 操作,查轨迹内容列表反映明细表数据,重新构造响应数据 var matchContent = new List(); var contents = res.Data.Select(x => new { diff --git a/src/SJZY.Track.Application/Services/TrackPush/TrackPushCacheService.cs b/src/SJZY.Track.Application/Services/TrackPush/TrackPushCacheService.cs index eb4c6388..5b64b496 100644 --- a/src/SJZY.Track.Application/Services/TrackPush/TrackPushCacheService.cs +++ b/src/SJZY.Track.Application/Services/TrackPush/TrackPushCacheService.cs @@ -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(); diff --git a/src/SJZY.Track.Application/SjzyTrackApplicationModule.cs b/src/SJZY.Track.Application/SjzyTrackApplicationModule.cs index 9318e6ca..a26eb785 100644 --- a/src/SJZY.Track.Application/SjzyTrackApplicationModule.cs +++ b/src/SJZY.Track.Application/SjzyTrackApplicationModule.cs @@ -142,7 +142,8 @@ public class SjzyTrackApplicationModule : AbpModule options.PoolSize = configuration.GetValue("RabbitMQ:PoolSize"); options.Declarations = new List>() { - 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 { @@ -158,6 +159,19 @@ public class SjzyTrackApplicationModule : AbpModule o.QueueBind(GlobalConst.TrackRefreshSubscribe, GlobalConst.InterfaceRelayExchange, GlobalConst.TrackRefreshSubscribe); } + }, + new() + { + Queue = GlobalConst.TrackPushCompletedSubscribe, + FetchCount = configuration.GetValue("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); diff --git a/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordEsRepository.cs b/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordEsRepository.cs index 9f3af683..18485b5a 100644 --- a/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordEsRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderOperationRecordEsRepository.cs @@ -9,6 +9,21 @@ public interface IOrderOperationRecordEsRepository /// Task AddAsync(OrderOperationRecordIndex index); + /// + /// 添加订单操作记录(批量) + /// + /// + /// + Task AddManyAsync(List indexes); + + /// + /// 根据id删除订单操作记录 + /// + /// + /// + /// + Task DeleteOperationRecordByIdAsync(string id, string indexName); + /// /// 获取订单操作记录 /// diff --git a/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderRepository.cs b/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderRepository.cs index 96be07a4..467ab9c0 100644 --- a/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/OrderAggregate/IOrderRepository.cs @@ -12,7 +12,7 @@ public interface IOrderRepository : IBasicRepository /// Task> GetTrackOrderPagedList(GetPageTrackOrderInputVO input); - /// + /// /// 通过三方平台和三方单号查询订单 /// /// 三方平台 @@ -62,6 +62,27 @@ public interface IOrderRepository : IBasicRepository /// Task> GetSubSystemListByCodesAsync(List codes); + #region 订单模板 + + /// + /// 根据订单id,获取订单所属的模板 + /// + /// + /// + Task> GetOrderTemplateAsync(int[] orderId); + + /// + /// 根据子系统+订单号+类型,获取订单所属的模板 + /// + /// + /// + /// + /// + Task> GetOrderTemplateAsync(int subSystemId,int orderType,List orderNos); + + #endregion + + #region 订单关联 /// diff --git a/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsEsRepository.cs b/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsEsRepository.cs index 6f182653..b434e8b6 100644 --- a/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsEsRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/ITrackDetailsEsRepository.cs @@ -10,7 +10,7 @@ public interface ITrackDetailsEsRepository : IElasticsearchClientService> GetLatestListAsync(int[] eventOrderIds, List trackCodes); - Task> GetListAsync(int subSystemId, int orderType, List orderNos); + Task> GetListAsync(int subSystemId, int orderType, List orderNos, List? trackCodes = null); Task AnyAsync(int orderId, string originTrackDataHash); @@ -31,6 +31,8 @@ public interface ITrackDetailsEsRepository : IElasticsearchClientService> DeleteTrackAsync(int[] orderIds, List trackCodes); Task DeleteTrackByIdAsync(string id, string indexName); + Task> GetDataByIds(string[] ids); + Task GetDetailAsync(string orderNo, string trackCode, int orderType, int subSystemId, DateTime trackTime); } diff --git a/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/TrackDetailsIndex.cs b/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/TrackDetailsIndex.cs index 60eed6a3..02cb6b51 100644 --- a/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/TrackDetailsIndex.cs +++ b/src/SJZY.Track.Domain/Aggregates/TrackDetailsAggregate/TrackDetailsIndex.cs @@ -190,5 +190,5 @@ public class TrackDetailsIndex : IdBaseIndex /// /// 扩展信息字段,用与轨迹推送的附加字段 /// - public Dictionary? Extra { get; set; } + public string? Extra { get; set; } } diff --git a/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordEsRepository.cs b/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordEsRepository.cs index ee5840f5..de1f0fa3 100644 --- a/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordEsRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/ITrackPushRecordEsRepository.cs @@ -11,6 +11,14 @@ public interface ITrackPushRecordEsRepository Task AddManyAsync(List index); + /// + /// 根据id删除轨迹推送记录 + /// + /// + /// + /// + Task DeletePushRecordByIdAsync(string id, string indexName); + /// /// 获取轨迹推送记录 /// @@ -22,6 +30,14 @@ public interface ITrackPushRecordEsRepository /// 获取最新轨迹推送记录 /// /// + /// 只选择成功的记录 /// - Task GetLatestTrackPushRecordAsync(int orderId); + Task GetLatestTrackPushRecordAsync(int orderId, bool isSuccess = false); + + /// + /// 获取历史中推送成功的记录 + /// + /// + /// + Task> GetHistorySuccessTrackPushRecordAsync(int orderId); } diff --git a/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/TrackPushConfigEntity.cs b/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/TrackPushConfigEntity.cs index e029921e..8dc04097 100644 --- a/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/TrackPushConfigEntity.cs +++ b/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/TrackPushConfigEntity.cs @@ -56,6 +56,12 @@ public class TrackPushConfigEntity : IFullAuditedBasicEntitys, IAggregateRoot [SugarColumn(ColumnName = "IsAllPush")] public bool IsAllPush { get; set; } + /// + /// 是否全量推送(最新轨迹) + /// + [SugarColumn(ColumnName = "IsAllPushWithLatest")] + public bool IsAllPushWithLatest { get; set; } + /// /// 日期时间格式类型 /// diff --git a/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/TrackPushRecordIndex.cs b/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/TrackPushRecordIndex.cs index f983a5a6..d127b5c4 100644 --- a/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/TrackPushRecordIndex.cs +++ b/src/SJZY.Track.Domain/Aggregates/TrackPushAggregate/TrackPushRecordIndex.cs @@ -82,4 +82,19 @@ public class TrackPushRecordIndex : IdBaseIndex /// 创建时间 public DateTime CreateTime { get; set; } + + /// + /// 子系统代码 + /// + public string SubSystemCode { get; set; } + + /// + /// 是否广播 + /// + public bool IsToMq { get; set; } + + /// + /// 轨迹额外信息 + /// + public string? Extra { get; set; } } diff --git a/src/SJZY.Track.Domain/Aggregates/TrackTemplateAggregate/ITrackTemplateRepository.cs b/src/SJZY.Track.Domain/Aggregates/TrackTemplateAggregate/ITrackTemplateRepository.cs index efae67b2..e8a50f2d 100644 --- a/src/SJZY.Track.Domain/Aggregates/TrackTemplateAggregate/ITrackTemplateRepository.cs +++ b/src/SJZY.Track.Domain/Aggregates/TrackTemplateAggregate/ITrackTemplateRepository.cs @@ -45,6 +45,8 @@ public interface ITrackTemplateRepository : IBasicRepository> whereExpression); Task> GetTrackTemplateListAsync(GetTrackTemplateListInputVo input); + Task> GetPageTrackTemplateListAsync(GetPageTrackTemplateListInputVo input); + Task> GetContentByTemplateCodes(string[] codes, int systemId); } diff --git a/src/SJZY.Track.Domain/Consts/GlobalConst.MQ.cs b/src/SJZY.Track.Domain/Consts/GlobalConst.MQ.cs index e7901efa..93b5b277 100644 --- a/src/SJZY.Track.Domain/Consts/GlobalConst.MQ.cs +++ b/src/SJZY.Track.Domain/Consts/GlobalConst.MQ.cs @@ -57,5 +57,8 @@ public partial class GlobalConst /// public const string TrackChangeExchange = "sjzy.track.on_track_change_ex"; - + /// + /// 轨迹推送通知交换机 + /// + public const string TrackPushExchange = "sjzy.track.on_track_push_ex"; } diff --git a/src/SJZY.Track.Domain/Consts/GlobalConst.cs b/src/SJZY.Track.Domain/Consts/GlobalConst.cs index c6416b1d..f081cacc 100644 --- a/src/SJZY.Track.Domain/Consts/GlobalConst.cs +++ b/src/SJZY.Track.Domain/Consts/GlobalConst.cs @@ -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; diff --git a/src/SJZY.Track.Domain/Dtos/Order/OrderTemplateVO.cs b/src/SJZY.Track.Domain/Dtos/Order/OrderTemplateVO.cs new file mode 100644 index 00000000..26c110a0 --- /dev/null +++ b/src/SJZY.Track.Domain/Dtos/Order/OrderTemplateVO.cs @@ -0,0 +1,13 @@ +namespace SJZY.Track.Domain.Dtos.Order; + +/// +/// 订单关联的模板模型 +/// +public class OrderTemplateVO +{ + public int OrderId { get; set; } + + public string OrderNo { get; set; } + + public string? TemplateCodes { get; set; } +} diff --git a/src/SJZY.Track.Domain/Dtos/TrackContents/TrackContentVO.cs b/src/SJZY.Track.Domain/Dtos/TrackContents/TrackContentVO.cs index 07982ec1..374787e5 100644 --- a/src/SJZY.Track.Domain/Dtos/TrackContents/TrackContentVO.cs +++ b/src/SJZY.Track.Domain/Dtos/TrackContents/TrackContentVO.cs @@ -2,6 +2,11 @@ namespace SJZY.Track.Domain.Dtos.TrackContents; public class TrackContentVO { + /// + /// 子系统Id + /// + public int SubSystemId { get; set; } + /// /// 子系统代码 /// diff --git a/src/SJZY.Track.Domain/Dtos/TrackPush/GetTrackPushConfigDetailOutputVO.cs b/src/SJZY.Track.Domain/Dtos/TrackPush/GetTrackPushConfigDetailOutputVO.cs index 42de155e..f8cff929 100644 --- a/src/SJZY.Track.Domain/Dtos/TrackPush/GetTrackPushConfigDetailOutputVO.cs +++ b/src/SJZY.Track.Domain/Dtos/TrackPush/GetTrackPushConfigDetailOutputVO.cs @@ -51,6 +51,11 @@ public class GetTrackPushConfigDetailOutputVO /// public bool IsAllPush { get; set; } + /// + /// 是否全量推送(最新轨迹) + /// + public bool IsAllPushWithLatest { get; set; } + /// /// 时间格式枚举 /// diff --git a/src/SJZY.Track.Domain/Dtos/TrackTemplate/TrackTemplateOutputVo.cs b/src/SJZY.Track.Domain/Dtos/TrackTemplate/TrackTemplateOutputVo.cs index f84f4294..9996c9b7 100644 --- a/src/SJZY.Track.Domain/Dtos/TrackTemplate/TrackTemplateOutputVo.cs +++ b/src/SJZY.Track.Domain/Dtos/TrackTemplate/TrackTemplateOutputVo.cs @@ -54,4 +54,9 @@ public class TemplateContentDtoVo /// 轨迹节点 /// public string Node { get; set; } + + /// + /// 排序键 + /// + public int Sort { get; set; } } diff --git a/src/SJZY.Track.Domain/Enums/TrackSourceEnum.cs b/src/SJZY.Track.Domain/Enums/TrackSourceEnum.cs index 021616af..a0107979 100644 --- a/src/SJZY.Track.Domain/Enums/TrackSourceEnum.cs +++ b/src/SJZY.Track.Domain/Enums/TrackSourceEnum.cs @@ -8,17 +8,17 @@ namespace SJZY.Track.Domain.Enums; public enum TrackSourceEnum { /// - /// 外部服务商创建 + /// 外部服务商 /// - [Description("外部服务商创建")] ExternalServiceProvide = 1, + [Description("外部服务商")] ExternalServiceProvide = 1, /// - /// 内部系统创建 + /// 内部系统 /// - [Description("内部系统创建")] InternalService = 2, + [Description("内部系统")] InternalService = 2, /// - /// 人工创建 + /// 人工 /// - [Description("人工创建")] Artificial = 3, + [Description("人工")] Artificial = 3, } diff --git a/src/SJZY.Track.Domain/Queries/IProvideQueries.cs b/src/SJZY.Track.Domain/Queries/IProvideQueries.cs index 351224c5..d955ffda 100644 --- a/src/SJZY.Track.Domain/Queries/IProvideQueries.cs +++ b/src/SJZY.Track.Domain/Queries/IProvideQueries.cs @@ -52,6 +52,14 @@ public interface IProvideQueries /// Task GetTrackContentAsync(int subSystemId, string trackCode); + /// + /// 获取轨迹内容 + /// + /// 子系统ids + /// 轨迹代码集合 + /// + Task> GetTrackContentsAsync(int[] subSystemIds, string[] trackCodes); + /// /// 获取所有拉取配置 /// diff --git a/src/SJZY.Track.Domain/Queries/IVSystemQueries.cs b/src/SJZY.Track.Domain/Queries/IVSystemQueries.cs new file mode 100644 index 00000000..0f1b201a --- /dev/null +++ b/src/SJZY.Track.Domain/Queries/IVSystemQueries.cs @@ -0,0 +1,7 @@ +using SJZY.Track.Domain.VEntities; + +namespace SJZY.Track.Domain.Queries; + +public interface IVSystemQueries : IBasicQuery +{ +} diff --git a/src/SJZY.Track.Domain/VEntities/VSystemEntity.cs b/src/SJZY.Track.Domain/VEntities/VSystemEntity.cs index 4639ca98..b5a84874 100644 --- a/src/SJZY.Track.Domain/VEntities/VSystemEntity.cs +++ b/src/SJZY.Track.Domain/VEntities/VSystemEntity.cs @@ -4,7 +4,7 @@ namespace SJZY.Track.Domain.VEntities; /// /// [SugarTable("v_system")] -public class VSystemEntity : IFullAuditedBasicEntitys +public class VSystemEntity : IFullAuditedBasicEntitys, IEntity { /// /// 子系统id diff --git a/src/SJZY.Track.Repository/Queries/ProvideQueries.cs b/src/SJZY.Track.Repository/Queries/ProvideQueries.cs index b59a308d..3dc93cee 100644 --- a/src/SJZY.Track.Repository/Queries/ProvideQueries.cs +++ b/src/SJZY.Track.Repository/Queries/ProvideQueries.cs @@ -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() .LeftJoin((x, y) => x.TrackStatusId == y.Id) .LeftJoin((x, y, z) => x.SubSystemId == z.Id) + .LeftJoin((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> GetTrackContentsAsync(int[] subSystemIds, string[] trackCodes) + { + return await Context.Queryable() + .LeftJoin((x, y) => x.TrackStatusId == y.Id) + .LeftJoin((x, y, z) => x.SubSystemId == z.Id) + .LeftJoin((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> GetAllPullConfigsAsync() { return await Context.Queryable() diff --git a/src/SJZY.Track.Repository/Queries/VSystemQueries.cs b/src/SJZY.Track.Repository/Queries/VSystemQueries.cs new file mode 100644 index 00000000..872f9ea2 --- /dev/null +++ b/src/SJZY.Track.Repository/Queries/VSystemQueries.cs @@ -0,0 +1,8 @@ +using SJZY.Track.Domain.Queries; +using SJZY.Track.Domain.VEntities; + +namespace SJZY.Track.Repository.Queries; + +public class VSystemQueries : BasicQuery, IVSystemQueries, ITransientDependency +{ +} diff --git a/src/SJZY.Track.Repository/Repositories/OrderOperationRecordEsRepository.cs b/src/SJZY.Track.Repository/Repositories/OrderOperationRecordEsRepository.cs index 99d71604..7230d375 100644 --- a/src/SJZY.Track.Repository/Repositories/OrderOperationRecordEsRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/OrderOperationRecordEsRepository.cs @@ -29,6 +29,36 @@ public class OrderOperationRecordEsRepository : ElasticSearchContextAbstract 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(id, u => u + .Index(indexName) + ); + + if (!r.IsValid) + { + throw new InvalidOperationException($"轨迹操作记录失败,id:{indexName},原因:{r.DebugInformation}。"); + } + } + public async Task> GetOrderOperationsByIdAsync(int orderId) { var data = await Context.SearchAsync(x => x @@ -39,6 +69,6 @@ public class OrderOperationRecordEsRepository : ElasticSearchContextAbstract s.Field(f => f.CreateTime, SortOrder.Descending))); - return data.Documents.ToList(); + return data.ToIdList(); } } diff --git a/src/SJZY.Track.Repository/Repositories/OrderRepository.cs b/src/SJZY.Track.Repository/Repositories/OrderRepository.cs index 413097c8..b07a00ec 100644 --- a/src/SJZY.Track.Repository/Repositories/OrderRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/OrderRepository.cs @@ -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, IOrderRepository, ITransientDependency { - public async Task> GetTrackOrderPagedList(GetPageTrackOrderInputVO input) + //MemoryCache本身线程安全简单使用,短暂数据不一致可以容忍,每过固定时间强制清除缓存 + private static readonly MemoryCache _memoryCache = new(new MemoryCacheOptions()); + private static readonly Dictionary _empty = new(); + private static readonly Dictionary> _empty2 = new(); + private const int ThirdPartyOrderNumberCacheTimeSpan = 3 * 60; + private const int MainDataEnumCacheTimeSpan = 5 * 60; + + private async Task> GetThirdPartyOrderNumberForCacheAsync(int[] orderIds) { - //查询完去重 - var pageList = await Context.Queryable() + const string key = "ThirdPartyOrderNumber"; + if (orderIds.Length == 0) return _empty; + + var dict = _memoryCache.Get>(key); + + orderIds = orderIds.Distinct().ToArray(); + if (dict != null && dict.Count != 0 && !orderIds.Except(dict.Keys).Any()) return dict; + + var orders = await Context.Queryable() .Includes(a => a.OrderPlatformNumbers) - .LeftJoin((a, b) => a.CustomId == b.Id && b.IsDeleted == false) - .LeftJoin((a, b, c) => a.SubSystemId == c.Id && c.IsDeleted == false) - .LeftJoin((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((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>> GetMainDataEnumForCacheAsync(List orderTypes, List questTypes) + { + const string key = "EnumForMainData"; + if (orderTypes.Count == 0 && questTypes.Count == 0) return _empty2; + + var dict = _memoryCache.Get>>(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() + dict = (await Context.Queryable() .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, 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> GetTrackOrderPagedList(GetPageTrackOrderInputVO input) + { + //查询完去重 + UnifyPageResultDto pageList; + //没传递单号时 + if (input.OrderNos.IsNullOrEmpty()) + { + pageList = await Context.Queryable() + .LeftJoin((a, b) => a.CustomId == b.Id && b.IsDeleted == false) + .LeftJoin((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() + .Where(x => orderNos.Contains(x.OrderNo)) + .Select(x => x.Id) + .ToListAsync(); + + //匹配外部单 + var orderIdsForThirdParty = await Context.Queryable() + .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 + { + List = new List(), + Pagination = new PageInfoOutput() + { + PageIndex = input.PageIndex, + PageSize = input.PageSize, + Total = 0 + } + }; + } + + pageList = await Context.Queryable() + .Where(x => orderIds.Contains(x.Id)) + .LeftJoin((a, b) => a.CustomId == b.Id && b.IsDeleted == false) + .LeftJoin((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, IOrderRepository, I return res.Select(x => (x.Key, x.Id)).ToList(); } + public Task> GetOrderTemplateAsync(int[] orderId) + { + return Context.Queryable() + .Where(x => orderId.Contains(x.Id)) + .Select(a => new OrderTemplateVO + { + OrderId = a.Id, + OrderNo = a.OrderNo, + TemplateCodes = a.TemplateCodes + }) + .ToListAsync(); + } + + public Task> GetOrderTemplateAsync(int subSystemId, int orderType, List orderNos) + { + return Context.Queryable() + .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 GetSystemInfoById(int id) { return Context.Queryable().FirstAsync(m => m.IsDeleted == false && m.Id == id); @@ -274,12 +449,15 @@ public class OrderRepository : BasicRepository, IOrderRepository, I /// /// /// - private static string BuildThirdPartyOrderNumber(List orderPlatformNumberEntities) + public string BuildThirdPartyOrderNumber(List 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); } diff --git a/src/SJZY.Track.Repository/Repositories/TrackDetailsEsRepository.cs b/src/SJZY.Track.Repository/Repositories/TrackDetailsEsRepository.cs index e25c9b89..b9bf22af 100644 --- a/src/SJZY.Track.Repository/Repositories/TrackDetailsEsRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/TrackDetailsEsRepository.cs @@ -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> GetListAsync(int[] orderIds, string trackCode, bool forUpsert = false) { - var r = !forUpsert + var r = forUpsert ? await Context.SearchAsync(s => s .Index(GlobalConst.TrackDetailsIndex) .Source() @@ -373,36 +372,38 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract> GetListAsync(int subSystemId, int orderType, List orderNos) + public async Task> GetListAsync(int subSystemId, int orderType, List orderNos, List? trackCodes = null) { if (orderNos.Count < 1) return new List(); + var musts = new List, 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(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> GetListAsync(int orderId) @@ -429,7 +430,10 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract 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 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) { diff --git a/src/SJZY.Track.Repository/Repositories/TrackPushRecordEsRepository.cs b/src/SJZY.Track.Repository/Repositories/TrackPushRecordEsRepository.cs index 5e448eaa..3cbef97d 100644 --- a/src/SJZY.Track.Repository/Repositories/TrackPushRecordEsRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/TrackPushRecordEsRepository.cs @@ -45,6 +45,18 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract(id, u => u + .Index(indexName) + ); + + if (!r.IsValid) + { + throw new InvalidOperationException($"轨迹推送记录失败,id:{indexName},原因:{r.DebugInformation}。"); + } + } + public async Task> GetTrackPushRecordsAsync(int orderId) { @@ -56,19 +68,46 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract s.Field(f => f.CreateTime, SortOrder.Descending))); - return data.Documents.ToList(); + return data.ToIdList(); } - public async Task GetLatestTrackPushRecordAsync(int orderId) + public async Task GetLatestTrackPushRecordAsync(int orderId, bool isSuccess = false) { + var musts = new List, QueryContainer>> + { + mu => mu.Term(t => t.OrderId, orderId), + }; + + if (isSuccess) + musts.Add(mu => mu.Term(t => t.IsSuccess, true)); + var data = await Context.SearchAsync(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> GetHistorySuccessTrackPushRecordAsync(int orderId) + { + var data = await Context.SearchAsync(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(); + } } diff --git a/src/SJZY.Track.Repository/Repositories/TrackTemplateRepository.cs b/src/SJZY.Track.Repository/Repositories/TrackTemplateRepository.cs index d399bcd1..2f9b4a2d 100644 --- a/src/SJZY.Track.Repository/Repositories/TrackTemplateRepository.cs +++ b/src/SJZY.Track.Repository/Repositories/TrackTemplateRepository.cs @@ -214,7 +214,8 @@ public class TrackTemplateRepository : BasicRepository, 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, 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, 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; }