Merge branch 'S01-轨迹中台' into dev

This commit is contained in:
moyongzhen 2024-03-02 15:53:21 +08:00
commit 078e2fd448
16 changed files with 264 additions and 45 deletions

View File

@ -8,12 +8,22 @@ public class TrackUpsertEvent : UpsertTrackInput
/// 订单id
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// 客户id
/// </summary>
public int CustomId { get; set; }
/// <summary>
/// 子系统id
/// </summary>
public int SubSystemId { get; set; }
/// <summary>
/// 订单是否推送完成(异步维护)
/// </summary>
public bool IsPushCompleted { get; set; }
/// <summary>
/// 上游子系统id
/// </summary>

View File

@ -0,0 +1,34 @@
namespace SJZY.Track.Application.Contract.Events;
public class UpdateOrderTrackEvent
{
/// <summary>
/// 订单Id
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// 此次发生的轨迹是否意味完成
/// </summary>
public bool IsCompleted { get; set; }
/// <summary>
/// 轨迹内容
/// </summary>
public string TrackContent { get; set; } = null!;
/// <summary>
/// 轨迹内容英文
/// </summary>
public string TrackEnContent { get; set; } = null!;
/// <summary>
/// 轨迹发生地
/// </summary>
public string TrackPlace { get; set; } = null!;
/// <summary>
/// 轨迹发生时间
/// </summary>
public DateTime TrackTime { get; set; }
}

View File

@ -0,0 +1,18 @@
namespace SJZY.Track.Application.Contract.Services;
public interface ITrackPushCacheService
{
/// <summary>
/// 判断客户是否需要推送
/// </summary>
/// <param name="customId"></param>
/// <returns></returns>
Task<bool> IsPushAsync(int customId);
/// <summary>
/// 更新缓存
/// </summary>
/// <param name="customId"></param>
/// <param name="isPush"></param>
Task UpdateAsync(int customId, bool isPush);
}

View File

@ -1,6 +1,7 @@
using DotNetCore.CAP;
using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Queries;
@ -14,29 +15,33 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
private readonly IOrderRepository _orderRepository;
private readonly ITrackQueries _trackQueries;
private readonly ITrackDetailsRepository _trackDetailsRepository;
private readonly ITrackPushCacheService _trackPushCacheService;
private readonly ICapPublisher _capPublisher;
public TrackUpsertEventHandler(IOrderRepository orderRepository, ICapPublisher capPublisher,
ITrackQueries trackQueries, ITrackDetailsRepository trackDetailsRepository)
ITrackQueries trackQueries, ITrackDetailsRepository trackDetailsRepository,
ITrackPushCacheService trackPushCacheService)
{
_orderRepository = orderRepository;
_capPublisher = capPublisher;
_trackQueries = trackQueries;
_trackDetailsRepository = trackDetailsRepository;
_trackPushCacheService = trackPushCacheService;
}
[NonAction]
[CapSubscribe(GlobalConst.TrackUpsertSubscribe)]
public async Task ConsumerAsync(TrackUpsertEvent @event)
{
// 不同子系统需要做内容映射
if (@event.UpSubSystemId != @event.SubSystemId)
{
var mapCode = await _trackQueries.MapTrackContentCodeAsync(@event.UpSubSystemId, @event.SubSystemId, @event.TrackCode);
var mapCode = await _trackQueries
.MapTrackContentCodeAsync(@event.UpSubSystemId, @event.SubSystemId, @event.TrackCode);
if (mapCode == default)
{
throw new BusinessException(message: "订单跨子系统,未配置内容映射,请联系管理员");
//throw new BusinessException(message: "订单跨子系统,未配置内容映射,请联系管理员");
return;
}
@event.TrackCode = mapCode;
@ -50,15 +55,17 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
// 不存在则新增轨迹,存在则更新
var track = await _trackDetailsRepository.GetFirstOrDefaultAsync(@event.OrderId, @event.TrackCode);
var isPush = false; // 推送只做新增的,更新和删除的不做先
var orderIsCompleted = false;
if (track == default)
{
// 根据轨迹code在订单模板内容中寻找匹配的
var trackContent = await _trackQueries.MatchTrackContentAsync(@event.OrderId, @event.TrackCode);
if (trackContent == default)
{
throw new BusinessException(message: $"订单:{@event.OrderNo} 所绑定的模板,没有一条内容可以匹配轨迹代码:{@event.TrackCode}");
//throw new BusinessException(message: $"订单:{@event.OrderNo} 所绑定的模板,没有一条内容可以匹配轨迹代码:{@event.TrackCode}");
return;
}
orderIsCompleted = trackContent.IsCompleted;
track = new TrackDetailsIndex
{
@ -72,22 +79,37 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
TrackPlace = @event.TrackPlace,
TrackTime = @event.TrackTime,
TraceTimeZone = @event.TraceTimeZone,
CreateUserId = CurrentUserRequest.CurrentUser.UserId,
CreateUser = CurrentUserRequest.CurrentUser.Name,
CreateTime = DateTime.Now
};
isPush = true;
// todo:推送只做新增的,更新和删除的不做先
if (!@event.IsPushCompleted && await _trackPushCacheService.IsPushAsync(@event.CustomId))
{
//await PushTrack();
}
}
else
{
track.TrackPlace = @event.TrackPlace;
track.TrackTime = @event.TrackTime;
track.TraceTimeZone = @event.TraceTimeZone;
track.UpdateUserId = CurrentUserRequest.CurrentUser.UserId;
track.UpdateUser = CurrentUserRequest.CurrentUser.Name;
track.UpdateTime = DateTime.Now;
}
await _trackDetailsRepository.UpsertAsync(track);
// 触发订单主表更新事件
await _capPublisher.PublishAsync(GlobalConst.UpdateOrderTrackSubscribe, new UpdateOrderTrackEvent
{
OrderId = track.OrderId,
IsCompleted = orderIsCompleted,
TrackContent = track.TrackContent,
TrackEnContent = track.TrackEnContent,
TrackPlace = track.TrackPlace,
TrackTime = track.TrackTime
});
// 根据订单关联关系异步向下刷
var linkedOrders = await _orderRepository.GetLinkedOrdersAsync(@event.OrderId);
@ -95,14 +117,13 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
{
var nextEvent = @event.DeepCopy();
nextEvent.OrderId = linkedOrder.OrderId;
nextEvent.OrderType = linkedOrder.OrderType;
nextEvent.OrderNo = linkedOrder.OrderNo;
nextEvent.SubSystemId = linkedOrder.SubSystemId;
nextEvent.IsPushCompleted = linkedOrder.IsPushCompleted;
nextEvent.CustomId = linkedOrder.CustomId;
nextEvent.UpSubSystemId = @event.SubSystemId;
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertSubscribe, nextEvent);
}
if (isPush)
{
//await PushTrack();
}
}
}

View File

@ -0,0 +1,38 @@
using DotNetCore.CAP;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.EventHandlers;
public class UpdateOrderTrackEventHandler : ITransientDependency, ICapSubscribe
{
private readonly IOrderRepository _orderRepository;
public UpdateOrderTrackEventHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
[CapSubscribe(GlobalConst.UpdateOrderTrackSubscribe)]
public async Task ConsumerAsync(UpdateOrderTrackEvent @event)
{
var order = await _orderRepository.GetFirstAsync(x => x.Id == @event.OrderId);
if (order == default) return;
if (order.IsCompleted) return;
if (order.LatestTrackTime > @event.TrackTime) return;
order.LatestTrackContent = @event.TrackContent;
order.LatestTrackEnContent = @event.TrackEnContent;
order.LatestTrackPlace = @event.TrackPlace;
order.LatestTrackTime = @event.TrackTime;
if (@event.IsCompleted) order.IsCompleted = true;
await _orderRepository.UpdateColumnAsync(order, x => new
{
x.IsCompleted, x.LatestTrackContent, x.LatestTrackEnContent, x.LatestTrackPlace, x.LatestTrackTime
});
}
}

View File

@ -27,9 +27,11 @@ public class OrderLinkService : ApplicationService
[HttpPost("Order/Link")]
public async Task<bool> LinkOrderAsync(LinkOrderInput input)
{
if (!await _orderRepository.IsAnyAsync(x => x.OrderType == input.OrderType && x.OrderNo == input.OrderNo))
var orderId = await _orderRepository.GetFirstAsync(x =>
x.OrderType == input.OrderType && x.OrderNo == input.OrderNo, x => x.Id);
if (orderId == default)
{
throw new BusinessException(message: $"单号:{input.OrderNo}不存在");
throw new BusinessException(message: $"订单:{input.OrderNo}不存在");
}
if (input.LinkOrderNos.Count < 1 || input.LinkOrderType == 0) return true;
@ -41,10 +43,10 @@ public class OrderLinkService : ApplicationService
if (!input.IsLink) return await UnLinkOrderAsync(input);
// 关联订单
return await DoLinkOrderAsync(input);
return await DoLinkOrderAsync(input, orderId);
}
private async Task<bool> DoLinkOrderAsync(LinkOrderInput input)
private async Task<bool> DoLinkOrderAsync(LinkOrderInput input, int orderId)
{
// 获取已关联的订单
var linkOrderNos = await _orderLinkQuery.GetListAsync(x =>
@ -55,15 +57,17 @@ public class OrderLinkService : ApplicationService
var noLinkOrderNos = input.LinkOrderNos.Except(linkOrderNos);
// 过滤掉不存在的订单
var canLinkOrderNos = await _orderRepository.GetListAsync(x =>
var canLinkOrders = await _orderRepository.GetListAsync(x =>
x.OrderType == input.LinkOrderType && noLinkOrderNos.Contains(x.OrderNo),
x => x.OrderNo);
x => new { x.Id, x.OrderNo });
await _orderRepository.LinkOrderAsync(canLinkOrderNos.Select(x => new OrderLinkEntity
await _orderRepository.LinkOrderAsync(canLinkOrders.Select(x => new OrderLinkEntity
{
OrderId = orderId,
OrderNo = input.OrderNo,
OrderType = input.OrderType,
LinkOrderNo = x,
LinkOrderId = x.Id,
LinkOrderNo = x.OrderNo,
LinkOrderType = input.LinkOrderType
}).ToList());

View File

@ -32,7 +32,7 @@ public class TrackService : ApplicationService
var order = await _orderRepository.GetFirstAsync(x =>
x.OrderType == input.OrderType && x.OrderNo == input.OrderNo, x => new
{
x.Id, x.SubSystemId, x.IsPushCompleted
x.Id, x.SubSystemId, x.IsPushCompleted, x.CustomId
});
if (order == default)
{
@ -43,6 +43,8 @@ public class TrackService : ApplicationService
var @event = ObjectMapper.Map<UpsertTrackInput, TrackUpsertEvent>(input);
@event.OrderId = order.Id;
@event.SubSystemId = order.SubSystemId;
@event.IsPushCompleted = order.IsPushCompleted;
@event.CustomId = order.CustomId;
await _trackUpsertEventHandler.UpsertTrackAsync(@event);
return true;
}

View File

@ -7,6 +7,7 @@ using SJZY.Track.Application.Contract.Dtos.TrackPull;
using SJZY.Track.Application.Localization;
using SJZY.Track.Domain.Aggregates.TrackContentAggregate;
using SJZY.Track.Domain.Aggregates.TrackPullAggregate;
using SJZY.Track.Domain.Aggregates.TrackTemplateAggregate;
using SJZY.Track.Domain.Dtos.TrackContents;
using SJZY.Track.Domain.Queries;
using System.Linq.Expressions;
@ -24,16 +25,19 @@ namespace SJZY.Track.Application.Services.TrackContents
private readonly IStringLocalizer<TrackLocalizationResource> _stringLocalizer;
private readonly ITrackContentRepository _trackContentRepository;
private readonly IBasicQuery<TrackContentMapEntity> _trackContentMapBasicQuery;
private readonly IBasicQuery<TrackTemplateContentEntity> _trackTemplateContentBasicQuery;
public TrackContentService(
IStringLocalizer<TrackLocalizationResource> stringLocalizer,
ITrackContentRepository trackContentRepository,
IBasicQuery<TrackContentMapEntity> trackContentMapBasicQuery
IBasicQuery<TrackContentMapEntity> trackContentMapBasicQuery,
IBasicQuery<TrackTemplateContentEntity> trackTemplateContentBasicQuery
)
{
this._stringLocalizer = stringLocalizer;
this._trackContentRepository = trackContentRepository;
this._trackContentMapBasicQuery=trackContentMapBasicQuery;
this._trackTemplateContentBasicQuery=trackTemplateContentBasicQuery;
}
/// <summary>
@ -161,6 +165,13 @@ namespace SJZY.Track.Application.Services.TrackContents
{
#region 业务校验
//查询是否关联了轨迹模版内容
var trackTemplateContents = await _trackTemplateContentBasicQuery.GetListAsync(a => a.TrackContentId==input.Id);
if (trackTemplateContents.Any())
{
throw new BusinessException(message: "该轨迹内容关联轨迹模板,禁止删除");
}
//需要加个判断,存在内容映射,则出提示
var trackContentMapEntities = await _trackContentMapBasicQuery.GetListAsync(a => a.TrackContentId==input.Id|| a.MapTrackContentId==input.Id);
if (trackContentMapEntities.Any())

View File

@ -0,0 +1,38 @@
using SJZY.Expand.ABP.Core;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Domain.Aggregates.TrackPushAggregate;
using SJZY.Track.Domain.Queries;
using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.Services.TrackPush;
public class TrackPushCacheService:ITrackPushCacheService, ITransientDependency
{
private readonly IBasicQuery<TrackPushConfigEntity> _trackPushConfigQuery;
private string RedisKey(int customId) => GlobalConst.TrackPushKeyPrefix + customId;
public TrackPushCacheService(IBasicQuery<TrackPushConfigEntity> trackPushConfigQuery)
{
_trackPushConfigQuery = trackPushConfigQuery;
}
public async Task<bool> IsPushAsync(int customId)
{
var isPush =
await RedisHelper.Client.GetAsync<bool?>(RedisKey(customId));
if (isPush == null)
{
isPush = await _trackPushConfigQuery.GetFirstAsync(x => x.CustomId == customId,
x => x.IsAutoPush);
await RedisHelper.Client.SetAsync(RedisKey(customId), isPush);
}
return (bool)isPush;
}
public async Task UpdateAsync(int customId, bool isPush)
{
await RedisHelper.Client.SetAsync(RedisKey(customId), isPush);
}
}

View File

@ -12,7 +12,7 @@ public class EditTrackContentMapInputValidation : AbstractValidator<EditTrackCon
{
RuleFor(x => x.Options).IsInEnum().WithMessage(stringLocalizer[SjzyBasicLocalization.Validation_IsInEnum, nameof(EditTrackContentMapInput.Options)]);
RuleFor(x => x.Id).GreaterThan(0).WithMessage(stringLocalizer[SjzyBasicLocalization.Validation_GreaterThan, nameof(EditTrackContentMapInput.Id), 0]);
//RuleFor(x => x.Id).GreaterThan(0).WithMessage(stringLocalizer[SjzyBasicLocalization.Validation_GreaterThan, nameof(EditTrackContentMapInput.Id), 0]);
RuleFor(x => x.MapSubSystemId).GreaterThan(0).WithMessage(stringLocalizer[SjzyBasicLocalization.Validation_GreaterThan, nameof(EditTrackContentMapInput.MapSubSystemId), 0]);
@ -22,10 +22,10 @@ public class EditTrackContentMapInputValidation : AbstractValidator<EditTrackCon
RuleFor(x => x.TrackContentId).GreaterThan(0).WithMessage(stringLocalizer[SjzyBasicLocalization.Validation_GreaterThan, nameof(EditTrackContentMapInput.TrackContentId), 0]);
When(x => x.Options != OptionTypeNoNull.新增, () =>
{
RuleFor(x => x.Id).NotNull().WithMessage(stringLocalizer[SjzyBasicLocalization.Validation_NotNull, nameof(EditTrackContentMapInput.Id)]);
});
//When(x => x.Options != OptionTypeNoNull.新增, () =>
//{
// RuleFor(x => x.Id).NotNull().WithMessage(stringLocalizer[SjzyBasicLocalization.Validation_NotNull, nameof(EditTrackContentMapInput.Id)]);
//});
When(x => x.Options != OptionTypeNoNull.删除, () =>
{

View File

@ -74,14 +74,14 @@ public class TrackDetailsIndex : IdBaseIndex
/// <summary>删除标记 0正常 1 已删除</summary>
public bool IsDeleted { get; set; }
/// <summary>创建人id</summary>
public int CreateUserId { get; set; }
/// <summary>创建人</summary>
public string? CreateUser { get; set; }
/// <summary>创建时间</summary>
public DateTime CreateTime { get; set; }
/// <summary>修改人id</summary>
public int? UpdateUserId { get; set; }
/// <summary>修改人</summary>
public string? UpdateUser { get; set; }
/// <summary>修改时间</summary>
public DateTime? UpdateTime { get; set; }

View File

@ -6,9 +6,29 @@ public class LinkedOrderVO
/// 关联订单id
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// 订单号
/// </summary>
public string OrderNo { get; set; } = null!;
/// <summary>
/// 订单类型(主数据枚举TrackOrderType)
/// </summary>
public int OrderType { get; set; }
/// <summary>
/// 子系统id
/// </summary>
public int SubSystemId { get; set; }
/// <summary>
/// 订单是否推送完成(异步维护)
/// </summary>
public bool IsPushCompleted { get; set; }
/// <summary>
/// 客户id
/// </summary>
public int CustomId { get; set; }
}

View File

@ -16,4 +16,9 @@ public class MatchTrackContentDto
/// 轨迹状态
/// </summary>
public string Status { get; set; } = null!;
/// <summary>
/// 是否意味着完成
/// </summary>
public bool IsCompleted { get; set; }
}

View File

@ -3,14 +3,24 @@
public class GlobalConst
{
/// <summary>
/// 轨迹明细es索引名称
/// 轨迹明细 es索引名称
/// </summary>
public const string TrackIndex = "sjzy_track_details";
/// <summary>
/// 轨迹新增/更新cap订阅名称
/// 轨迹新增/更新 mq订阅名称
/// </summary>
public const string TrackUpsertSubscribe = "SJZY.Track.Upsert";
public const string TrackUpsertSubscribe = "SJZY.Track.TrackUpsert";
/// <summary>
/// 轨迹新增/更新后触发更新订单轨迹 mq订阅名称
/// </summary>
public const string UpdateOrderTrackSubscribe = "SJZY.Track.UpdateOrderTrack";
/// <summary>
/// 轨迹要推送的客户redis key前缀
/// </summary>
public const string TrackPushKeyPrefix = "TrackPush:";
/// <summary>
/// 自动推送名单RedisKey

View File

@ -1,5 +1,6 @@
using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackContentAggregate;
using SJZY.Track.Domain.Aggregates.TrackFlagAggregate;
using SJZY.Track.Domain.Aggregates.TrackStatusAggregate;
using SJZY.Track.Domain.Aggregates.TrackTemplateAggregate;
using SJZY.Track.Domain.Dtos.Order;
@ -66,12 +67,14 @@ public class TrackQueries : ISugarRepository, ITrackQueries,
.LeftJoin<TrackTemplateContentEntity>((x, a) => x.TrackTemplateId == a.TrackTemplateId)
.LeftJoin<TrackContentEntity>((x, a, b) => a.TrackContentId == b.Id)
.LeftJoin<TrackStatusEntity>((x, a, b, c) => b.TrackStatusId == c.Id)
.LeftJoin<TrackFlagEntity>((x, a, b, c, d) => b.TrackFlagId == d.Id)
.Where((x, a, b) => x.OrderId == orderId && b.Code == code)
.Select((x, a, b, c) => new MatchTrackContentDto
.Select((x, a, b, c,d) => new MatchTrackContentDto
{
Content = b.Content,
EnContent = b.EnContent,
Status = c.Status
Status = c.Status,
IsCompleted = d.IsCompleted
})
.FirstAsync();
}

View File

@ -55,13 +55,18 @@ public class OrderRepository : BasicRepository<OrderEntity>, IOrderRepository, I
public async Task<List<LinkedOrderVO>> GetLinkedOrdersAsync(int orderId)
{
return await Context.Queryable<OrderEntity>()
.LeftJoin<OrderLinkEntity>((a, b) => a.Id == b.OrderId)
return await Context.Queryable<OrderLinkEntity>()
.LeftJoin<OrderLinkEntity>((a , b) => a.Id == b.OrderId)
.LeftJoin<OrderEntity>((a, b, c) => b.LinkOrderId == c.Id)
.Where(a => a.Id == orderId)
.Select((a, b) => new LinkedOrderVO
.Select((a, b,c) => new LinkedOrderVO
{
OrderId = b.LinkOrderId,
SubSystemId = a.SubSystemId
OrderId = c.Id,
OrderType = c.OrderType,
OrderNo = c.OrderNo,
SubSystemId = c.SubSystemId,
IsPushCompleted = c.IsPushCompleted,
CustomId = c.CustomId
})
.ToListAsync();
}