using Microsoft.AspNetCore.Http; using SJZY.Expand.AliyunOSS; using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Helpers; using TrackCenter.Domain.Aggregates.TrackDetailsAggregate; using TrackCenter.Domain.Aggregates.TrackTemplateAggregate; using TrackCenter.Domain.Dtos.TrackTemplate; using TrackCenter.Domain.Queries; namespace TrackCenter.Application.Services.ProvideServices; /// /// 轨迹查询服务 /// public partial class TrackService { private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; private readonly ITrackTemplateRepository _trackTemplateRepository; private readonly IHttpContextAccessor _contextAccessor; private readonly IVSystemQueries _vSystemQueries; /// /// 查询轨迹(时区补正) /// /// /// /// [HttpPost("Track/Query")] public async Task>> QueryAsync(GetTrackInput input) { 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 ossKeys = details .Where(x => x.Files != null && x.Files.Count > 0) .SelectMany(x => x.Files!.Select(y => y.OssKey)) .ToArray(); //根据osskey获取下载地址 var ossKeyUrlDict = OSSHelper.GetUrlByKeys(ossKeys); foreach (var group in details.GroupBy(x => x.OrderId)) { var item = new GetTrackOutput(); var trackDetails = group .WhereIf(isFilterExternal, x => x.IsExternal) .ToList(); if (trackDetails.Count == 0) continue; item.OrderNo = trackDetails.First().OrderNo; item.TrackDetails = trackDetails.Select(x => new TrackDetail { EsId = x.Id, Code = x.TrackCode, Status = x.TrackStatus, Content = x.TrackContent, EnContent = x.TrackEnContent, OriginContent = x.OriginTrackContent, Location = x.TrackPlace, Time = Math.Abs(x.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1 ? x.TrackTime : x.ZeroTrackTime.AddHours(input.TimeZone ?? 8), TimeZone = Math.Abs(x.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1 ? null : input.TimeZone ?? 8, IsDisplay = x.IsExternal, IsVisibility = x.IsVisibility, IsExternal = x.IsExternal, Source = x.Source, Remarks = x.Remarks, Files = x.Files?.Select(y => new FileItemDto { FileName = y.FileName, OssKey = y.OssKey, DownloadUrl = ossKeyUrlDict.TryGetValue(y.OssKey, out var url) ? url : null }).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, CreateTime = x.CreateTime }).ToList(); res.Add(item); } // 排序 var sortedList = await TracksOrdered(res, input, input.SortMethod); return new UnifyResultDto>(true, sortedList, "操作成功"); } /// /// 查询轨迹(原样返回) /// /// /// [HttpPost("Track/QueryV2")] public async Task>> QueryV2Async(GetTrackInputV2 input) { 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 ossKeys = details .Where(x => x.Files != null && x.Files.Count > 0) .SelectMany(x => x.Files!.Select(y => y.OssKey)) .ToArray(); //根据osskey获取下载地址 var ossKeyUrlDict = OSSHelper.GetUrlByKeys(ossKeys); foreach (var group in details.GroupBy(x => x.OrderId)) { var item = new GetTrackOutput(); var trackDetails = group .WhereIf(isFilterExternal, x => x.IsExternal) .ToList(); if (trackDetails.Count == 0) continue; item.OrderNo = trackDetails.First().OrderNo; item.TrackDetails = trackDetails.Select(x => new TrackDetail { EsId = x.Id, Code = x.TrackCode, Status = x.TrackStatus, Content = x.TrackContent, EnContent = x.TrackEnContent, OriginContent = x.OriginTrackContent, Location = x.TrackPlace, Time = x.TrackTime, TimeZone = x.TrackTimeZone, IsDisplay = x.IsExternal, IsVisibility = x.IsVisibility, IsExternal = x.IsExternal, Source = x.Source, Remarks = x.Remarks, Files = x.Files?.Select(y => new FileItemDto { FileName = y.FileName, OssKey = y.OssKey, DownloadUrl = ossKeyUrlDict.TryGetValue(y.OssKey, out var url) ? url : null }).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, CreateTime = x.CreateTime, }).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) => DateTimeHelper.ConvertUtc0Compare(a.Time, a.TimeZone ?? 8, b.Time, b.TimeZone ?? 8))); break; case null: case SortMethod.ByTrackTimeDesc: case SortMethod.ByTemplateAndTrackTimeDesc: list.ForEach(item => item.TrackDetails.Sort((a, b) => DateTimeHelper.ConvertUtc0Compare(b.Time, b.TimeZone ?? 8, a.Time, a.TimeZone ?? 8))); break; case SortMethod.ByCreateTime: list.ForEach(item => item.TrackDetails.Sort((a, b) => a.CreateTime.CompareTo(b.CreateTime))); break; case SortMethod.ByCreateTimeDesc: list.ForEach(item => item.TrackDetails.Sort((a, b) => b.CreateTime.CompareTo(a.CreateTime))); break; default: 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 sealed class TrackIdxWarp { public TrackDetail Track { get; set; } public int Index { get; set; } public TrackIdxWarp(TrackDetail track, int index) { Track = track; Index = index; } } private sealed 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)); } } }