406 lines
16 KiB
C#
406 lines
16 KiB
C#
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));
|
||
}
|
||
}
|
||
}
|