feat:查询轨迹响应结果相同时间按模板排序(按时间+时区转换为统一时间后排序)

This commit is contained in:
hardstylewyl 2025-01-03 17:42:18 +08:00
parent 5a43e54139
commit bb96f96695
6 changed files with 229 additions and 117 deletions

View File

@ -14,7 +14,7 @@ public partial class TrackService
private readonly ITrackTemplateRepository _trackTemplateRepository; private readonly ITrackTemplateRepository _trackTemplateRepository;
/// <summary> /// <summary>
/// 查询轨迹 /// 查询轨迹(时区补正)
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
@ -90,12 +90,17 @@ public partial class TrackService
} }
// 排序 // 排序
var sortedList = await TracksOrdered(res, input); var sortedList = await TracksOrdered(res, input, input.SortMethod, input.TrackTemplateCode);
return new UnifyResultDto<List<GetTrackOutput>>(true, sortedList, "操作成功"); return new UnifyResultDto<List<GetTrackOutput>>(true, sortedList, "操作成功");
} }
/// <summary>
/// 查询轨迹(原样返回)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Track/QueryV2")] [HttpPost("Track/QueryV2")]
public async Task<UnifyResultDto<List<GetTrackOutput>>> QueryV2Async(GetTrackInputV2 input) public async Task<UnifyResultDto<List<GetTrackOutput>>> QueryV2Async(GetTrackInputV2 input)
{ {
@ -163,15 +168,15 @@ public partial class TrackService
} }
// 排序 // 排序
var sortedList = await TracksOrdered(res, input); var sortedList = await TracksOrdered(res, input, input.SortMethod);
return new UnifyResultDto<List<GetTrackOutput>>(true, sortedList, "操作成功"); return new UnifyResultDto<List<GetTrackOutput>>(true, sortedList, "操作成功");
} }
private async Task<List<GetTrackOutput>> TracksOrdered(List<GetTrackOutput> list, GetTrackInput input) private async Task<List<GetTrackOutput>> TracksOrdered(List<GetTrackOutput> list, OrderNosInfo orderNosInfo, SortMethod? sortMethod, string? trackTemplateCode = null)
{ {
switch (input.SortMethod) switch (sortMethod)
{ {
case SortMethod.ByTrackTime: case SortMethod.ByTrackTime:
case SortMethod.ByTemplateAndTrackTime: case SortMethod.ByTemplateAndTrackTime:
@ -184,11 +189,11 @@ public partial class TrackService
list.ForEach(item => item.TrackDetails.Sort((a, b) => b.Time.CompareTo(a.Time))); list.ForEach(item => item.TrackDetails.Sort((a, b) => b.Time.CompareTo(a.Time)));
break; break;
default: default:
throw new ArgumentOutOfRangeException(nameof(input.SortMethod), input.SortMethod, "不支持的排序方式"); throw new ArgumentOutOfRangeException(nameof(sortMethod), sortMethod, "不支持的排序方式");
} }
if (input.SortMethod != SortMethod.ByTemplateAndTrackTime if (sortMethod != SortMethod.ByTemplateAndTrackTime
&& input.SortMethod != SortMethod.ByTemplateAndTrackTimeDesc) && sortMethod != SortMethod.ByTemplateAndTrackTimeDesc)
{ {
return list; return list;
} }
@ -196,134 +201,178 @@ public partial class TrackService
//查出订单模板 //查出订单模板
var templates = await _trackTemplateRepository.GetTrackTemplateListAsync(new GetTrackTemplateListInputVo var templates = await _trackTemplateRepository.GetTrackTemplateListAsync(new GetTrackTemplateListInputVo
{ {
SubSystemId = input.SubSystemId, SubSystemId = orderNosInfo.SubSystemId,
TemplateCode = input.TrackTemplateCode TemplateCode = trackTemplateCode
}); });
if (templates.Count == 0) if (templates.Count == 0)
{ {
throw new BusinessException("轨迹模板不存在"); 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) foreach (var getTrackOutput in list)
{ {
getTrackOutput.TrackDetails = SortByTemplate(getTrackOutput.TrackDetails, templates, input.SortMethod.Value); //查看该订单具有的模板
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; return list;
} }
private async Task<List<GetTrackOutput>> TracksOrdered(List<GetTrackOutput> list, GetTrackInputV2 input) /// <summary>
/// 轨迹发生时间(统一utc)排序,相同时间按模板排序,考虑多个模板的情况
/// </summary>
/// <returns></returns>
private static List<TrackDetail> SortByTemplatesAsync(List<TrackDetail> seq, List<TrackTemplateOutputVo> templates, bool isAsc = true)
{ {
switch (input.SortMethod) //转化为统一时间
var trackWraps = seq.Select(x => new TrackWrap(x));
var sortedTracks = (isAsc
? trackWraps.OrderBy(x => x.Time)
: trackWraps.OrderByDescending(x => x.Time)).ToList();
//用于统计时间相同的轨迹
var timeEqTrackDict = new Dictionary<DateTime, List<TrackIdxWarp>>();
for (var i = 0; i < sortedTracks.Count; i++)
{ {
case SortMethod.ByTrackTime: var trackWrap = sortedTracks[i];
case SortMethod.ByTemplateAndTrackTime: if (timeEqTrackDict.TryGetValue(trackWrap.Time, out var list))
// tips:quick-sort 直接修改原list list.Add(new TrackIdxWarp(trackWrap.Track, i));
list.ForEach(item => item.TrackDetails.Sort((a, b) => a.Time.CompareTo(b.Time))); else
break; timeEqTrackDict.Add(trackWrap.Time, new List<TrackIdxWarp>
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 || new TrackIdxWarp(trackWrap.Track, i)
tracks.Count == 0) });
}
//选出需要按模板重排序的轨迹
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--)
{ {
return new List<TrackDetail>(); var curCodeSortDict = templates[i]
} .TemplateContents
.ToDictionary(x => x.Code, y => y.Sort);
var isAsc = sortMethod == SortMethod.ByTemplateAndTrackTime; //没在之前模板出现的code
var noTipPreCodeSortDict = curCodeSortDict
.Where(x => !preSortDict.ContainsKey(x.Key))
.ToDictionary(x => x.Key, y => y.Value);
var allTemplateCodes = new HashSet<string>(); //插入未命中的模板code
foreach (var (noTipCode, curNoTipCodeSort) in noTipPreCodeSortDict)
foreach (var template in templates)
{ {
tracks = Sort(tracks, template, isAsc, allTemplateCodes); //初始化插入位置
} var insertPos = -1;
// tracks = templates.Aggregate(tracks, foreach (var (preCode, _) in preSortDict)
// (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) if (curCodeSortDict.TryGetValue(preCode, out var preCodeSort))
{ {
return seq; //未命中的相对于之前模板的偏后,设置插入位置在
} if (preCodeSort < curNoTipCodeSort)
// 创建一个字典,用于存储排序序列中元素的索引
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; insertPos = preSortDict[preCode] + 1;
}
}
} }
// 将模板code添加到上下文集合中 //结束了还没选好位置,未命中的code相对于之前的模板code在当前模板最前面
if (allTemplateCodes != null) if (insertPos == -1)
{ {
allTemplateCodes.UnionWith(template.TemplateContents.Select(x => x.Code)); foreach (var (c, s) in preSortDict)
preSortDict[c] = s + 1;
preSortDict[noTipCode] = 0;
} }
else
// 根据排序序列和字典中的索引对序列进行排序
// 使用稳定排序算法,如OrderBy,它保持相等元素的相对顺序
var sorted = seq.OrderBy(item => sortDict.GetValueOrDefault(item, int.MaxValue)).ToList();
// 存在
if (allTemplateCodes != null)
{ {
//查询到没有命中到模板的轨迹 foreach (var (c, s) in preSortDict.Where(x => x.Value >= insertPos))
var noTipTemplateTracks = sorted.Where(x => !allTemplateCodes.Contains(x.Code)).ToList(); preSortDict[c] = s + 1;
preSortDict[noTipCode] = insertPos;
}
}
}
//查询未命中轨迹的索引字典
var trackIdxDict = new Dictionary<TrackDetail, int>(); foreach (var trackIdxWarps in timeEqTracks.Values)
var trackSortDict = new SortedDictionary<int, TrackDetail>();
foreach (var track in noTipTemplateTracks)
{ {
trackIdxDict[track] = sorted.IndexOf(track); var curIndex = trackIdxWarps.Min(x => x.Index);
trackSortDict[trackIdxDict[track]] = track; //按模板从小到大排序
} var sortByTemplate = (isAsc
? trackIdxWarps
//没有命中的轨迹排序 .OrderBy(x => preSortDict.GetValueOrDefault(x.Track.Code, int.MaxValue))
var noTipTemplateTrackSorted = (isAsc : trackIdxWarps
? noTipTemplateTracks.OrderBy(x => x.Time) .OrderByDescending(x => preSortDict.GetValueOrDefault(x.Track.Code, int.MaxValue))
: noTipTemplateTracks.OrderByDescending(x => x.Time)) ).ToList();
.ToList(); for (var j = 0; j < sortByTemplate.Count; j++)
for (var i = 0; i < trackSortDict.Count; i++)
{ {
//在序列中的实际位置 var track = sortByTemplate[j];
var (idx, st) = trackSortDict.ElementAt(0); //当前索引不对应
//在这个位置它应该是哪个轨迹 if (track.Index != curIndex + j)
var track = noTipTemplateTrackSorted[0];
//交换位置
if (track != st)
{ {
var idx2 = trackIdxDict[track]; var temp1 = sortedTracks[track.Index].Track;
sorted[idx2] = st; var temp2 = sortedTracks[curIndex + j].Track;
sorted[idx] = 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 sorted; return sortedTracks.Select(x => x.Track).ToList();
}
private class TrackIdxWarp
{
public TrackDetail Track { get; set; }
public int Index { get; set; }
public TrackIdxWarp(TrackDetail track, int index)
{
Track = track;
Index = index;
}
}
private class TrackWrap
{
public TrackDetail Track { get; set; }
public DateTime Time { get; set; }
public TrackWrap(TrackDetail track)
{
Track = track;
var timeZone = track.TimeZone ?? GlobalConst.NoneTrackTimeZone;
Time = Math.Abs(timeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.Time
: track.Time.Subtract(TimeSpan.FromHours(timeZone));
}
} }
} }

View File

@ -30,6 +30,7 @@ public partial class TrackService : ApplicationService, ITrackService
IHttpClientProxy<IUserBaseInfoService> requestCurrentUserService, ITrackTemplateRepository trackTemplateRepository) IHttpClientProxy<IUserBaseInfoService> requestCurrentUserService, ITrackTemplateRepository trackTemplateRepository)
{ {
_orderRepository = orderRepository; _orderRepository = orderRepository;
this._orderRepository = orderRepository;
_capPublisher = capPublisher; _capPublisher = capPublisher;
_trackContentRepository = trackContentRepository; _trackContentRepository = trackContentRepository;
_requestCurrentUserService = requestCurrentUserService; _requestCurrentUserService = requestCurrentUserService;

View File

@ -62,6 +62,27 @@ public interface IOrderRepository : IBasicRepository<OrderEntity>
/// <returns></returns> /// <returns></returns>
Task<List<(string code, int id)>> GetSubSystemListByCodesAsync(List<string> codes); Task<List<(string code, int id)>> GetSubSystemListByCodesAsync(List<string> codes);
#region 订单模板
/// <summary>
/// 根据订单id,获取订单所属的模板
/// </summary>
/// <param name="orderId"></param>
/// <returns></returns>
Task<List<OrderTemplateVO>> GetOrderTemplateAsync(int[] orderId);
/// <summary>
/// 根据子系统+订单号+类型,获取订单所属的模板
/// </summary>
/// <param name="subSystemId"></param>
/// <param name="orderType"></param>
/// <param name="orderNos"></param>
/// <returns></returns>
Task<List<OrderTemplateVO>> GetOrderTemplateAsync(int subSystemId,int orderType,List<string> orderNos);
#endregion
#region 订单关联 #region 订单关联
/// <summary> /// <summary>

View File

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

View File

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

View File

@ -396,6 +396,32 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
return res.Select(x => (x.Key, x.Id)).ToList(); return res.Select(x => (x.Key, x.Id)).ToList();
} }
public Task<List<OrderTemplateVO>> GetOrderTemplateAsync(int[] orderId)
{
return Context.Queryable<OrderEntity>()
.Where(x => orderId.Contains(x.Id))
.Select(a => new OrderTemplateVO
{
OrderId = a.Id,
OrderNo = a.OrderNo,
TemplateCodes = a.TemplateCodes
})
.ToListAsync();
}
public Task<List<OrderTemplateVO>> GetOrderTemplateAsync(int subSystemId, int orderType, List<string> orderNos)
{
return Context.Queryable<OrderEntity>()
.Where(x => orderNos.Contains(x.OrderNo) && x.SubSystemId == subSystemId && x.OrderType == orderType)
.Select(a => new OrderTemplateVO
{
OrderId = a.Id,
OrderNo = a.OrderNo,
TemplateCodes = a.TemplateCodes
})
.ToListAsync();
}
public Task<VSystemEntity> GetSystemInfoById(int id) public Task<VSystemEntity> GetSystemInfoById(int id)
{ {
return Context.Queryable<VSystemEntity>().FirstAsync(m => m.IsDeleted == false && m.Id == id); return Context.Queryable<VSystemEntity>().FirstAsync(m => m.IsDeleted == false && m.Id == id);