track/src/TrackCenter.Application/Services/ProvideServices/TrackService.Query.cs
2025-07-30 22:00:02 +08:00

406 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.AspNetCore.Http;
using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Helpers;
using TrackCenter.Application.Helpers.OSS;
using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
using TrackCenter.Domain.Aggregates.TrackTemplateAggregate;
using TrackCenter.Domain.Dtos.TrackTemplate;
namespace TrackCenter.Application.Services.ProvideServices;
/// <summary>
/// 轨迹查询服务
/// </summary>
public partial class TrackService
{
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
private readonly ITrackTemplateRepository _trackTemplateRepository;
private readonly IHttpContextAccessor _contextAccessor;
/// <summary>
/// 查询轨迹(时区补正)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/Query")]
public async Task<List<GetTrackOutput>> QueryAsync(GetTrackInput 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 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,
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 sortedList;
}
/// <summary>
/// 查询轨迹(原样返回)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Track/QueryV2")]
public async Task<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 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,
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,
CreateUser = x.CreateUser,
CreateTime = x.CreateTime,
PodFileUrls = x.PodFileUrls?.Split(",").ToList() ?? Empty<string>.List,
TrackFlags = BuildTrackFlags(trackFlagIdsDict, flagDict, x)
}).ToList();
res.Add(item);
}
// 排序
var sortedList = await TracksOrdered(res, input, input.SortMethod);
return new UnifyResultDto<List<GetTrackOutput>>(true, sortedList, "操作成功");
}
//内部系统展示所有轨迹,外部系统则要求过滤外部轨迹
private async Task<bool> HasFilterExternalTrack()
{
var isFilter = true;
if (_contextAccessor.HttpContext!.Request.Headers.TryGetValue(GlobalConst.HTTP_HEADER_SUBSYSTEMID, out var subsystemIdStr))
{
if (int.TryParse(subsystemIdStr, out var subsystemId))
{
var systemEntity = await _vSystemQueries.GetFirstAsync(x => x.Id == subsystemId);
isFilter = systemEntity?.SystemProperty != 1;
}
}
return isFilter;
}
private async Task<List<GetTrackOutput>> TracksOrdered(List<GetTrackOutput> list, OrderNosInfo orderNosInfo, SortMethod? sortMethod)
{
switch (sortMethod)
{
case SortMethod.ByTrackTime:
case SortMethod.ByTemplateAndTrackTime:
// tips:quick-sort 直接修改原list
list.ForEach(item => item.TrackDetails.Sort((a, b) => 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;
}
/// <summary>
/// 轨迹发生时间(统一utc)排序,相同时间按模板排序,考虑多个模板的情况
/// </summary>
/// <returns></returns>
private static List<TrackDetail> SortByTemplatesAsync(List<TrackDetail> seq, List<TrackTemplateOutputVo> templates, bool isAsc = true)
{
//转化为统一时间
var trackWraps = seq.Select(x => new TrackWrap(x));
var sortedTracks = (isAsc
? trackWraps.OrderBy(x => x.Time)
: trackWraps.OrderByDescending(x => x.Time)).ToList();
//用于统计时间相同的轨迹
var timeEqTrackDict = new Dictionary<DateTime, List<TrackIdxWarp>>();
for (var i = 0; i < sortedTracks.Count; i++)
{
var trackWrap = sortedTracks[i];
if (timeEqTrackDict.TryGetValue(trackWrap.Time, out var list))
list.Add(new TrackIdxWarp(trackWrap.Track, i));
else
timeEqTrackDict.Add(trackWrap.Time, new List<TrackIdxWarp>
{
new TrackIdxWarp(trackWrap.Track, i)
});
}
//选出需要按模板重排序的轨迹
var timeEqTracks = timeEqTrackDict
.Where(kv => kv.Value.Count > 1)
.ToDictionary(kv => kv.Key, kv => kv.Value);
//初始化为最后一个模板排序
var preSortDict = templates[templates.Count - 1]
.TemplateContents
.OrderBy(x => x.Sort)
.ToDictionary(x => x.Code, x => x.Sort);
for (var i = templates.Count - 2; i >= 0; i--)
{
var curCodeSortDict = templates[i]
.TemplateContents
.ToDictionary(x => x.Code, y => y.Sort);
//没在之前模板出现的code
var noTipPreCodeSortDict = curCodeSortDict
.Where(x => !preSortDict.ContainsKey(x.Key))
.ToDictionary(x => x.Key, y => y.Value);
//插入未命中的模板code
foreach (var (noTipCode, curNoTipCodeSort) in noTipPreCodeSortDict)
{
//初始化插入位置
var insertPos = -1;
foreach (var (preCode, _) in preSortDict)
{
if (curCodeSortDict.TryGetValue(preCode, out var preCodeSort))
{
//未命中的相对于之前模板的偏后,设置插入位置在
if (preCodeSort < curNoTipCodeSort)
{
insertPos = preSortDict[preCode] + 1;
}
}
}
//结束了还没选好位置,未命中的code相对于之前的模板code在当前模板最前面
if (insertPos == -1)
{
foreach (var (c, s) in preSortDict)
preSortDict[c] = s + 1;
preSortDict[noTipCode] = 0;
}
else
{
foreach (var (c, s) in preSortDict.Where(x => x.Value >= insertPos))
preSortDict[c] = s + 1;
preSortDict[noTipCode] = insertPos;
}
}
}
foreach (var trackIdxWarps in timeEqTracks.Values)
{
var curIndex = trackIdxWarps.Min(x => x.Index);
//按模板从小到大排序
var sortByTemplate = (isAsc
? trackIdxWarps
.OrderBy(x => preSortDict.GetValueOrDefault(x.Track.Code, int.MaxValue))
: trackIdxWarps
.OrderByDescending(x => preSortDict.GetValueOrDefault(x.Track.Code, int.MaxValue))
).ToList();
for (var j = 0; j < sortByTemplate.Count; j++)
{
var track = sortByTemplate[j];
//当前索引不对应
if (track.Index != curIndex + j)
{
var temp1 = sortedTracks[track.Index].Track;
var temp2 = sortedTracks[curIndex + j].Track;
sortedTracks[track.Index].Track = temp2;
sortedTracks[curIndex + j].Track = temp1;
trackIdxWarps.First(x => x.Index == curIndex + j).Index = track.Index;
track.Index = curIndex + j;
}
}
}
return sortedTracks.Select(x => x.Track).ToList();
}
private 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));
}
}
}