refactor: slim ProviderService upsert APIs and clarify failure tips

Remove debug APIs and transport scan Hangfire; move Upsert to Order/Upsert*; keep original response shapes and put result+reason only in exception messages.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-07-31 21:12:56 +08:00
parent e315e03922
commit dabb9a4edb
31 changed files with 439 additions and 1291 deletions

View File

@ -18,7 +18,6 @@ public class TrackContentAutoMapperProfile : Profile
CreateMap<PagedResult<GetTrackContentListVO>, PagedResult<GetTrackContentListDto>>( CreateMap<PagedResult<GetTrackContentListVO>, PagedResult<GetTrackContentListDto>>(
MemberList.None); MemberList.None);
CreateMap<EditTrackContentMapInput, TrackContentMapEntity>(MemberList.None); CreateMap<EditTrackContentMapInput, TrackContentMapEntity>(MemberList.None);
CreateMap<TrackContentVO, TrackContentOutput>(MemberList.None);
CreateMap<TrackDetailsIndex, GetContentDetailOutput>(MemberList.None).ReverseMap(); CreateMap<TrackDetailsIndex, GetContentDetailOutput>(MemberList.None).ReverseMap();
CreateMap<TrackCenter.Domain.Aggregates.TrackDetailsAggregate.FileItem, TrackCenter.Application.Dtos.TrackContents.FileItem>(MemberList.None).ReverseMap(); CreateMap<TrackCenter.Domain.Aggregates.TrackDetailsAggregate.FileItem, TrackCenter.Application.Dtos.TrackContents.FileItem>(MemberList.None).ReverseMap();

View File

@ -56,22 +56,22 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
await HandleTrackAsync(@event, canNextOrderIds); await HandleTrackAsync(@event, canNextOrderIds);
if (canNextOrderIds.Count == 0) return; if (canNextOrderIds.Count == 0) return;
// 根据订单关联关系异步向下刷(异步) // ?????????????????
await DownByOrderLink(@event, canNextOrderIds); await DownByOrderLink(@event, canNextOrderIds);
} }
private async Task DownByOrderLink(TrackUpsertEvent @event, List<int> canNextOrderIds) private async Task DownByOrderLink(TrackUpsertEvent @event, List<int> canNextOrderIds)
{ {
// 根据订单关联关系异步向下刷(异步) // ?????????????????
var linkedOrders = await _orderRepository.GetLinkedOrdersAsync(canNextOrderIds.ToArray()); var linkedOrders = await _orderRepository.GetLinkedOrdersAsync(canNextOrderIds.ToArray());
foreach (var order in @event.OrderIdentifiers) foreach (var order in @event.OrderIdentifiers)
{ {
// 理论上小包在这不会分组 // ???????????
foreach (var group in linkedOrders.Where(x => x.OwnerOrderId == order.OrderId) foreach (var group in linkedOrders.Where(x => x.OwnerOrderId == order.OrderId)
.GroupBy(x => new .GroupBy(x => new
{ {
x.AppKey, x.AppKey,
x.Customer // 这里按客户分组是为了后续的推送方便 x.Customer // ?????????????????
})) }))
{ {
var nextEvent = @event.DeepCopy(); var nextEvent = @event.DeepCopy();
@ -85,7 +85,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash, OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash,
}; };
// 不同应用需要做内容映射 // ???????????
if (@event.AppKey != nextEvent.AppKey) if (@event.AppKey != nextEvent.AppKey)
{ {
var mapCode = (await _provideQueries.MapTrackCodesAsync(@event.AppKey, nextEvent.AppKey, @event.TrackCode)) var mapCode = (await _provideQueries.MapTrackCodesAsync(@event.AppKey, nextEvent.AppKey, @event.TrackCode))
@ -93,7 +93,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
if (mapCode == null) if (mapCode == null)
{ {
// 跨应用没做映射的轨迹不处理 // ?????????????
continue; continue;
} }
@ -119,10 +119,10 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
private static bool CanDelete(TrackUpsertEvent @event) private static bool CanDelete(TrackUpsertEvent @event)
{ {
// 系统创建的轨迹不允许修改 // ????????????
if (@event.Visited.IsNullOrEmpty() && @event is { OperationMan: GlobalConst.SystemUser, IsAdd: false }) if (@event.Visited.IsNullOrEmpty() && @event is { OperationMan: GlobalConst.SystemUser, IsAdd: false })
{ {
Log.Warning($"系统创建的轨迹不允许被修改:{@event}", true); Log.Warning($"??????????????{@event}", true);
return false; return false;
} }
@ -131,7 +131,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
var dup = @event.Visited.Intersect(orderIds).ToArray(); var dup = @event.Visited.Intersect(orderIds).ToArray();
if (dup.Any()) if (dup.Any())
{ {
Log.Warning($"{(@event.IsAdd ? "添加" : "修改")}轨迹时,发现循环依赖:{dup.JoinAsString(",")}", true); Log.Warning($"{(@event.IsAdd ? "??" : "??")}???????????{dup.JoinAsString(",")}", true);
return false; return false;
} }
@ -140,29 +140,29 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
private async Task HandleTrackAsync(TrackUpsertEvent @event, List<int> canNextOrderIds) private async Task HandleTrackAsync(TrackUpsertEvent @event, List<int> canNextOrderIds)
{ {
//#7001:未来潜在需求,当轨迹内容匹配预设,记录一个标记,保证下刷过程都是使用预设内容 //#7001:??????????????????????????????????????
var trackContent = await _provideQueries.GetTrackContentAsync(@event.AppKey, @event.TrackCode); var trackContent = await _provideQueries.GetTrackContentAsync(@event.AppKey, @event.TrackCode);
if (trackContent == null) if (trackContent == null)
{ {
// 找不到轨迹内容,直接结束 // ????????????
Log.Warning($"找不到轨迹内容:{@event}", true); Log.Warning($"????????{@event}", true);
return; return;
} }
// 根据轨迹内容标识,确定操作权限 // ???????????????
var manualOperation = @event.Source == (int) TrackSourceEnum.Artificial; var manualOperation = @event.Source == (int) TrackSourceEnum.Artificial;
// 不允许手动创建 // ???????
if (manualOperation && @event.IsAdd && trackContent.IsAllowManuallyCreate == false) if (manualOperation && @event.IsAdd && trackContent.IsAllowManuallyCreate == false)
{ {
PublicTopicAllFailed(@event, trackContent, "轨迹内容标识不允许手动创建"); PublicTopicAllFailed(@event, trackContent, "?????????????");
return; return;
} }
// 不允许手动更新 // ???????
if (manualOperation && !@event.IsAdd && trackContent.IsAllowManuallyUpdate == false) if (manualOperation && !@event.IsAdd && trackContent.IsAllowManuallyUpdate == false)
{ {
PublicTopicAllFailed(@event, trackContent, "轨迹标识内容不允许手动更新"); PublicTopicAllFailed(@event, trackContent, "?????????????");
return; return;
} }
@ -181,27 +181,27 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
if (insertTracks.Any()) if (insertTracks.Any())
{ {
// 批量add to es tips:注意副作用填充版本号 // ??add to es tips:??????????
await _trackDetailsEsRepository.AddManyAsync(insertTracks); await _trackDetailsEsRepository.AddManyAsync(insertTracks);
// es新增后填充版本号字典 // es??????????
insertTracks.ForEach(t => orderIdTrackMetadataDict.Add(t.OrderId, TrackMetadata.Build(t.Id, t.Version, t.CreateTime))); insertTracks.ForEach(t => orderIdTrackMetadataDict.Add(t.OrderId, TrackMetadata.Build(t.Id, t.Version, t.CreateTime)));
//批量写入订单操作日志 //??????????
_ = _orderOperationRecordEsRepository.AddManyAsync(insertTracks.Select(t => BuildOrderOperationRecord(@event, t, new OrderIdentifier _ = _orderOperationRecordEsRepository.AddManyAsync(insertTracks.Select(t => BuildOrderOperationRecord(@event, t, new OrderIdentifier
{ {
OrderNo = t.OrderNo, OrderNo = t.OrderNo,
OrderId = t.OrderId OrderId = t.OrderId
})).ToList()); })).ToList());
// 添加轨迹触发订单拉取状态更新 // ??????????????
await _orderRepository.UpdatePullStatusToSilenceAsync(insertTracks.Select(x => x.OrderId).ToList(), await _orderRepository.UpdatePullStatusToSilenceAsync(insertTracks.Select(x => x.OrderId).ToList(),
@event.TrackCode); @event.TrackCode);
// 发布预警信号,稍后将进行预警计算 // ????????????????
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹新增, insertTracks); _warningSignalPublisher.PublishSignal(WarningSignalType.轨迹新增, insertTracks);
// 如果存在轨迹标记,则触发订单pod拉取 // ??????????????pod??
if (!string.IsNullOrWhiteSpace(trackContent.TrackFlagId)) if (!string.IsNullOrWhiteSpace(trackContent.TrackFlagId))
{ {
await _capPublisher.PublishAsync(GlobalConst.OrderPodPullSubscribe, new OrderPodPullEvent await _capPublisher.PublishAsync(GlobalConst.OrderPodPullSubscribe, new OrderPodPullEvent
@ -213,10 +213,10 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
} }
} }
// 触发轨迹变更广播 // ????????
await TrackChangePublicTopicAsync(trackContent, trackChangeEtos, true); await TrackChangePublicTopicAsync(trackContent, trackChangeEtos, true);
// 触发订单主表更新事件(异步) // ??????????????
await _capPublisher.PublishAsync(GlobalConst.TrackChangeAfterUpdateOrderSubscribe, await _capPublisher.PublishAsync(GlobalConst.TrackChangeAfterUpdateOrderSubscribe,
new TrackChangeAfterUpdateOrderEvent new TrackChangeAfterUpdateOrderEvent
{ {
@ -226,14 +226,12 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
TrackContent = trackContent.TrackContent, TrackContent = trackContent.TrackContent,
TrackEnContent = trackContent.TrackEnContent, TrackEnContent = trackContent.TrackEnContent,
TrackPlace = @event.TrackPlace, TrackPlace = @event.TrackPlace,
TrackTime = @event.NewTrackTime ?? @event.TrackTime, TrackTime = @event.TrackTime,
TrackTimeZone = @event.TrackTimeZone, TrackTimeZone = @event.TrackTimeZone,
DestinationTimeZone = @event.DestinationTimeZone,
EstimatedDeliveryDate = @event.EstimatedDeliveryDate
}); });
// 触发轨迹推送事件(异步) // 触发轨迹推送事件(异步)
// tips:同一个轨迹内容,多个订单多条轨迹 // tips:????????????????
var trackPushEvent = new TrackPushEvent var trackPushEvent = new TrackPushEvent
{ {
OptionType = @event.IsAdd ? OptionType.新增 : OptionType.修改, OptionType = @event.IsAdd ? OptionType.新增 : OptionType.修改,
@ -248,10 +246,9 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
TrackContentId = trackContent.TrackContentId, TrackContentId = trackContent.TrackContentId,
TrackContent = trackContent.TrackContent, TrackContent = trackContent.TrackContent,
TrackPlace = @event.TrackPlace, TrackPlace = @event.TrackPlace,
TrackTime = @event.NewTrackTime ?? @event.TrackTime, TrackTime = @event.TrackTime,
TrackTimeZone = @event.TrackTimeZone, TrackTimeZone = @event.TrackTimeZone,
CompareFields = @event.CompareFields, Extra = @event.Extra
Extra = @event.NewExtra ?? @event.Extra
}, },
ForcedPush = @event.ForcedPush, ForcedPush = @event.ForcedPush,
IsToMq = (trackContent.IsToMq || @event.IsToMq) IsToMq = (trackContent.IsToMq || @event.IsToMq)
@ -293,7 +290,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
} }
else else
{ {
var message = $"没有通过传入的EsId:[{@event.EsId}]查询到轨迹信息,更新失败"; var message = $"???????EsId:[{@event.EsId}]????????????";
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message); AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
} }
@ -302,31 +299,21 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
var existTracks = tracks.Where(x => x.OrderId == order.OrderId).ToList(); var existTracks = tracks.Where(x => x.OrderId == order.OrderId).ToList();
// tips:如果没传时区则使用+8兜底进行过滤 // tips:??????????8??????
var timezone = Math.Abs(@event.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 0.01f ? 8.0f : @event.TrackTimeZone; var timezone = Math.Abs(@event.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 0.01f ? 8.0f : @event.TrackTimeZone;
// 根据时间过滤 // ??????
existTracks = @event.IsAdd existTracks = existTracks.Where(x =>
? existTracks.Where(x => x.GetUtc0TrackTime() == (@event.NewTrackTime ?? @event.TrackTime).Subtract(TimeSpan.FromHours(timezone))).ToList() x.GetUtc0TrackTime() == @event.TrackTime.Subtract(TimeSpan.FromHours(timezone))).ToList();
: existTracks.Where(x =>
x.GetUtc0TrackTime() == @event.TrackTime.Subtract(TimeSpan.FromHours(timezone))
// 修改了时间,要校验修改后的时间是否存在
|| (@event.NewTrackTime != null && x.GetUtc0TrackTime() == @event.NewTrackTime.Value.Subtract(TimeSpan.FromHours(timezone)))
).ToList();
// 根据扩展信息过滤 // ????????
existTracks = @event.IsAdd existTracks = existTracks.Where(x => x.Extra == @event.Extra).ToList();
? existTracks.Where(x => x.Extra == (@event.NewExtra ?? @event.Extra)).ToList()
: existTracks.Where(x =>
x.Extra == @event.Extra
// 修改了extra,要校验修改后的extra是否存在
|| (!string.IsNullOrWhiteSpace(@event.NewExtra) && x.Extra == @event.NewExtra)).ToList();
if (@event.IsAdd) if (@event.IsAdd)
{ {
if (existTracks.Count > 0) if (existTracks.Count > 0)
{ {
const string message = "订单已经存在不允许重复的轨迹,不允许重复添加"; const string message = "??????????????????????";
traceLog.Add($"OrderNo-{order.OrderNo}", message); traceLog.Add($"OrderNo-{order.OrderNo}", message);
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message); AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
continue; continue;
@ -338,10 +325,10 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
} }
else else
{ {
// 不存在不让改 // ??????
if (existTracks.Count == 0) if (existTracks.Count == 0)
{ {
const string message = "订单不存在轨迹,不允许修改"; const string message = "?????????????";
traceLog.Add($"OrderNo-{order.OrderNo}", message); traceLog.Add($"OrderNo-{order.OrderNo}", message);
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message); AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
continue; continue;
@ -349,7 +336,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
if (existTracks.Count > 1) if (existTracks.Count > 1)
{ {
const string message = "订单存在多条轨迹不允许修改"; const string message = "?????????????";
traceLog.Add($"OrderNo-{order.OrderNo}", message); traceLog.Add($"OrderNo-{order.OrderNo}", message);
AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message); AddTrackChangeEto(trackChangeEtos, @event, trackContent, order.OrderNo, null, message);
continue; continue;
@ -369,13 +356,12 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
if (updateTracks.Any()) if (updateTracks.Any())
{ {
// 批量差异化更新轨迹 // ?????????
await _trackDetailsEsRepository.BulkUpdateAsync(updateTracks); await _trackDetailsEsRepository.BulkUpdateAsync(updateTracks);
// 填充轨迹元数据 // ???????? updateTracks.ForEach(x => orderIdTrackMetadataDict.Add(x.OrderId, TrackMetadata.Build(x.Id, x.Version, x.CreateTime)));
updateTracks.ForEach(x => orderIdTrackMetadataDict.Add(x.OrderId, TrackMetadata.Build(x.Id, x.Version, x.CreateTime))); // ????????
// 批量写入操作日志
_ = _orderOperationRecordEsRepository.AddManyAsync(operationRecords); _ = _orderOperationRecordEsRepository.AddManyAsync(operationRecords);
// 发布预警信号稍后将进行预警计算 // ???????????????
_warningSignalPublisher.PublishSignal(WarningSignalType.轨迹更新, tracks); _warningSignalPublisher.PublishSignal(WarningSignalType.轨迹更新, tracks);
} }
@ -385,41 +371,41 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
private static TrackDetailsIndex BuiltUpdateTrack(TrackUpsertEvent @event, TrackDetailsIndex track, OrderIdentifier order, TrackContentVO trackContent) private static TrackDetailsIndex BuiltUpdateTrack(TrackUpsertEvent @event, TrackDetailsIndex track, OrderIdentifier order, TrackContentVO trackContent)
{ {
// 非外部轨迹才允许修改 【发生地】【时间】【时区】 // ?????????? ?????????????
if (track.Source != (int) TrackSourceEnum.ExternalServiceProvide) if (track.Source != (int) TrackSourceEnum.ExternalServiceProvide)
{ {
track.TrackPlace = @event.TrackPlace; track.TrackPlace = @event.TrackPlace;
track.TrackTime = @event.NewTrackTime ?? @event.TrackTime; track.TrackTime = @event.TrackTime;
track.TrackTimeZone = @event.TrackTimeZone; track.TrackTimeZone = @event.TrackTimeZone;
} }
//允许二次修改轨迹内容 //??????????
track.TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content; track.TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content;
track.TrackEnContent = string.IsNullOrWhiteSpace(@event.EnStatus) ? trackContent.TrackEnContent : @event.EnStatus; track.TrackEnContent = string.IsNullOrWhiteSpace(@event.EnStatus) ? trackContent.TrackEnContent : @event.EnStatus;
// 轨迹发生地的更多信息 // ??????????
track.TrackPlaceCity = @event.TrackPlaceCity ?? GlobalConst.EmptyString; track.TrackPlaceCity = GlobalConst.EmptyString;
track.TrackPlaceCityId = @event.TrackPlaceCityId ?? GlobalConst.NotExistId; track.TrackPlaceCityId = GlobalConst.NotExistId;
track.TrackPlaceProvince = @event.TrackPlaceProvince ?? GlobalConst.EmptyString; track.TrackPlaceProvince = GlobalConst.EmptyString;
track.TrackPlaceProvinceId = @event.TrackPlaceProvinceId ?? GlobalConst.NotExistId; track.TrackPlaceProvinceId = GlobalConst.NotExistId;
track.TrackPlaceNation = @event.TrackPlaceNation ?? GlobalConst.EmptyString; track.TrackPlaceNation = GlobalConst.EmptyString;
track.TrackPlaceNationId = @event.TrackPlaceNationId ?? GlobalConst.NotExistId; track.TrackPlaceNationId = GlobalConst.NotExistId;
track.TrackPlacePostcode = @event.TrackPlacePostcode ?? GlobalConst.EmptyString; track.TrackPlacePostcode = GlobalConst.EmptyString;
// 签收地时区,签收地时间 // ??????????????????????
track.DestinationTimeZone = @event.DestinationTimeZone; track.DestinationTimeZone = null;
track.EstimatedDeliveryDate = @event.EstimatedDeliveryDate; track.EstimatedDeliveryDate = null;
// 转换为utc0的时间 // ???utc0???
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1 track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.TrackTime ? track.TrackTime
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone)); : track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
// 轨迹标记ids,是否对外,备注,扩展信息,附件 // ????ids????????????????
track.TrackFlagIds = trackContent.TrackFlagId; track.TrackFlagIds = trackContent.TrackFlagId;
track.IsExternal = @event.IsExternal ?? trackContent.IsExternal; track.IsExternal = @event.IsExternal ?? trackContent.IsExternal;
track.Remarks = @event.Remarks ?? GlobalConst.EmptyString; track.Remarks = @event.Remarks ?? GlobalConst.EmptyString;
track.Extra = @event.NewExtra ?? @event.Extra; track.Extra = @event.Extra;
track.Files = @event.Files?.Select(x => new FileItem track.Files = @event.Files?.Select(x => new FileItem
{ {
OssKey = x.OssKey, OssKey = x.OssKey,
@ -427,7 +413,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
}).ToList(); }).ToList();
// 审计属性 // ????
track.UpdateUser = @event.OperationMan ?? GlobalConst.SystemUser; track.UpdateUser = @event.OperationMan ?? GlobalConst.SystemUser;
track.UpdateTime = DateTime.Now; track.UpdateTime = DateTime.Now;
@ -438,13 +424,13 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
{ {
var track = new TrackDetailsIndex var track = new TrackDetailsIndex
{ {
// 订单信息 // ????
AppKey = @event.AppKey, AppKey = @event.AppKey,
OrderId = order.OrderId, OrderId = order.OrderId,
OrderNo = order.OrderNo, OrderNo = order.OrderNo,
OrderType = order.OrderType, OrderType = order.OrderType,
// 轨迹信息 // ????
TrackCode = @event.TrackCode, TrackCode = @event.TrackCode,
TrackContentId = trackContent.TrackContentId, TrackContentId = trackContent.TrackContentId,
TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content, TrackContent = string.IsNullOrWhiteSpace(@event.Content) ? trackContent.TrackContent : @event.Content,
@ -452,44 +438,41 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
TrackStatusId = trackContent.TrackStatusId, TrackStatusId = trackContent.TrackStatusId,
TrackStatus = trackContent.TrackStatus, TrackStatus = trackContent.TrackStatus,
TrackStatusCode = trackContent.TrackStatusCode, TrackStatusCode = trackContent.TrackStatusCode,
TrackTime = @event.NewTrackTime ?? @event.TrackTime, TrackTime = @event.TrackTime,
TrackTimeZone = @event.TrackTimeZone, TrackTimeZone = @event.TrackTimeZone,
TrackPlace = @event.TrackPlace, TrackPlace = @event.TrackPlace,
// 服务商轨迹信息 // ???????
ServiceProviderCode = @event.TrackRefreshData.ServiceProviderCode ?? GlobalConst.EmptyString, ServiceProviderCode = @event.TrackRefreshData.ServiceProviderCode ?? GlobalConst.EmptyString,
OriginTrackContent = @event.TrackRefreshData.OriginTrackContent ?? GlobalConst.EmptyString, OriginTrackContent = @event.TrackRefreshData.OriginTrackContent ?? GlobalConst.EmptyString,
OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash ?? GlobalConst.EmptyString, OriginTrackDataHash = @event.TrackRefreshData.OriginTrackDataHash ?? GlobalConst.EmptyString,
// 轨迹发生地的更多描述 // ??????????
TrackPlaceCity = @event.TrackPlaceCity ?? GlobalConst.EmptyString, TrackPlaceCity = GlobalConst.EmptyString,
TrackPlaceCityId = @event.TrackPlaceCityId ?? GlobalConst.NotExistId, TrackPlaceCityId = GlobalConst.NotExistId,
TrackPlaceProvince = @event.TrackPlaceProvince ?? GlobalConst.EmptyString, TrackPlaceProvince = GlobalConst.EmptyString,
TrackPlaceProvinceId = @event.TrackPlaceProvinceId ?? GlobalConst.NotExistId, TrackPlaceProvinceId = GlobalConst.NotExistId,
TrackPlaceNation = @event.TrackPlaceNation ?? GlobalConst.EmptyString, TrackPlaceNation = GlobalConst.EmptyString,
TrackPlaceNationId = @event.TrackPlaceNationId ?? GlobalConst.NotExistId, TrackPlaceNationId = GlobalConst.NotExistId,
TrackPlacePostcode = @event.TrackPlacePostcode ?? GlobalConst.EmptyString, TrackPlacePostcode = GlobalConst.EmptyString,
DestinationTimeZone = @event.DestinationTimeZone, // ????ids?????????????????????
EstimatedDeliveryDate = @event.EstimatedDeliveryDate,
// 轨迹标记ids,轨迹来源,是否对外,备注,扩展信息,附件
TrackFlagIds = trackContent.TrackFlagId, TrackFlagIds = trackContent.TrackFlagId,
Source = @event.Source, Source = @event.Source,
IsExternal = @event.IsExternal ?? trackContent.IsExternal, IsExternal = @event.IsExternal ?? trackContent.IsExternal,
Remarks = @event.Remarks ?? GlobalConst.EmptyString, Remarks = @event.Remarks ?? GlobalConst.EmptyString,
Extra = @event.NewExtra ?? @event.Extra, Extra = @event.Extra,
Files = @event.Files?.Select(x => new FileItem Files = @event.Files?.Select(x => new FileItem
{ {
OssKey = x.OssKey, OssKey = x.OssKey,
FileName = x.FileName FileName = x.FileName
}).ToList(), }).ToList(),
// 审计属性 // ????
CreateUser = @event.OperationMan ?? GlobalConst.SystemUser, CreateUser = @event.OperationMan ?? GlobalConst.SystemUser,
CreateTime = DateTime.Now CreateTime = DateTime.Now
}; };
// 转换为utc0的时间 // ???utc0???
track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1 track.ZeroTrackTime = Math.Abs(track.TrackTimeZone - GlobalConst.NoneTrackTimeZone) < 1
? track.TrackTime ? track.TrackTime
: track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone)); : track.TrackTime.Subtract(TimeSpan.FromHours(track.TrackTimeZone));
@ -514,12 +497,10 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
private async Task TrackChangePublicTopicAsync(TrackContentVO trackContent, List<TrackChangeEto> list, bool isDelay = false) private async Task TrackChangePublicTopicAsync(TrackContentVO trackContent, List<TrackChangeEto> list, bool isDelay = false)
{ {
if (list.Count == 0) return; if (list.Count == 0) return;
//理想情况在写入之后1s会刷盘 https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-refresh.html //??????????s????https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-refresh.html
//增加一个版本号来确定是否真的变更了 //?????????????????
//没有刷盘成功事件回调 //??????????
//方案1:!!【轮询等待静默刷盘】查出版本号确实发生了变更在发送广播 //??1??!???????????????????????????? //??2?????????es???????? //??3????????????????
//方案2:考虑强制刷盘(对es压力大)后发送
//方案3:采用一个较大延时,不查询直接发
if (isDelay) if (isDelay)
await Task.Delay(1000); await Task.Delay(1000);
@ -551,21 +532,19 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
Option = @event.IsAdd ? 1 : 3, Option = @event.IsAdd ? 1 : 3,
IsSuccess = string.IsNullOrWhiteSpace(failedReason), IsSuccess = string.IsNullOrWhiteSpace(failedReason),
FailedReason = failedReason, FailedReason = failedReason,
EstimatedDeliveryDate = @event.EstimatedDeliveryDate, TrackPlacePostcode = null,
DestinationTimeZone = @event.DestinationTimeZone, TrackPlaceNation = null,
TrackPlacePostcode = @event.TrackPlacePostcode, TrackPlaceNationId = null,
TrackPlaceNation = @event.TrackPlaceNation, TrackPlaceProvince = null,
TrackPlaceNationId = @event.TrackPlaceNationId, TrackPlaceProvinceId = null,
TrackPlaceProvince = @event.TrackPlaceProvince, TrackPlaceCity = null,
TrackPlaceProvinceId = @event.TrackPlaceProvinceId, TrackPlaceCityId = null,
TrackPlaceCity = @event.TrackPlaceCity,
TrackPlaceCityId = @event.TrackPlaceCityId,
Files = @event.Files, Files = @event.Files,
Remarks = @event.Remarks, Remarks = @event.Remarks,
Source = @event.Source, Source = @event.Source,
IsExternal = @event.IsExternal ?? trackContent.IsExternal, IsExternal = @event.IsExternal ?? trackContent.IsExternal,
TrackTimeZone = @event.TrackTimeZone, TrackTimeZone = @event.TrackTimeZone,
TrackTime = @event.NewTrackTime ?? @event.TrackTime, TrackTime = @event.TrackTime,
TrackPlace = @event.TrackPlace, TrackPlace = @event.TrackPlace,
TrackStatusCode = trackContent.TrackStatusCode, TrackStatusCode = trackContent.TrackStatusCode,
OrderNo = orderNo, OrderNo = orderNo,
@ -576,7 +555,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
OrderIdentifiers = @event.OrderIdentifiers, OrderIdentifiers = @event.OrderIdentifiers,
Customer = @event.Customer, Customer = @event.Customer,
AppKey = @event.AppKey, AppKey = @event.AppKey,
Extra = @event.NewExtra ?? @event.Extra Extra = @event.Extra
}; };
} }
} }

View File

@ -1,18 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace TrackCenter.Application.Dtos.ProvideServices;
public class BatchDeleteTrackByEsIdInput
{
/// <summary>
/// 轨迹内容es id,必填
/// </summary>
[MaxLength(500)]
public List<string> TrackContentEsIds { get; set; } = new();
/// <summary>
/// 删除轨迹来源 1.外部服务商 2.内部系统 3.人工
/// </summary>
public int? Source { get; set; } = 2;
}

View File

@ -29,26 +29,11 @@ public class BatchUpsertTrackInput : OrderNosInfo
[Required] [Required]
public DateTime TrackTime { get; set; } public DateTime TrackTime { get; set; }
/// <summary>
/// 新的轨迹发生时间
/// 如果是新增,那么优先级高于TrackTime
/// 如果要修改TrackTime,请同时传递TrackTime(旧的用于匹配),和NewTrackTime
/// </summary>
public DateTime? NewTrackTime { get; set; }
/// <summary> /// <summary>
/// 轨迹发生时区 /// 轨迹发生时区
/// </summary> /// </summary>
// [Required]
// [Range(-12, 12)]
public float? TrackTimeZone { get; set; } public float? TrackTimeZone { get; set; }
/// <summary>
/// 客户是否可见
/// </summary>
[Obsolete("废弃的,请使用IsExternal")]
public bool IsVisibility { get; set; }
/// <summary> /// <summary>
/// 是否对外(可选的) /// 是否对外(可选的)
/// 如果未填则按照【轨迹内容】中的【是否可见】做为默认值 /// 如果未填则按照【轨迹内容】中的【是否可见】做为默认值
@ -57,7 +42,6 @@ public class BatchUpsertTrackInput : OrderNosInfo
/// <summary> /// <summary>
/// 数据来源:1外部服务商创建,2内部系统创建,3人工创建 /// 数据来源:1外部服务商创建,2内部系统创建,3人工创建
/// 批量新增时使用指定轨迹来源,更新时无需指定
/// </summary> /// </summary>
[Required] [Required]
public int Source { get; set; } public int Source { get; set; }
@ -83,68 +67,10 @@ public class BatchUpsertTrackInput : OrderNosInfo
public string? EnStatus { get; set; } public string? EnStatus { get; set; }
/// <summary> /// <summary>
/// 轨迹发生地(城市) /// 扩展信息字段,用于轨迹推送的附加字段
/// </summary>
public string? TrackPlaceCity { get; set; }
/// <summary>
/// 轨迹发生地(城市)Id
/// </summary>
public int? TrackPlaceCityId { get; set; }
/// <summary>
/// 轨迹发生地(省/州)
/// </summary>
public string? TrackPlaceProvince { get; set; }
/// <summary>
///轨迹发生地(省/州)Id
/// </summary>
public int? TrackPlaceProvinceId { get; set; }
/// <summary>
/// 轨迹发生地(国家/地区)
/// </summary>
public string? TrackPlaceNation { get; set; }
/// <summary>
/// 轨迹发生地(国家/地区)Id
/// </summary>
public int? TrackPlaceNationId { get; set; }
/// <summary>
/// 轨迹发生地邮编
/// </summary>
public string? TrackPlacePostcode { get; set; }
/// <summary>
/// 预计到达时间
/// </summary>
public DateTime? EstimatedDeliveryDate { get; set; }
/// <summary>
/// 目的地时区
/// </summary>
[Range(-12, 12)]
public float? DestinationTimeZone { get; set; }
/// <summary>
/// 重复的情况下,用什么比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary> /// </summary>
public string? Extra { get; set; } public string? Extra { get; set; }
/// <summary>
/// 新的扩展信息,
/// 如果是新增,那么优先级高于Extra
/// 如果要修改Extra,请同时传递Extra(用于匹配旧的)和NewExtra
/// </summary>
public string? NewExtra { get; set; }
/// <summary> /// <summary>
/// 是否强制推送 /// 是否强制推送
/// </summary> /// </summary>

View File

@ -0,0 +1,16 @@
namespace TrackCenter.Application.Dtos.ProvideServices;
/// <summary>
/// ProviderService 提示文案助手(不改变接口返回结构,仅用于异常 message:结果;原因:...)
/// </summary>
public static class ProviderServiceActionResult
{
/// <summary>
/// 抛出结构化业务异常(message = 结果;原因:...)
/// </summary>
public static BusinessException Exception(string result, string reason) =>
new(message: Format(result, reason));
public static string Format(string result, string reason) =>
string.IsNullOrWhiteSpace(reason) ? result : $"{result};原因:{reason}";
}

View File

@ -10,7 +10,7 @@ public class UpsertTrackInput : OrderNoInfo
public bool IsAdd { get; set; } public bool IsAdd { get; set; }
/// <summary> /// <summary>
/// 轨迹唯一标识(可选) /// 轨迹唯一标识(可选,更新时优先按 EsId 匹配)
/// </summary> /// </summary>
public string? EsId { get; set; } public string? EsId { get; set; }
@ -34,26 +34,11 @@ public class UpsertTrackInput : OrderNoInfo
[Required] [Required]
public DateTime TrackTime { get; set; } public DateTime TrackTime { get; set; }
/// <summary>
/// 新的轨迹发生时间
/// 如果是新增,那么优先级高于TrackTime
/// 如果要修改TrackTime,请同时传递TrackTime(旧的用于匹配),和NewTrackTime
/// </summary>
public DateTime? NewTrackTime { get; set; }
/// <summary> /// <summary>
/// 轨迹发生时区 /// 轨迹发生时区
/// </summary> /// </summary>
// [Required]
// [Range(-12, 12)]
public float? TrackTimeZone { get; set; } public float? TrackTimeZone { get; set; }
/// <summary>
/// 客户是否可见
/// </summary>
[Obsolete("废弃的,请使用IsExternal")]
public bool IsVisibility { get; set; }
/// <summary> /// <summary>
/// 是否对外(可选的) /// 是否对外(可选的)
/// 如果未填则按照【轨迹内容】中的【是否可见】做为默认值 /// 如果未填则按照【轨迹内容】中的【是否可见】做为默认值
@ -87,68 +72,10 @@ public class UpsertTrackInput : OrderNoInfo
public string? EnStatus { get; set; } public string? EnStatus { get; set; }
/// <summary> /// <summary>
/// 轨迹发生地(城市) /// 扩展信息字段,用于轨迹推送的附加字段
/// </summary>
public string? TrackPlaceCity { get; set; }
/// <summary>
/// 轨迹发生地(城市)Id
/// </summary>
public int? TrackPlaceCityId { get; set; }
/// <summary>
/// 轨迹发生地(省/州)
/// </summary>
public string? TrackPlaceProvince { get; set; }
/// <summary>
/// 轨迹发生地(省/州)Id
/// </summary>
public int? TrackPlaceProvinceId { get; set; }
/// <summary>
/// 轨迹发生地(国家/地区)
/// </summary>
public string? TrackPlaceNation { get; set; }
/// <summary>
/// 轨迹发生地(国家/地区)Id
/// </summary>
public int? TrackPlaceNationId { get; set; }
/// <summary>
/// 轨迹发生地邮编
/// </summary>
public string? TrackPlacePostcode { get; set; }
/// <summary>
/// 预计到达时间
/// </summary>
public DateTime? EstimatedDeliveryDate { get; set; }
/// <summary>
/// 目的地时区
/// </summary>
[Range(-12, 12)]
public float? DestinationTimeZone { get; set; }
/// <summary>
/// 重复的情况下,用什么比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary> /// </summary>
public string? Extra { get; set; } public string? Extra { get; set; }
/// <summary>
/// 新的扩展信息,
/// 如果是新增,那么优先级高于Extra
/// 如果要修改Extra,请同时传递Extra(用于匹配旧的)和NewExtra
/// </summary>
public string? NewExtra { get; set; }
/// <summary> /// <summary>
/// 是否强制推送 /// 是否强制推送
/// </summary> /// </summary>

View File

@ -1,79 +0,0 @@
namespace TrackCenter.Application.Dtos.TrackContents;
public sealed class TrackContentOutput
{
/// <summary>
/// 应用
/// </summary>
public string AppKey { get; set; }
/// <summary>
/// 应用代码
/// </summary>
public string AppCode { get; set; }
/// <summary>
/// 轨迹状态Id
/// </summary>
public int TrackStatusId { get; set; }
/// <summary>
/// 轨迹状态
/// </summary>
public string TrackStatus { get; set; } = null!;
/// <summary>
/// 轨迹内容Id
/// </summary>
public int TrackContentId { get; set; }
/// <summary>
/// 轨迹内容
/// </summary>
public string TrackContent { get; set; } = null!;
/// <summary>
/// 轨迹内容英文
/// </summary>
public string TrackEnContent { get; set; } = null!;
/// <summary>
/// 是否完成态
/// </summary>
public bool IsCompleted { get; set; }
/// <summary>
/// 轨迹状态代码
/// </summary>
public string TrackStatusCode { get; set; }
/// <summary>
/// 是否可重复
/// </summary>
public bool IsRepeatable { get; set; }
/// <summary>
/// 是否允许手动修改
/// </summary>
public bool IsAllowManuallyUpdate { get; set; }
/// <summary>
/// 是否允许手动创建
/// </summary>
public bool IsAllowManuallyCreate { get; set; }
/// <summary>
/// 是否广播
/// </summary>
public bool IsToMq { get; set; }
/// <summary>
/// 是否对外
/// </summary>
public bool IsExternal { get; set; }
/// <summary>
/// 轨迹内容code
/// </summary>
public string TrackContentCode { get; set; }
}

View File

@ -54,13 +54,6 @@ public class TrackUpsertEvent
/// </summary> /// </summary>
public DateTime TrackTime { get; set; } public DateTime TrackTime { get; set; }
/// <summary>
/// 新的轨迹发生时间
/// 如果是新增,那么优先级高于TrackTime
/// 如果要修改TrackTime,请同时传递TrackTime(旧的用于匹配),和NewTrackTime
/// </summary>
public DateTime? NewTrackTime { get; set; }
/// <summary> /// <summary>
/// 轨迹发生时区 /// 轨迹发生时区
/// </summary> /// </summary>
@ -104,67 +97,10 @@ public class TrackUpsertEvent
public string? EnStatus { get; set; } public string? EnStatus { get; set; }
/// <summary> /// <summary>
/// 轨迹发生地(城市) /// 扩展信息字段,用于轨迹推送的附加字段
/// </summary>
public string? TrackPlaceCity { get; set; }
/// <summary>
/// 轨迹发生地(城市)Id
/// </summary>
public int? TrackPlaceCityId { get; set; }
/// <summary>
/// 轨迹发生地(省/州)
/// </summary>
public string? TrackPlaceProvince { get; set; }
/// <summary>
///轨迹发生地(省/州)Id
/// </summary>
public int? TrackPlaceProvinceId { get; set; }
/// <summary>
/// 轨迹发生地(国家/地区)
/// </summary>
public string? TrackPlaceNation { get; set; }
/// <summary>
/// 轨迹发生地(国家/地区)Id
/// </summary>
public int? TrackPlaceNationId { get; set; }
/// <summary>
/// 轨迹发生地邮编
/// </summary>
public string? TrackPlacePostcode { get; set; }
/// <summary>
/// 预计到达时间
/// </summary>
public DateTime? EstimatedDeliveryDate { get; set; }
/// <summary>
/// 目的地时区
/// </summary>
public float? DestinationTimeZone { get; set; }
/// <summary>
/// 重复的情况下,用什么比较
/// </summary>
public string? CompareFields { get; set; }
/// <summary>
/// 扩展信息字段,用与轨迹推送的附加字段
/// </summary> /// </summary>
public string? Extra { get; set; } public string? Extra { get; set; }
/// <summary>
/// 新的扩展信息,
/// 如果是新增,那么优先级高于Extra
/// 如果要修改Extra,请同时传递Extra(用于匹配旧的)和NewExtra
/// </summary>
public string? NewExtra { get; set; }
/// <summary> /// <summary>
/// 是否强制推送 /// 是否强制推送
/// </summary> /// </summary>

View File

@ -1,7 +1,4 @@
using Hangfire; using Hangfire;
using Microsoft.Extensions.Options;
using TrackCenter.Application.Options;
using TrackCenter.Application.Services.Warning.Handlers;
using TrackCenter.Domain.Aggregates.WarningAggregate; using TrackCenter.Domain.Aggregates.WarningAggregate;
namespace TrackCenter.Application.Hangfire; namespace TrackCenter.Application.Hangfire;
@ -11,8 +8,6 @@ public interface IWarningTaskScanner
Task ScanNotifyTaskAndSchedulingAsync(); Task ScanNotifyTaskAndSchedulingAsync();
Task ScanReturnRetryPushTaskAndSchedulingAsync(); Task ScanReturnRetryPushTaskAndSchedulingAsync();
Task ScanTransportDetailsGenerateTaskAndSchedulingAsync();
} }
/// <summary> /// <summary>
@ -21,23 +16,17 @@ public interface IWarningTaskScanner
/// </summary> /// </summary>
public class WarningTaskScanner : IWarningTaskScanner, ITransientDependency public class WarningTaskScanner : IWarningTaskScanner, ITransientDependency
{ {
// private readonly IWarningTaskExecuteWrapper _taskExecuteWrapper;
// private readonly IWarningTaskStatusCacheService _warningTaskStatusCacheService;
private readonly IWarningTaskRepository _warningTaskRepository; private readonly IWarningTaskRepository _warningTaskRepository;
private readonly ITransportWarningHandler _transportWarningHandler;
private readonly WarningNotifyAggregator _warningNotifyAggregator; private readonly WarningNotifyAggregator _warningNotifyAggregator;
private readonly IWarningTaskService _warningTaskService; private readonly IWarningTaskService _warningTaskService;
private readonly IOptionsMonitor<TrackWarningTaskOptions> _optionsMonitor;
public WarningTaskScanner(IWarningTaskRepository warningTaskRepository, public WarningTaskScanner(IWarningTaskRepository warningTaskRepository,
ITransportWarningHandler transportWarningHandler, WarningNotifyAggregator warningNotifyAggregator, WarningNotifyAggregator warningNotifyAggregator,
IWarningTaskService warningTaskService, IOptionsMonitor<TrackWarningTaskOptions> optionsMonitor) IWarningTaskService warningTaskService)
{ {
_warningTaskRepository = warningTaskRepository; _warningTaskRepository = warningTaskRepository;
_transportWarningHandler = transportWarningHandler;
_warningNotifyAggregator = warningNotifyAggregator; _warningNotifyAggregator = warningNotifyAggregator;
_warningTaskService = warningTaskService; _warningTaskService = warningTaskService;
_optionsMonitor = optionsMonitor;
} }
// 入队等待调度 // 入队等待调度
@ -73,20 +62,4 @@ public class WarningTaskScanner : IWarningTaskScanner, ITransientDependency
!x.IsClosed && taskIds.Contains(x.Id)); !x.IsClosed && taskIds.Contains(x.Id));
} }
} }
// 批量执行
public async Task ScanTransportDetailsGenerateTaskAndSchedulingAsync()
{
// 1.查出所有可以执行预警生成任务(未关闭&当前时间大于等于计划执行时间)
var tasks = await _warningTaskRepository.ScanTransportWarningDetailsGenerateTaskAsync();
if (tasks.Count == 0) return;
// 2.执行预警生成任务(批量)
await _transportWarningHandler.HandleWarningDetailsGenerateTask(tasks);
// 3.关闭预警生成任务
var taskIds = tasks.Select(x => x.Id).ToList();
await _warningTaskRepository.CloseTransportWarningDetailsGenerateTaskAsync(x =>
!x.IsClosed && taskIds.Contains(x.Id));
}
} }

View File

@ -47,10 +47,9 @@ public class WarningTaskComponentHostedService : IHostedService
private void ReloadConfig(IOptionsMonitor<TrackWarningTaskOptions> optionsMonitor) private void ReloadConfig(IOptionsMonitor<TrackWarningTaskOptions> optionsMonitor)
{ {
var newVal = optionsMonitor.CurrentValue; var newVal = optionsMonitor.CurrentValue;
_logger.LogInformation("扫描任务配置,预警通知扫描周期{a}h,回传重试推送任务扫描周期{b}min,运输预警明细扫描周期{c}min", _logger.LogInformation("扫描任务配置,预警通知扫描周期{a}h,回传重试推送任务扫描周期{b}min",
newVal.ScanWarningNotifyTaskHourInterval, newVal.ScanWarningNotifyTaskHourInterval,
newVal.ScanReturnWarningRetryPushTaskMinuteInterval, newVal.ScanReturnWarningRetryPushTaskMinuteInterval);
newVal.ScanTransportWarningDetailsGenerateTaskMinuteInterval);
if ((_oldOptions?.ScanWarningNotifyTaskHourInterval ?? 0) != newVal.ScanWarningNotifyTaskHourInterval) if ((_oldOptions?.ScanWarningNotifyTaskHourInterval ?? 0) != newVal.ScanWarningNotifyTaskHourInterval)
{ {
var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.ScanWarningNotifyTaskLevel]; var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.ScanWarningNotifyTaskLevel];
@ -71,15 +70,8 @@ public class WarningTaskComponentHostedService : IHostedService
$"*/{newVal.ScanReturnWarningRetryPushTaskMinuteInterval} * * * *"); $"*/{newVal.ScanReturnWarningRetryPushTaskMinuteInterval} * * * *");
} }
if ((_oldOptions?.ScanTransportWarningDetailsGenerateTaskMinuteInterval ?? 0) != newVal.ScanTransportWarningDetailsGenerateTaskMinuteInterval) // 移除历史运输预警明细生成任务扫描(若仍挂在 Hangfire 上)
{ _recurringJobManager.RemoveIfExists("Scan_TransportWarningDetailsGenerateTask");
var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.ScanTransportWarningDetailsGenerateTaskLevel];
// 运输预警明细生成任务扫描
_recurringJobManager.AddOrUpdate<IWarningTaskScanner>("Scan_TransportWarningDetailsGenerateTask",
q,
x => x.ScanTransportDetailsGenerateTaskAndSchedulingAsync(),
$"*/{newVal.ScanTransportWarningDetailsGenerateTaskMinuteInterval} * * * *");
}
_oldOptions = newVal; _oldOptions = newVal;
} }

View File

@ -28,10 +28,4 @@ public sealed class TrackWarningTaskOptions
/// <summary> 回传重试推送任务扫描间隔(分钟) </summary> /// <summary> 回传重试推送任务扫描间隔(分钟) </summary>
public int ScanReturnWarningRetryPushTaskMinuteInterval { get; set; } public int ScanReturnWarningRetryPushTaskMinuteInterval { get; set; }
/// <summary> 运输预警明细生成任务扫描优先级 </summary>
public int ScanTransportWarningDetailsGenerateTaskLevel { get; set; }
/// <summary> 预警明细生成任务扫描间隔(分钟) </summary>
public int ScanTransportWarningDetailsGenerateTaskMinuteInterval { get; set; }
} }

View File

@ -70,6 +70,84 @@ public class ProvideQueries : SqlSugarRootlessRepository<TrackCenterDbContext>,
.ToListAsync(); .ToListAsync();
} }
/// <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( public async Task<List<OrderWithServiceProviderVO>> FindOrderWithServiceProviderAsync(
TrackPullConfigVo config, TrackPullConfigVo config,
int pageIndex, int pageSize = 1000) int pageIndex, int pageSize = 1000)

View File

@ -1,27 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using TrackCenter.Domain.Aggregates.OrderAggregate;
namespace TrackCenter.Application.Services.DebugApis;
[AllowAnonymous]
[ApiExplorerSettings(GroupName = "调试专用")]
public class NormalService : ApplicationServiceBase
{
private readonly IOrderRepository _orderRepository;
public NormalService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task<string> Hello(string name)
{
var order = await _orderRepository.GetFirstAsync(x => true);
if (order == null)
{
throw new BusinessException(message: "订单不存在");
}
return $"hello {name}";
}
}

View File

@ -1,50 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using TrackCenter.Domain.Aggregates.OrderAggregate;
namespace TrackCenter.Application.Services.DebugApis;
[AllowAnonymous]
[ApiExplorerSettings(GroupName = "调试专用")]
public class OTelService : ApplicationServiceBase
{
private readonly IBasicRepository<OrderEntity> _orderRepository;
public OTelService(IBasicRepository<OrderEntity> orderRepository)
{
_orderRepository = orderRepository;
}
// [OTel]
public async Task<string> Hello(string name)
{
var order = await _orderRepository.GetFirstAsync(x => true);
if (order == null)
{
throw new BusinessException(message: "订单不存在");
}
await DoSomethingAsync(order);
return $"hello {name}";
}
// [OTel]
private async Task DoSomethingAsync(OrderEntity order)
{
var order2 = await _orderRepository.GetFirstAsync(x => true);
if (order.Id == order2?.Id)
{
await Task.Delay(3000);
}
await DoWorkAsync();
}
// [OTel]
private async Task DoWorkAsync()
{
await Task.Delay(1000);
}
}

View File

@ -1,37 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using TrackCenter.Application.Services.Warning.Handlers;
using TrackCenter.Domain.Aggregates.WarningAggregate;
namespace TrackCenter.Application.Services.DebugApis.TransportDetailsGenerate;
/// <summary>
/// 运输预警明细生成任务
/// </summary>
[AllowAnonymous]
[ApiExplorerSettings(GroupName = "调试专用")]
[Route("/S07")]
public class _TransportDetailsGenerateService : ApplicationServiceBase
{
private readonly IWarningTaskRepository _warningTaskRepository;
private readonly ITransportWarningHandler _transportWarningHandler;
public _TransportDetailsGenerateService(IWarningTaskRepository warningTaskRepository, ITransportWarningHandler transportWarningHandler)
{
_warningTaskRepository = warningTaskRepository;
_transportWarningHandler = transportWarningHandler;
}
/// <summary>
/// 运输预警明细生成任务立即执行,不考虑计划执行时间,执行完成任务会关闭
///
/// </summary>
[HttpGet("TransportWarningDetailsGenerate_ScanAndExec")]
public async Task TransportWarningDetailsGenerate_ScanAndExec()
{
var tasks = await _warningTaskRepository.GetTransportWarningDetailsGenerateTaskAsync(x => !x.IsClosed);
var taskIds = tasks.Select(x => x.Id).ToHashSet();
await _warningTaskRepository.CloseTransportWarningDetailsGenerateTaskAsync(x => !x.IsClosed && taskIds.Contains(x.Id));
await _transportWarningHandler.HandleWarningDetailsGenerateTask(tasks);
}
}

View File

@ -1,20 +0,0 @@
using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
using TrackCenter.Domain.Aggregates.WarningAggregate;
namespace TrackCenter.Application.Services.DebugApis;
[ApiExplorerSettings(GroupName = "调试专用")]
public class _EsInitService(
ITrackDetailsEsRepository trackDetailsEsRepository,
IWarningDetailsEsRepository warningDetailsEsRepository)
: ApplicationService
{
[HttpGet("EsInit")]
public async Task EsInit()
{
Log.Warning($"ES索引初始化开始 OperationUserId:[{CurrentUser.Id}]", true);
await trackDetailsEsRepository.MigrateAsync();
await warningDetailsEsRepository.CreateAliasAsync();
Log.Warning($"ES索引初始化完成 OperationUserId:[{CurrentUser.Id}]", true);
}
}

View File

@ -1,54 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using TrackCenter.Application.Dtos.TrackPush;
using TrackCenter.Application.Services.Warning.Handlers;
using TrackCenter.Domain.Aggregates.TrackPushAggregate;
namespace TrackCenter.Application.Services.DebugApis;
[AllowAnonymous]
[ApiExplorerSettings(GroupName = "调试专用")]
[Route("/S07")]
public class _MockReturnWarningService : ApplicationServiceBase
{
private readonly IReturnWarningHandler _returnWarningHandler;
public _MockReturnWarningService(IReturnWarningHandler returnWarningHandler)
{
_returnWarningHandler = returnWarningHandler;
}
// /// <summary>
// /// 模拟接口通回调
// /// </summary>
// [HttpPost("MockReturn")]
// public async Task<string> MockReturn(TrackPushRecordIndex record)
// {
// await _returnWarningHandler.ReceiveTrackPushRecordHandleAsync(record);
// return "Ok";
// }
/// <summary>
/// 模拟接口通回调
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("MockReturn")]
public async Task<string> MockReturn(MockReturnInput input)
{
var requestPrams = TextJsonHelper.Serialize(input.PushInputs)!;
input.Records.ForEach(x =>
{
x.RequestId = input.RequestId;
x.IsSuccess = input.IsSuccess;
x.Message = input.ReturnMessage;
});
await _returnWarningHandler.ReceiveTrackPushRecordHandleAsync(input.IsSuccess, requestPrams, input.Records, input.PushInputs, new List<TrackUpdateInput>(0));
return "Ok";
}
}
public record MockReturnInput(bool IsSuccess, string ReturnMessage, Guid RequestId, List<TrackPushInput> PushInputs,List<TrackPushRecordIndex> Records);

View File

@ -1,4 +1,4 @@
using DotNetCore.CAP; using DotNetCore.CAP;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Events; using TrackCenter.Application.Events;
using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.OrderAggregate;
@ -28,16 +28,19 @@ public partial class OrderService
x => x.Id); x => x.Id);
if (orderId == default) if (orderId == default)
{ {
throw new BusinessException(message: $"订单:{input.OrderNo}不存在"); throw ProviderServiceActionResult.Exception("关联失败", $"订单:{input.OrderNo}不存在");
} }
// tips:2025-11-29 zzh 订单类型(1是小包,2是大包,4是主单),必填,同时orderType小于linkOrderType // tips:2025-11-29 zzh 订单类型(1是小包,2是大包,4是主单),必填,同时orderType小于linkOrderType
if (input.OrderType < input.LinkOrderType) if (input.OrderType < input.LinkOrderType)
{ {
throw new BusinessException(message: "orderType小于linkOrderType"); throw ProviderServiceActionResult.Exception("关联失败", "orderType小于linkOrderType");
} }
if (input.LinkOrderNos.Count < 1 || input.LinkOrderType == 0) return; if (input.LinkOrderNos.Count < 1 || input.LinkOrderType == 0)
{
return;
}
// 单号去重,去空,去自己 // 单号去重,去空,去自己
input.LinkOrderNos = input.LinkOrderNos input.LinkOrderNos = input.LinkOrderNos
@ -46,17 +49,14 @@ public partial class OrderService
if (!input.IsLink) if (!input.IsLink)
{ {
// 解绑
await UnLinkOrderAsync(input, orderId); await UnLinkOrderAsync(input, orderId);
} return;
else
{
// 关联订单
await DoLinkOrderAsync(input, orderId);
}
} }
private async Task DoLinkOrderAsync(LinkOrderInput input, int orderId) await DoLinkOrderAsync(input, orderId);
}
private async Task<int> DoLinkOrderAsync(LinkOrderInput input, int orderId)
{ {
// 获取已关联的订单 // 获取已关联的订单
var linkOrderNos = var linkOrderNos =
@ -81,8 +81,13 @@ public partial class OrderService
// 如果有不存在的单,需要抛异常 // 如果有不存在的单,需要抛异常
if (existedOrders.Count != noLinkOrderNos.Count) if (existedOrders.Count != noLinkOrderNos.Count)
{ {
throw new BusinessException( throw ProviderServiceActionResult.Exception("关联失败",
message: $"关联单:{noLinkOrderNos.Except(existedOrders.Select(x => x.OrderNo)).JoinAsString(", ")}不存在"); $"关联单:{noLinkOrderNos.Except(existedOrders.Select(x => x.OrderNo)).JoinAsString(", ")}不存在");
}
if (existedOrders.Count == 0)
{
return 0;
} }
await _orderRepository.LinkOrderAsync(existedOrders.Select(x => new OrderLinkEntity await _orderRepository.LinkOrderAsync(existedOrders.Select(x => new OrderLinkEntity
@ -125,14 +130,17 @@ public partial class OrderService
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, fillTrackEvent); await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, fillTrackEvent);
} }
} }
return existedOrders.Count;
} }
private async Task UnLinkOrderAsync(LinkOrderInput input, int orderId) private async Task<int> UnLinkOrderAsync(LinkOrderInput input, int orderId)
{ {
var unLinkIds = await _orderRepository.GetLinkedOrdersAsync(orderId, input.LinkAppKey, input.LinkOrderType, var unLinkIds = await _orderRepository.GetLinkedOrdersAsync(orderId, input.LinkAppKey, input.LinkOrderType,
input.LinkOrderNos); input.LinkOrderNos);
// real del // real del
await _orderRepository.UnLinkByIdsAsync(unLinkIds); await _orderRepository.UnLinkByIdsAsync(unLinkIds);
return unLinkIds.Count;
} }
} }

View File

@ -1,4 +1,4 @@
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Domain.Dtos.Order; using TrackCenter.Domain.Dtos.Order;
@ -61,6 +61,11 @@ public partial class OrderService
x => x.AppKey == input.AppKey && x.OrderType == input.OrderType && x => x.AppKey == input.AppKey && x.OrderType == input.OrderType &&
input.OrderNos.Contains(x.OrderNo), input.OrderNos.Contains(x.OrderNo),
x => x.Id); x => x.Id);
if (orderIds.Count == 0)
{
throw ProviderServiceActionResult.Exception("修改拉取配置失败", "未找到匹配订单");
}
var inputVo = ObjectMapper var inputVo = ObjectMapper
.Map<PullConfigInfo, UpdateOrderPullConfigsInputVo>(input.Config); .Map<PullConfigInfo, UpdateOrderPullConfigsInputVo>(input.Config);
inputVo.OrderIds = orderIds.ToArray(); inputVo.OrderIds = orderIds.ToArray();
@ -69,5 +74,6 @@ public partial class OrderService
Log.Warning( Log.Warning(
$"{CurrentUser.UserName}正在修改服务商拉取信息,本次共计影响了{orderIds.Count}个订单、{cnt}段拉取,请求参数是:{input.ToJson()}", $"{CurrentUser.UserName}正在修改服务商拉取信息,本次共计影响了{orderIds.Count}个订单、{cnt}段拉取,请求参数是:{input.ToJson()}",
true); true);
} }
} }

View File

@ -1,36 +1,31 @@
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Dtos.ProvideServices.V2; using TrackCenter.Application.Dtos.ProvideServices.V2;
using TrackCenter.Application.Filters; using TrackCenter.Application.Filters;
using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.OrderAggregate;
using TrackCenter.Domain.Aggregates.TrackPullAggregate; using TrackCenter.Domain.Aggregates.TrackPullAggregate;
using TrackCenter.Repository.Aggregates.OrderAggregate;
namespace TrackCenter.Application.Services.ProvideServices; namespace TrackCenter.Application.Services.ProvideServices;
[VerifySignature] /// <summary>
public class OrderServiceV2( /// 订单服务 - 小包/大包/主单 Upsert(原 OrderServiceV2)
IOrderRepository orderRepository, /// </summary>
IOrderOperationRecordEsRepository orderOperationRecordEsRepository, public partial class OrderService
ITrackPullConfigRepository trackPullConfigRepository)
: ApplicationServiceBase
{ {
/// <summary> /// <summary>
/// 新增、更新小包(存在就更新) /// 新增、更新小包(存在就更新)
/// </summary> /// </summary>
/// <param name="input"></param> [HttpPost("Order/UpsertSmallBag")]
/// <returns></returns> [VerifySignature]
[HttpPost("OrderV2/UpsertSmallBag")] public async Task<IdBaseDto> UpsertSmallBagAsync(UpsertSmallBagInput input)
public async Task<IdBaseDto> UpsertAsync(UpsertSmallBagInput input)
{ {
// 校验订单是否已存在,存在则更新 var order = await _orderRepository.GetFirstAsync(x =>
var order = await orderRepository.GetFirstAsync(x =>
x.AppKey == AppKey && x.OrderType == TrackOrderType.小包 && x.OrderNo == input.OrderNo); x.AppKey == AppKey && x.OrderType == TrackOrderType.小包 && x.OrderNo == input.OrderNo);
// 订单已存在则视为更新
if (order != null) if (order != null)
{ {
order = await BuiltOrder(input); order = await BuiltOrder(input);
await orderRepository.UpdateOrderAsync(order); await _orderRepository.UpdateOrderAsync(order);
var index = new OrderOperationRecordIndex var index = new OrderOperationRecordIndex
{ {
@ -42,18 +37,14 @@ public class OrderServiceV2(
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
}; };
_ = orderOperationRecordEsRepository.AddAsync(index); _ = _orderOperationRecordEsRepository.AddAsync(index);
return new IdBaseDto return new IdBaseDto { Id = order.Id };
{
Id = order.Id
};
} }
order = await BuiltOrder(input); order = await BuiltOrder(input);
var orderId = await orderRepository.CreateOrderAsync(order); var orderId = await _orderRepository.CreateOrderAsync(order);
// 写入操作日志
var indexForAdd = new OrderOperationRecordIndex var indexForAdd = new OrderOperationRecordIndex
{ {
OrderId = orderId, OrderId = orderId,
@ -64,32 +55,25 @@ public class OrderServiceV2(
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
CreateTime = DateTime.Now CreateTime = DateTime.Now
}; };
_ = orderOperationRecordEsRepository.AddAsync(indexForAdd); _ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
return new IdBaseDto { Id = orderId };
return new IdBaseDto
{
Id = orderId
};
} }
/// <summary> /// <summary>
/// 新增、更新大包(存在就更新) /// 新增、更新大包(存在就更新)
/// </summary> /// </summary>
/// <param name="input"></param> [HttpPost("Order/UpsertBigBag")]
/// <returns></returns> [VerifySignature]
[HttpPost("OrderV2/UpsertBigBag")] public async Task<IdBaseDto> UpsertBigBagAsync(UpsertBigBagInput input)
public async Task<IdBaseDto> UpsertAsync(UpsertBigBagInput input)
{ {
// 校验订单是否已存在,存在则更新 var order = await _orderRepository.GetFirstAsync(x =>
var order = await orderRepository.GetFirstAsync(x =>
x.AppKey == AppKey && x.OrderType == TrackOrderType.大包 && x.OrderNo == input.BagNo); x.AppKey == AppKey && x.OrderType == TrackOrderType.大包 && x.OrderNo == input.BagNo);
// 订单已存在则视为更新
if (order != null) if (order != null)
{ {
order = await BuiltOrder(input); order = await BuiltOrder(input);
await orderRepository.UpdateOrderAsync(order); await _orderRepository.UpdateOrderAsync(order);
var index = new OrderOperationRecordIndex var index = new OrderOperationRecordIndex
{ {
@ -101,18 +85,14 @@ public class OrderServiceV2(
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
}; };
_ = orderOperationRecordEsRepository.AddAsync(index); _ = _orderOperationRecordEsRepository.AddAsync(index);
return new IdBaseDto return new IdBaseDto { Id = order.Id };
{
Id = order.Id
};
} }
order = await BuiltOrder(input); order = await BuiltOrder(input);
var orderId = await orderRepository.CreateOrderAsync(order); var orderId = await _orderRepository.CreateOrderAsync(order);
// 写入操作日志
var indexForAdd = new OrderOperationRecordIndex var indexForAdd = new OrderOperationRecordIndex
{ {
OrderId = orderId, OrderId = orderId,
@ -123,32 +103,25 @@ public class OrderServiceV2(
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
CreateTime = DateTime.Now CreateTime = DateTime.Now
}; };
_ = orderOperationRecordEsRepository.AddAsync(indexForAdd); _ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
return new IdBaseDto { Id = orderId };
return new IdBaseDto
{
Id = orderId
};
} }
/// <summary> /// <summary>
/// 新增、更新主单(存在就更新) /// 新增、更新主单(存在就更新)
/// </summary> /// </summary>
/// <param name="input"></param> [HttpPost("Order/UpsertMasterOrder")]
/// <returns></returns> [VerifySignature]
[HttpPost("OrderV2/UpsertMasterOrder")] public async Task<IdBaseDto> UpsertMasterOrderAsync(UpsertMasterOrderInput input)
public async Task<IdBaseDto> UpsertAsync(UpsertMasterOrderInput input)
{ {
// 校验订单是否已存在,存在则更新 var order = await _orderRepository.GetFirstAsync(x =>
var order = await orderRepository.GetFirstAsync(x =>
x.AppKey == AppKey && x.OrderType == TrackOrderType.主单 && x.OrderNo == input.MasterNo); x.AppKey == AppKey && x.OrderType == TrackOrderType.主单 && x.OrderNo == input.MasterNo);
// 订单已存在则视为更新
if (order != null) if (order != null)
{ {
order = await BuiltOrder(input); order = await BuiltOrder(input);
await orderRepository.UpdateOrderAsync(order); await _orderRepository.UpdateOrderAsync(order);
var index = new OrderOperationRecordIndex var index = new OrderOperationRecordIndex
{ {
@ -160,18 +133,14 @@ public class OrderServiceV2(
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
}; };
_ = orderOperationRecordEsRepository.AddAsync(index); _ = _orderOperationRecordEsRepository.AddAsync(index);
return new IdBaseDto return new IdBaseDto { Id = order.Id };
{
Id = order.Id
};
} }
order = await BuiltOrder(input); order = await BuiltOrder(input);
var orderId = await orderRepository.CreateOrderAsync(order); var orderId = await _orderRepository.CreateOrderAsync(order);
// 写入操作日志
var indexForAdd = new OrderOperationRecordIndex var indexForAdd = new OrderOperationRecordIndex
{ {
OrderId = orderId, OrderId = orderId,
@ -182,13 +151,9 @@ public class OrderServiceV2(
CreateUserName = CurrentUser.UserName ?? string.Empty, CreateUserName = CurrentUser.UserName ?? string.Empty,
CreateTime = DateTime.Now CreateTime = DateTime.Now
}; };
_ = orderOperationRecordEsRepository.AddAsync(indexForAdd); _ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
return new IdBaseDto { Id = orderId };
return new IdBaseDto
{
Id = orderId
};
} }
private async Task<OrderEntity> BuiltOrder(UpsertSmallBagInput input) private async Task<OrderEntity> BuiltOrder(UpsertSmallBagInput input)
@ -212,11 +177,15 @@ public class OrderServiceV2(
ArrivalEmail = input.Consignee.Email, ArrivalEmail = input.Consignee.Email,
}; };
var config = await trackPullConfigRepository var config = await _trackPullConfigRepository
.FirstOrDefaultAsync(x => x.AppKey == AppKey .FirstOrDefaultAsync(x => x.AppKey == AppKey
&& x.ServiceProviderCode == input.ApiCode); && x.ServiceProviderCode == input.ApiCode);
if (config == null) throw new BusinessException(message: "没有一个拉取配置可以适用,请配置,否则无法拉取轨迹"); if (config == null)
{
throw ProviderServiceActionResult.Exception("创建/更新失败",
"没有适用的拉取配置,请先配置轨迹拉取,否则无法拉取轨迹");
}
order.OrderPullStatus = [CreateOrderPullStatus(config, order)]; order.OrderPullStatus = [CreateOrderPullStatus(config, order)];
@ -228,7 +197,6 @@ public class OrderServiceV2(
var order = new OrderEntity var order = new OrderEntity
{ {
AppKey = AppKey!, AppKey = AppKey!,
//ServiceProviderCode = input.ApiCode,
OrderType = TrackOrderType.大包, OrderType = TrackOrderType.大包,
OrderNo = input.BagNo, OrderNo = input.BagNo,
ThirdPartyOrderNumber = input.OtherNo, ThirdPartyOrderNumber = input.OtherNo,
@ -239,15 +207,6 @@ public class OrderServiceV2(
}; };
// tips: 2025-11-29 zzh 大包没有轨迹拉取能力 // tips: 2025-11-29 zzh 大包没有轨迹拉取能力
// var config = await trackPullConfigRepository
// .FirstOrDefaultAsync(x => x.AppKey == AppKey
// && x.ServiceProviderCode == input.ApiCode);
//
// if (config == null) throw new BusinessException(message: "没有一个拉取配置可以适用,请配置,否则无法拉取轨迹");
//
// order.OrderPullStatus = [CreateOrderPullStatus(config, order)];
return Task.FromResult(order); return Task.FromResult(order);
} }
@ -266,11 +225,15 @@ public class OrderServiceV2(
order.OrderFlights = new List<OrderFlightEntity>(); order.OrderFlights = new List<OrderFlightEntity>();
var config = await trackPullConfigRepository var config = await _trackPullConfigRepository
.FirstOrDefaultAsync(x => x.AppKey == AppKey .FirstOrDefaultAsync(x => x.AppKey == AppKey
&& x.ServiceProviderCode == input.ApiCode); && x.ServiceProviderCode == input.ApiCode);
if (config == null) throw new BusinessException(message: "没有一个拉取配置可以适用,请配置,否则无法拉取轨迹"); if (config == null)
{
throw ProviderServiceActionResult.Exception("创建/更新失败",
"没有适用的拉取配置,请先配置轨迹拉取,否则无法拉取轨迹");
}
order.OrderPullStatus = [CreateOrderPullStatus(config, order)]; order.OrderPullStatus = [CreateOrderPullStatus(config, order)];

View File

@ -1,9 +1,8 @@
using DotNetCore.CAP; using DotNetCore.CAP;
using InterfaceForward.Domain.Shared.Helpers;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.OrderAggregate;
using TrackCenter.Domain.Aggregates.TrackDetailsAggregate; using TrackCenter.Domain.Aggregates.TrackDetailsAggregate;
using SqlSugar; using TrackCenter.Domain.Aggregates.TrackPullAggregate;
using TrackCenter.Application.Services.Warning.Handlers; using TrackCenter.Application.Services.Warning.Handlers;
using TrackCenter.Repository.Aggregates.OrderAggregate; using TrackCenter.Repository.Aggregates.OrderAggregate;
@ -12,187 +11,32 @@ namespace TrackCenter.Application.Services.ProvideServices;
/// <summary> /// <summary>
/// 订单服务 /// 订单服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "提供接口")] [ApiExplorerSettings(GroupName = "OpenApi")]
[Route("/ProviderService")] [Route("/ProviderService")]
public partial class OrderService : ApplicationServiceBase public partial class OrderService : ApplicationServiceBase
{ {
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
private readonly IOrderRepository _orderRepository; private readonly IOrderRepository _orderRepository;
private readonly IOrderOperationRecordEsRepository _orderOperationRecordEsRepository;
private readonly ITrackPullConfigRepository _trackPullConfigRepository;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
public OrderService( public OrderService(
IOrderRepository orderRepository, IOrderRepository orderRepository,
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
ITrackPullConfigRepository trackPullConfigRepository,
ICapPublisher capPublisher, ICapPublisher capPublisher,
ITrackDetailsEsRepository trackDetailsEsRepository, ITrackDetailsEsRepository trackDetailsEsRepository,
IOrderOperationRecordEsRepository orderOperationRecordEsRepository,
IServiceProvider serviceProvider IServiceProvider serviceProvider
) )
{ {
_orderRepository = orderRepository; _orderRepository = orderRepository;
_orderOperationRecordEsRepository = orderOperationRecordEsRepository; _orderOperationRecordEsRepository = orderOperationRecordEsRepository;
_trackPullConfigRepository = trackPullConfigRepository;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_trackDetailsEsRepository = trackDetailsEsRepository; _trackDetailsEsRepository = trackDetailsEsRepository;
_capPublisher = capPublisher; _capPublisher = capPublisher;
} }
/// <summary>
/// 新增、更新小包(存在就更新)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Order/Upsert")]
public async Task<IdBaseDto> UpsertAsync(UpsertOrderInput input)
{
// 校验预计到达时间是否不是同时存在或同时不存在
if ((input.EstimatedDeliveryDate.HasValue && input.DestinationTimeZone.HasValue == false) ||
(input.EstimatedDeliveryDate.HasValue == false && input.DestinationTimeZone.HasValue))
{
throw new BusinessException(message: "【预计签收时间】和【签收地时区】必须同时输入");
}
// 校验订单是否已存在,存在则更新
var order = await _orderRepository.GetFirstAsync(x =>
x.AppKey == input.AppKey && x.OrderType == input.OrderType && x.OrderNo == input.OrderNo);
if (order != null)
{
// 订单已存在则视为更新
await UpdateOrderAsync(input, order);
var index = new OrderOperationRecordIndex
{
OrderId = order.Id,
OrderNo = input.OrderNo,
OptionType = OrderOperationType.修改订单.ToString(),
Content = input.ToJson(),
CreateUserId = CurrentUserId,
CreateUserName = CurrentUser.UserName ?? string.Empty,
};
_ = _orderOperationRecordEsRepository.AddAsync(index);
return new IdBaseDto
{
Id = order.Id
};
}
order = ObjectMapper.Map<UpsertOrderInput, OrderEntity>(input);
order.OrderFlights = ObjectMapper.Map<List<OrderFlightInfo>, List<OrderFlightEntity>>(input.FlightInfos);
order.OrderPullStatus = BuiltOrderPullStatus(input.PullConfigInfos);
order.SetThirdPartyOrderNumber(order.OrderPullStatus);
var orderId = await _orderRepository.CreateOrderAsync(order);
// 写入操作日志
var indexForAdd = new OrderOperationRecordIndex
{
OrderId = orderId,
OrderNo = input.OrderNo,
OptionType = nameof(OrderOperationType.创建订单),
Content = input.ToJson(),
CreateUserId = CurrentUserId,
CreateUserName = CurrentUser.UserName ?? string.Empty,
CreateTime = DateTime.Now
};
_ = _orderOperationRecordEsRepository.AddAsync(indexForAdd);
return new IdBaseDto
{
Id = orderId
};
}
/// <summary>
/// 批量创建/新增 轨迹订单
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Order/BatchUpsert")]
public async Task BatchUpsertAsync(BatchUpsertOrderInput input)
{
if (input.OrderInputs.Any(x => (x.EstimatedDeliveryDate.HasValue && x.DestinationTimeZone.HasValue == false) ||
(x.EstimatedDeliveryDate.HasValue == false && x.DestinationTimeZone.HasValue)))
{
throw new BusinessException(message: "【预计签收时间】和【签收地时区】必须同时输入");
}
var exp = Expressionable.Create<OrderEntity>();
foreach (var order in input.OrderInputs)
exp.Or(x => x.OrderType == order.OrderType && x.AppKey == order.AppKey && x.OrderNo == order.OrderNo);
// 获取已存在的订单
var existsOrders = await _orderRepository.GetListAsync(exp.ToExpression());
var updateOrders = existsOrders.Count > 0 ? new List<OrderEntity>(existsOrders.Count) : Empty<OrderEntity>.List;
var updateRecords = existsOrders.Count > 0 ? new List<OrderOperationRecordIndex>(existsOrders.Count) : Empty<OrderOperationRecordIndex>.List;
if (existsOrders.Count > 0)
{
foreach (var order in existsOrders)
{
var input2 = input.OrderInputs.First(x => x.OrderType == order.OrderType && x.AppKey == order.AppKey && x.OrderNo == order.OrderNo);
var order2 = ObjectMapper.Map(input2, order);
order2.OrderFlights = ObjectMapper.Map<List<OrderFlightInfo>, List<OrderFlightEntity>>(input2.FlightInfos);
order2.OrderPullStatus = BuiltOrderPullStatus(input2.PullConfigInfos);
order2.SetThirdPartyOrderNumber(order.OrderPullStatus);
updateOrders.Add(order2);
updateRecords.Add(new OrderOperationRecordIndex
{
OrderId = order.Id,
OrderNo = order.OrderNo,
OptionType = nameof(OrderOperationType.修改订单),
Content = input2.ToJson(),
CreateUserId = CurrentUserId,
CreateUserName = CurrentUser.UserName ?? string.Empty,
CreateTime = DateTime.Now
});
}
await _orderRepository.UpdateBatchOrderAsync(updateOrders);
_ = _orderOperationRecordEsRepository.AddManyAsync(updateRecords);
}
// 如果全部操作为修改
if (existsOrders.Count == input.OrderInputs.Count) return;
// 新增订单
var existsOrderNos = existsOrders.Select(x => x.OrderNo).ToHashSet();
var insertInputs = input.OrderInputs
.WhereIf(existsOrderNos.Count > 0, x => !existsOrderNos.Contains(x.OrderNo))
.ToList();
var insertOrders = new List<OrderEntity>(insertInputs.Count);
var createRecords = new List<OrderOperationRecordIndex>(insertInputs.Count);
foreach (var insertInput in insertInputs)
{
var order = ObjectMapper.Map<UpsertOrderInput, OrderEntity>(insertInput);
order.OrderFlights = ObjectMapper.Map<List<OrderFlightInfo>, List<OrderFlightEntity>>(insertInput.FlightInfos);
order.OrderPullStatus = BuiltOrderPullStatus(insertInput.PullConfigInfos);
order.SetThirdPartyOrderNumber(order.OrderPullStatus);
insertOrders.Add(order);
createRecords.Add(new OrderOperationRecordIndex
{
//OrderId = default, //稍后补充
OrderNo = order.OrderNo,
OptionType = OrderOperationType.创建订单.ToString(),
Content = insertInput.ToJson(),
CreateUserId = CurrentUserId,
CreateUserName = CurrentUser.UserName ?? string.Empty,
CreateTime = DateTime.Now
});
}
// 创建订单,创建操作记录
var orderNoIdDict = await _orderRepository.CreateBatchOrderAsync(insertOrders);
foreach (var record in createRecords)
{
var orderId = orderNoIdDict[record.OrderNo];
record.OrderId = orderId;
}
_ = _orderOperationRecordEsRepository.AddManyAsync(createRecords);
}
/// <summary> /// <summary>
/// 删除订单 /// 删除订单
/// </summary> /// </summary>
@ -206,7 +50,7 @@ public partial class OrderService : ApplicationServiceBase
x.AppKey == input.AppKey && x.OrderType == input.OrderType && x.OrderNo == input.OrderNo); x.AppKey == input.AppKey && x.OrderType == input.OrderType && x.OrderNo == input.OrderNo);
if (order == null) if (order == null)
{ {
throw new BusinessException(message: $"订单:{input.OrderNo}不存在"); throw ProviderServiceActionResult.Exception("删除订单失败", $"订单:{input.OrderNo}不存在");
} }
await _orderRepository.DeleteOrderAsync(order.Id); await _orderRepository.DeleteOrderAsync(order.Id);
@ -214,50 +58,7 @@ public partial class OrderService : ApplicationServiceBase
// 预警计算 // 预警计算
WarningCalc_OrderDeletedOrCompleted(order); WarningCalc_OrderDeletedOrCompleted(order);
return new IdBaseDto return new IdBaseDto { Id = order.Id };
{
Id = order.Id
};
}
private async Task UpdateOrderAsync(UpdateOrderInput input, OrderEntity order)
{
order = ObjectMapper.Map(input, order);
order.OrderFlights = ObjectMapper.Map<List<OrderFlightInfo>, List<OrderFlightEntity>>(input.FlightInfos);
order.OrderPullStatus = BuiltOrderPullStatus(input.PullConfigInfos);
order.SetThirdPartyOrderNumber(order.OrderPullStatus);
await _orderRepository.UpdateOrderAsync(order);
}
private static List<OrderPullStatusEntity> BuiltOrderPullStatus(List<PullConfigInfo>? pullConfigInfos)
{
if (pullConfigInfos == null) return new List<OrderPullStatusEntity>();
return pullConfigInfos.Select(x =>
{
var status = new OrderPullStatusEntity
{
ServiceProviderCode = x.ServiceCode,
OrderNo = x.OrderNo,
PullStatus = TrackPullStatus.Init,
StartTrackCode = x.StartCode ?? GlobalConst.EmptyString,
EndTrackCode = x.EndCode ?? GlobalConst.EmptyString,
SilenceBeforeStart = x.SilenceTime,
SilenceAfterEnd = x.StopTime,
StartTime = GlobalConst.MaxDateTime,
EndTime = GlobalConst.MaxDateTime,
};
// 没有开始轨迹则立即进入拉取前静默期
if (x.StartCode == null)
{
status.PullStatus = TrackPullStatus.SilenceBeforeStart; // 会被定时任务扫描到并启动
status.StartTime = DateTime.Now.AddHours(x.SilenceTime);
}
return status;
}).ToList();
} }
private void WarningCalc_OrderDeletedOrCompleted(OrderEntity order) private void WarningCalc_OrderDeletedOrCompleted(OrderEntity order)

View File

@ -1,34 +0,0 @@
using TrackCenter.Application.Dtos.TrackContents;
using TrackCenter.Application.Services.ProvideServices;
using TrackCenter.Domain.Aggregates.TrackContentAggregate;
using TrackCenter.Domain.Dtos.TrackContents;
namespace TrackCenter.Application.Services.ProvideServices;
/// <summary>
/// 轨迹内容服务
/// </summary>
[ApiExplorerSettings(GroupName = "提供接口")]
[Route("/ProviderService")]
public class TrackContentService : ApplicationServiceBase
{
private readonly ITrackContentRepository _trackContentRepository;
public TrackContentService(ITrackContentRepository trackContentRepository)
{
_trackContentRepository = trackContentRepository;
}
/// <summary>
/// 根据应用获取轨迹内容列表
/// </summary>
/// <param name="AppKey"></param>
/// <returns></returns>
[HttpPost("Track/GetTrackContents")]
public async Task<List<TrackContentOutput>> GetTrackContentsAsync(string AppKey)
{
var res = await _trackContentRepository.GetTrackContentByAppKeyAsync(AppKey);
var result = ObjectMapper.Map<List<TrackContentVO>, List<TrackContentOutput>>(res);
return result;
}
}

View File

@ -1,20 +1,19 @@
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Dtos.TrackPull; using TrackCenter.Application.Dtos.TrackPull;
using TrackCenter.Application.Helpers; using TrackCenter.Application.Helpers;
using TrackCenter.Application.Options; using TrackCenter.Application.Options;
using TrackCenter.Application.Queries; using TrackCenter.Application.Queries;
using TrackCenter.Application.Rabbit;
using TrackCenter.Application.Track2InterfaceRelay; using TrackCenter.Application.Track2InterfaceRelay;
using TrackCenter.Domain.Consts;
namespace TrackCenter.Application.Services.ProvideServices; namespace TrackCenter.Application.Services.ProvideServices;
/// <summary> /// <summary>
/// 轨迹刷新服务 /// 轨迹刷新服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "提供接口")] [ApiExplorerSettings(GroupName = "OpenApi")]
[Route("/ProviderService")] [Route("/ProviderService")]
public class TrackPullService : ApplicationServiceBase public class TrackPullService : ApplicationServiceBase
{ {
@ -39,20 +38,28 @@ public class TrackPullService : ApplicationServiceBase
[HttpPost("Track/Refresh")] [HttpPost("Track/Refresh")]
public async Task<bool> RefreshTrackAsync(RefreshTrackInput input) public async Task<bool> RefreshTrackAsync(RefreshTrackInput input)
{ {
// todo:罚时? input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList();
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空 if (input.OrderNos.Count == 0)
{
throw ProviderServiceActionResult.Exception("刷新失败", "未传入有效订单号");
}
// tips:这里没有区分订单类型,就算的不同类型的单一起刷新了也无所谓 // tips:这里没有区分订单类型,就算的不同类型的单一起刷新了也无所谓
var orders = await _provideQueries.FindOrderWithServiceProviderAsync(input.AppKey, input.OrderNos); var (orders, skipReasons) =
await _provideQueries.DiagnoseManualRefreshAsync(input.AppKey, input.OrderNos);
if (orders.Count == 0) if (orders.Count == 0)
{ {
// tips:可能是没有配置、或者所有单都刷完了、或者单不存在 var reason = skipReasons.Count > 0
throw new BusinessException(message: "一个能更新的都没有"); ? string.Join(";", skipReasons)
: "可能原因:订单不存在、已完成、无拉取状态或不在拉取时间窗口内";
throw ProviderServiceActionResult.Exception("刷新失败:没有可刷新的订单", reason);
} }
foreach (var group in orders.GroupBy(x => var forwardErrors = new List<string>();
new var submittedOrderCount = 0;
foreach (var group in orders.GroupBy(x => new
{ {
x.ServiceProviderCode, x.ServiceProviderCode,
x.AppKey, x.AppKey,
@ -63,15 +70,13 @@ public class TrackPullService : ApplicationServiceBase
{ {
OrderNo = x.OrderNo OrderNo = x.OrderNo
})); }));
var serviceProviderCode = group.FirstOrDefault()!.ServiceProviderCode; var serviceProviderCode = group.Key.ServiceProviderCode;
var attach = Base64Helper.SerializeObject(new TrackRefreshAttach(group.Key.ServiceProviderCode, var attach = Base64Helper.SerializeObject(new TrackRefreshAttach(group.Key.ServiceProviderCode,
group.Key.AppKey, group.Key.OrderType)); group.Key.AppKey, group.Key.OrderType));
using (Track2InterfaceRelayAppContext.Use( using (Track2InterfaceRelayAppContext.Use(
new Track2InterfaceRelayAppContext(group.Key.AppKey))) new Track2InterfaceRelayAppContext(group.Key.AppKey)))
{ {
// 批量获取轨迹
var res = await _interfaceForwardService.BatchForwardThenPushToQueueAsync( var res = await _interfaceForwardService.BatchForwardThenPushToQueueAsync(
_options.CurrentValue.GetTrackInterfaceCode, serviceProviderCode, data, _options.CurrentValue.GetTrackInterfaceCode, serviceProviderCode, data,
GlobalConst.TrackRefreshSubscribe, GlobalConst.TrackRefreshSubscribe,
@ -79,29 +84,21 @@ public class TrackPullService : ApplicationServiceBase
if (res.Code != GlobalConst.SuccessCode) if (res.Code != GlobalConst.SuccessCode)
{ {
var err =
$"服务商[{serviceProviderCode}]下{group.Count()}单转发失败:{(string.IsNullOrWhiteSpace(res.Msg) ? res.ToJson() : res.Msg)}";
Log.Error($"刷新轨迹失败:{res.ToJson()}"); Log.Error($"刷新轨迹失败:{res.ToJson()}");
forwardErrors.Add(err);
continue;
} }
submittedOrderCount += group.Count();
} }
} }
return true; if (submittedOrderCount == 0)
}
/// <summary>
/// 轨迹刷新测试
/// </summary>
/// <param name="trackRefreshEventHandler"></param>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Track/TrackRefreshEventTest")]
#if DEBUG
[AllowAnonymous]
#endif
public async Task<bool> TrackRefreshEventTest([FromServices] TrackRefreshEventHandler trackRefreshEventHandler,
InterfaceRelayForwardReplayEvent input)
{ {
await trackRefreshEventHandler.ConsumerAsync(input); throw ProviderServiceActionResult.Exception("刷新失败:下游转发均未成功", string.Join(";", forwardErrors));
}
return true; return true;
} }

View File

@ -1,4 +1,4 @@
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Services; using TrackCenter.Application.Services;
using TrackCenter.Domain.Aggregates.TrackPushAggregate; using TrackCenter.Domain.Aggregates.TrackPushAggregate;
using TrackCenter.Domain.Dtos.TrackPush; using TrackCenter.Domain.Dtos.TrackPush;
@ -8,7 +8,7 @@ namespace TrackCenter.Application.Services.ProvideServices;
/// <summary> /// <summary>
/// 轨迹推送配置服务 /// 轨迹推送配置服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "提供接口")] [ApiExplorerSettings(GroupName = "OpenApi")]
[Route("/ProviderService")] [Route("/ProviderService")]
public class TrackPushConfigService : ApplicationServiceBase public class TrackPushConfigService : ApplicationServiceBase
{ {

View File

@ -1,4 +1,4 @@
using DotNetCore.CAP; using DotNetCore.CAP;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Dtos.TrackOrder; using TrackCenter.Application.Dtos.TrackOrder;
using TrackCenter.Application.Dtos.TrackPush; using TrackCenter.Application.Dtos.TrackPush;
@ -14,7 +14,7 @@ namespace TrackCenter.Application.Services.ProvideServices;
/// <summary> /// <summary>
/// 轨迹推送服务 /// 轨迹推送服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "提供接口")] [ApiExplorerSettings(GroupName = "OpenApi")]
[Route("/ProviderService")] [Route("/ProviderService")]
public class TrackPushService : ApplicationServiceBase public class TrackPushService : ApplicationServiceBase
{ {
@ -45,7 +45,16 @@ public class TrackPushService : ApplicationServiceBase
[HttpPost("Track/Push")] [HttpPost("Track/Push")]
public async Task PushTrackAsync(PushTrackInput input) public async Task PushTrackAsync(PushTrackInput input)
{ {
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空 input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList();
if (input.OrderNos.Count == 0)
{
throw ProviderServiceActionResult.Exception("推送失败", "未传入有效订单号");
}
if (string.IsNullOrWhiteSpace(input.TrackCode))
{
throw ProviderServiceActionResult.Exception("推送失败", "未指定轨迹代码 TrackCode");
}
var orders = await _orderRepository.GetListAsync(x => var orders = await _orderRepository.GetListAsync(x =>
x.AppKey == input.AppKey x.AppKey == input.AppKey
@ -53,62 +62,70 @@ public class TrackPushService : ApplicationServiceBase
&& input.OrderNos.Contains(x.OrderNo), x => new && input.OrderNos.Contains(x.OrderNo), x => new
{ {
x.Id, x.Id,
x.OrderNo,
x.Customer x.Customer
}); });
if (!orders.Any()) var foundOrderNos = orders.Select(x => x.OrderNo).ToHashSet(StringComparer.OrdinalIgnoreCase);
var missingOrderNos = input.OrderNos
.Where(x => !foundOrderNos.Contains(x))
.ToList();
if (orders.Count == 0)
{ {
throw new BusinessException(message: "一个订单都不存在"); throw ProviderServiceActionResult.Exception("推送失败:没有匹配的订单", $"订单不存在或不匹配 AppKey/OrderType:{string.Join("、", input.OrderNos)}");
} }
var name = CurrentUser.UserName; var name = CurrentUser.UserName;
//查出可能需要推送的轨迹 var tracks = await _trackDetailsEsRepository.GetListAsync(input.AppKey, input.OrderType, input.OrderNos,
var tracks = await _trackDetailsEsRepository.GetListAsync(input.AppKey, input.OrderType, input.OrderNos, new List<string> new List<string>
{ {
input.TrackCode input.TrackCode
}); });
if (tracks.Count == 0) if (tracks.Count == 0)
{ {
throw new BusinessException(message: "没有需要推送的轨迹"); throw ProviderServiceActionResult.Exception("推送失败:没有需要推送的轨迹", $"订单存在,但未找到轨迹代码[{input.TrackCode}]对应的轨迹明细");
} }
//存在比较字段,需要做过滤处理
if (!string.IsNullOrWhiteSpace(input.CompareFields)) if (!string.IsNullOrWhiteSpace(input.CompareFields))
{ {
//根据输入值来进行过滤,比如选取code1,批次1的轨迹,可能会有多条,下面使用创建时间最新的一条
tracks = tracks.Where(x => tracks = tracks.Where(x =>
{ {
if (string.IsNullOrWhiteSpace(x.Extra)) if (string.IsNullOrWhiteSpace(x.Extra))
{ {
throw new BusinessException(message: $"当前轨迹不存在扩展信息,无法利用扩展字段:{input.CompareFields}"); throw ProviderServiceActionResult.Exception("推送失败:扩展字段比较失败", $"轨迹[{x.OrderNo}/{input.TrackCode}]不存在扩展信息 Extra,无法用比较字段[{input.CompareFields}]过滤");
} }
var extraDict = TextJsonHelper.DeserializeToDictionary(x.Extra); var extraDict = TextJsonHelper.DeserializeToDictionary(x.Extra);
if (extraDict == null || !extraDict.TryGetValue(input.CompareFields, out var compareValue)) if (extraDict == null || !extraDict.TryGetValue(input.CompareFields, out var compareValue))
{ {
throw new BusinessException(message: $"请检查拓展字段,为什么找不到比较字段:{input.CompareFields}"); throw ProviderServiceActionResult.Exception("推送失败:扩展字段比较失败", $"轨迹[{x.OrderNo}/{input.TrackCode}]的 Extra 中找不到比较字段[{input.CompareFields}]");
} }
//补充轨迹内容
return TextJsonHelper.EqJsonElementAndObject(compareValue, input.CompareValue); return TextJsonHelper.EqJsonElementAndObject(compareValue, input.CompareValue);
}).ToList(); }).ToList();
} }
// 经过过滤后不存在需要推送的轨迹
if (tracks.Count == 0) if (tracks.Count == 0)
{ {
throw new BusinessException(message: "没有需要推送的轨迹"); throw ProviderServiceActionResult.Exception("推送失败:没有需要推送的轨迹", $"按比较字段[{input.CompareFields}]过滤后无匹配轨迹");
} }
// 构建每个单最早的轨迹(已经指定了code)
var orderIdTrackDict = tracks.GroupBy(x => x.OrderId) var orderIdTrackDict = tracks.GroupBy(x => x.OrderId)
.ToDictionary(x => x.Key, y => y.OrderByDescending(a => a.CreateTime).First()); .ToDictionary(x => x.Key, y => y.OrderByDescending(a => a.CreateTime).First());
//不同单可能会有不同的轨迹内容,手动推送只推送最新的一条 var pushedOrderNos = new List<string>();
var skippedNoTrack = new List<string>();
foreach (var order in orders) foreach (var order in orders)
{ {
var track = orderIdTrackDict.GetValueOrDefault(order.Id); var track = orderIdTrackDict.GetValueOrDefault(order.Id);
if (track == null) continue; if (track == null)
{
skippedNoTrack.Add(order.OrderNo);
continue;
}
var trackPushEvent = new TrackPushEvent var trackPushEvent = new TrackPushEvent
{ {
@ -119,7 +136,6 @@ public class TrackPushService : ApplicationServiceBase
{ {
[order.Id] = TrackMetadata.Build(track.Id, track.Version, track.CreateTime) [order.Id] = TrackMetadata.Build(track.Id, track.Version, track.CreateTime)
}, },
// OrderIds = new[] { order.Id },
TrackCode = input.TrackCode, TrackCode = input.TrackCode,
OperationMan = name, OperationMan = name,
SystemPushInfo = new SystemPushStruct SystemPushInfo = new SystemPushStruct
@ -137,9 +153,15 @@ public class TrackPushService : ApplicationServiceBase
}; };
await _capPublisher.PublishAsync(GlobalConst.TrackManualPushSubscribe, trackPushEvent); await _capPublisher.PublishAsync(GlobalConst.TrackManualPushSubscribe, trackPushEvent);
pushedOrderNos.Add(order.OrderNo);
} }
Log.Information($"即将推送轨迹代码{input.TrackCode}给{orderIdTrackDict.Count}个订单"); if (pushedOrderNos.Count == 0)
{
throw ProviderServiceActionResult.Exception("推送失败:没有订单成功提交推送", $"请求 {input.OrderNos.Count} 单均未匹配到可推送轨迹[{input.TrackCode}]");
}
Log.Information($"即将推送轨迹代码{input.TrackCode}给{pushedOrderNos.Count}个订单");
} }
@ -175,7 +197,8 @@ public class TrackPushService : ApplicationServiceBase
); );
if (orderId == default) if (orderId == default)
{ {
throw new BusinessException(message: "订单不存在"); throw ProviderServiceActionResult.Exception("查询推送记录失败",
$"订单不存在:{input.OrderNo}(AppKey/OrderType 不匹配)");
} }
var records = await _trackPushRecordEsRepository.GetTrackPushRecordsAsync(orderId); var records = await _trackPushRecordEsRepository.GetTrackPushRecordsAsync(orderId);
@ -200,7 +223,11 @@ public class TrackPushService : ApplicationServiceBase
y => y.Id y => y.Id
); );
if (orderIds.Count == 0) throw new BusinessException(message: "订单一个都不存在"); if (orderIds.Count == 0)
{
throw ProviderServiceActionResult.Exception("查询推送记录失败",
$"订单一个都不存在:{string.Join("、", input.OrderNos)}");
}
var isSuccess = input.ResultType switch var isSuccess = input.ResultType switch
{ {
@ -217,7 +244,7 @@ public class TrackPushService : ApplicationServiceBase
{ {
false false
}, },
_ => throw new BusinessException(message: "结果类型值只能取0,1,2") _ => throw ProviderServiceActionResult.Exception("查询推送记录失败", "结果类型 ResultType 只能取 0/1/2")
}; };
var records = await _trackPushRecordEsRepository.GetTrackPushRecordsAsync(new GetTrackPushRecordsVO var records = await _trackPushRecordEsRepository.GetTrackPushRecordsAsync(new GetTrackPushRecordsVO

View File

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Helpers; using TrackCenter.Application.Helpers;
using TrackCenter.Application.Helpers.OSS; using TrackCenter.Application.Helpers.OSS;

View File

@ -1,4 +1,4 @@
using DotNetCore.CAP; using DotNetCore.CAP;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.ProvideServices;
using TrackCenter.Application.Events; using TrackCenter.Application.Events;
@ -13,7 +13,7 @@ namespace TrackCenter.Application.Services.ProvideServices;
/// <summary> /// <summary>
/// 轨迹服务 /// 轨迹服务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "提供接口")] [ApiExplorerSettings(GroupName = "OpenApi")]
[Route("/ProviderService")] [Route("/ProviderService")]
public partial class TrackService : ApplicationServiceBase public partial class TrackService : ApplicationServiceBase
{ {
@ -38,7 +38,6 @@ public partial class TrackService : ApplicationServiceBase
/// 删除轨迹 /// 删除轨迹
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
[HttpPost("Track/Delete")] [HttpPost("Track/Delete")]
public async Task DeleteTrackAsync(DeleteTrackInput input) public async Task DeleteTrackAsync(DeleteTrackInput input)
@ -51,7 +50,7 @@ public partial class TrackService : ApplicationServiceBase
if (orderId == 0) if (orderId == 0)
{ {
throw new BusinessException(message: $"订单:{input.OrderNo}不存在"); throw ProviderServiceActionResult.Exception("删除失败", $"订单:{input.OrderNo}不存在");
} }
// 查询轨迹优先使用esId,tips:考虑删除事件处理直接通过EsId而不是再查询一遍? // 查询轨迹优先使用esId,tips:考虑删除事件处理直接通过EsId而不是再查询一遍?
@ -76,7 +75,8 @@ public partial class TrackService : ApplicationServiceBase
.WhereIf(!input.CompareFields.IsNullOrWhiteSpace(), y => .WhereIf(!input.CompareFields.IsNullOrWhiteSpace(), y =>
{ {
if (string.IsNullOrWhiteSpace(y.Extra)) if (string.IsNullOrWhiteSpace(y.Extra))
throw new BusinessException(message: "轨迹extra为空无法使用比较字段比较,请检查轨迹"); throw ProviderServiceActionResult.Exception("删除失败",
"轨迹extra为空无法使用比较字段比较,请检查轨迹");
var extraDict = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(y.Extra); var extraDict = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(y.Extra);
if (extraDict!.TryGetValue(input.CompareFields!, out var compareValueJsonElement)) if (extraDict!.TryGetValue(input.CompareFields!, out var compareValueJsonElement))
@ -89,13 +89,13 @@ public partial class TrackService : ApplicationServiceBase
if (tracks == null || tracks.Count == 0) if (tracks == null || tracks.Count == 0)
{ {
throw new BusinessException(message: "没有任何轨迹要删除,请检查输入参数"); throw ProviderServiceActionResult.Exception("删除失败", "没有任何轨迹要删除,请检查输入参数");
} }
//删除轨迹约束,只要其中有一条来源是外部的就立即返回错误 //删除轨迹约束,只要其中有一条来源是外部的就立即返回错误
if (tracks.Any(x => x.Source == (int) TrackSourceEnum.ExternalServiceProvide)) if (tracks.Any(x => x.Source == (int) TrackSourceEnum.ExternalServiceProvide))
{ {
throw new BusinessException(message: "存在来源于外部服务商的轨迹,不允许删除"); throw ProviderServiceActionResult.Exception("删除失败", "存在来源于外部服务商的轨迹,不允许删除");
} }
// 删除轨迹(异步) // 删除轨迹(异步)
@ -112,57 +112,26 @@ public partial class TrackService : ApplicationServiceBase
}); });
} }
/// <summary>
/// 根据esid删除轨迹内容(FBA专用)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Track/BatchDeleteByEsId")]
public async Task BatchDeleteTrackByEsIdAsync(BatchDeleteTrackByEsIdInput input)
{
var esIds = input.TrackContentEsIds.ToArray();
var tracks = await _trackDetailsEsRepository.GetDataByIds(esIds);
if (tracks.Count == 0)
throw new BusinessException("未找到指定id下的轨迹内容");
if (tracks.Any(m => m.Source == TrackSourceEnum.ExternalServiceProvide.GetHashCode()))
throw new BusinessException("存在来源于外部服务商的轨迹,不允许删除");
if (tracks.GroupBy(m => m.AppKey).Count() > 1)
throw new BusinessException("暂不支持跨应用批量删除");
// 删除轨迹
var @event = new TrackDeleteEvent
{
EsIds = esIds,
AppKey = tracks.First().AppKey,
Source = input.Source,
OperationMan = CurrentUser.UserName,
OperationManId = CurrentUserId,
};
await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe, @event);
}
/// <summary> /// <summary>
/// 批量删除轨迹 /// 批量删除轨迹
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
[HttpPost("Track/BatchDelete")] [HttpPost("Track/BatchDelete")]
public async Task BatchDeleteTrackAsync(BatchDeleteTrackInput input) public async Task BatchDeleteTrackAsync(BatchDeleteTrackInput input)
{ {
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空 input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空
var orderIds = await _orderRepository.GetListAsync(x => var orders = await _orderRepository.GetListAsync(x =>
x.AppKey == input.AppKey x.AppKey == input.AppKey
&& x.OrderType == input.OrderType && x.OrderType == input.OrderType
&& input.OrderNos.Contains(x.OrderNo), && input.OrderNos.Contains(x.OrderNo),
x => x.Id); x => new { x.Id, x.OrderNo });
var orderIds = orders.Select(x => x.Id).ToList();
if (orderIds.Count == 0) if (orderIds.Count == 0)
{ {
throw new BusinessException(message: "订单一个都不存在"); throw ProviderServiceActionResult.Exception("删除失败", "订单一个都不存在");
} }
//删除轨迹约束,只要其中有一条来源是外部的就立即返回错误 //删除轨迹约束,只要其中有一条来源是外部的就立即返回错误
@ -176,7 +145,8 @@ public partial class TrackService : ApplicationServiceBase
.WhereIf(!input.CompareFields.IsNullOrWhiteSpace(), y => .WhereIf(!input.CompareFields.IsNullOrWhiteSpace(), y =>
{ {
if (string.IsNullOrWhiteSpace(y.Extra)) if (string.IsNullOrWhiteSpace(y.Extra))
throw new BusinessException(message: "轨迹extra为空无法使用比较字段比较,请检查轨迹"); throw ProviderServiceActionResult.Exception("删除失败",
"轨迹extra为空无法使用比较字段比较,请检查轨迹");
var extraDict = TextJsonHelper.DeserializeToDictionary(y.Extra); var extraDict = TextJsonHelper.DeserializeToDictionary(y.Extra);
if (extraDict!.TryGetValue(input.CompareFields!, out var compareValueJsonElement)) if (extraDict!.TryGetValue(input.CompareFields!, out var compareValueJsonElement))
@ -189,7 +159,7 @@ public partial class TrackService : ApplicationServiceBase
.ToList(); .ToList();
if (intersect.Count > 0) if (intersect.Count > 0)
{ {
throw new BusinessException(message: "存在来源于外部服务商的轨迹,不允许删除"); throw ProviderServiceActionResult.Exception("删除失败", "存在来源于外部服务商的轨迹,不允许删除");
} }
// 删除轨迹 // 删除轨迹
@ -210,7 +180,6 @@ public partial class TrackService : ApplicationServiceBase
/// 新增、更新轨迹(存在就更新) /// 新增、更新轨迹(存在就更新)
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
[HttpPost("Track/Upsert")] [HttpPost("Track/Upsert")]
public async Task UpsertTrackAsync(UpsertTrackInput input) public async Task UpsertTrackAsync(UpsertTrackInput input)
@ -229,12 +198,9 @@ public partial class TrackService : ApplicationServiceBase
}); });
if (order == null) if (order == null)
{ {
throw new BusinessException(message: $"订单:{input.OrderNo}不存在"); throw ProviderServiceActionResult.Exception("提交失败", $"订单:{input.OrderNo}不存在");
} }
// 输入参数的静态规则检查
// 轨迹内容上的约束检查
// 更新轨迹时是否可以匹配到轨迹的检查
await TrackUpsertValidation(input.IsAdd, await TrackUpsertValidation(input.IsAdd,
input.EsId, input.EsId,
input.AppKey, input.AppKey,
@ -243,16 +209,10 @@ public partial class TrackService : ApplicationServiceBase
input.TrackCode, input.TrackCode,
input.Source, input.Source,
input.TrackTime, input.TrackTime,
input.TrackTimeZone ?? 8.0f, //没传时区默认使用+8参与过滤 input.TrackTimeZone ?? 8.0f,
input.NewTrackTime, input.Extra);
input.EstimatedDeliveryDate,
input.DestinationTimeZone,
input.Extra,
input.NewExtra,
input.CompareFields);
// 新增/更新轨迹 input.TrackTimeZone ??= GlobalConst.NoneTrackTimeZone;
input.TrackTimeZone ??= GlobalConst.NoneTrackTimeZone; //时区为空使用一个特殊值填充
var @event = ObjectMapper.Map<UpsertTrackInput, TrackUpsertEvent>(input); var @event = ObjectMapper.Map<UpsertTrackInput, TrackUpsertEvent>(input);
@event.AppKey = input.AppKey; @event.AppKey = input.AppKey;
@event.Customer = order.Customer; @event.Customer = order.Customer;
@ -273,14 +233,13 @@ public partial class TrackService : ApplicationServiceBase
/// 批量新增、更新轨迹 /// 批量新增、更新轨迹
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception> /// <exception cref="BusinessException"></exception>
[HttpPost("Track/BatchUpsert")] [HttpPost("Track/BatchUpsert")]
public async Task BatchUpsertTrackAsync(BatchUpsertTrackInput input) public async Task BatchUpsertTrackAsync(BatchUpsertTrackInput input)
{ {
using var activity = ActivityHelper.CreateSpanActivity("BatchUpsert"); using var activity = ActivityHelper.CreateSpanActivity("BatchUpsert");
activity.SetTag("BatchUpsertTrackInput", input.ToJson()); activity.SetTag("BatchUpsertTrackInput", input.ToJson());
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空 input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList();
var orders = await _orderRepository.GetListAsync(x => var orders = await _orderRepository.GetListAsync(x =>
x.AppKey == input.AppKey x.AppKey == input.AppKey
&& x.OrderType == input.OrderType && x.OrderType == input.OrderType
@ -294,17 +253,11 @@ public partial class TrackService : ApplicationServiceBase
if (orders.Count == 0) if (orders.Count == 0)
{ {
throw new BusinessException(message: "订单一个都不存在"); throw ProviderServiceActionResult.Exception("提交失败", "订单一个都不存在");
} }
// 校验主体:批量新增只选取第一条订单进行校验
//【新增】存在指定轨迹的内容,进行更新覆盖(只处理一条的情况,并且决定权交给用户),不存在直接插入即可
//【更新】存在指定轨迹的内容(匹配到多条则不处理)
var order = orders.First(); var order = orders.First();
// 输入参数的静态规则检查
// 轨迹内容上的约束检查
// 更新轨迹时是否可以匹配到轨迹的检查
await TrackUpsertValidation(input.IsAdd, await TrackUpsertValidation(input.IsAdd,
null, null,
input.AppKey, input.AppKey,
@ -313,15 +266,9 @@ public partial class TrackService : ApplicationServiceBase
input.TrackCode, input.TrackCode,
input.Source, input.Source,
input.TrackTime, input.TrackTime,
input.TrackTimeZone ?? 8.0f, //没传时区默认使用+8参与过滤 input.TrackTimeZone ?? 8.0f,
input.NewTrackTime, input.Extra);
input.EstimatedDeliveryDate,
input.DestinationTimeZone,
input.Extra,
input.NewExtra,
input.CompareFields);
// 按客户新增/更新轨迹(为了推送轨迹时方便)
foreach (var group in orders.GroupBy(x => x.Customer)) foreach (var group in orders.GroupBy(x => x.Customer))
{ {
var @event = new TrackUpsertEvent var @event = new TrackUpsertEvent
@ -338,8 +285,7 @@ public partial class TrackService : ApplicationServiceBase
TrackCode = input.TrackCode, TrackCode = input.TrackCode,
TrackPlace = input.TrackPlace, TrackPlace = input.TrackPlace,
TrackTime = input.TrackTime, TrackTime = input.TrackTime,
NewTrackTime = input.NewTrackTime, TrackTimeZone = input.TrackTimeZone ?? GlobalConst.NoneTrackTimeZone,
TrackTimeZone = input.TrackTimeZone ?? GlobalConst.NoneTrackTimeZone, //时区为空使用一个特殊值填充
OperationMan = CurrentUser.UserName, OperationMan = CurrentUser.UserName,
OperationManId = CurrentUserId, OperationManId = CurrentUserId,
IsExternal = input.IsExternal, IsExternal = input.IsExternal,
@ -348,18 +294,7 @@ public partial class TrackService : ApplicationServiceBase
Files = input.Files, Files = input.Files,
Content = input.Content, Content = input.Content,
EnStatus = input.EnStatus, EnStatus = input.EnStatus,
TrackPlaceCity = input.TrackPlaceCity,
TrackPlaceCityId = input.TrackPlaceCityId,
TrackPlaceProvince = input.TrackPlaceProvince,
TrackPlaceProvinceId = input.TrackPlaceProvinceId,
TrackPlaceNation = input.TrackPlaceNation,
TrackPlaceNationId = input.TrackPlaceNationId,
TrackPlacePostcode = input.TrackPlacePostcode,
EstimatedDeliveryDate = input.EstimatedDeliveryDate,
DestinationTimeZone = input.DestinationTimeZone,
CompareFields = input.CompareFields,
Extra = input.Extra, Extra = input.Extra,
NewExtra = input.NewExtra,
ForcedPush = input.ForcedPush, ForcedPush = input.ForcedPush,
IsToMq = input.IsToMq, IsToMq = input.IsToMq,
}; };
@ -367,7 +302,6 @@ public partial class TrackService : ApplicationServiceBase
} }
} }
//在轨迹更新主流程之前的前置检查
private async Task TrackUpsertValidation( private async Task TrackUpsertValidation(
bool isAdd, bool isAdd,
string? esId, string? esId,
@ -378,46 +312,27 @@ public partial class TrackService : ApplicationServiceBase
int source, int source,
DateTime trackTime, DateTime trackTime,
float trackTimezone, float trackTimezone,
DateTime? newTrackTime, string? extra
DateTime? estimatedDeliveryDate,
float? destinationTimeZone,
string? extra,
string? newExtra,
string? compareFields
) )
{ {
#region 静态规则检查 #region 静态规则检查
// 约束轨迹来源只能选取 1-外部 2-内部 3-手动
if (!GlobalConst.SupportTrackSources.Contains(source)) if (!GlobalConst.SupportTrackSources.Contains(source))
{ {
throw new BusinessException(message: "轨迹来源是必须的,1:外部服务商 2:内部系统 3:人工操作"); throw ProviderServiceActionResult.Exception("校验失败", "轨迹来源是必须的,1:外部服务商 2:内部系统 3:人工操作");
} }
//当修改时,如果为来源为外部提供商则不允许修改
if (!isAdd && source == (int) TrackSourceEnum.ExternalServiceProvide) if (!isAdd && source == (int) TrackSourceEnum.ExternalServiceProvide)
{ {
throw new BusinessException(message: "来源为外部服务商轨迹不允许修改"); throw ProviderServiceActionResult.Exception("校验失败", "来源为外部服务商轨迹不允许修改");
} }
//要求时间在当前时间的前后100年之间内
bool ValidateTrackTime(DateTime? d) => bool ValidateTrackTime(DateTime? d) =>
(d != null && d < DateTime.Now.AddYears(-100) || d > DateTime.Now.AddYears(100)); d != null && (d < DateTime.Now.AddYears(-100) || d > DateTime.Now.AddYears(100));
if (ValidateTrackTime(trackTime) || ValidateTrackTime(newTrackTime) || ValidateTrackTime(estimatedDeliveryDate)) if (ValidateTrackTime(trackTime))
{ {
throw new BusinessException(message: "轨迹时间不合法"); throw ProviderServiceActionResult.Exception("校验失败", "轨迹时间不合法");
}
if ((estimatedDeliveryDate.HasValue == false && destinationTimeZone.HasValue) ||
(estimatedDeliveryDate.HasValue && destinationTimeZone.HasValue == false))
{
throw new BusinessException(message: "【预计签收时间】和【签收地时区】必须同时输入");
}
if (!string.IsNullOrWhiteSpace(compareFields) && string.IsNullOrWhiteSpace(newExtra ?? extra))
{
throw new BusinessException(message: "当输入了【轨迹对比字段】则轨迹扩展是必须的的");
} }
#endregion #endregion
@ -427,22 +342,19 @@ public partial class TrackService : ApplicationServiceBase
var trackContent = await _trackContentRepository.GetByCodeAsync(appKey, trackCode); var trackContent = await _trackContentRepository.GetByCodeAsync(appKey, trackCode);
if (trackContent == null) if (trackContent == null)
{ {
throw new BusinessException(message: $"轨迹代码:{trackCode}轨迹内容不存在"); throw ProviderServiceActionResult.Exception("校验失败", $"轨迹代码:{trackCode}轨迹内容不存在");
} }
//指示是否为手动操作
var manualOperation = source == (int) TrackSourceEnum.Artificial; var manualOperation = source == (int) TrackSourceEnum.Artificial;
//不允许手动创建
if (manualOperation && isAdd && !trackContent.IsAllowManuallyCreate) if (manualOperation && isAdd && !trackContent.IsAllowManuallyCreate)
{ {
throw new BusinessException(message: $"轨迹代码:{trackCode}不允许手动创建"); throw ProviderServiceActionResult.Exception("校验失败", $"轨迹代码:{trackCode}不允许手动创建");
} }
//不允许手动更新
if (manualOperation && !isAdd && !trackContent.IsAllowManuallyUpdate) if (manualOperation && !isAdd && !trackContent.IsAllowManuallyUpdate)
{ {
throw new BusinessException(message: $"轨迹代码:{trackCode}不允许手动更新"); throw ProviderServiceActionResult.Exception("校验失败", $"轨迹代码:{trackCode}不允许手动更新");
} }
#endregion #endregion
@ -454,7 +366,7 @@ public partial class TrackService : ApplicationServiceBase
var tracks = await _trackDetailsEsRepository.GetDataByIds(new[] { esId }); var tracks = await _trackDetailsEsRepository.GetDataByIds(new[] { esId });
if (tracks.Count == 0) if (tracks.Count == 0)
{ {
throw new BusinessException(message: $"轨迹esId:[{esId}]不存在,无法修改轨迹"); throw ProviderServiceActionResult.Exception("校验失败", $"轨迹esId:[{esId}]不存在,无法修改轨迹");
} }
return; return;
@ -462,83 +374,61 @@ public partial class TrackService : ApplicationServiceBase
#endregion #endregion
//轨迹新增,不允许重复的轨迹提前拦截
if (isAdd) if (isAdd)
{ {
//找到这个订单的这个轨迹code的所有轨迹 var tracks = await RetryQueryTrackAsync(isAdd, orderId, trackCode, trackTime, trackTimezone, extra,
var tracks = await RetryQueryTrackAsync(isAdd, orderId, trackCode, trackTime, trackTimezone, newTrackTime, trackContent.IsRepeatable);
extra, newExtra, compareFields, trackContent.IsRepeatable);
if (tracks.Count > 0) if (tracks.Count > 0)
throw new BusinessException(message: $"订单:[{orderNo}],轨迹代码:{trackCode}不允许重复创建轨迹"); throw ProviderServiceActionResult.Exception("校验失败",
$"订单:[{orderNo}],轨迹代码:{trackCode}不允许重复创建轨迹");
} }
//针对于轨迹更新检查
else else
{ {
//找到这个订单的这个轨迹code的所有轨迹, var tracks = await RetryQueryTrackAsync(isAdd, orderId, trackCode, trackTime, trackTimezone, extra,
var tracks = await RetryQueryTrackAsync(isAdd, orderId, trackCode, trackTime, trackTimezone, newTrackTime, trackContent.IsRepeatable);
extra, newExtra, compareFields, trackContent.IsRepeatable);
// 不存在需要更新的轨迹
if (tracks.Count == 0) if (tracks.Count == 0)
throw new BusinessException(message: $"订单:[{orderNo}],轨迹代码:[{trackCode}]没有要更新的记录"); throw ProviderServiceActionResult.Exception("校验失败",
$"订单:[{orderNo}],轨迹代码:[{trackCode}]没有要更新的记录");
// 更新轨迹,同一时间存在多条轨迹记录,理由:不清晰到底要修改哪一条或是全部修改
if (tracks.Count > 1) if (tracks.Count > 1)
{ {
if (newTrackTime.HasValue && tracks.Any(track => throw ProviderServiceActionResult.Exception("校验失败",
DateTimeHelper.ConvertUtc0Compare(track.TrackTime, track.TrackTimeZone, newTrackTime.Value, $"订单:{orderNo},轨迹代码:[{trackCode}]存在多条记录,不允许修改");
trackTimezone) == 0))
{
throw new BusinessException(
message: $"订单:[{orderNo}],轨迹代码:[{trackCode}]和现存轨迹时间重复,不允许修改,请检查轨迹NewTrackTime");
}
if (!string.IsNullOrWhiteSpace(newExtra) && tracks.Any(track => string.Equals(track.Extra, newExtra)))
{
throw new BusinessException(
message: $"订单:[{orderNo}],轨迹代码:[{trackCode}]和现存轨迹Extra重复,不允许修改,请检查轨迹NewExtra");
}
throw new BusinessException(message: $"订单:{orderNo},轨迹代码:[{trackCode}]存在多条记录,不允许修改");
} }
var track = tracks.First(); var track = tracks.First();
if (DateTimeHelper.ConvertUtc0Compare(track.TrackTime, track.TrackTimeZone, trackTime, trackTimezone) != 0) if (DateTimeHelper.ConvertUtc0Compare(track.TrackTime, track.TrackTimeZone, trackTime, trackTimezone) != 0)
{ {
throw new BusinessException( throw ProviderServiceActionResult.Exception("校验失败",
message: $"订单:[{orderNo}],轨迹代码:[{trackCode}]找不到要更新的记录,不允许修改,请检查轨迹TrackTime"); $"订单:[{orderNo}],轨迹代码:[{trackCode}]找不到要更新的记录,请检查轨迹TrackTime");
} }
if (!string.Equals(track.Extra, extra)) if (!string.Equals(track.Extra, extra))
{ {
throw new BusinessException(message: $"订单:[{orderNo}],轨迹代码:[{trackCode}]找不到要更新的记录,不允许修改,请检查轨迹Extra"); throw ProviderServiceActionResult.Exception("校验失败",
$"订单:[{orderNo}],轨迹代码:[{trackCode}]找不到要更新的记录,请检查轨迹Extra");
} }
} }
} }
// 重试查询轨迹,直到存在符合要求的轨迹,或者超过最大重试次数
private async Task<List<TrackDetailsIndex>> RetryQueryTrackAsync(bool isAdd, int orderId, string trackCode, private async Task<List<TrackDetailsIndex>> RetryQueryTrackAsync(bool isAdd, int orderId, string trackCode,
DateTime trackTime, float trackTimezone, DateTime? newTrackTime, string? extra, string? newExtra, DateTime trackTime, float trackTimezone, string? extra, bool isRepeatable)
string? compareFields, bool isRepeatable)
{ {
var tracks = await QueryAndFilterAsync(isAdd, orderId, trackCode, trackTime, trackTimezone, newTrackTime, extra, var tracks = await QueryAndFilterAsync(isAdd, orderId, trackCode, trackTime, trackTimezone, extra, isRepeatable);
newExtra, compareFields, isRepeatable);
if (tracks.Count > 0) if (tracks.Count > 0)
return tracks; return tracks;
//重试阶段
var curRetryCount = 0; var curRetryCount = 0;
while (curRetryCount < GlobalConst.MaxUpdateTrackRetryCount) //tips:后续将最大重试次数提出到配置中 while (curRetryCount < GlobalConst.MaxUpdateTrackRetryCount)
{ {
try try
{ {
tracks = await QueryAndFilterAsync(isAdd, orderId, trackCode, trackTime, trackTimezone, newTrackTime, tracks = await QueryAndFilterAsync(isAdd, orderId, trackCode, trackTime, trackTimezone, extra,
extra, newExtra, compareFields, isRepeatable); isRepeatable);
if (tracks.Count > 0) if (tracks.Count > 0)
return tracks; return tracks;
//指数退避等待 var delay = TimeSpan.FromMilliseconds(Math.Pow(2, curRetryCount) * 150);
var delay = TimeSpan.FromMilliseconds(Math.Pow(2, curRetryCount) * 150); // 150ms, 300ms, 600ms, ...
// 可以执行下一次才延时,否则延时没意义
if (curRetryCount + 1 < GlobalConst.MaxUpdateTrackRetryCount) if (curRetryCount + 1 < GlobalConst.MaxUpdateTrackRetryCount)
await Task.Delay(delay); await Task.Delay(delay);
} }
@ -548,42 +438,19 @@ public partial class TrackService : ApplicationServiceBase
} }
} }
//超过最大重试次数还没找到
return tracks; return tracks;
} }
//查询并按比较字段过滤轨迹,当比较字段为空时则使用轨迹发生时间过滤
//tips:后续进行投影缩参优化
private async Task<List<TrackDetailsIndex>> QueryAndFilterAsync(bool isAdd, int orderId, string trackCode, private async Task<List<TrackDetailsIndex>> QueryAndFilterAsync(bool isAdd, int orderId, string trackCode,
DateTime trackTime, float trackTimezone, DateTime? newTrackTime, string? extra, string? newExtra, DateTime trackTime, float trackTimezone, string? extra, bool isRepeatable)
string? compareFields, bool isRepeatable)
{ {
var tracks = await _trackDetailsEsRepository.GetListAsync(new[] { orderId }, trackCode, true); var tracks = await _trackDetailsEsRepository.GetListAsync(new[] { orderId }, trackCode, true);
// 如果不允许重复,则直接返回
if (!isRepeatable) return tracks; if (!isRepeatable) return tracks;
if (tracks.Count == 0) return tracks;
// 没有任何轨迹,也直接返回 tracks = tracks.Where(x =>
if (tracks.Count == 0) x.GetUtc0TrackTime() == trackTime.Subtract(TimeSpan.FromHours(trackTimezone))).ToList();
return tracks;
tracks = isAdd return tracks.Where(x => x.Extra == extra).ToList();
? tracks.Where(x =>
x.GetUtc0TrackTime() == (newTrackTime ?? trackTime).Subtract(TimeSpan.FromHours(trackTimezone)))
.ToList()
: tracks.Where(x =>
x.GetUtc0TrackTime() == trackTime.Subtract(TimeSpan.FromHours(trackTimezone))
// 修改了时间,要校验修改后的时间是否存在
|| (newTrackTime != null && x.GetUtc0TrackTime() ==
newTrackTime.Value.Subtract(TimeSpan.FromHours(trackTimezone)))
).ToList();
return isAdd
? tracks.Where(x => x.Extra == (newExtra ?? extra)).ToList()
: tracks.Where(x =>
x.Extra == extra
// 修改了extra,要校验修改后的extra是否存在
|| (!string.IsNullOrWhiteSpace(newExtra) && x.Extra == newExtra)
)
.ToList();
} }
} }

View File

@ -249,25 +249,6 @@ public class TrackOrderService : ApplicationServiceBase
// 按轨迹发生时间正序排序 // 按轨迹发生时间正序排序
res = res.OrderBy(m => m.TrackTime).ToList(); res = res.OrderBy(m => m.TrackTime).ToList();
if (!order.IsCompleted) //如果没完结则显示签收节点
{
var eddUnixTimestamp = order.EstimatedDeliveryDate.HasValue ? ToUnixTimestamp(DateTime.Parse(order.EstimatedDeliveryDate.ToString()!)) : 0;
var isShowTime = eddUnixTimestamp > 0 && res.All(m => m.TrackTime < eddUnixTimestamp);
//当存在需要显示的轨迹才需要补充签收节点
if (res.Count != 0)
{
res.Add(new GetDetailTimelineOutput()
{
Content = "签收",
IsHighlight = false,
TrackTimeZone = isShowTime ? $"GMT{(order.DestinationTimeZone >= 0 ? "+" : "-")}{order.DestinationTimeZone}" : "",
TrackTime = isShowTime ? eddUnixTimestamp : 0,
TrackTimePrefix = isShowTime ? "预计签收时间:" : "",
});
}
}
return res; return res;
} }

View File

@ -55,9 +55,16 @@ public class InterfaceForwardService : ITransientDependency
var res = await response.Content.ReadAsStringAsync(); var res = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
throw new BusinessException(message: $"返回的状态码有问题: {response.StatusCode}, {response.ReasonPhrase}"); throw new BusinessException(
message: $"调用接口通失败:HTTP {(int)response.StatusCode} {response.ReasonPhrase};响应:{Truncate(res, 300)}");
} }
return res.ToObject<InterfaceRelayUnifyResultDto>()!; return res.ToObject<InterfaceRelayUnifyResultDto>()!;
} }
private static string Truncate(string? text, int maxLen)
{
if (string.IsNullOrEmpty(text)) return string.Empty;
return text.Length <= maxLen ? text : text[..maxLen] + "...";
}
} }

View File

@ -54,9 +54,6 @@ public interface IWarningTaskRepository
#region 运输预警_预警明细生成任务 #region 运输预警_预警明细生成任务
// 扫描预警明细生成任务
Task<List<TransportWarningDetailsGenerateTaskEntity>> ScanTransportWarningDetailsGenerateTaskAsync();
// 条件获取运输预警明细生成任务 // 条件获取运输预警明细生成任务
Task<List<TransportWarningDetailsGenerateTaskEntity>> GetTransportWarningDetailsGenerateTaskAsync(Expression<Func<TransportWarningDetailsGenerateTaskEntity, bool>> expression); Task<List<TransportWarningDetailsGenerateTaskEntity>> GetTransportWarningDetailsGenerateTaskAsync(Expression<Func<TransportWarningDetailsGenerateTaskEntity, bool>> expression);

View File

@ -110,13 +110,6 @@ public class WarningTaskRepository : BasicRepository<WarningNotifyTaskEntity>, I
#region 运输预警_预警明细生成任务 #region 运输预警_预警明细生成任务
public Task<List<TransportWarningDetailsGenerateTaskEntity>> ScanTransportWarningDetailsGenerateTaskAsync()
{
return Context.Queryable<TransportWarningDetailsGenerateTaskEntity>()
.Where(x => DateTime.Now >= x.PlanExecutionTime && x.IsClosed == false)
.ToListAsync();
}
public Task<List<TransportWarningDetailsGenerateTaskEntity>> GetTransportWarningDetailsGenerateTaskAsync(Expression<Func<TransportWarningDetailsGenerateTaskEntity, bool>> expression) public Task<List<TransportWarningDetailsGenerateTaskEntity>> GetTransportWarningDetailsGenerateTaskAsync(Expression<Func<TransportWarningDetailsGenerateTaskEntity, bool>> expression)
{ {
return Context.Queryable<TransportWarningDetailsGenerateTaskEntity>() return Context.Queryable<TransportWarningDetailsGenerateTaskEntity>()