track/src/TrackCenter.Application/Queries/ProvideQueries.cs
xiaolipro 67d00c1f67
All checks were successful
Build and Deploy / deploy (push) Successful in 53s
fix:匹配轨迹刷新单号
2026-10-16 10:47:02 +08:00

382 lines
15 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 Fake.DomainDrivenDesign.Repositories.SqlSugarCore;
using SqlSugar;
using TrackCenter.Domain.Aggregates.OrderAggregate;
using TrackCenter.Domain.Aggregates.TrackContentAggregate;
using TrackCenter.Domain.Aggregates.TrackFlagAggregate;
using TrackCenter.Domain.Aggregates.TrackPullAggregate;
using TrackCenter.Domain.Aggregates.TrackStatusAggregate;
using TrackCenter.Domain.Dtos.Order;
using TrackCenter.Domain.Dtos.TrackContents;
using TrackCenter.Domain.Dtos.TrackPull;
using TrackCenter.Repository;
namespace TrackCenter.Application.Queries;
/// <summary>
/// 提供服务相关查询
/// </summary>
public class ProvideQueries : SqlSugarRootlessRepository<TrackCenterDbContext>, ITransientDependency
{
public ISqlSugarClient Context => GetDbContextAsync().Result;
public async Task<List<string>> MapTrackCodesAsync(string upAppKey, string appKey, params string[] trackCodes)
{
// 根据上游code得到下游code
return await Context.Queryable<TrackContentMapEntity>()
.Where(x => x.AppKey == appKey && x.MapAppKey == upAppKey &&
trackCodes.Contains(x.MapTrackContentCode))
.Select(x => x.TrackContentCode)
.ToListAsync();
}
public async Task<List<TrackCodeMapVO>> GetTrackCodeMapsAsync(string upAppKey, string appKey,
params string[] trackCodes)
{
// 根据上游code得到下游code
return await Context.Queryable<TrackContentMapEntity>()
.Where(x => x.AppKey == appKey && x.MapAppKey == upAppKey &&
trackCodes.Contains(x.MapTrackContentCode))
.Select(x => new TrackCodeMapVO()
{
AppKey = x.AppKey,
TrackContentCode = x.TrackContentCode,
MapAppKey = x.MapAppKey,
MapTrackContentCode = x.MapTrackContentCode
})
.ToListAsync();
}
public async Task<List<OrderWithServiceProviderVO>> FindOrderWithServiceProviderAsync(string appKey,
List<string> orderNos)
{
var orderRows = await Context.Queryable<OrderEntity>()
.InnerJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId)
.WhereIF(!appKey.IsNullOrWhiteSpace(), a => a.AppKey == appKey)
.Where((a, b) => !a.IsCompleted && orderNos.Contains(a.OrderNo)
&& b.StartTime <= DateTime.Now && b.EndTime >= DateTime.Now)
.Select((a, b) => new
{
CustomerOrderNo = a.OrderNo,
a.TrackNo,
a.ThirdPartyOrderNumber,
a.Customer,
a.OrderType,
a.AppKey,
ServiceProviderCode = b.ServiceProviderCode,
})
.ToListAsync();
if (orderRows.Count == 0) return [];
var appKeys = orderRows.Select(x => x.AppKey).Distinct().ToList();
var serviceProviderCodes = orderRows.Select(x => x.ServiceProviderCode).Distinct().ToList();
// 拉取优先级:精确客户优先于 * 兜底;同级按 Priority 升序
var priorities = await Context.Queryable<TrackPullConfigEntity>()
.InnerJoin<TrackPullPriorityEntity>((c, d) => c.Id == d.TrackPullConfigId)
.Where((c, d) => appKeys.Contains(c.AppKey) && serviceProviderCodes.Contains(c.ServiceProviderCode))
.Select((c, d) => new
{
c.AppKey,
c.ServiceProviderCode,
c.OrderType,
d.Customer,
d.OrderNoType,
d.Priority,
})
.ToListAsync();
return orderRows.Select(o =>
{
var matched = priorities
.Where(p => p.AppKey == o.AppKey
&& p.ServiceProviderCode == o.ServiceProviderCode
&& p.OrderType == o.OrderType
&& (p.Customer == o.Customer || p.Customer == GlobalConst.All))
.OrderBy(p => p.Customer == o.Customer ? 0 : 1)
.ThenBy(p => p.Priority)
.FirstOrDefault();
return new OrderWithServiceProviderVO
{
OrderNo = ResolvePullOrderNo(matched?.OrderNoType, o.CustomerOrderNo, o.TrackNo,
o.ThirdPartyOrderNumber),
TrackNo = o.TrackNo,
OtherNo = o.ThirdPartyOrderNumber,
OrderType = o.OrderType,
AppKey = o.AppKey,
ServiceProviderCode = o.ServiceProviderCode,
};
}).ToList();
}
/// <summary>
/// 按拉取优先级配置的单号类型解析下游拉取主单号。
/// </summary>
private static string ResolvePullOrderNo(TrackOrderNoType? orderNoType, string customerOrderNo, string? trackNo,
string? otherNo)
{
return orderNoType switch
{
TrackOrderNoType.客户单号 => customerOrderNo,
TrackOrderNoType.跟踪单号 => trackNo.IsNullOrWhiteSpace() ? customerOrderNo : trackNo!,
TrackOrderNoType.其他单号 => otherNo.IsNullOrWhiteSpace() ? customerOrderNo : otherNo!,
_ => customerOrderNo,
};
}
/// <summary>
/// 手工刷新诊断:区分可刷新订单与不可刷新原因
/// </summary>
public async Task<(List<OrderWithServiceProviderVO> CanRefresh, List<string> SkipReasons)>
DiagnoseManualRefreshAsync(string appKey, List<string> orderNos)
{
var now = DateTime.Now;
var canRefresh = await FindOrderWithServiceProviderAsync(appKey, orderNos);
var eligibleOrderNos = await Context.Queryable<OrderEntity>()
.InnerJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId)
.WhereIF(!appKey.IsNullOrWhiteSpace(), a => a.AppKey == appKey)
.Where((a, b) => !a.IsCompleted && orderNos.Contains(a.OrderNo)
&& b.StartTime <= now && b.EndTime >= now)
.Select((a, b) => a.OrderNo)
.Distinct()
.ToListAsync();
var eligibleSet = eligibleOrderNos.ToHashSet(StringComparer.OrdinalIgnoreCase);
var orderStates = await Context.Queryable<OrderEntity>()
.LeftJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId)
.WhereIF(!appKey.IsNullOrWhiteSpace(), a => a.AppKey == appKey)
.Where(a => orderNos.Contains(a.OrderNo))
.Select((a, b) => new
{
a.OrderNo,
a.IsCompleted,
PullStatusId = SqlFunc.IsNull(b.Id, 0),
b.StartTime,
b.EndTime
})
.ToListAsync();
var skipReasons = new List<string>();
var foundSet = orderStates.Select(x => x.OrderNo).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var orderNo in orderNos)
{
if (eligibleSet.Contains(orderNo))
{
continue;
}
if (!foundSet.Contains(orderNo))
{
skipReasons.Add($"{orderNo}:订单不存在或不属于当前应用");
continue;
}
var rows = orderStates.Where(x => string.Equals(x.OrderNo, orderNo, StringComparison.OrdinalIgnoreCase))
.ToList();
if (rows.Any(x => x.IsCompleted))
{
skipReasons.Add($"{orderNo}:订单已完成,不再拉取轨迹");
continue;
}
var pullRows = rows.Where(x => x.PullStatusId > 0).ToList();
if (pullRows.Count == 0)
{
skipReasons.Add($"{orderNo}:无拉取状态(未配置拉取或尚未生成拉取任务)");
continue;
}
if (!pullRows.Any(x => x.StartTime <= now && x.EndTime >= now))
{
var latest = pullRows.OrderByDescending(x => x.EndTime).First();
skipReasons.Add(
$"{orderNo}:不在拉取时间窗口内({latest.StartTime:yyyy-MM-dd HH:mm} ~ {latest.EndTime:yyyy-MM-dd HH:mm})");
continue;
}
skipReasons.Add($"{orderNo}:暂不可刷新,请检查拉取配置/服务商映射");
}
return (canRefresh, skipReasons);
}
public async Task<List<OrderWithServiceProviderVO>> FindOrderWithServiceProviderAsync(
TrackPullConfigVo config,
int pageIndex, int pageSize = 1000)
{
var rows = await Context.Queryable<OrderEntity>()
.InnerJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId)
.Where((a, b) => b.ServiceProviderCode == config.ServiceProviderCode
&& a.AppKey == config.AppKey
&& !a.IsCompleted
&& b.StartTime <= DateTime.Now
&& b.EndTime >= DateTime.Now
)
.WhereIF(config.Customer != GlobalConst.All, a => a.Customer == config.Customer)
.Select((a, b) => new
{
CustomerOrderNo = a.OrderNo,
a.TrackNo,
a.ThirdPartyOrderNumber,
a.OrderType,
a.AppKey,
ServiceProviderCode = b.ServiceProviderCode
})
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return rows.Select(o => new OrderWithServiceProviderVO
{
OrderNo = ResolvePullOrderNo(config.OrderNoType, o.CustomerOrderNo, o.TrackNo, o.ThirdPartyOrderNumber),
TrackNo = o.TrackNo,
OtherNo = o.ThirdPartyOrderNumber,
OrderType = o.OrderType,
AppKey = o.AppKey,
ServiceProviderCode = o.ServiceProviderCode
}).ToList();
}
public async Task<TrackContentVO?> GetTrackContentAsync(string appKey, string trackCode)
{
var content = await Context.Queryable<TrackContentEntity>()
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
.Where(x => x.AppKey == appKey && x.Code == trackCode)
.Select((x, y) => new TrackContentVO
{
Id = x.Id,
AppKey = x.AppKey,
TrackStatusId = x.TrackStatusId,
TrackStatus = y.Status,
TrackStatusCode = y.Code,
TrackContentCode = x.Code,
TrackContentId = x.Id,
TrackContent = x.Content,
TrackEnContent = x.EnContent,
IsCompleted = y.IsCompleted,
IsRepeatable = x.IsRepeatable,
IsExternal = x.IsExternal,
IsAllowManuallyUpdate = x.IsAllowManuallyUpdate,
IsAllowManuallyCreate = x.IsAllowManuallyCreate,
IsToMq = y.IsToMq,
TrackFlagId = x.TrackFlagId
})
.FirstAsync();
if (content == null) return null;
// 填充轨迹标记
await FillTrackContentFlagsAsync(new List<TrackContentVO>(1)
{
content
});
return content;
}
public async Task<List<TrackContentVO>> GetTrackContentsAsync(string[] appKeys, string[] trackCodes)
{
var contents = await Context.Queryable<TrackContentEntity>()
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
.Where(x => appKeys.Contains(x.AppKey) && trackCodes.Contains(x.Code))
.Select((x, y) => new TrackContentVO
{
Id = x.Id,
AppKey = x.AppKey,
TrackStatusId = x.TrackStatusId,
TrackStatus = y.Status,
TrackStatusCode = y.Code,
TrackContentCode = x.Code,
TrackContentId = x.Id,
TrackContent = x.Content,
TrackEnContent = x.EnContent,
IsCompleted = y.IsCompleted,
IsRepeatable = x.IsRepeatable,
IsExternal = x.IsExternal,
IsAllowManuallyUpdate = x.IsAllowManuallyUpdate,
IsAllowManuallyCreate = x.IsAllowManuallyCreate,
IsToMq = y.IsToMq,
TrackFlagId = x.TrackFlagId
})
.ToListAsync();
// 填充轨迹标记
await FillTrackContentFlagsAsync(contents);
return contents;
}
public async Task<List<TrackContentVO>> GetTrackContentAsync(HashSet<int> contentIds)
{
var contents = await Context.Queryable<TrackContentEntity>()
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
.Where(x => contentIds.Contains(x.Id))
.Select((x, y) => new TrackContentVO
{
Id = x.Id,
AppKey = x.AppKey,
TrackStatusId = x.TrackStatusId,
TrackStatus = y.Status,
TrackStatusCode = y.Code,
TrackContentCode = x.Code,
TrackContentId = x.Id,
TrackContent = x.Content,
TrackEnContent = x.EnContent,
IsCompleted = y.IsCompleted,
IsRepeatable = x.IsRepeatable,
IsExternal = x.IsExternal,
IsAllowManuallyUpdate = x.IsAllowManuallyUpdate,
IsAllowManuallyCreate = x.IsAllowManuallyCreate,
IsToMq = y.IsToMq,
TrackFlagId = x.TrackFlagId
})
.ToListAsync();
// 在轨迹内容填充轨迹标记
await FillTrackContentFlagsAsync(contents);
return contents;
}
private async Task FillTrackContentFlagsAsync(List<TrackContentVO> contents)
{
// 1.构建轨迹内容和轨迹标记Ids的映射关系
var contentFlagIdsDict = contents
.Where(x => !string.IsNullOrWhiteSpace(x.TrackFlagId))
.ToDictionary(x => x, y => y.TrackFlagId!.Split(",").Select(int.Parse).ToHashSet());
if (contentFlagIdsDict.Count == 0) return;
// 2.查轨迹标记
var flagIds = contentFlagIdsDict.SelectMany(x => x.Value).ToHashSet();
var flags = await Context.Queryable<TrackFlagEntity>()
.Where(x => flagIds.Contains(x.Id))
.Select(x => new TrackFlagVO
{
Id = x.Id,
AppKey = x.AppKey,
Code = x.Code,
Name = x.Name,
IsToMq = x.IsToMq
})
.ToListAsync();
if (flags.Count == 0) return;
// 3.填充轨迹标记
var flagDict = flags.ToDictionary(x => x.Id);
foreach (var (content, flagsIds1) in contentFlagIdsDict)
{
content.TrackFlags = flagsIds1
.Where(x => flagDict.ContainsKey(x))
.Select(x => flagDict[x])
.ToList();
// 轨迹状态为不广播,根据轨迹标记是否为广播
if (!content.IsToMq)
content.IsToMq = content.TrackFlags.Any(x => x.IsToMq);
}
}
}