track/src/SJZY.Track.Application/Services/ProvideServices/TrackService.cs

588 lines
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

using DotNetCore.CAP;
using Microsoft.AspNetCore.Http;
using SJZY.Track.Application.Contract.Dtos.ProvideServices;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services.ProvideServices;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackContentAggregate;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Aggregates.TrackTemplateAggregate;
using SJZY.Track.Domain.Enums;
using SJZY.Track.Domain.Extensions;
using SJZY.Track.Domain.Queries;
using SJZY.UserCenter.HttpApi.Services;
using SJZY.UserCenter.HttpApi.Services.User;
using Volo.Abp.Http.Client.DynamicProxying;
namespace SJZY.Track.Application.Services.ProvideServices;
/// <summary>
/// 轨迹服务
/// </summary>
[ApiExplorerSettings(GroupName = "提供接口")]
[Route("/ProviderService")]
public partial class TrackService : ApplicationService, ITrackService
{
private readonly ICapPublisher _capPublisher;
private readonly IOrderRepository _orderRepository;
private readonly IHttpClientProxy<IUserBaseInfoService> _requestCurrentUserService;
private readonly ITrackContentRepository _trackContentRepository;
public TrackService(IOrderRepository orderRepository,
ICapPublisher capPublisher,
ITrackDetailsEsRepository trackDetailsEsRepository,
ITrackContentRepository trackContentRepository,
IHttpClientProxy<IUserBaseInfoService> requestCurrentUserService,
ITrackTemplateRepository trackTemplateRepository,
IHttpContextAccessor contextAccessor,
IVSystemQueries vSystemQueries)
{
_orderRepository = orderRepository;
_capPublisher = capPublisher;
_trackContentRepository = trackContentRepository;
_requestCurrentUserService = requestCurrentUserService;
_vSystemQueries = vSystemQueries;
_contextAccessor = contextAccessor;
_trackTemplateRepository = trackTemplateRepository;
_trackDetailsEsRepository = trackDetailsEsRepository;
}
/// <summary>
/// 删除轨迹
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/Delete")]
public async Task<UnifyResultDto> DeleteTrackAsync(DeleteTrackInput input)
{
var orderId = await _orderRepository.GetFirstAsync(
x => x.SubSystemId == input.SubSystemId && x.OrderType == input.OrderType && x.OrderNo == input.OrderNo,
x => x.Id);
if (orderId == null)
{
throw new BusinessException(message: $"订单:{input.OrderNo}不存在");
}
var userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync();
if (userInfo.Code != GlobalConst.SuccessCode || userInfo.Data is null)
{
//TODO: log || ex
throw new BusinessException(message: $"调用用户中心,获取用户信息失败:{userInfo.Msg}");
}
var name = userInfo.Data.UserName;
List<TrackDetailsIndex> tracks;
bool existsExternal;
var esIds = input.EsIds?.Distinct().ToArray();
//优先使用esId
if (esIds != null && esIds.Length > 0)
{
tracks = await _trackDetailsEsRepository.GetDataByIds(esIds);
existsExternal = tracks.Any(x => x.Source == (int) TrackSourceEnum.ExternalServiceProvide);
}
else
{
var trackCodes = input.TrackIdentifiers?.Select(x => x.TrackCode).ToList();
var trackIds = input.TrackIdentifiers?.Select(y => $"{y.TrackCode}{y.TrackTime}").ToHashSet();
//没有任何需要操作的轨迹
if (trackIds == null || trackIds.Count == 0)
{
throw new BusinessException(message: "没有任何轨迹要删除,请检查输入参数");
}
//删除轨迹约束,只要其中有一条来源是外部的就立即返回错误
tracks = await _trackDetailsEsRepository.GetListAsync(new[] { orderId }, trackCodes);
//检查删除的轨迹中是否包含外部轨迹
existsExternal = tracks
.Where(x => x.Source == (int) TrackSourceEnum.ExternalServiceProvide)
.WhereIf(!input.CompareFields.IsNullOrWhiteSpace(), y =>
{
if (string.IsNullOrWhiteSpace(y.Extra))
{
throw new BusinessException(message: "轨迹extra为空无法使用比较字段比较,请检查轨迹");
}
var extraDict = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(y.Extra);
if (extraDict!.TryGetValue(input.CompareFields!, out var compareValueJsonElement))
return TextJsonHelper.EqJsonElementAndObject(compareValueJsonElement, input.CompareValue);
return false;
})
.Select(x => $"{x.TrackCode}{x.TrackTime}")
.Any(x => trackIds.Contains(x));
}
if (existsExternal)
{
throw new BusinessException(message: "存在来源于外部服务商的轨迹,不允许删除");
}
if (tracks.Count == 0)
{
throw new BusinessException(message: "没有任何轨迹要删除,请检查输入参数");
}
// 删除轨迹
await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe,
new TrackDeleteEvent
{
EsIds = input.EsIds?.ToArray(),
OrderIds = new[] { orderId },
CompareFields = input.CompareFields,
CompareValue = input.CompareValue,
Source = input.Source,
TrackIdentifiers = input.TrackIdentifiers,
SubSystemId = input.SubSystemId,
OperationMan = name
});
return new UnifyResultDto(true, true);
}
/// <summary>
/// 根据esid删除轨迹内容
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Track/BatchDeleteByEsId")]
public async Task<UnifyResultDto> BatchDeleteTrackByEsIdAsync(BatchDeleteTrackByEsIdInput input)
{
var tracks = await _trackDetailsEsRepository.GetDataByIds(input.TrackContentEsIds.ToArray());
if (tracks == null || tracks.Count == 0)
return new UnifyResultDto(false, "未找到指定id下的轨迹内容");
if (tracks.Any(m => m.Source == TrackSourceEnum.ExternalServiceProvide.GetHashCode()))
return new UnifyResultDto(false, "存在来源于外部服务商的轨迹,不允许删除");
if (tracks.GroupBy(m => m.SubSystemId).Count() > 1)
return new UnifyResultDto(false, "暂不支持跨子系统批量删除");
// 获取用户name
var userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync();
if (userInfo.Code != GlobalConst.SuccessCode || userInfo.Data is null)
{
throw new BusinessException(message: $"调用用户中心,获取用户信息失败:{userInfo.Msg}");
}
// 删除轨迹
var currentUser = CurrentUserRequest.CurrentUser;
var userName = userInfo.Data.UserName;
if (string.IsNullOrEmpty(userName))
userName = (await _orderRepository.GetUserById(currentUser.UserId))?.Name;
var @event = new TrackDeleteEvent
{
EsIds = input.TrackContentEsIds.ToArray(),
SubSystemId = tracks.First().SubSystemId,
Source = input.Source,
OperationMan = userName,
OperationManId = currentUser.UserId
};
await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe, @event);
return new UnifyResultDto(true);
}
/// <summary>
/// 批量删除轨迹
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/BatchDelete")]
public async Task<UnifyResultDto> BatchDeleteTrackAsync(BatchDeleteTrackInput input)
{
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空
var orderIds = await _orderRepository.GetListAsync(x =>
x.SubSystemId == input.SubSystemId && x.OrderType == input.OrderType &&
input.OrderNos.Contains(x.OrderNo)
, x => x.Id);
if (orderIds.Count == 0)
{
throw new BusinessException(message: "订单一个都不存在");
}
// 获取用户name
var userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync();
if (userInfo.Code != GlobalConst.SuccessCode || userInfo.Data is null)
{
throw new BusinessException(message: $"调用用户中心,获取用户信息失败:{userInfo.Msg}");
}
//删除轨迹约束,只要其中有一条来源是外部的就立即返回错误
var tracks = await _trackDetailsEsRepository
.GetListAsync(orderIds.ToArray(),
input.TrackIdentifiers.Select(x => x.TrackCode).ToList(),
TrackSourceEnum.ExternalServiceProvide);
//当要删除的轨迹标识出现交叉则不允许删除
var intersect = tracks
.WhereIf(!input.CompareFields.IsNullOrWhiteSpace(), y =>
{
if (string.IsNullOrWhiteSpace(y.Extra))
{
throw new BusinessException(message: "轨迹extra为空无法使用比较字段比较,请检查轨迹");
}
var extraDict = TextJsonHelper.DeserializeToDictionary(y.Extra);
if (extraDict!.TryGetValue(input.CompareFields!, out var compareValueJsonElement))
return TextJsonHelper.EqJsonElementAndObject(compareValueJsonElement, input.CompareValue);
return false;
})
.Select(x => $"{x.TrackCode}{x.TrackTime}")
.Intersect(input.TrackIdentifiers.Select(y => $"{y.TrackCode}{y.TrackTime}")).ToList();
if (intersect.Count > 0)
{
throw new BusinessException(message: "存在来源于外部服务商的轨迹,不允许删除");
}
// 删除轨迹
var @event = new TrackDeleteEvent
{
OrderIds = orderIds.ToArray(),
Source = input.Source,
TrackIdentifiers = input.TrackIdentifiers,
CompareFields = input.CompareFields,
CompareValue = input.CompareValue,
SubSystemId = input.SubSystemId,
OperationMan = userInfo.Data.UserName,
};
await _capPublisher.PublishAsync(GlobalConst.TrackDeleteSubscribe, @event);
return new UnifyResultDto(true, true);
}
/// <summary>
/// 新增、更新轨迹(存在就更新)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/Upsert")]
public async Task<UnifyResultDto> UpsertTrackAsync(UpsertTrackInput input)
{
var order = await _orderRepository.GetFirstAsync(x =>
x.SubSystemId == input.SubSystemId
&& x.OrderType == input.OrderType
&& x.OrderNo == input.OrderNo,
x => new
{
x.Id,
x.OrderNo,
x.CustomId
});
if (order == null)
{
throw new BusinessException(message: $"订单:{input.OrderNo}不存在");
}
// 输入参数的静态规则检查
// 轨迹内容上的约束检查
// 更新轨迹时是否可以匹配到轨迹的检查
await TrackUpsertValidation(input.IsAdd,
input.SubSystemId,
order.Id,
order.OrderNo,
input.TrackCode,
input.Source,
input.TrackTime,
input.NewTrackTime,
input.EstimatedDeliveryDate,
input.DestinationTimeZone,
input.Extra,
input.CompareFields);
var userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync();
if (userInfo.Code != GlobalConst.SuccessCode || userInfo.Data is null)
{
//todo:保留之前的方式(FBA在用) 后续可能去除
userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync(input.OptionUserId);
if (userInfo.Code != GlobalConst.SuccessCode || userInfo.Data is null)
throw new BusinessException(message: $"调用用户中心,获取用户信息失败:{userInfo.Msg}");
}
var name = userInfo.Data.UserName;
// 新增/更新轨迹
var @event = ObjectMapper.Map<UpsertTrackInput, TrackUpsertEvent>(input);
@event.SubSystemId = input.SubSystemId;
@event.CustomId = order.CustomId;
@event.OrderIdentifiers = new[]
{
new OrderIdentifier
{
OrderId = order.Id,
OrderNo = input.OrderNo,
OrderType = input.OrderType,
}
};
@event.OperationMan = name;
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
return new UnifyResultDto(true, true);
}
/// <summary>
/// 批量新增、更新轨迹
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/BatchUpsert")]
public async Task<UnifyResultDto> BatchUpsertTrackAsync(BatchUpsertTrackInput input)
{
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空
var orders = await _orderRepository.GetListAsync(x =>
x.SubSystemId == input.SubSystemId
&& x.OrderType == input.OrderType
&& input.OrderNos.Contains(x.OrderNo),
x => new
{
x.Id,
x.OrderNo,
x.CustomId
});
if (orders.Count == 0)
{
throw new BusinessException(message: "订单一个都不存在");
}
// 校验主体:批量新增只选取第一条订单进行校验
//【新增】存在指定轨迹的内容,进行更新覆盖(只处理一条的情况,并且决定权交给用户),不存在直接插入即可
//【更新】存在指定轨迹的内容(匹配到多条则不处理)
var order = orders.First();
// 输入参数的静态规则检查
// 轨迹内容上的约束检查
// 更新轨迹时是否可以匹配到轨迹的检查
await TrackUpsertValidation(input.IsAdd,
input.SubSystemId,
order.Id,
order.OrderNo,
input.TrackCode,
input.Source,
input.TrackTime,
input.NewTrackTime,
input.EstimatedDeliveryDate,
input.DestinationTimeZone,
input.Extra,
input.CompareFields);
// 获取用户name
var userInfo = await _requestCurrentUserService.Service.GetCurrentUserBasicInfoAsync();
if (userInfo.Code != GlobalConst.SuccessCode || userInfo.Data is null)
{
throw new BusinessException(message: $"调用用户中心,获取用户信息失败:{userInfo.Msg}");
}
// 按客户新增/更新轨迹(为了推送轨迹时方便)
foreach (var group in orders.GroupBy(x => x.CustomId))
{
var @event = new TrackUpsertEvent
{
SubSystemId = input.SubSystemId,
CustomId = group.Key,
OrderIdentifiers = group.Select(o => new OrderIdentifier
{
OrderId = o.Id,
OrderNo = o.OrderNo,
OrderType = input.OrderType,
}).ToArray(),
IsAdd = input.IsAdd,
TrackCode = input.TrackCode,
TrackPlace = input.TrackPlace,
TrackTime = input.TrackTime,
NewTrackTime = input.NewTrackTime,
TrackTimeZone = input.TrackTimeZone,
OperationMan = userInfo.Data.UserName,
OperationManId = userInfo.Data.UserId,
IsVisibility = input.IsVisibility,
IsExternal = input.IsExternal,
Source = input.Source,
Remarks = input.Remarks,
Files = input.Files,
Content = input.Content,
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,
ForcedPush = input.ForcedPush,
IsToMq = input.IsToMq,
};
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
}
return new UnifyResultDto(true, true);
}
//在轨迹更新主流程之前的前置检查
private async Task TrackUpsertValidation(
bool isAdd,
int subSystemId,
int orderId,
string orderNo,
string trackCode,
int source,
DateTime trackTime,
DateTime? newTrackTime,
DateTime? estimatedDeliveryDate,
float? destinationTimeZone,
string? extra,
string? compareFields
)
{
#region 静态规则检查
// 约束轨迹来源只能选取 1-外部 2-内部 3-手动
if (!GlobalConst.SupportTrackSources.Contains(source))
{
throw new BusinessException(message: "轨迹来源是必须的,1:外部服务商 2:内部系统 3:人工操作");
}
//当修改时,如果为来源为外部提供商则不允许修改
if (!isAdd && source == (int) TrackSourceEnum.ExternalServiceProvide)
{
throw new BusinessException(message: "来源为外部服务商轨迹不允许修改");
}
//要求时间在当前时间的前后100年之间内
bool ValidateTrackTime(DateTime? d) => (d != null && d < DateTime.Now.AddYears(-100) || d > DateTime.Now.AddYears(100));
if (ValidateTrackTime(trackTime) || ValidateTrackTime(newTrackTime) || ValidateTrackTime(estimatedDeliveryDate))
{
throw new BusinessException(message: "轨迹时间不合法");
}
if ((estimatedDeliveryDate.HasValue == false && destinationTimeZone.HasValue) ||
(estimatedDeliveryDate.HasValue && destinationTimeZone.HasValue == false))
{
throw new BusinessException(message: "【预计签收时间】和【签收地时区】必须同时输入");
}
if (!string.IsNullOrWhiteSpace(compareFields) && string.IsNullOrWhiteSpace(extra))
{
throw new BusinessException(message: "当输入了【轨迹对比字段】则轨迹扩展是必须的的");
}
#endregion
#region 轨迹内容中的配置规则检查
var trackContent = await _trackContentRepository.GetByCodeAsync(subSystemId, trackCode);
if (trackContent == null)
{
throw new BusinessException(message: $"轨迹代码:{trackCode}轨迹内容不存在");
}
//指示是否为手动操作
var manualOperation = source == (int) TrackSourceEnum.Artificial;
//不允许手动创建
if (manualOperation && isAdd && !trackContent.IsAllowManuallyCreate)
{
throw new BusinessException(message: $"轨迹代码:{trackCode}不允许手动创建");
}
//不允许手动更新
if (manualOperation && !isAdd && !trackContent.IsAllowManuallyUpdate)
{
throw new BusinessException(message: $"轨迹代码:{trackCode}不允许手动更新");
}
#endregion
//轨迹新增,不允许重复的轨迹提前拦截
if (isAdd)
{
//允许重复,跳过检查
if (trackContent.IsRepeatable) return;
//找到这个订单的这个轨迹code的所有轨迹
var tracks = await RetryQueryTrackAsync(orderId, trackCode, trackTime, extra, compareFields, trackContent.IsRepeatable);
if (tracks.Count > 0)
throw new BusinessException(message: $"订单:[{orderNo}],轨迹代码:{trackCode}不允许重复创建轨迹");
}
//针对于轨迹更新检查
else
{
//找到这个订单的这个轨迹code的所有轨迹,
var tracks = await RetryQueryTrackAsync(orderId, trackCode, trackTime, extra, compareFields, trackContent.IsRepeatable);
//不存在需要更新的轨迹
if (tracks.Count == 0)
throw new BusinessException(message: $"订单:[{orderNo}],轨迹代码:[{trackCode}]没有要更新的记录");
//更新轨迹,同一时间存在多条轨迹记录,理由:不清晰到底要修改哪一条或是全部修改
if (tracks.Count > 1)
throw new BusinessException(message: $"订单:{orderNo},轨迹代码:[{trackCode}]存在多条记录,不允许修改");
}
}
// 重试查询轨迹,直到存在符合要求的轨迹,或者超过最大重试次数
private async Task<List<TrackDetailsIndex>> RetryQueryTrackAsync(int orderId, string trackCode, DateTime trackTime, string? extra, string? compareFields, bool isRepeatable)
{
var tracks = await QueryAndFilterAsync(orderId, trackCode, trackTime, extra, compareFields, isRepeatable);
if (tracks.Count > 0)
return tracks;
//重试阶段
var curRetryCount = 0;
while (curRetryCount < GlobalConst.MaxUpdateTrackRetryCount) //tips:后续将最大重试次数提出到配置中
{
try
{
tracks = await QueryAndFilterAsync(orderId, trackCode, trackTime, extra, compareFields, isRepeatable);
if (tracks.Count > 0)
return tracks;
//指数退避等待
var delay = TimeSpan.FromMilliseconds(Math.Pow(2, curRetryCount) * 150); // 150ms, 300ms, 600ms, ...
await Task.Delay(delay);
}
finally
{
curRetryCount++;
}
}
//超过最大重试次数还没找到
return tracks;
}
//查询并按比较字段过滤轨迹,当比较字段为空时则使用轨迹发生时间过滤
//tips:后续进行投影缩参优化
private async Task<List<TrackDetailsIndex>> QueryAndFilterAsync(int orderId, string trackCode, DateTime trackTime, string? extra, string? compareFields, bool isRepeatable)
{
var tracks = await _trackDetailsEsRepository.GetListAsync(new[] { orderId }, trackCode, true);
// 如果不允许重复,则直接返回
if (!isRepeatable) return tracks;
//无比较字段时使用轨迹时间参与过滤(兼容老版本)
if (string.IsNullOrWhiteSpace(compareFields))
{
return tracks.Where(x => x.TrackTime == trackTime).ToList();
}
return tracks.Where(x =>
{
if (string.IsNullOrWhiteSpace(x.Extra))
throw new BusinessException(message: "轨迹extra为空无法使用比较字段比较,请检查轨迹");
var extraJsonElement = TextJsonHelper.DeserializeToJsonElement(x.Extra);
var inputExtraJsonElement = TextJsonHelper.DeserializeToJsonElement(extra);
return TextJsonHelper.EqByJsonElementField(extraJsonElement, inputExtraJsonElement, compareFields);
})
.ToList();
}
}