add-interface:增加轨迹查询提供接口(返回原始轨迹)QueryV2Async

update:更新轨迹删除广播方式,减少不必要的查库

update:添加轨迹查询的创建时间的二级排序(原只按轨迹发生时间)
This commit is contained in:
hardstylewyl 2024-12-31 11:58:47 +08:00
parent c92351a601
commit a7e303a77a
10 changed files with 346 additions and 70 deletions

View File

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

View File

@ -4,10 +4,10 @@ using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.Contract.Services;
public interface IBroadcastingService :ITransientDependency
public interface IBroadcastingService : ITransientDependency
{
/// <summary>
/// 轨迹变更广播
/// 轨迹变更广播(新增/修改)
/// </summary>
/// <param name="eto"></param>
/// <param name="subSystemCode"></param>
@ -15,7 +15,15 @@ public interface IBroadcastingService :ITransientDependency
void TrackChangePublicTopicAsync(TrackUpsertEvent eto, string subSystemCode, string trackStatusCode);
/// <summary>
/// 轨迹推送完成广播
/// 轨迹变更广播(删除)
/// </summary>
/// <param name="eto"></param>
/// <param name="subSystemCode"></param>
/// <param name="trackStatusCode"></param>
void TrackChangePublicTopicAsync(TrackDeleteEvent eto, OrderIdentifier[] orderIdentifiers, string trackCode, string trackStatusCode, string subSystemCode);
/// <summary>
/// 轨迹推送完成广播(推送完成回调)
/// </summary>
/// <param name="log"></param>
void TrackPushCompletedPublicTopicAsync(TrackPushRecordDto log);

View File

@ -35,8 +35,17 @@ public interface ITrackService : IRemoteService
/// <summary>
/// 根据订单号获取轨迹
/// 支持模板,传入时区补正,按轨迹发生时间升序降序
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<UnifyResultDto<List<GetTrackOutput>>> QueryAsync(GetTrackInput input);
/// <summary>
/// 根据订单号获取轨迹
/// 不支持模板,无时区补正,可按轨迹发生时间升序降序
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<UnifyResultDto<List<GetTrackOutput>>> QueryV2Async(GetTrackInputV2 input);
}

View File

@ -1,14 +1,11 @@
using System.Text;
using BSI.Stack.Rabbit;
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.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;
@ -21,27 +18,21 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
private readonly IOrderRepository _orderRepository;
private readonly IProvideQueries _provideQueries;
private readonly RabbitClientBus _rabbitClientBus;
private readonly ITrackContentRepository _trackContentRepository;
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly ITrackStatusRepository _trackStatusRepository;
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,7 +42,8 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
var trackList = await DeleteTrackAsync(@event);
//发布事件
//当轨迹删除,根据轨迹标记/状态上IsToMq选择进行广播
if (trackList.Count != 0)
await PublicTrackTopicAsync(@event, trackList);
// 根据订单关联关系异步向下刷(异步)
@ -128,48 +120,41 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
private async Task PublicTrackTopicAsync(TrackDeleteEvent eto, List<TrackDetailsIndex> trackList)
{
// // todo: review 这里查库太多了
if (trackList == null)
return;
var systemIds = trackList.Select(x => x.SubSystemId).Distinct().ToArray();
var trackCodes = trackList.Select(x => x.TrackCode).Distinct().ToArray();
var statusList = await _trackStatusRepository.GetListAsync(m => m.SubSystemId == eto.SubSystemId);
if (statusList == null)
return;
//从轨迹内容中查询出标记是否需要mq广播
var contentList = await _provideQueries.GetTrackContentsAsync(systemIds, trackCodes);
var toMqDict = contentList
.ToDictionary(x => x.SubSystemId + x.TrackContentCode, y => y.IsToMq);
var contentList = await _trackContentRepository.GetTrackContentBySyetemIdAsync(eto.SubSystemId);
if (contentList == null)
return;
foreach (var dataItem in trackList.GroupBy(m => new
//选出需要广播的轨迹,并按code和订单号分组
var trackGroup = trackList
.Where(x => toMqDict[x.SubSystemId + x.TrackCode])
.GroupBy(m => new
{
m.TrackCode,
m.OrderNo
}))
})
.ToList();
if (trackGroup.Count == 0) return;
foreach (var dataItem in trackGroup)
{
var item = dataItem.First();
var status = statusList.FirstOrDefault(m => m.Code == item.TrackStatusCode);
if (status == null || !status.IsToMq)
continue;
var content = contentList.First(m => m.TrackContentCode == item.TrackCode);
var content = contentList.FirstOrDefault(m => m.TrackContentCode == item.TrackCode);
var data = new TrackChangeEto()
{
OrderIdentifiers = dataItem.Select(m => new OrderIdentifier
_broadcastingService.TrackChangePublicTopicAsync(eto,
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()));
item.TrackCode,
item.TrackStatusCode,
content.SubSystemCode
);
}
}

View File

@ -65,6 +65,22 @@ public class BroadcastingService : IBroadcastingService
Encoding.UTF8.GetBytes(data.ToJson()));
}
public void TrackChangePublicTopicAsync(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()));
}
/// <summary>
/// 轨迹推送完成广播
/// </summary>

View File

@ -1,5 +1,7 @@
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;
namespace SJZY.Track.Application.Services.ProvideServices;
@ -9,6 +11,7 @@ namespace SJZY.Track.Application.Services.ProvideServices;
public partial class TrackService
{
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly ITrackTemplateRepository _trackTemplateRepository;
/// <summary>
/// 查询轨迹
@ -87,25 +90,240 @@ public partial class TrackService
}
// 排序
TracksOrdered(res, input);
var sortedList = await TracksOrdered(res, input);
return new UnifyResultDto<List<GetTrackOutput>>(true, res, "操作成功");
return new UnifyResultDto<List<GetTrackOutput>>(true, sortedList, "操作成功");
}
private void TracksOrdered(List<GetTrackOutput> list, GetTrackInput input)
[HttpPost("Track/QueryV2")]
public async Task<UnifyResultDto<List<GetTrackOutput>>> QueryV2Async(GetTrackInputV2 input)
{
var res = new List<GetTrackOutput>();
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空
var details = await _trackDetailsEsRepository.GetListAsync(input.SubSystemId, input.OrderType, input.OrderNos);
// 是否对外
var trackCodes = details.Select(x => x.TrackCode)
.Where(x => x != GlobalConst.EmptyString)
.Distinct()
.ToList();
var trackContentDic = new Dictionary<string, bool>();
if (!trackCodes.IsNullOrEmpty())
{
var trackContents = await _trackContentRepository.GetListAsync(x =>
x.SubSystemId == input.SubSystemId && trackCodes.Contains(x.Code),
x => new
{
x.Code,
x.IsExternal
});
trackContents.ForEach(x => trackContentDic.Add(x.Code, x.IsExternal));
}
foreach (var group in details.GroupBy(x => x.OrderId))
{
var item = new GetTrackOutput();
var trackDetails = group.ToList();
item.OrderNo = trackDetails.First().OrderNo;
item.TrackDetails = trackDetails.Select(x => new TrackDetail
{
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);
return new UnifyResultDto<List<GetTrackOutput>>(true, sortedList, "操作成功");
}
private async Task<List<GetTrackOutput>> TracksOrdered(List<GetTrackOutput> list, GetTrackInput input)
{
switch (input.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, "不支持的排序方式");
}
if (input.SortMethod != SortMethod.ByTemplateAndTrackTime
&& input.SortMethod != SortMethod.ByTemplateAndTrackTimeDesc)
{
return list;
}
//查出订单模板
var templates = await _trackTemplateRepository.GetTrackTemplateListAsync(new GetTrackTemplateListInputVo
{
SubSystemId = input.SubSystemId,
TemplateCode = input.TrackTemplateCode
});
if (templates.Count == 0)
{
throw new BusinessException("轨迹模板不存在");
}
foreach (var getTrackOutput in list)
{
getTrackOutput.TrackDetails = SortByTemplate(getTrackOutput.TrackDetails, templates, input.SortMethod.Value);
}
return list;
}
private async Task<List<GetTrackOutput>> TracksOrdered(List<GetTrackOutput> list, GetTrackInputV2 input)
{
switch (input.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, "不支持的排序方式");
}
return list;
}
//按模板排序
private static List<TrackDetail> SortByTemplate(List<TrackDetail> tracks, List<TrackTemplateOutputVo> templates, SortMethod sortMethod)
{
if (templates.Count == 0 ||
tracks.Count == 0)
{
return new List<TrackDetail>();
}
var isAsc = sortMethod == SortMethod.ByTemplateAndTrackTime;
var allTemplateCodes = new HashSet<string>();
foreach (var template in templates)
{
tracks = Sort(tracks, template, isAsc, allTemplateCodes);
}
// tracks = templates.Aggregate(tracks,
// (current, template) => Sort(current, template, isAsc, allTemplateCodes));
return tracks;
}
private static List<TrackDetail> Sort(List<TrackDetail> seq, TrackTemplateOutputVo template, bool isAsc = true, HashSet<string>? allTemplateCodes = null)
{
if (template.TemplateContents.Count == 0)
{
return seq;
}
// 创建一个字典,用于存储排序序列中元素的索引
var sortDict = new Dictionary<TrackDetail, int>();
template.TemplateContents.Sort((x, y) => x.Sort.CompareTo(y.Sort));
var sortSeq = template.TemplateContents
.Select(x => seq.FirstOrDefault(y => y.Code == x.Code))
.Where(x => x != null)
.ToList();
for (var i = 0; i < sortSeq.Count; i++)
{
sortDict[sortSeq[i]!] = isAsc ? i : sortSeq.Count - i;
}
// 将模板code添加到上下文集合中
if (allTemplateCodes != null)
{
allTemplateCodes.UnionWith(template.TemplateContents.Select(x => x.Code));
}
// 根据排序序列和字典中的索引对序列进行排序
// 使用稳定排序算法,如OrderBy,它保持相等元素的相对顺序
var sorted = seq.OrderBy(item => sortDict.GetValueOrDefault(item, int.MaxValue)).ToList();
// 存在
if (allTemplateCodes != null)
{
//查询到没有命中到模板的轨迹
var noTipTemplateTracks = sorted.Where(x => !allTemplateCodes.Contains(x.Code)).ToList();
//查询未命中轨迹的索引字典
var trackIdxDict = new Dictionary<TrackDetail, int>();
var trackSortDict = new SortedDictionary<int, TrackDetail>();
foreach (var track in noTipTemplateTracks)
{
trackIdxDict[track] = sorted.IndexOf(track);
trackSortDict[trackIdxDict[track]] = track;
}
//没有命中的轨迹排序
var noTipTemplateTrackSorted = (isAsc
? noTipTemplateTracks.OrderBy(x => x.Time)
: noTipTemplateTracks.OrderByDescending(x => x.Time))
.ToList();
for (var i = 0; i < trackSortDict.Count; i++)
{
//在序列中的实际位置
var (idx, st) = trackSortDict.ElementAt(0);
//在这个位置它应该是哪个轨迹
var track = noTipTemplateTrackSorted[0];
//交换位置
if (track != st)
{
var idx2 = trackIdxDict[track];
sorted[idx2] = st;
sorted[idx] = track;
}
}
}
return sorted;
}
}

View File

@ -5,6 +5,7 @@ using SJZY.Track.Application.Contract.Services.ProvideServices;
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.UserCenter.HttpApi.Services;
using SJZY.UserCenter.HttpApi.Services.User;
@ -26,12 +27,13 @@ public partial class TrackService : ApplicationService, ITrackService
public TrackService(IOrderRepository orderRepository, ICapPublisher capPublisher,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackContentRepository trackContentRepository,
IHttpClientProxy<IUserBaseInfoService> requestCurrentUserService)
IHttpClientProxy<IUserBaseInfoService> requestCurrentUserService, ITrackTemplateRepository trackTemplateRepository)
{
_orderRepository = orderRepository;
_capPublisher = capPublisher;
_trackContentRepository = trackContentRepository;
_requestCurrentUserService = requestCurrentUserService;
_trackTemplateRepository = trackTemplateRepository;
_trackDetailsEsRepository = trackDetailsEsRepository;
}

View File

@ -31,6 +31,8 @@ public interface ITrackDetailsEsRepository : IElasticsearchClientService<TrackDe
Task<List<int>> DeleteTrackAsync(int[] orderIds, List<string> trackCodes);
Task DeleteTrackByIdAsync(string id, string indexName);
Task<List<TrackDetailsIndex>> GetDataByIds(string[] ids);
Task<TrackDetailsIndex> GetDetailAsync(string orderNo, string trackCode, int orderType, int subSystemId, DateTime trackTime);
}

View File

@ -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,20 +100,22 @@ public class ProvideQueries : ISugarRepository, IProvideQueries, ITransientDepen
return await Context.Queryable<TrackContentEntity>()
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
.LeftJoin<VSystemEntity>((x, y, z) => x.SubSystemId == z.Id)
.LeftJoin<TrackFlagEntity>((x, y, z, k) => x.SubSystemId == k.SubSystemId && k.Code == x.Code)
.Where(x => x.SubSystemId == subSystemId && x.Code == trackCode)
.Select((x, y, z) => new TrackContentVO
.Select((x, y, z, k) => new TrackContentVO
{
SubSystemCode = z.Key,
TrackStatusId = x.TrackStatusId,
TrackStatus = y.Status,
TrackStatusCode = y.Code,
TrackContentCode = x.Code,
TrackContent = x.Content,
TrackEnContent = x.EnContent,
IsCompleted = y.IsCompleted,
IsRepeatable = x.IsRepeatable,
IsAllowManuallyUpdate = x.IsAllowManuallyUpdate,
IsAllowManuallyCreate = x.IsAllowManuallyCreate,
IsToMq = y.IsToMq,
IsToMq = y.IsToMq || k.IsToMq,
})
.FirstAsync();
}
@ -122,21 +125,23 @@ public class ProvideQueries : ISugarRepository, IProvideQueries, ITransientDepen
return await Context.Queryable<TrackContentEntity>()
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
.LeftJoin<VSystemEntity>((x, y, z) => x.SubSystemId == z.Id)
.LeftJoin<TrackFlagEntity>((x, y, z, k) => x.SubSystemId == k.SubSystemId && k.Code == x.Code)
.Where(x => subSystemIds.Contains(x.SubSystemId) && trackCodes.Contains(x.Code))
.Select((x, y, z) => new TrackContentVO
.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,
IsToMq = y.IsToMq || k.IsToMq,
})
.ToListAsync();
}

View File

@ -430,7 +430,10 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
q.Term(t => t.OrderId, orderId)
)
.Size(SafelyLimit)
.Sort(s => s.Field(f => f.ZeroTrackTime, SortOrder.Descending)));
.Sort(s => s
.Field(f => f.ZeroTrackTime, SortOrder.Descending)
.Field(f => f.CreateTime, SortOrder.Descending)
));
if (!r.IsValid)
{
@ -463,7 +466,10 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
.Index(GlobalConst.TrackDetailsIndex)
.Query(q => query)
.Size(SafelyLimit)
.Sort(s => s.Field(f => f.ZeroTrackTime, SortOrder.Descending)));
.Sort(s => s
.Field(f => f.ZeroTrackTime, SortOrder.Descending)
.Field(f => f.CreateTime, SortOrder.Descending))
);
if (!r.IsValid)
{