支持批量新增、更新轨迹

This commit is contained in:
李鹏军 2024-03-12 15:59:34 +08:00
parent ba78ba46ae
commit f4b7645ddf
15 changed files with 187 additions and 73 deletions

View File

@ -2,21 +2,8 @@
namespace SJZY.Track.Application.Contract.Dtos.ProvideServices; namespace SJZY.Track.Application.Contract.Dtos.ProvideServices;
public class BatchDeleteTrackInput public class BatchDeleteTrackInput : OrderNosInfo
{ {
/// <summary>
/// 订单类型,必填
/// </summary>
[Required]
[Range(1, int.MaxValue)]
public int OrderType { get; set; }
/// <summary>
/// 订单号,必填
/// </summary>
[Required]
public List<string> OrderNos { get; set; } = new();
/// <summary> /// <summary>
/// 轨迹code,必填 /// 轨迹code,必填
/// </summary> /// </summary>

View File

@ -0,0 +1,33 @@
using System.ComponentModel.DataAnnotations;
namespace SJZY.Track.Application.Contract.Dtos.ProvideServices;
public class BatchUpsertTrackInput : OrderNosInfo
{
/// <summary>
/// 轨迹内容代码
/// </summary>
[Required]
[MaxLength(50)]
public string TrackCode { get; set; } = null!;
/// <summary>
/// 轨迹发生地
/// </summary>
[Required]
[MaxLength(200)]
public string TrackPlace { get; set; } = null!;
/// <summary>
/// 轨迹发生时间
/// </summary>
[Required]
public DateTime? TrackTime { get; set; }
/// <summary>
/// 轨迹发生时区
/// </summary>
[Required]
[Range(-12, 12)]
public float? TrackTimeZone { get; set; }
}

View File

@ -2,20 +2,8 @@ using System.ComponentModel.DataAnnotations;
namespace SJZY.Track.Application.Contract.Dtos.ProvideServices; namespace SJZY.Track.Application.Contract.Dtos.ProvideServices;
public class GetTrackInput public class GetTrackInput : OrderNosInfo
{ {
/// <summary>
/// 订单类型
/// </summary>
[Required]
[Range(1, int.MaxValue)]
public int OrderType { get; set; }
/// <summary>
/// 订单号
/// </summary>
public List<string> OrderNos { get; set; } = new();
/// <summary> /// <summary>
/// 时区 +8 +8.25 -8.5 -9 /// 时区 +8 +8.25 -8.5 -9
/// 不传默认+8 /// 不传默认+8

View File

@ -17,4 +17,20 @@ public class OrderNoInfo
[Required] [Required]
[MaxLength(100)] [MaxLength(100)]
public string OrderNo { get; set; } = null!; public string OrderNo { get; set; } = null!;
}
public class OrderNosInfo
{
/// <summary>
/// 订单类型
/// </summary>
[Required]
[Range(1, int.MaxValue)]
public int OrderType { get; set; }
/// <summary>
/// 订单号
/// </summary>
[Required]
public List<string> OrderNos { get; set; } = new();
} }

View File

@ -30,18 +30,4 @@ public class UpsertTrackInput : OrderNoInfo
[Required] [Required]
[Range(-12, 12)] [Range(-12, 12)]
public float? TrackTimeZone { get; set; } public float? TrackTimeZone { get; set; }
}
public class NotNullAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
return new ValidationResult(ErrorMessage ?? "The field is required.",
new[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
} }

View File

@ -4,6 +4,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Version>1.0.0</Version> <Version>1.0.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -29,7 +29,7 @@ public class TrackDeleteEventHandler : ITransientDependency, ICapSubscribe
{ {
if (@event.Visited.Contains(@event.OrderId)) if (@event.Visited.Contains(@event.OrderId))
{ {
LogHelper.Error($"轨迹订单循环依赖预警:{@event.Visited.JoinAsString(",")}"); LogHelper.Error($"删除轨迹订单时,发现循环依赖:{@event.Visited.JoinAsString(",")}");
return; return;
} }

View File

@ -1,4 +1,5 @@
using System.Linq.Expressions; using System.Diagnostics;
using System.Linq.Expressions;
using DotNetCore.CAP; using DotNetCore.CAP;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -94,47 +95,71 @@ public class TrackRefreshEventHandler : ITransientDependency, ICapSubscribe
var trackUpsertEvent = @event.DeepCopy(); var trackUpsertEvent = @event.DeepCopy();
var paramConfig = config.ParamConfig; var paramConfig = config.ParamConfig;
var contentConfigs = config.ContentConfigs; var contentConfigs = config.ContentConfigs;
TrackPullContentConfigDto? trackContent = null; TrackPullContentConfigDto? matchedContentConfig = null;
// 1、匹配轨迹代码 // 1、匹配轨迹代码
var serviceProviderTrackCode = trackToken[paramConfig.TrackContentCodeName]?.Value<string>(); var trackCode = trackToken[paramConfig.TrackContentCodeName]?.Value<string>();
if (!serviceProviderTrackCode.IsNullOrWhiteSpace()) if (!trackCode.IsNullOrWhiteSpace())
{ {
trackContent = contentConfigs.FirstOrDefault(x => x.TrackContentCodeValue == serviceProviderTrackCode); matchedContentConfig = contentConfigs.FirstOrDefault(x => x.TrackContentCodeValue == trackCode);
} }
// 2、匹配轨迹状态 // 2、匹配轨迹状态
if (trackContent is null) if (matchedContentConfig is null)
{ {
var serviceProviderTrackStatus = trackToken[paramConfig.TrackStatusName]?.Value<string>(); var trackStatus = trackToken[paramConfig.TrackStatusName]?.Value<string>();
if (!serviceProviderTrackStatus.IsNullOrWhiteSpace()) if (!trackStatus.IsNullOrWhiteSpace())
{ {
trackContent = contentConfigs.FirstOrDefault(x => x.TrackStatusValue == serviceProviderTrackCode); matchedContentConfig = contentConfigs.FirstOrDefault(x => x.TrackStatusValue == trackCode);
} }
} }
// 3、匹配轨迹内容 // 3、匹配轨迹内容
if (trackContent is null) if (matchedContentConfig is null)
{ {
var serviceProviderTrackContent = trackToken[paramConfig.TrackContentName]?.Value<string>(); var trackContent = trackToken[paramConfig.TrackContentName]?.Value<string>();
if (!serviceProviderTrackContent.IsNullOrWhiteSpace()) if (!trackContent.IsNullOrWhiteSpace())
{ {
trackContent = contentConfigs.FirstOrDefault(x => x.TrackContentValue == serviceProviderTrackCode); Debug.Assert(trackContent != null, nameof(trackContent) + " != null");
matchedContentConfig = contentConfigs.FirstOrDefault(x => x.TrackContentValue == trackCode);
// 3.1、按关键字匹配 // 按关键字分析轨迹内容
if (trackContent is null) if (matchedContentConfig is null)
{ {
foreach (var contentConfig in contentConfigs)
{
foreach (var keyword in contentConfig.TrackKeyword?.Split(',') ?? Enumerable.Empty<string>())
{
if (trackContent.Contains(keyword, StringComparison.OrdinalIgnoreCase))
{
matchedContentConfig = contentConfig;
break;
}
}
if (matchedContentConfig is null) continue;
foreach (var keyword in contentConfig.TrackKeywordReverse?.Split(',') ??
Enumerable.Empty<string>())
{
if (trackContent.Contains(keyword, StringComparison.OrdinalIgnoreCase))
{
matchedContentConfig = null;
break;
}
}
}
} }
} }
} }
if (trackContent is null) return; if (matchedContentConfig is null) return;
var trackPlace = trackToken[paramConfig.TrackPlaceName]?.Value<string>(); var trackPlace = trackToken[paramConfig.TrackPlaceName]?.Value<string>();
if (trackPlace.IsNullOrWhiteSpace()) if (trackPlace.IsNullOrWhiteSpace())
{ {
Expression<Func<OrderEntity, string?>> resultExpression = Expression<Func<OrderEntity, string?>> resultExpression =
(TrackLocationValue?)trackContent.TrackPlaceValue switch (TrackLocationValue?)matchedContentConfig.TrackPlaceValue switch
{ {
TrackLocationValue.ArrivalPort => x => x.ArrivalPort, TrackLocationValue.ArrivalPort => x => x.ArrivalPort,
TrackLocationValue.ArriveCity => x => x.ArrivalCity, TrackLocationValue.ArriveCity => x => x.ArrivalCity,
@ -149,11 +174,11 @@ public class TrackRefreshEventHandler : ITransientDependency, ICapSubscribe
} }
// 新增/更新轨迹 // 新增/更新轨迹
trackUpsertEvent.TrackCode = trackContent.TrackContentCode; trackUpsertEvent.TrackCode = matchedContentConfig.TrackContentCode;
trackUpsertEvent.TrackPlace = trackPlace ?? "CN"; trackUpsertEvent.TrackPlace = trackPlace ?? "CN";
trackUpsertEvent.TrackTime = trackToken[paramConfig.TrackTimeName]?.Value<DateTime>() ?? DateTime.Now; trackUpsertEvent.TrackTime = trackToken[paramConfig.TrackTimeName]?.Value<DateTime>() ?? DateTime.Now;
trackUpsertEvent.TrackTimeZone = trackToken[paramConfig.TrackTimeZoneName]?.Value<float>() ?? trackUpsertEvent.TrackTimeZone = trackToken[paramConfig.TrackTimeZoneName]?.Value<float>() ??
trackContent.TrackTimeZoneValue ?? 8; matchedContentConfig.TrackTimeZoneValue ?? 8;
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, trackUpsertEvent); await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, trackUpsertEvent);
} }
} }

View File

@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SJZY.InterfaceRelay.Application.Contracts" Version="7.1.2" /> <PackageReference Include="SJZY.InterfaceRelay.Application.Contracts" Version="7.1.3"/>
<PackageReference Include="Volo.Abp.Http.Client" Version="7.0.2" /> <PackageReference Include="Volo.Abp.Http.Client" Version="7.0.2" />
<PackageReference Include="Volo.Abp.FluentValidation" Version="7.0.2" /> <PackageReference Include="Volo.Abp.FluentValidation" Version="7.0.2" />
<PackageReference Include="SJZY.UserCenter.HttpApi" Version="3.0.3-alpha8" /> <PackageReference Include="SJZY.UserCenter.HttpApi" Version="3.0.3-alpha8" />

View File

@ -14,6 +14,12 @@ public partial class OrderService
private readonly IBasicQuery<OrderLinkEntity> _orderLinkQuery; private readonly IBasicQuery<OrderLinkEntity> _orderLinkQuery;
private readonly ICapPublisher _capPublisher; private readonly ICapPublisher _capPublisher;
/// <summary>
/// 关联、解除关联订单
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Order/Link")] [HttpPost("Order/Link")]
public async Task<bool> LinkOrderAsync(LinkOrderInput input) public async Task<bool> LinkOrderAsync(LinkOrderInput input)
{ {

View File

@ -24,6 +24,11 @@ public partial class OrderService : ApplicationService, IOrderService
_orderLinkQuery = orderLinkQuery; _orderLinkQuery = orderLinkQuery;
} }
/// <summary>
/// 新增、更新订单(存在就更新)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Order/Upsert")] [HttpPost("Order/Upsert")]
public async Task<string> UpsertAsync(UpsertOrderInput input) public async Task<string> UpsertAsync(UpsertOrderInput input)
{ {
@ -45,6 +50,12 @@ public partial class OrderService : ApplicationService, IOrderService
return "创建成功"; return "创建成功";
} }
/// <summary>
/// 删除订单
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Order/Delete")] [HttpPost("Order/Delete")]
public async Task<bool> DeleteOrderAsync(DeleteOrderInput input) public async Task<bool> DeleteOrderAsync(DeleteOrderInput input)
{ {

View File

@ -15,10 +15,10 @@ namespace SJZY.Track.Application.Services.ProvideServices;
public class TrackPushPullService : ApplicationService public class TrackPushPullService : ApplicationService
{ {
private readonly IOrderRepository _orderRepository; private readonly IOrderRepository _orderRepository;
private readonly IHttpClientProxy<IInterfaceForwardService> _httpClientProxy; private readonly IHttpClientProxy<IInterfaceForwardServiceV2> _httpClientProxy;
public TrackPushPullService(IOrderRepository orderRepository, public TrackPushPullService(IOrderRepository orderRepository,
IHttpClientProxy<IInterfaceForwardService> httpClientProxy) IHttpClientProxy<IInterfaceForwardServiceV2> httpClientProxy)
{ {
_orderRepository = orderRepository; _orderRepository = orderRepository;
_httpClientProxy = httpClientProxy; _httpClientProxy = httpClientProxy;
@ -51,7 +51,7 @@ public class TrackPushPullService : ApplicationService
// 批量获取轨迹 // 批量获取轨迹
await _httpClientProxy.Service.BatchForwardThenPushToQueueAsync( await _httpClientProxy.Service.BatchForwardThenPushToQueueAsync(
"A012", group.Key.ServiceProviderCode, 42, data, "A012", group.Key.ServiceProviderCode, data,
GlobalConst.TrackRefreshSubscribe, JsonConvert.SerializeObject(group.Key)); GlobalConst.TrackRefreshSubscribe, JsonConvert.SerializeObject(group.Key));
} }

View File

@ -10,6 +10,12 @@ public partial class TrackService
{ {
private readonly ITrackDetailsRepository _trackDetailsRepository; private readonly ITrackDetailsRepository _trackDetailsRepository;
/// <summary>
/// 查询轨迹
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/Query")] [HttpPost("Track/Query")]
public async Task<List<GetTrackOutput>> QueryAsync(GetTrackInput input) public async Task<List<GetTrackOutput>> QueryAsync(GetTrackInput input)
{ {

View File

@ -29,6 +29,12 @@ public partial class TrackService : ApplicationService, ITrackService
_trackDetailsRepository = trackDetailsRepository; _trackDetailsRepository = trackDetailsRepository;
} }
/// <summary>
/// 新增、更新轨迹(存在就更新)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/Upsert")] [HttpPost("Track/Upsert")]
public async Task<bool> UpsertTrackAsync(UpsertTrackInput input) public async Task<bool> UpsertTrackAsync(UpsertTrackInput input)
{ {
@ -52,24 +58,68 @@ public partial class TrackService : ApplicationService, ITrackService
return true; return true;
} }
/// <summary>
/// 批量新增、更新轨迹
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/BatchUpsert")]
public async Task<bool> BatchUpsertTrackAsync(BatchUpsertTrackInput input)
{
input.OrderNos = input.OrderNos.Where(x => !x.IsNullOrWhiteSpace()).Distinct().ToList(); // 单号去重、去空
if (input.OrderNos.Count == 0)
{
throw new BusinessException(message: "订单一个都不存在");
}
var orders = await _orderRepository.GetListAsync(x =>
x.OrderType == input.OrderType && input.OrderNos.Contains(x.OrderNo), x => new
{
x.Id, x.OrderNo, x.SubSystemId, x.IsPushCompleted, x.CustomId
});
foreach (var order in orders)
{
// 新增/更新轨迹
var @event = new TrackUpsertEvent
{
TrackCode = input.TrackCode,
TrackPlace = input.TrackPlace,
TrackTime = input.TrackTime,
TrackTimeZone = input.TrackTimeZone,
OrderType = input.OrderType,
OrderNo = order.OrderNo,
OrderId = order.Id,
SubSystemId = order.SubSystemId,
IsPushCompleted = order.IsPushCompleted,
CustomId = order.CustomId
};
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, @event);
}
return true;
}
/// <summary>
/// 删除轨迹
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/Delete")] [HttpPost("Track/Delete")]
public async Task<bool> DeleteTrackAsync(DeleteTrackInput input) public async Task<bool> DeleteTrackAsync(DeleteTrackInput input)
{ {
var order = await _orderRepository.GetFirstAsync(x => var order = await _orderRepository.GetFirstAsync(x =>
x.OrderType == input.OrderType && x.OrderNo == input.OrderNo, x => new x.OrderType == input.OrderType && x.OrderNo == input.OrderNo, x => new
{ {
x.Id, x.SubSystemId, x.IsCompleted x.Id, x.SubSystemId
}); });
if (order == default) if (order == default)
{ {
throw new BusinessException(message: $"订单:{input.OrderNo}不存在"); throw new BusinessException(message: $"订单:{input.OrderNo}不存在");
} }
if (order.IsCompleted)
{
throw new BusinessException(message: $"订单:{input.OrderNo}已完成无法删除轨迹");
}
var codes = await _provideQueries.GetCanDeleteTrackCodesAsync(new List<int>(order.Id), input.TrackCodes); var codes = await _provideQueries.GetCanDeleteTrackCodesAsync(new List<int>(order.Id), input.TrackCodes);
if (codes.Count == 0) if (codes.Count == 0)
@ -89,6 +139,12 @@ public partial class TrackService : ApplicationService, ITrackService
return true; return true;
} }
/// <summary>
/// 批量删除轨迹
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Track/BatchDelete")] [HttpPost("Track/BatchDelete")]
public async Task<bool> BatchDeleteTrackAsync(BatchDeleteTrackInput input) public async Task<bool> BatchDeleteTrackAsync(BatchDeleteTrackInput input)
{ {

View File

@ -1,4 +1,3 @@
using Elasticsearch.Net;
using Nest; using Nest;
using SJZY.Expand.ABP.Core.Logger; using SJZY.Expand.ABP.Core.Logger;
using SJZY.Expand.ElasticSearch.Services; using SJZY.Expand.ElasticSearch.Services;
@ -40,7 +39,7 @@ internal class TrackDetailsRepository : ElasticSearchContextAbstract<TrackDetail
// 按天写入es // 按天写入es
var r = await Context.IndexAsync(index, request => request var r = await Context.IndexAsync(index, request => request
.Index(index.IndexName) .Index(index.IndexName)
.Refresh(Refresh.True) // 可选:立即刷新索引使更改立即可见 //.Refresh(Refresh.True) // 可选:立即刷新索引使更改立即可见
); );
if (!r.IsValid) if (!r.IsValid)