Merge branch 'refs/heads/S04轨迹预警' into test

This commit is contained in:
xiaolipro 2024-08-02 09:08:32 +08:00
commit ef102e9177
147 changed files with 5025 additions and 300 deletions

3
.gitignore vendored
View File

@ -1,5 +1,5 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
## files generated by popular Visual Studio add-ons..
# User-specific files
*.suo
@ -189,3 +189,4 @@ UpgradeLog*.htm
FakesAssemblies/
logs/
/.vs
.idea/

View File

@ -9,6 +9,7 @@ using SJZY.Expand.ABP.Core.Prometheus;
using SJZY.Expand.ABP.Core.Redis;
using SJZY.Expand.ABP.Core.Swagger;
using SJZY.Track.Application;
using SJZY.Track.Application.HostServices;
using SJZY.Track.Repository;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
@ -62,6 +63,7 @@ namespace SJZY.Track.Api
Configure<AbpAuditingOptions>(options => { options.IsEnabled = false; });
context.Services.AddRedis();
context.Services.AddHostedService<RedisHostService>();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)

View File

@ -5,7 +5,7 @@
"Apollo": {
"AppId": "SJZY.Track.Configuration",
"Env": "DEV",
"Namespace": "Test",
"Namespace": "DEV",
"MetaServer": "http://apollo-service-test-apollo-configservice.apollo.svc.cluster.local:8080",
"ConfigServer": [ "http://apollo-service-test-apollo-configservice.apollo.svc.cluster.local:8080" ]
}

View File

@ -0,0 +1,8 @@
namespace SJZY.Track.Application.Contract.Dtos.Common;
public class IdWithNameDto
{
public int Id { get; set; }
public string Name { get; set; }
}

View File

@ -0,0 +1,29 @@
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class AreaCacheEntry
{
/// <summary>
/// 国家或地区
/// </summary>
public string Country { get; set; } = null!;
/// <summary>
/// 省份
/// </summary>
public string? Province { get; set; }
/// <summary>
/// 城市
/// </summary>
public string? City { get; set; }
/// <summary>
/// 开始邮编
/// </summary>
public string? LeftPostcode { get; set; }
/// <summary>
/// 截止邮编
/// </summary>
public string? RightPostcode { get; set; }
}

View File

@ -0,0 +1,9 @@
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class ClearRulCacheEvent
{
public List<RuleAreaBaseInfo> Items { get; set; }
}

View File

@ -0,0 +1,42 @@
using SJZY.Track.Domain.Dtos.Forewarning;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class CreateWarningInput
{
/// <summary>
/// 模板名称
/// </summary>
public string Name { get; set; } = null!;
/// <summary>
/// 子系统id
/// </summary>
public int SubSystemId { get; set; }
/// <summary>
/// 客户id
/// </summary>
public int CustomerId { get; set; }
/// <summary>
/// 通知方式
/// </summary>
public NotifyMode NotifyMode { get; set; }
/// <summary>
/// 通知模板
/// </summary>
public string NotifyTemplate { get; set; } = null!;
/// <summary>
/// 轨迹状态跨度列表
/// </summary>
public List<WarningNitifyDto> TrackStatusSpans { get; set; } = new();
/// <summary>
/// 预警因素列表。
/// </summary>
public List<WarningFactorDto> WarningFactors { get; set; } = new();
}

View File

@ -0,0 +1,58 @@
using SJZY.Track.Domain.Dtos.Forewarning;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class EditWarningOutput
{
/// <summary>
/// 模板名称
/// </summary>
public string Name { get; set; } = null!;
/// <summary>
/// 子系统id
/// </summary>
public int SubSystemId { get; set; }
/// <summary>
/// 客户id
/// </summary>
public int CustomerId { get; set; }
/// <summary>
/// 通知方式
/// </summary>
public NotifyMode NotifyMode { get; set; }
/// <summary>
/// 通知模板
/// </summary>
public string NotifyTemplate { get; set; } = null!;
/// <summary>
/// 通知列表
/// </summary>
public List<WarningNotifyWithIdDto> TrackStatusSpans { get; set; } = new();
/// <summary>
/// 预警因素列表。
/// </summary>
public List<WarningFactorWithIdDto> WarningFactors { get; set; } = new();
}
public class WarningNotifyWithIdDto: WarningNitifyDto
{
/// <summary>
/// 通知id
/// </summary>
public int Id { get; set; }
}
public class WarningFactorWithIdDto : WarningFactorDto
{
/// <summary>
/// 因素id
/// </summary>
public int Id { get; set; }
}

View File

@ -0,0 +1,69 @@
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class ForewarningRuleCacheEntry
{
/// <summary>
/// 自增主键
/// </summary>
public int Id { get; set; }
/// <summary>
/// 预警id
/// </summary>
public int WarningId { get; set; }
/// <summary>
/// 时效(小时) e.g. 24
/// </summary>
public float TotalTimeSpan { get; set; }
/// <summary>
/// 提醒的时间(小时) e.g. 9 10 11..
/// </summary>
public float WarnTimeSpan { get; set; }
/// <summary>
/// 提醒次数 e.g. 3 2 1
/// </summary>
public int WarnCount { get; set; }
/// <summary>
/// 起始轨迹状态id
/// </summary>
public int StartTrackStatusId { get; set; }
/// <summary>
/// 终止轨迹状态id
/// </summary>
public int EndTrackStatusId { get; set; }
/// <summary>
/// 货物类型
/// </summary>
public string? CargoType { get; set; }
/// <summary>
/// 月份
/// </summary>
public List<int>? Months { get; set; }
/// <summary>
/// 区域
/// </summary>
public List<AreaCacheEntry>? Areas { get; set; }
/// <summary>
/// 是否中转
/// </summary>
public bool? IsTransit { get; set; }
/// <summary>
/// 起运港口
/// </summary>
public string? LeavePort { get; set; }
/// <summary>
/// 产品编码
/// </summary>
public string? ProductCode { get; set; }
}

View File

@ -0,0 +1,17 @@
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class ModifyTimeSpanInput
{
/// <summary>
/// 预警id
/// </summary>
public int WarningId { get; set; }
/// <summary>
/// 规则详情中的时效相关信息
/// </summary>
public List<WarningTimeSpanDto> Items { get; set; } = new();
}

View File

@ -0,0 +1,45 @@
using SJZY.Track.Domain.Dtos.Forewarning;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class UpdateWarningInput
{
/// <summary>
/// 预警id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 模板名称
/// </summary>
public string Name { get; set; } = null!;
/// <summary>
/// 通知方式
/// </summary>
public NotifyMode NotifyMode { get; set; }
/// <summary>
/// 通知模板
/// </summary>
public string NotifyTemplate { get; set; } = null!;
/// <summary>
/// 通知列表
/// </summary>
public List<NotifyDto> Notifies { get; set; } = new();
}
public class NotifyDto
{
/// <summary>
/// 通知id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 通知人id
/// </summary>
public int NotifyUserId { get; set; }
}

View File

@ -0,0 +1,17 @@
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class WarningDto
{
/// <summary>
/// 当前轨迹状态
/// </summary>
public int TrackStatusId { get; set; }
public int SubSystemId { get; set; }
public int CustomerId { get; set; }
public List<WarningOrderDto> Orders { get; set; } = new();
}

View File

@ -0,0 +1,32 @@
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class WarningNotifyCacheEntry
{
/// <summary>
/// 规则id
/// </summary>
public int RuleId { get; set; }
/// <summary>
/// 订单id
/// </summary>
public int OrderId { get; set; }
public WarningNotifyCacheEntry(int ruleId, int orderId)
{
RuleId = ruleId;
OrderId = orderId;
}
public WarningNotifyCacheEntry(string notifyValue)
{
var values = notifyValue.Split(':');
RuleId = int.Parse(values[0]);
OrderId = int.Parse(values[1]);
}
public override string ToString()
{
return $"{RuleId}:{OrderId}";
}
}

View File

@ -0,0 +1,47 @@
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class WarningOrderDto
{
/// <summary>
/// 订单id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 轨迹发生时间
/// </summary>
public DateTime TrackTime { get; set; }
public string? CargoType { get; set; }
public int Month { get; set; }
public bool? IsTransit { get; set; }
/// <summary>
/// 起运港
/// </summary>
public string? LeavePort { get; set; }
public string? ProductCode { get; set; }
/// <summary>
/// 目的城市
/// </summary>
public string ArrivalCity { get; set; } = null!;
/// <summary>
/// 目的国家
/// </summary>
public string ArrivalCountry { get; set; } = null!;
/// <summary>
/// 到达邮编
/// </summary>
public string? ArrivalPostCode { get; set; }
/// <summary>
/// 到达州省
/// </summary>
public string? ArrivalProvince { get; set; }
}

View File

@ -0,0 +1,27 @@
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
/// <summary>
/// 预警规则时效
/// </summary>
public class WarningTimeSpanDto
{
/// <summary>
/// 预警规则id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 时效
/// </summary>
public float TotalTimeSpan { get; set; }
/// <summary>
/// 提醒时效
/// </summary>
public float WarnTimeSpan { get; set; }
/// <summary>
/// 提醒次数
/// </summary>
public int WarnCount { get; set; }
}

View File

@ -51,4 +51,19 @@ public class UpdateOrderInput : OrderNoInfo
/// 订单航班信息
/// </summary>
public List<OrderFlightInfo> FlightInfos { get; set; } = new();
/// <summary>
/// 货物类型
/// </summary>
public string? CargoType { get; set; }
/// <summary>
/// 是否转运
/// </summary>
public bool? IsTransfer { get; set; }
/// <summary>
/// 产品编码
/// </summary>
public string? ProductCode { get; set; }
}

View File

@ -0,0 +1,29 @@
namespace SJZY.Track.Application.Contract.Dtos.TrackNode;
public class EditTrackNodeInput
{
/// <summary>
/// 操作类型
/// </summary>
public OptionType Options { get; set; }
/// <summary>
/// 轨迹节点id,创建不用填
/// </summary>
public long Id { get; set; }
/// <summary>
/// 系统id
/// </summary>
public int? SubSystemId { get; set; }
/// <summary>
/// 节点名称
/// </summary>
public string? NodeName { get; set; }
/// <summary>
/// 节点英文名称
/// </summary>
public string? NodeEnName { get; set; }
}

View File

@ -70,7 +70,11 @@ public class EditTrackPullConfigInput
public string? OrderPath { get; set; }
/// <summary>
/// 轨迹明细路径
/// </summary>
public string? TrackDetailsPath { get; set; }
/// <summary>
/// 轨迹内容code
/// </summary>
public string? TrackContentCode { get; set; }
}

View File

@ -52,8 +52,14 @@ public class EditTrackPullContentInput
/// <summary>
/// 轨迹时区GMT
/// </summary>
[Obsolete]
public double? TrackTimeZone { get; set; }
/// <summary>
/// 兜底轨迹时区发生地
/// </summary>
public int? TrackTimePlaceValue { get; set; }
/// <summary>
/// 是否完结
/// </summary>

View File

@ -37,13 +37,13 @@ public class TrackPullContentConfigDto
/// </summary>
public int? TrackPlaceValue { get; set; }
/// <summary>
/// 轨迹时区GMT值
/// </summary>
public float? TrackTimeZoneValue { get; set; }
/// <summary>
/// 订单是否完成(由轨迹刷新配置决定)
/// </summary>
public bool IsCompleted { get; set; }
/// <summary>
/// 轨迹时区发生地参数值
/// </summary>
public int? TrackZonePlaceValue { get; set; }
}

View File

@ -46,4 +46,9 @@ public class TrackPullParamConfigDto
/// 轨迹明细数组路径
/// </summary>
public string TrackDetailsPath { get; set; } = null!;
/// <summary>
/// 轨迹内容代码
/// </summary>
public string? TrackContentCode { get; set; }
}

View File

@ -1,4 +1,6 @@
namespace SJZY.Track.Application.Contract.Dtos.TrackPush;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Application.Contract.Dtos.TrackPush;
public class EditTrackPushConfigInput
{
@ -48,4 +50,19 @@ public class EditTrackPushConfigInput
/// 是否全量推送
/// </summary>
public bool? IsAllPush { get; set; }
/// <summary>
/// 时间格式枚举
/// </summary>
public DateTimeFormatEnum DateTimeFormatType { get; set; }
/// <summary>
/// 日期时间格式模板
/// </summary>
public string? DateTimeFormat { get; set; }
/// <summary>
/// 时区格式格式模板
/// </summary>
public string? TimezoneFormat { get; set; }
}

View File

@ -1,4 +1,6 @@
namespace SJZY.Track.Application.Contract.Dtos.TrackPush;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Application.Contract.Dtos.TrackPush;
public class TrackPushContentConfigDto
{
@ -71,4 +73,19 @@ public class TrackPushContentConfigDto
/// 扩展字段参数值
/// </summary>
public string? ExpandFieldsValue { get; set; }
/// <summary>
/// 时间格式枚举
/// </summary>
public DateTimeFormatEnum DateTimeFormatType { get; set; }
/// <summary>
/// 日期时间格式模板
/// </summary>
public string? DateTimeFormat { get; set; }
/// <summary>
/// 时区格式格式模板
/// </summary>
public string? TimezoneFormat { get; set; }
}

View File

@ -21,7 +21,7 @@ public class TrackUpdateInput
public class TrackItem
{
public string Location { get; set; } = null!;
public DateTime Date { get; set; }
public string Date { get; set; }
public double TimeZone { get; set; }
public string? Description { get; set; }
public string? DescriptionCn { get; set; }

View File

@ -36,5 +36,10 @@
/// 轨迹内容英文
/// </summary>
public string EntrackStatus { get; set; }
/// <summary>
/// 节点名称
/// </summary>
public string? NodeName { get; set; }
}
}

View File

@ -0,0 +1,14 @@
namespace SJZY.Track.Application.Contract.Dtos.TrackStatus;
public class QueryTrackNodeRequest:PageInfoInput
{
/// <summary>
/// 关键字,节点中文和英文
/// </summary>
public string? KeyWord { get; set; }
/// <summary>
/// 子系统id
/// </summary>
public long? SystemId { get; set; }
}

View File

@ -0,0 +1,23 @@
namespace SJZY.Track.Application.Contract.Events;
/// <summary>
/// redis 的发布订阅事件
/// </summary>
public class ModifyWarningEvent:RedisEvent
{
public string Key { get; set; }
}
public class RedisEvent : IRedisEvent
{
public string EventType { get; set; }
}
/// <summary>
/// 简单的事件接口,后续可以根据需要扩展
/// </summary>
public interface IRedisEvent
{
string EventType { get; set; }
}

View File

@ -5,6 +5,4 @@ public struct OrderIdentifier
public int OrderId { get; set; }
public string OrderNo { get; set; }
public int OrderType { get; set; }
public int CustomId { get; set; }
}

View File

@ -2,6 +2,16 @@
public class TrackChangeAfterUpdateOrderEvent
{
/// <summary>
/// 子系统id
/// </summary>
public int SubSystemId { get; set; }
/// <summary>
/// 客户id
/// </summary>
public int CustomId { get; set; }
/// <summary>
/// 订单Id
/// </summary>
@ -22,6 +32,11 @@ public class TrackChangeAfterUpdateOrderEvent
/// </summary>
public string TrackEnContent { get; set; } = null!;
/// <summary>
/// 轨迹状态Id
/// </summary>
public int TrackStatusId { get; set; }
/// <summary>
/// 轨迹发生地
/// </summary>
@ -36,4 +51,14 @@ public class TrackChangeAfterUpdateOrderEvent
/// 轨迹时区
/// </summary>
public float TrackTimeZone { get; set; }
/// <summary>
/// 轨迹内容代码
/// </summary>
public string? TrackCode { get; set; }
/// <summary>
/// 时区缺失和发生地缺失标记,1表示时区,2表示发生地,3表示两种
/// </summary>
public int DeficiencyTagValue { get; set; }
}

View File

@ -3,10 +3,15 @@
public class TrackUpsertEvent
{
/// <summary>
/// 子系统id(操作的订单都是相同子系统下的)
/// 子系统id
/// </summary>
public int SubSystemId { get; set; }
/// <summary>
/// 客户id
/// </summary>
public int CustomId { get; set; }
/// <summary>
/// 订单id
/// </summary>
@ -48,4 +53,9 @@ public class TrackUpsertEvent
public List<int> Visited { get; set; } = new();
public TrackRefreshStruct TrackRefreshData { get; set; }
/// <summary>
/// 时区缺失和发生地缺失标记,1表示时区,2表示发生地,3表示两种
/// </summary>
public int DeficiencyTagValue { get; set; }
}

View File

@ -12,5 +12,8 @@
<PackageReference Include="SJZY.Expand.ABP.Common" Version="7.0.11" />
<PackageReference Include="Volo.Abp.Core" Version="7.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SJZY.Track.Domain\SJZY.Track.Domain.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,42 @@
using SJZY.Track.Application.Contract.Dtos.Forewarning;
namespace SJZY.Track.Application.Contract.Services;
public interface IForewarningCacheService
{
/// <summary>
/// 获取预警规则,如果没有则返回null
/// </summary>
/// <param name="subSystemId">子系统id</param>
/// <param name="customerId">客户id</param>
/// <returns></returns>
Task<List<ForewarningRuleCacheEntry>?> GetRulesAsync(int subSystemId, int customerId);
/// <summary>
/// 删除规则
/// </summary>
/// <param name="subSystemId"></param>
/// <param name="customerId"></param>
/// <returns></returns>
Task DeleteRulesAsync(int subSystemId, int customerId);
/// <summary>
/// 重置本地缓存
/// </summary>
/// <returns></returns>
Task Clear(ClearRulCacheEvent @event);
/// <summary>
/// 清除redis 缓存
/// </summary>
/// <returns></returns>
Task ClearRedisByAreaId(int areaId);
/// <summary>
/// 清除redis 缓存
/// </summary>
/// <param name="subSystemId">子系统id</param>
/// <param name="customerId">客户id</param>
/// <returns></returns>
Task ClearRedisBySystemIdAndCustomer(int subSystemId, int customerId);
}

View File

@ -0,0 +1,23 @@
using SJZY.Track.Application.Contract.Dtos.Forewarning;
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Application.Contract.Services;
/// <summary>
/// 预警通知服务接口定义。
/// 提供创建、删除预警通知和发送通知的功能。
/// </summary>
public interface IForewarningNotifyService
{
/// <summary>
/// 创建预警通知。
/// </summary>
/// <param name="order">预警订单模型。</param>
/// <returns>创建操作的结果,以整数形式返回。</returns>
Task WarningCalculateAsync(WarningDto order);
/// <summary>
/// 发送通知。
/// </summary>
Task SendNotificationAsync();
}

View File

@ -0,0 +1,6 @@
namespace SJZY.Track.Application.Contract.Services;
public interface IKpiAreaCacheService
{
}

View File

@ -0,0 +1,39 @@
using AutoMapper;
using SJZY.Track.Application.Contract.Dtos.Forewarning;
using SJZY.Track.Domain.Aggregates.ForewarningAggregate;
using SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
using SJZY.Track.Domain.Dtos.Forewarning;
using Volo.Abp.AutoMapper;
namespace SJZY.Track.Application.AutoMapper;
public class ForewarningMapperProfile :Profile
{
public ForewarningMapperProfile()
{
CreateMap<CreateWarningInput, WarningEntity>(MemberList.None)
.Ignore(x => x.WarningFactors);
CreateMap<WarningTimeSpanDto, WarningRuleEntity>(MemberList.None);
CreateMap<UpdateRuleAreaRequest, RuleAreaEntity>(MemberList.None);
CreateMap<AddRuleAreaInfoRequest, RuleAreaInfoEntity>(MemberList.None)
.ForMember(dest => dest.CountryCode, opt => opt.MapFrom(src => src.CountryCode))
.ForMember(dest => dest.CountryName, opt => opt.MapFrom(src => src.CountryName))
.ForMember(dest => dest.Province, opt => opt.MapFrom(src => src.Province))
.ForMember(dest => dest.City, opt => opt.MapFrom(src => src.City))
.ForMember(dest => dest.LeftPostcode, opt => opt.MapFrom(src => src.LeftPostCode))
.ForMember(dest => dest.RightPostcode, opt => opt.MapFrom(src => src.RightPostCode))
.ForMember(dest => dest.AreaId, opt => opt.MapFrom(src => src.AreaId));
CreateMap<CreateRuleAreaRequest, RuleAreaEntity>(MemberList.None)
.ForMember(dest => dest.SubSystemId, opt => opt.MapFrom(src => src.SystemId))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name));
CreateMap<UpdateRuleAreaInfoRequest, RuleAreaInfoEntity>(MemberList.None)
.ForMember(dest => dest.CountryCode, opt => opt.MapFrom(src => src.CountryCode))
.ForMember(dest => dest.CountryName, opt => opt.MapFrom(src => src.CountryName))
.ForMember(dest => dest.Province, opt => opt.MapFrom(src => src.Province))
.ForMember(dest => dest.City, opt => opt.MapFrom(src => src.City))
.ForMember(dest => dest.LeftPostcode, opt => opt.MapFrom(src => src.LeftPostCode))
.ForMember(dest => dest.RightPostcode, opt => opt.MapFrom(src => src.RightPostCode));
}
}

View File

@ -0,0 +1,20 @@
using AutoMapper;
using SJZY.Track.Application.Contract.Dtos.TrackNode;
using SJZY.Track.Application.Contract.Dtos.TrackStatus;
using SJZY.Track.Domain.Aggregates.TrackNodeAggregate;
using SJZY.Track.Domain.Dtos.TrackNode;
namespace SJZY.Track.Application.AutoMapper;
public class TrackNodeMapperProfile :Profile
{
public TrackNodeMapperProfile()
{
CreateMap<QueryTrackNodeRequest, TrackNodePageDto>();
CreateMap<EditTrackNodeInput, TrackNodeEntity>(MemberList.None)
.ForMember(val => val.NodeName, opt => opt.MapFrom(src => src.NodeName))
.ForMember(val => val.NodeEnName, opt => opt.MapFrom(src => src.NodeEnName))
.ForMember(val => val.SubSystemId, opt => opt.MapFrom(src => src.SubSystemId));
}
}

View File

@ -15,6 +15,7 @@ public class TrackPullAutoMapperProfile : Profile
.ForMember(dest => dest.IsPush, opt => opt.MapFrom(source => false));
CreateMap<EditTrackPullContentInput, TrackPullContentConfigEntity>(MemberList.None)
.ForMember(dest => dest.TrackKeyword, opt => opt.MapFrom(src => string.Join(',', src.TrackKeywords)))
.ForMember(dest => dest.TrackZonePlaceValue, opt => opt.MapFrom(src => string.Join(',', src.TrackTimePlaceValue)))
.ForMember(dest => dest.TrackKeywordReverse, opt => opt.MapFrom(src => string.Join(',', src.TrackKeywordReverses)));
CreateMap<GetTrackPullConfigDetailOutputVO, TrackPullParamConfigDto>(MemberList.None);
CreateMap<GetTrackPullContentListCacheVO, TrackPullContentConfigDto>(MemberList.None);

View File

@ -1,8 +1,11 @@
using DotNetCore.CAP;
using SJZY.Track.Application.Contract.Dtos.Forewarning;
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.Consts;
using SJZY.Track.Domain.Dtos.Order;
using SJZY.Track.Domain.Queries;
using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.CAP.EventHandlers;
@ -10,33 +13,28 @@ namespace SJZY.Track.Application.CAP.EventHandlers;
public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICapSubscribe
{
private readonly IOrderRepository _orderRepository;
private readonly ITrackDetailsRepository _trackDetailsRepository;
private readonly IForewarningNotifyService _forewarningNotifyService;
private readonly ITrackPullCacheService _trackPullCacheService;
private readonly IProvideQueries _provideQueries;
public TrackChangeAfterUpdateOrderEventHandler(IOrderRepository orderRepository,
ITrackDetailsRepository trackDetailsRepository)
IForewarningNotifyService forewarningNotifyService, ITrackPullCacheService trackPullCacheService, IProvideQueries provideQueries)
{
_orderRepository = orderRepository;
_trackDetailsRepository = trackDetailsRepository;
_forewarningNotifyService = forewarningNotifyService;
_trackPullCacheService = trackPullCacheService;
_provideQueries = provideQueries;
}
[CapSubscribe(GlobalConst.TrackChangeAfterUpdateOrderSubscribe)]
public async Task ConsumerAsync(TrackChangeAfterUpdateOrderEvent @event)
{
var orders = await _orderRepository.GetListAsync(x => @event.OrderIds.Contains(x.Id),
x => new OrderEntity
{
Id = x.Id,
IsCompleted = x.IsCompleted,
LatestTrackContent = x.LatestTrackContent,
LatestTrackEnContent = x.LatestTrackEnContent,
LatestTrackPlace = x.LatestTrackPlace,
LatestTrackTime = x.LatestTrackTime,
LatestTrackTimeZone = x.LatestTrackTimeZone,
});
var orders = await GetOrdersAsync(@event.OrderIds);
if (orders.Count == 0) return;
var updateOrders = new List<OrderEntity>();
var providerDic= await GetProviderDic(@event.OrderIds);
foreach (var order in orders)
{
if (order.LatestTrackContent != @event.TrackContent)
@ -49,11 +47,28 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa
}
}
// 执行轨迹兜底时 时区缺失和发生地缺失标记,1表示时区,2表示发生地,3表示两种
if (@event.DeficiencyTagValue > 0 && @event.DeficiencyTagValue<=3)
{
order.DeficiencyTag = @event.DeficiencyTagValue;
}
if (!order.IsCompleted)
{
order.IsCompleted = @event.IsCompleted;
}
if (!string.IsNullOrEmpty(@event.TrackCode) && providerDic.TryGetValue(order.Id, out var provider))
{
var config = await _trackPullCacheService.GetPullParamWithContentConfigAsync(provider.SubSystemId, provider.ServiceProviderId);
if (!string.IsNullOrEmpty(config?.ParamConfig.TrackContentCode) && config.ParamConfig.TrackContentCode!.ToLower() == @event.TrackCode.ToLower())
{
// 触发拉取节点
order.IsTriggerPull = 1;
}
}
order.LatestTrackContent = @event.TrackContent;
order.LatestTrackEnContent = @event.TrackEnContent;
order.LatestTrackPlace = @event.TrackPlace;
@ -65,7 +80,75 @@ public class TrackChangeAfterUpdateOrderEventHandler : ITransientDependency, ICa
await _orderRepository.UpdateRangeAsync(updateOrders, x => new
{
x.IsCompleted, x.LatestTrackContent, x.LatestTrackEnContent, x.LatestTrackPlace, x.LatestTrackTime,
x.LatestTrackTimeZone
x.LatestTrackTimeZone,x.IsTriggerPull,x.DeficiencyTag
});
// 预警计算
await _forewarningNotifyService.WarningCalculateAsync(BuiltWarningDto(orders, @event));
}
private async Task<List<OrderEntity>> GetOrdersAsync(int[] orderIds)
{
return await _orderRepository.GetListAsync(order => orderIds.Contains(order.Id), order => new OrderEntity
{
Id = order.Id,
// 轨迹
IsCompleted = order.IsCompleted,
LatestTrackContent = order.LatestTrackContent,
LatestTrackEnContent = order.LatestTrackEnContent,
LatestTrackPlace = order.LatestTrackPlace,
LatestTrackTime = order.LatestTrackTime,
LatestTrackTimeZone = order.LatestTrackTimeZone,
// 因素
CargoType = order.CargoType,
CreateTime = order.CreateTime,
ArrivalCountry = order.ArrivalCountry,
ArrivalProvince = order.ArrivalProvince,
ArrivalCity = order.ArrivalCity,
ArrivalPostCode = order.ArrivalPostCode,
IsTransit = order.IsTransit,
LeavePort = order.LeavePort,
ProductCode = order.ProductCode
});
}
private WarningDto BuiltWarningDto(List<OrderEntity> orders, TrackChangeAfterUpdateOrderEvent @event)
{
return new WarningDto
{
TrackStatusId = @event.TrackStatusId,
SubSystemId = @event.SubSystemId,
CustomerId = @event.CustomId,
Orders = orders.Select(order => new WarningOrderDto
{
Id = order.Id,
TrackTime = @event.TrackTime,
CargoType = order.CargoType,
Month = order.CreateTime.Month,
IsTransit = order.IsTransit,
LeavePort = order.LeavePort,
ProductCode = order.ProductCode,
ArrivalCountry = order.ArrivalCountry,
ArrivalProvince = order.ArrivalProvince,
ArrivalCity = order.ArrivalCity,
ArrivalPostCode = order.ArrivalPostCode,
}).ToList()
};
}
/// <summary>
/// 获取服务商字典
/// </summary>
/// <param name="orderIds"></param>
/// <returns></returns>
private async Task<Dictionary<int, OrderTransportDto>> GetProviderDic(int[] orderIds)
{
var providerList = await _provideQueries.FindOrderWithServiceProviderAsync(orderIds);
return providerList.GroupBy(val => val.OrderId)
.ToDictionary(val => val.Key, val => val.First());
}
}

View File

@ -26,7 +26,7 @@ public class TrackPushCompletedEventHandler : ITransientDependency, ICapSubscrib
[CapSubscribe(GlobalConst.TrackPushCompletedSubscribe, Group = GlobalConst.TrackPushSubscribe)]
public async Task ConsumerAsync(InterfaceRelayForwardReplayEvent @event)
{
string logStr = null;
string? logStr = null;
var optionType = Base64Helper.DeSerialize<string>(@event.Attach);
if (optionType == OptionType.新增.ToString())
{

View File

@ -1,4 +1,5 @@
using DotNetCore.CAP;
using System.Globalization;
using DotNetCore.CAP;
using Newtonsoft.Json;
using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.InterfaceRelay.Application.Contracts;
@ -23,8 +24,8 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
private readonly ITrackPushCacheService _trackPushCacheService;
private readonly ITrackDetailsRepository _trackDetailsRepository;
private readonly ITrackPushRecordsRepository _trackPushRecordsRepository;
private List<TrackPushContentConfigDto> _pushConfigs;
private List<OrderEntity> _orders;
private List<TrackPushContentConfigDto> _pushConfigs = null!;
private List<OrderEntity> _orders = null!;
public TrackPushEventHandler(IHttpClientProxy<IInterfaceForwardServiceV2> httpClientProxy,
IOrderRepository orderRepository, ITrackPushCacheService trackPushCacheService,
@ -112,7 +113,6 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
var tracks = await _trackDetailsRepository.GetListAsync(@event.OrderIds, @event.TrackCode);
tracks = tracks.DistinctBy(x => x.OrderId).ToList(); // 防止重复推送
if (tracks.Count == 0) return; // 一个能推的轨迹都不存在
// tips:人工推送只要能推就推了,不会校验已推和顺序问题,因为可能是更新
@ -202,12 +202,14 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
private TrackUpdateInput BuiltUpdateItem(TrackPushContentConfigDto config, TrackPushRecordIndex log)
{
var trackDatetime = log.TrackTime.AddHours(config.TrackTimeZone ?? log.TrackTimeZone - log.TrackTimeZone);
var trackDateStr = DateTimeFormat(trackDatetime,config.TrackTimeZone ?? log.TrackTimeZone,config);
var track = new TrackItem
{
// 推真实的发生地和发生时间(时间是计算后的)
Location = log.TrackPlace,
Date = log.TrackTime.AddHours(
config.TrackTimeZone ?? log.TrackTimeZone - log.TrackTimeZone),
Date = trackDateStr,
TimeZone = config.TrackTimeZone ?? log.TrackTimeZone,
Description = config.TrackEnContentValue,
DescriptionCn = config.TrackContentValue,
@ -228,12 +230,14 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
private async Task<TrackPushInput> BuiltPushItem(TrackPushContentConfigDto config, TrackPushRecordIndex log)
{
var trackDatetime = log.TrackTime.AddHours(config.TrackTimeZone ?? log.TrackTimeZone - log.TrackTimeZone);
var trackDateStr = DateTimeFormat(trackDatetime,config.TrackTimeZone ?? log.TrackTimeZone,config);
var track = new TrackItem
{
// 推真实的发生地和发生时间(时间是计算后的)
Location = log.TrackPlace,
Date = log.TrackTime.AddHours(
config.TrackTimeZone ?? log.TrackTimeZone - log.TrackTimeZone),
Date = trackDateStr,
TimeZone = config.TrackTimeZone ?? log.TrackTimeZone,
Description = config.TrackEnContentValue,
DescriptionCn = config.TrackContentValue,
@ -287,4 +291,28 @@ public class TrackPushEventHandler : ITransientDependency, ICapSubscribe
return matched != null && matched.TrackCode == trackCode ? matched : default;
}
/// <summary>
/// 转换推送时间格式
/// </summary>
/// <param name="dateTime"></param>
/// <param name="timezone"></param>
/// <param name="config"></param>
/// <returns></returns>
private string DateTimeFormat(DateTime dateTime,double timezone, TrackPushContentConfigDto config)
{
if (config.DateTimeFormatType == DateTimeFormatEnum.Timestamp)
return DateTimeHelper.ConvertToUnixTimestamp(dateTime).ToString();
if (config.DateTimeFormatType == DateTimeFormatEnum.DateTimeAndTimezone
&& !string.IsNullOrEmpty(config.DateTimeFormat)
&& !string.IsNullOrEmpty(config.TimezoneFormat))
{
var zoneStr = DateTimeHelper.TimezoneFormat(timezone, config.TimezoneFormat);
var datetimeStr = DateTimeHelper.FormatDateTime(dateTime, config.DateTimeFormat);
return $"{datetimeStr} {zoneStr}";
}
return dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffK");
}
}

View File

@ -12,6 +12,7 @@ using SJZY.Track.Domain.Aggregates.OrderAggregate;
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
using SJZY.Track.Domain.Consts;
using SJZY.Track.Domain.Enums;
using SJZY.Track.Domain.Queries;
using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.CAP.EventHandlers;
@ -22,15 +23,17 @@ public class TrackRefreshEventHandler : ITransientDependency, ICapSubscribe
private readonly ICapPublisher _capPublisher;
private readonly ITrackPullCacheService _trackPullCacheService;
private readonly ITrackDetailsRepository _trackDetailsRepository;
private readonly ITimezoneQueries _timezoneQueries;
public TrackRefreshEventHandler(IOrderRepository orderRepository,
ICapPublisher capPublisher, ITrackPullCacheService trackPullCacheService,
ITrackDetailsRepository trackDetailsRepository)
ITrackDetailsRepository trackDetailsRepository, ITimezoneQueries timezoneQueries)
{
_orderRepository = orderRepository;
_capPublisher = capPublisher;
_trackPullCacheService = trackPullCacheService;
_trackDetailsRepository = trackDetailsRepository;
_timezoneQueries = timezoneQueries;
}
[CapSubscribe(GlobalConst.TrackRefreshSubscribe, Group = GlobalConst.TrackRefreshSubscribe)]
@ -172,21 +175,12 @@ public class TrackRefreshEventHandler : ITransientDependency, ICapSubscribe
var trackPlace = paramConfig.TrackPlaceName == null
? null
: trackToken[paramConfig.TrackPlaceName]?.Value<string>();
if (trackPlace.IsNullOrWhiteSpace())
var hasPlaceBottomLine = false; //执行了兜底但是没有兜底住,需要记录
if (trackPlace.IsNullOrWhiteSpace() && matchedContentConfig?.TrackPlaceValue != null && matchedContentConfig.TrackPlaceValue.HasValue)
{
Expression<Func<OrderEntity, string?>> resultExpression =
(TrackLocationValue?)matchedContentConfig?.TrackPlaceValue switch
{
TrackLocationValue.ArrivalPort => x => x.ArrivalPort,
TrackLocationValue.ArriveCity => x => x.ArrivalCity,
TrackLocationValue.ArrivalCountry => x => x.ArrivalCountry,
TrackLocationValue.LeavePort => x => x.LeavePort,
TrackLocationValue.LeaveCity => x => x.LeaveCity,
TrackLocationValue.LeaveCountry => x => x.LeaveCountry,
_ => x => x.LeaveCountry
};
trackPlace = await _orderRepository.GetFirstAsync(x =>
x.Id == context.OrderId, resultExpression);
//兜底没配置的就不需要兜底,拉取的发生地没就没
trackPlace = await GetPlaceByOrder(matchedContentConfig!.TrackPlaceValue.Value,context.OrderId);
hasPlaceBottomLine = string.IsNullOrWhiteSpace(trackPlace);
}
// 轨迹发生时间
@ -198,6 +192,17 @@ public class TrackRefreshEventHandler : ITransientDependency, ICapSubscribe
? null
: trackToken[paramConfig.TrackTimeZoneName]?.Value<float>();
//兜底时区
var hasTimezoneBottomLine = false; //执行了兜底但是没有兜底住,需要记录
if (!trackTimeZone.HasValue && matchedContentConfig?.TrackZonePlaceValue != null && matchedContentConfig.TrackZonePlaceValue.HasValue)
{
//兜底没配置的就不需要兜底,拉取的发生地没就没
var timezone= await GetTimezone(matchedContentConfig,context.OrderId);
trackTimeZone = timezone;
hasTimezoneBottomLine = !timezone.HasValue;
}
if (matchedContentConfig is null)
{
var notMappedEvent = new TrackInsertForNotMappedEvent
@ -209,7 +214,7 @@ public class TrackRefreshEventHandler : ITransientDependency, ICapSubscribe
OriginTrackData = JsonConvert.SerializeObject(trackToken),
TrackPlace = trackPlace ?? GlobalConst.EmptyString,
TrackTime = trackTime ?? DateTime.Now,
TrackTimeZone = trackTimeZone ?? matchedContentConfig?.TrackTimeZoneValue ?? 8
TrackTimeZone = trackTimeZone ?? 8
};
if (paramConfig.TrackContentName != null)
{
@ -220,6 +225,16 @@ public class TrackRefreshEventHandler : ITransientDependency, ICapSubscribe
return;
}
int deficiencyTagValue = 0;
// 触发了兜底,但是没有兜底住,对订单进行标记
if (hasPlaceBottomLine || hasTimezoneBottomLine)
{
//1表示时区,2表示发生地,3表示两种
var placeTagValue = hasPlaceBottomLine ? 2 : 0;
var timezoneTagValue = hasTimezoneBottomLine ? 1 : 0;
deficiencyTagValue = placeTagValue + timezoneTagValue;
}
// 新增/更新轨迹
var trackUpsertEvent = new TrackUpsertEvent
{
@ -230,9 +245,9 @@ public class TrackRefreshEventHandler : ITransientDependency, ICapSubscribe
OrderId = context.OrderId,
OrderNo = context.OrderNo,
OrderType = context.OrderType,
CustomId = context.CustomId
}
},
TrackRefreshData = new TrackRefreshStruct
{
ServiceProviderName = context.ServiceProviderName,
@ -246,11 +261,61 @@ public class TrackRefreshEventHandler : ITransientDependency, ICapSubscribe
TrackCode = matchedContentConfig.TrackContentCode,
TrackPlace = trackPlace ?? GlobalConst.EmptyString,
TrackTime = trackTime ?? DateTime.Now,
TrackTimeZone = trackTimeZone ?? matchedContentConfig.TrackTimeZoneValue ?? 8
TrackTimeZone = trackTimeZone ?? 8,
DeficiencyTagValue = deficiencyTagValue
};
trackUpsertEvent.IsAdd =
!await _trackDetailsRepository.AnyByCodeAsync(context.OrderId, matchedContentConfig.TrackContentCode);
await _capPublisher.PublishAsync(GlobalConst.TrackUpsertForRefreshSubscribe, trackUpsertEvent);
}
/// <summary>
/// 根据订单id获取兜底位置
/// </summary>
/// <param name="trackValue"></param>
/// <param name="orderId"></param>
/// <returns></returns>
private Task<string?> GetPlaceByOrder(int trackValue,int orderId)
{
Expression<Func<OrderEntity, string?>> resultExpression =
(TrackLocationValue)trackValue switch
{
TrackLocationValue.ArrivalPort => x => x.ArrivalPort,
TrackLocationValue.ArriveCity => x => x.ArrivalCity,
TrackLocationValue.ArrivalCountry => x => x.ArrivalCountry,
TrackLocationValue.LeavePort => x => x.LeavePort,
TrackLocationValue.LeaveCity => x => x.LeaveCity,
TrackLocationValue.LeaveCountry => x => x.LeaveCountry,
_ => x => x.LeaveCountry
};
return _orderRepository.GetFirstAsync(x => x.Id == orderId, resultExpression);
}
/// <summary>
/// 获取时区
/// </summary>
/// <param name="matchedContentConfig"></param>
/// <param name="orderId"></param>
/// <returns></returns>
private async Task<float?> GetTimezone(TrackPullContentConfigDto matchedContentConfig,int orderId )
{
var placeOrCode = await GetPlaceByOrder(matchedContentConfig!.TrackZonePlaceValue.Value,orderId);
string timezone = null;
if (!string.IsNullOrEmpty(placeOrCode))
{
if (matchedContentConfig!.TrackZonePlaceValue.Value == (int)TrackLocationValue.ArrivalPort
|| matchedContentConfig!.TrackZonePlaceValue.Value == (int)TrackLocationValue.LeavePort)
{
timezone = await _timezoneQueries.GetPortTimezone(placeOrCode);
return DateTimeHelper.ConvertTimeZoneToFloat(timezone);
}
timezone = await _timezoneQueries.GetCityTimezone(placeOrCode);
return DateTimeHelper.ConvertTimeZoneToFloat(timezone);
}
return null;
}
}

View File

@ -2,11 +2,14 @@
using SJZY.Expand.ABP.Core.Logger;
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.Consts;
using SJZY.Track.Domain.Dtos.Order;
using SJZY.Track.Domain.Queries;
using Volo.Abp.DependencyInjection;
using OrderIdentifier = SJZY.Track.Application.Contract.Events.OrderIdentifier;
namespace SJZY.Track.Application.CAP.EventHandlers;
@ -53,6 +56,7 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
var nextEvent = @event.DeepCopy();
nextEvent.Visited.AddRange(orderIds);
nextEvent.SubSystemId = group.Key.SubSystemId;
nextEvent.CustomId = group.Key.CustomId;
nextEvent.TrackRefreshData = new TrackRefreshStruct
{
ServiceProviderName = @event.TrackRefreshData.ServiceProviderName,
@ -83,7 +87,6 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
OrderId = x.OrderId,
OrderNo = x.OrderNo,
OrderType = x.OrderType,
CustomId = x.CustomId
}).ToList();
var idx = 0;
while (idx < identifiers.Count)
@ -103,6 +106,10 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
private async Task HandleTrackAsync(TrackUpsertEvent @event, List<int> canNextOrderIds)
{
var content = await _provideQueries.GetTrackContentAsync(@event.SubSystemId, @event.TrackCode);
if (content == null) return;
var orderIds = @event.OrderIdentifiers.Select(x => x.OrderId).ToArray();
var tracks = await _trackDetailsRepository.GetListAsync(orderIds, @event.TrackCode, true);
@ -170,8 +177,6 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
if (canNextOrderIds.Count == 0) return;
var content = await _provideQueries.GetTrackContentAsync(@event.SubSystemId, @event.TrackCode);
// 批量add to es
await _trackDetailsRepository.AddManyAsync(insertTracks);
@ -179,26 +184,27 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
await _capPublisher.PublishAsync(GlobalConst.TrackChangeAfterUpdateOrderSubscribe,
new TrackChangeAfterUpdateOrderEvent
{
SubSystemId = @event.SubSystemId,
CustomId = @event.CustomId,
OrderIds = canNextOrderIds.ToArray(),
IsCompleted = @event.TrackRefreshData.IsCompleted,
TrackContent = content.TrackContent,
TrackEnContent = content.TrackEnContent,
TrackStatusId = content.TrackStatusId,
TrackPlace = @event.TrackPlace,
TrackTime = @event.TrackTime,
TrackTimeZone = @event.TrackTimeZone
TrackTimeZone = @event.TrackTimeZone,
TrackCode = @event.TrackCode,
DeficiencyTagValue = @event.DeficiencyTagValue
});
// 触发轨迹推送事件(异步)
foreach (var group in @event.OrderIdentifiers
.Where(x => x.OrderId.IsIn(canNextOrderIds))
.GroupBy(x => x.CustomId))
{
var trackPushEvent = new TrackPushEvent
{
OptionType = @event.IsAdd ? OptionType.新增 : OptionType.修改,
SubSystemId = @event.SubSystemId,
CustomId = group.Key,
OrderIds = group.Select(x => x.OrderId).ToArray(),
CustomId = @event.CustomId,
OrderIds = canNextOrderIds.ToArray(),
TrackCode = @event.TrackCode,
SystemPushInfo = new SystemPushStruct
{
@ -212,4 +218,3 @@ public class TrackUpsertEventHandler : ITransientDependency, ICapSubscribe
await _capPublisher.PublishAsync(GlobalConst.TrackPushSubscribe, trackPushEvent);
}
}
}

View File

@ -0,0 +1,60 @@
using SJZY.OTel;
namespace SJZY.Track.Application.Helpers;
public static class DateTimeHelper
{
/// <summary>
/// convert to a Unix timestamp from a DateTime object.
/// </summary>
/// <param name="dateTime">The DateTime object to convert to a Unix timestamp.</param>
/// <returns>The Unix timestamp as a long.</returns>
public static long ConvertToUnixTimestamp(DateTime dateTime)
{
return new DateTimeOffset(dateTime).ToUnixTimeSeconds();
}
/// <summary>
/// Formats a DateTime object into a specified format.
/// </summary>
/// <param name="dateTime">The DateTime object to format.</param>
/// <param name="format">The format string.</param>
/// <returns>The formatted date string.</returns>
public static string FormatDateTime(DateTime dateTime, string format)
{
return dateTime.ToString(format);
}
/// <summary>
/// 时区格式化
/// </summary>
/// <param name="timezone"></param>
/// <param name="format"></param>
/// <returns></returns>
public static string TimezoneFormat(double timezone, string format)
{
//时区是整数丢掉浮点数部分
return format.Replace("hh", ((int)timezone).ToString());
}
/// <summary>
/// 转换主数据中心类似GMT-09:00这种的时区为浮点数
/// </summary>
/// <param name="timeZone">分区</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static float? ConvertTimeZoneToFloat(string timeZone)
{
if (string.IsNullOrEmpty(timeZone)|| !timeZone.StartsWith("GMT"))
{
return null;
}
var sign = timeZone[3] == '+' ? 1 : -1;
var parts = timeZone.Substring(4).Split(':');
var hours = int.Parse(parts[0]);
var minutes = int.Parse(parts[1]);
return sign * (hours + minutes / 60f);
}
}

View File

@ -0,0 +1,243 @@
using System.Dynamic;
using Nest;
using Newtonsoft.Json.Linq;
using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.Track.Application.Contract.Dtos.Common;
using SJZY.Track.Application.Contract.Dtos.Forewarning;
using SJZY.Track.Domain.Aggregates.ForewarningAggregate;
using SJZY.Track.Domain.Dtos.Forewarning;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Application.Helpers;
public class ForewarningHelper
{
/// <summary>
/// 递推分裂预警规则列表
/// </summary>
/// <param name="statusSpans"></param>
/// <param name="factors"></param>
/// <returns></returns>
public static List<WarningRuleEntity> CalcRuleList(List<WarningNitifyDto> statusSpans,
List<WarningFactorDto> factors)
{
if (statusSpans.Count == 0 || factors.Count == 0)
{
throw new BusinessException(message: "轨迹状态区间或因素列表为空");
}
var len = statusSpans.Count * factors.Select(f => f.FactorValue.Count).Aggregate((pre, cur) => pre * cur);
if (len == 0)
{
throw new BusinessException(message: "轨迹状态区间或因素列表为空");
}
var rules = Enumerable.Range(0, len).Select(_ => new WarningRuleEntity()).ToList();
var repeat = len / statusSpans.Count;
FillStatus(statusSpans, rules, repeat);
foreach (var factor in factors)
{
var factorValueCount = factor.FactorValue.Count;
// repeat梯度下降,与times成反比关系
repeat /= factorValueCount;
var times = len / repeat / factorValueCount;
FillFactor(factor, rules, repeat, times);
}
return rules;
}
private static void FillStatus(List<WarningNitifyDto> statusSpans, List<WarningRuleEntity> rules, int repeat)
{
var row = 0;
foreach (var span in statusSpans)
{
for (var i = 0; i < repeat; i++, row++)
{
rules[row].StartTrackStatusId = span.StartTrackStatusId;
rules[row].StartTrackStatus = span.StartTrackStatus;
rules[row].EndTrackStatusId = span.EndTrackStatusId;
rules[row].EndTrackStatus = span.EndTrackStatus;
}
}
}
private static void FillFactor(WarningFactorDto factor, List<WarningRuleEntity> rules, int repeat, int times)
{
var row = 0;
for (var i = 0; i < times; i++)
{
foreach (var value in factor.FactorValue)
{
var strValue = value.ToString();
if (strValue.IsNullOrEmpty())
{
throw new BusinessException(message: "因素值不能为空");
}
for (var j = 0; j < repeat; j++, row++)
{
switch (factor.FactorType)
{
case RuleFactorType.CargoType:
if (value.Type is not JTokenType.String)
{
throw new BusinessException(message: "货物类型只接收字符串");
}
rules[row].CargoType = strValue;
break;
case RuleFactorType.Months:
foreach (var item in strValue.Split("/"))
{
if (!int.TryParse(item, out var month) || month < 1 || month > 12)
{
throw new BusinessException(message: "请输入合法的月份,多个月份请以'/'连接");
}
}
rules[row].Months = strValue;
break;
case RuleFactorType.Area:
if (value.Type is not JTokenType.Integer)
{
throw new BusinessException(message: "分区请传id");
}
rules[row].AreaId = int.Parse(strValue);
break;
case RuleFactorType.IsTransit:
if (value.Type is not JTokenType.Boolean)
{
throw new BusinessException(message: "是否中转只接收bool类型");
}
rules[row].IsTransit = bool.Parse(strValue);
break;
case RuleFactorType.LeavePort:
if (value.Type is not JTokenType.String)
{
throw new BusinessException(message: "起运港只接收字符串");
}
rules[row].LeavePort = strValue;
break;
case RuleFactorType.ProductCode:
if (value.Type is not JTokenType.String)
{
throw new BusinessException(message: "产品代码只接收字符串");
}
rules[row].ProductCode = strValue;
break;
default:
throw new BusinessException(message: $"不支持的因素:{factor.FactorType}");
}
}
}
}
}
public static List<WarningFactorEntity> MapFactorEntities(List<WarningFactorDto> warningFactors)
{
return warningFactors.Select(x => new WarningFactorEntity
{
Type = x.FactorType,
Value = x.FactorValue.ToJson()
}).ToList();
}
public static List<WarningNotifyEntity> MapNotifyEntities(CreateWarningInput input)
{
return input.TrackStatusSpans.Select(x => new WarningNotifyEntity
{
StartTrackStatusId = x.StartTrackStatusId,
EndTrackStatusId = x.EndTrackStatusId,
NotifyMode = input.NotifyMode,
NotifyTemplate = input.NotifyTemplate,
NotifyUserId = x.NotifyUserId
}).ToList();
}
public static EditWarningOutput MapWarningSummary(WarningEntity warning, List<IdWithNameDto> statusList)
{
var statusDic = statusList.ToDictionary(x => x.Id, x => x.Name);
return new EditWarningOutput
{
Name = warning.Name,
SubSystemId = warning.SubSystemId,
CustomerId = warning.CustomerId,
NotifyMode = warning.WarningNotifies.First().NotifyMode,
NotifyTemplate = warning.WarningNotifies.First().NotifyTemplate,
TrackStatusSpans = warning.WarningNotifies.Select(x => new WarningNotifyWithIdDto
{
Id = x.Id,
StartTrackStatusId = x.StartTrackStatusId,
StartTrackStatus = statusDic.TryGetValue(x.StartTrackStatusId, out var start) ? start : "",
EndTrackStatusId = x.EndTrackStatusId,
EndTrackStatus = statusDic.TryGetValue(x.EndTrackStatusId, out var end) ? end : "",
NotifyUserId = x.NotifyUserId,
}).ToList(),
WarningFactors = warning.WarningFactors.Select(x => new WarningFactorWithIdDto
{
Id = x.Id,
FactorType = x.Type,
FactorValue = JArray.Parse(x.Value),
}).ToList()
};
}
public static List<dynamic> DynamicCalcRuleList(List<WarningRuleVo> rules, List<RuleFactorType> factors)
{
var dynamicList = new List<dynamic>();
foreach (var rule in rules)
{
dynamic dynamicRule = new ExpandoObject();
var dynamicRuleDict = (IDictionary<string, object?>)dynamicRule;
dynamicRuleDict[nameof(Id)] = rule.Id;
dynamicRuleDict[nameof(rule.StartTrackStatus)] = rule.StartTrackStatus;
dynamicRuleDict[nameof(rule.EndTrackStatus)] = rule.EndTrackStatus;
foreach (var factor in factors)
{
switch (factor)
{
case RuleFactorType.CargoType:
dynamicRuleDict[nameof(rule.CargoType)] = rule.CargoType;
break;
case RuleFactorType.Months:
dynamicRuleDict[nameof(rule.Months)] = rule.Months;
break;
case RuleFactorType.Area:
dynamicRuleDict[nameof(rule.Area)] = rule.Area;
break;
case RuleFactorType.IsTransit:
dynamicRuleDict[nameof(rule.IsTransit)] = rule.IsTransit;
break;
case RuleFactorType.LeavePort:
dynamicRuleDict[nameof(rule.LeavePort)] = rule.LeavePort;
break;
case RuleFactorType.ProductCode:
dynamicRuleDict[nameof(rule.ProductCode)] = rule.ProductCode;
break;
default:
throw new BusinessException(message: $"不支持的因素:{factor}");
}
}
dynamicRuleDict[nameof(rule.TotalTimeSpan)] = rule.TotalTimeSpan;
dynamicRuleDict[nameof(rule.WarnTimeSpan)] = rule.WarnTimeSpan;
dynamicRuleDict[nameof(rule.WarnCount)] = rule.WarnCount;
dynamicList.Add(dynamicRule);
}
return dynamicList;
}
}

View File

@ -0,0 +1,50 @@
using Microsoft.Extensions.Hosting;
using SJZY.Expand.ABP.Core;
using SJZY.Track.Application.Contract.Dtos.Forewarning;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Domain.Consts;
using SJZY.Track.Domain.Dtos.Forewarning;
using SJZY.UserCenter.SharedModel.Dtos.PHP.User;
namespace SJZY.Track.Application.HostServices;
public class RedisHostService:IHostedService
{
readonly IForewarningCacheService _forewarningCacheService;
public RedisHostService(IForewarningCacheService forewarningCacheService)
{
_forewarningCacheService = forewarningCacheService;
}
public Task StartAsync(CancellationToken cancellationToken)
{
// 订阅频道
RedisHelper.Client.Subscribe(GetKey(), Subscribe);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
RedisHelper.Client.UnSubscribe(GetKey());
return Task.CompletedTask;
}
private void Subscribe(string message,object value)
{
if (value is string)
{
// 暂时只处理一个事件,不做反序列化
//var instance = Newtonsoft.Json.JsonConvert.DeserializeObject<ModifyWarningEvent>(value as string);
var instance = Newtonsoft.Json.JsonConvert.DeserializeObject<ClearRulCacheEvent>(value as string);
if(instance!=null)
_forewarningCacheService.Clear(instance);
}
}
private string GetKey()
{
return $"{GlobalConst.PublishNotifyChannelKey}";
}
}

View File

@ -29,4 +29,8 @@
<ProjectReference Include="..\SJZY.Track.Domain\SJZY.Track.Domain.csproj" />
<ProjectReference Include="..\SJZY.Track.Application.Contract\SJZY.Track.Application.Contract.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Validations\Forewarning\FactorItemDtoValidator.cs" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,223 @@
using System.Collections.Concurrent;
using SJZY.Expand.ABP.Core;
using SJZY.Expand.ABP.Core.PublicMethords;
using SJZY.Track.Application.Contract.Dtos.Forewarning;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
using SJZY.Track.Domain.Consts;
using SJZY.Track.Domain.Dtos.Forewarning;
using SJZY.Track.Domain.Queries;
using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.Services.ForewarningServices;
public class ForewarningCacheService : IForewarningCacheService, ISingletonDependency
{
readonly IKpiAreaRepository _kpiAreaRepository;
private readonly IWarningQueries _warningQueries;
private const string GlobalWaningLockKey = "GlobalWaningLockKey_GlobalLock";
private readonly ConcurrentDictionary<string, List<ForewarningRuleCacheEntry>> _rules = new();
private readonly ConcurrentDictionary<int, List<AreaCacheEntry>> _areas = new();
object _lock = new object();
public ForewarningCacheService(IKpiAreaRepository kpiAreaRepository, IWarningQueries warningQueries)
{
_kpiAreaRepository = kpiAreaRepository;
_warningQueries = warningQueries;
}
string WarningKey(int subSystemId, int customerId) =>
$"{GlobalConst.TrackWarningKeyPrefix}:{subSystemId}:{customerId}";
string AreaKey(int areaId) => $"{GlobalConst.TrackAreaKeyPrefix}:{areaId}";
public async Task<List<ForewarningRuleCacheEntry>?> GetRulesAsync(int subSystemId, int customerId)
{
var key = WarningKey(subSystemId, customerId);
if( _rules.TryGetValue(key,out var rules))
{
return rules;
}
return await GetRules(subSystemId, customerId);
}
private List<ForewarningRuleCacheEntry> WriteRules(List<WarningRuleCacheDto> rules)
{
List<ForewarningRuleCacheEntry> result = new List<ForewarningRuleCacheEntry>();
foreach (var item in rules)
{
List<AreaCacheEntry>? areaList = null;
if (item.AreaId.HasValue)
{
areaList = GetAreaInfoByAreaId(item.AreaId!.Value);
}
var entry = new ForewarningRuleCacheEntry
{
Id = item.Id,
WarningId = item.WarningId,
TotalTimeSpan = item.TotalTimeSpan,
WarnTimeSpan = item.WarnTimeSpan,
WarnCount = item.WarnCount,
StartTrackStatusId = item.StartTrackStatusId,
EndTrackStatusId = item.EndTrackStatusId,
CargoType = item.CargoType,
Months = string.IsNullOrEmpty(item.Months)
? new List<int>()
: item.Months.Split('/').Select(int.Parse).ToList(),
IsTransit = item.IsTransit,
Areas = areaList,
ProductCode = item.ProductCode,
LeavePort = item.LeavePort
};
result.Add(entry);
}
return result;
}
private async Task<List<ForewarningRuleCacheEntry>?> GetRules(int systemId, int customerId)
{
var key = WarningKey(systemId, customerId);
var result = RedisHelper.Client.Get<List<WarningRuleCacheDto>>(key);
if (result != null)
{
// 非线程安全,需要加锁
lock (_lock)
{
if (_rules.TryGetValue(key, out var ruleResult))
{
return ruleResult;
}
ruleResult = WriteRules(result);
_rules.TryAdd(key,ruleResult);
return ruleResult;
}
}
// 非线程安全,需要加锁
using (RedisHelper.Client.Lock(GlobalWaningLockKey, 20))
{
if (_rules.TryGetValue(key, out var ruleResult)) return ruleResult;
var warnings = await _warningQueries.GetWarningRule(systemId, customerId);
if (warnings == null || warnings.Count == 0)
{
RedisHelper.Client.Set(key, new List<WarningRuleCacheDto>(), TimeSpan.FromSeconds(20));
return new List<ForewarningRuleCacheEntry>();
}
RedisHelper.Client.Set(key, warnings);
ruleResult = WriteRules(warnings);
_rules.TryAdd(key, ruleResult);
return ruleResult;
}
}
public Task DeleteRulesAsync(int subSystemId, int customerId)
{
_rules.Remove(WarningKey(subSystemId, customerId),out _);
return Task.CompletedTask;
}
private List<AreaCacheEntry>? GetAreaInfoByAreaId(int areaId)
{
if (_areas.TryGetValue(areaId, out var areaInfos)) return areaInfos;
var areas = GetAreas(areaId);
if (areas == null || areas.Count == 0) return null;
areaInfos = areas.Select(KpiAreaInfoToEntry).ToList();
_areas.TryAdd(areaId, areaInfos);
return areaInfos;
}
AreaCacheEntry KpiAreaInfoToEntry(KpiAreaInfo model)
{
return new AreaCacheEntry
{
Country = model.Country,
Province = model.Province,
City = model.City,
LeftPostcode = model.LeftPostcode,
RightPostcode = model.RightPostcode
};
}
/// <summary>
/// 获取区域
/// </summary>
/// <returns></returns>
private List<KpiAreaInfo>? GetAreas(int areaId)
{
var key = AreaKey(areaId);
var result = RedisHelper.Client.Get<List<KpiAreaInfo>>(key);
if (result == null)
{
result = RedisHelper.Client.Get<List<KpiAreaInfo>>(key);
if (result != null) return new List<KpiAreaInfo>();
var kpiArea = _kpiAreaRepository.GetAreaInfosByAreaId(areaId);
if (kpiArea == null)
{
RedisHelper.Client.Set(key, new List<KpiAreaInfo>(), TimeSpan.FromSeconds(20));
return new List<KpiAreaInfo>();
}
RedisHelper.Client.Set(key, kpiArea);
return kpiArea;
}
return result;
}
public Task Clear(ClearRulCacheEvent @event)
{
foreach (var item in @event.Items)
{
DeleteRulesAsync(item.SystemId, item.CustomerId);
}
return Task.CompletedTask;
}
public async Task ClearRedisBySystemIdAndCustomer(int subSystemId,int customerId)
{
await RedisHelper.Client.DelAsync(WarningKey(subSystemId, customerId));
var eventModel = new ClearRulCacheEvent
{
Items = new List<RuleAreaBaseInfo>()
{
new RuleAreaBaseInfo(subSystemId, customerId)
}
};
await PublishEvent(eventModel);
}
public async Task ClearRedisByAreaId(int areaId)
{
var baseInfos = await _warningQueries.GetWarningRuleByAreaId(areaId);
if(baseInfos==null||baseInfos.Count==0) return;
baseInfos = baseInfos.DistinctBy(val => WarningKey(val.SystemId, val.CustomerId)).ToList();
var willDelKeys = baseInfos.Select(val => WarningKey(val.SystemId, val.CustomerId)).ToArray();
await RedisHelper.Client.DelAsync(willDelKeys);
await RedisHelper.Client.DelAsync(AreaKey(areaId));
var eventModel = new ClearRulCacheEvent
{
Items = baseInfos
};
await PublishEvent(eventModel);
}
private async Task PublishEvent(ClearRulCacheEvent @event)
{
await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, @event.ToJson());
}
}

View File

@ -0,0 +1,188 @@
using System.Text;
using FreeRedis;
using SJZY.Expand.ABP.Core;
using SJZY.Track.Application.Contract.Dtos.Forewarning;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Domain.Consts;
using SJZY.Track.Domain.Queries;
using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.Services.ForewarningServices;
/// <summary>
/// 用于处理预警通知
/// </summary>
public class ForewarningNotifyService : IForewarningNotifyService, ITransientDependency
{
private readonly IForewarningCacheService _cacheService;
private readonly IWarningQueries _warningQueries;
public ForewarningNotifyService(IForewarningCacheService cacheService, IWarningQueries warningQueries)
{
_cacheService = cacheService;
_warningQueries = warningQueries;
}
/// <summary>
/// 创建预警通知
/// </summary>
/// <param name="dto">创建预警通知需要的订单信息</param>
/// <returns>创建的通知总量,0表示没有规则</returns>
public async Task WarningCalculateAsync(WarningDto dto)
{
if (dto.Orders.Count == 0) return;
var rules = await _cacheService.GetRulesAsync(dto.SubSystemId, dto.CustomerId);
if (rules == null || rules.Count == 0) return;
var adds = new List<(int ruleId, int orderId, int cnt, long ts)>();
var dels = new List<(int ruleId, int orderId)>();
foreach (var order in dto.Orders)
{
MatchRule(
rules.Where(rule => rule.StartTrackStatusId == dto.TrackStatusId).ToList(),
order
).ForEach(rule =>
{
var timestamp = GetTimestamp(order.TrackTime.AddMinutes(rule.WarnTimeSpan));
adds.Add((rule.Id, order.Id, rule.WarnCount, timestamp));
});
MatchRule(
rules.Where(rule => rule.EndTrackStatusId == dto.TrackStatusId).ToList(),
order
).ForEach(rule => { dels.Add((rule.Id, order.Id)); });
}
var tran = RedisHelper.Client.Multi();
// 维护预警队列
foreach (var kvp in adds.ToDictionary(x => $"{x.ruleId}_{x.orderId}", x => x.cnt))
{
await tran.HSetNxAsync(GlobalConst.ForewarningNotifyCntKey, kvp.Key, kvp.Value); // nx: not exist
}
await tran.ZAddAsync(GlobalConst.ForewarningNotifyKey, adds.Select(x =>
{
var entry = new WarningNotifyCacheEntry(x.ruleId, x.orderId);
return new ZMember(entry.ToString(), x.ts); // update timestamp
}).ToArray());
// 移出预警队列
await tran.HDelAsync(GlobalConst.ForewarningNotifyCntKey,
dels.Select(x => $"{x.ruleId}_{x.orderId}").ToArray());
await tran.ZRemAsync(GlobalConst.ForewarningNotifyKey,
dels.Select(x => $"{x.ruleId}:{x.orderId}").ToArray());
var res = tran.Exec();
if (res == null) throw new BusinessException(message: "轨迹预警计算失败");
}
private static List<ForewarningRuleCacheEntry> MatchRule(List<ForewarningRuleCacheEntry> rules,
WarningOrderDto order)
{
var res = new List<ForewarningRuleCacheEntry>();
foreach (var rule in rules)
{
if (rule.IsTransit != null)
{
if (order.IsTransit != rule.IsTransit) continue;
}
if (!rule.CargoType.IsNullOrEmpty())
{
if (order.CargoType != rule.CargoType) continue;
}
if (!rule.LeavePort.IsNullOrEmpty())
{
if (order.LeavePort != rule.LeavePort) continue;
}
if (!rule.ProductCode.IsNullOrEmpty())
{
if (order.ProductCode != rule.ProductCode) continue;
}
if (!rule.Months.IsNullOrEmpty())
{
if (!order.Month.IsIn(rule.Months)) continue;
}
if (!rule.Areas.IsNullOrEmpty())
{
var inArea = false;
foreach (var area in rule.Areas!.Where(area => order.ArrivalCountry == area.Country))
{
if (!area.LeftPostcode.IsNullOrEmpty() && !area.RightPostcode.IsNullOrEmpty())
{
// tips: 参考小包的邮编匹配,字典序
if (string.CompareOrdinal(order.ArrivalPostCode, area.LeftPostcode) < 0 ||
string.CompareOrdinal(order.ArrivalPostCode, area.RightPostcode) > 0) continue;
}
if (!area.City.IsNullOrEmpty() && order.ArrivalCity != area.City) continue;
if (!area.Province.IsNullOrEmpty() && order.ArrivalProvince != area.Province) continue;
inArea = true;
break;
}
if (!inArea) continue;
}
// 命中规则
res.Add(rule);
}
return res;
}
public async Task SendNotificationAsync()
{
var max = GetTimestamp(DateTime.Now);
var set = await RedisHelper.Client.ZRangeByScoreAsync(GlobalConst.ForewarningNotifyKey, 0, max);
if (set.IsNullOrEmpty()) return;
var entries = set.Select(x => new WarningNotifyCacheEntry(x)).ToList();
var ruleIds = entries.Select(x => x.RuleId).ToList();
var notifyRules = await _warningQueries.GetWarningNotifyRulesAsync(ruleIds);
/*
*
预警名称:字节全程时效
预警规则:
{起飞-落地+普货+12345678+美国 时效9,预警时效7,次数3} 命中订单总数:124,详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder
{清关-提货+普货+12345678+美国 时效4,预警时效3,次数1} 命中订单总数:3250,详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder
预警名称:字节全程时效
预警规则:
{起飞-落地+普货+12345678+美国 时效9,预警时效7,次数3} 命中订单总数:124,详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder
{清关-提货+普货+12345678+美国 时效4,预警时效3,次数1} 命中订单总数:3250,详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder
*/
foreach (var group in notifyRules.GroupBy(x => new { x.NotifyMode, x.NotifyUserId }))
{
var notifyContent = new StringBuilder();
foreach (var warningGroup in group.GroupBy(x => x.WarningName))
{
notifyContent.AppendLine($"预警名称:{warningGroup.Key}");
notifyContent.AppendLine($"预警规则:");
foreach (var rule in warningGroup.ToList())
{
var cnt = entries.Count(x => x.RuleId == rule.RuleId);
if (cnt > 0)
{
notifyContent.AppendLine(
$"{rule.ToString()} 命中订单总数:{cnt},详情点击https://middlebusiness.pet.shijizhongyun.com/trajectoryCenter/trajectoryOrder");
}
}
}
}
}
private static long GetTimestamp(DateTime dateTime)
{
return new DateTimeOffset(dateTime).ToUnixTimeSeconds();
}
}

View File

@ -0,0 +1,203 @@
using System.ComponentModel.DataAnnotations;
using SJZY.Track.Application.Contract.Dtos.Common;
using SJZY.Track.Application.Contract.Dtos.Forewarning;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Application.Helpers;
using SJZY.Track.Domain.Aggregates.ForewarningAggregate;
using SJZY.Track.Domain.Aggregates.TrackStatusAggregate;
using SJZY.Track.Domain.Dtos.Forewarning;
using SJZY.Track.Domain.Dtos.TrackStatus;
using SJZY.Track.Domain.Queries;
namespace SJZY.Track.Application.Services.ForewarningServices;
[ApiExplorerSettings(GroupName = "预警服务")]
[Route("/ForewarningService")]
public class ForewarningService : ApplicationService
{
private readonly IWarningRepository _warningRepository;
private readonly IForewarningCacheService _forewarningCacheService;
public ForewarningService(IWarningRepository warningRepository, IForewarningCacheService forewarningCacheService)
{
_warningRepository = warningRepository;
_forewarningCacheService = forewarningCacheService;
}
/// <summary>
/// 获取预警分页列表
/// </summary>
/// <param name="inputVo"></param>
/// <returns></returns>
[HttpPost("GetWarningPageList")]
public async Task<UnifyPageResultDto<WarningPageOutput>> GetWarningPageList(GetWarningPageInputVo inputVo)
{
if (!inputVo.KeyWord.IsNullOrEmpty()) inputVo.KeyWord = inputVo.KeyWord!.Trim();
return await _warningRepository.GetWarningPageListAsync(inputVo);
}
/// <summary>
/// 创建预警
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("CreateWarning")]
public async Task<bool> CreateWarning(CreateWarningInput input)
{
var warning = ObjectMapper.Map<CreateWarningInput, WarningEntity>(input);
if (await _warningRepository.IsAnyAsync(x =>
x.SubSystemId == warning.SubSystemId && x.CustomerId == warning.CustomerId && x.Name == warning.Name))
{
throw new BusinessException(message: "该子系统下的客户已经存在一个同名预警: " + warning.Name);
}
warning.WarningRules = ForewarningHelper.CalcRuleList(input.TrackStatusSpans, input.WarningFactors);
warning.FactorTypeSort = input.WarningFactors.Select(x => x.FactorType).JoinAsString(",");
warning.WarningFactors = ForewarningHelper.MapFactorEntities(input.WarningFactors);
warning.WarningNotifies = ForewarningHelper.MapNotifyEntities(input);
await _warningRepository.CreateForewarningAsync(warning);
return true;
}
/// <summary>
/// 编辑预警
/// </summary>
/// <param name="warningId"></param>
/// <param name="statusQuery"></param>
/// <returns></returns>
[HttpPost("EditWarning")]
public async Task<EditWarningOutput> EditWarning(int warningId,
[FromServices] IBasicQuery<TrackStatusEntity> statusQuery)
{
var warning = await _warningRepository.GetByIdAsync(warningId);
if (warning == null)
{
throw new BusinessException(message: "预警不存在");
}
var statusList = await statusQuery.GetListAsync(x => x.SubSystemId == warning.SubSystemId, x =>
new IdWithNameDto
{
Id = x.Id,
Name = x.Status
});
return ForewarningHelper.MapWarningSummary(warning, statusList);
}
/// <summary>
/// 更新预警
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("UpdateWarning")]
public async Task<bool> UpdateWarning(UpdateWarningInput input)
{
var warning = await _warningRepository.GetFirstAsync(x => x.Id == input.Id);
if (warning == null)
{
throw new BusinessException(message: "预警不存在");
}
if (await _warningRepository.IsAnyAsync(x =>
x.SubSystemId == warning.SubSystemId && x.CustomerId == warning.CustomerId && x.Id != warning.Id &&
x.Name == input.Name))
{
throw new BusinessException(message: "该子系统下的客户已经存在一个同名预警: " + warning.Name);
}
warning.SetName(input.Name);
warning.WarningNotifies = input.Notifies.Select(x => new WarningNotifyEntity
{
Id = x.Id,
NotifyMode = input.NotifyMode,
NotifyTemplate = input.NotifyTemplate,
NotifyUserId = x.NotifyUserId
}).ToList();
await _warningRepository.UpdateForewarningAsync(warning);
return true;
}
/// <summary>
/// 启用or禁用预警
/// </summary>
/// <param name="warningId">预警id</param>
/// <param name="isEnable">是否启用</param>
/// <returns></returns>
[HttpPost("TurnWarning")]
public async Task<bool> TurnWarning(int warningId, bool isEnable)
{
var warning = await _warningRepository.GetFirstAsync(x => x.Id == warningId);
if (warning == null)
{
throw new BusinessException(message: "预警不存在");
}
if (isEnable)
{
if (await _warningRepository.IsAnyAsync(x =>
x.SubSystemId == warning.SubSystemId && x.CustomerId == warning.CustomerId && x.IsEnable))
{
throw new BusinessException(message: "该子系统下的客户已经存在一个启用的预警,请先禁用");
}
}
warning.SetEnable(isEnable);
await _warningRepository.TurnWarningAsync(warning);
// 清除缓存
await _forewarningCacheService.DeleteRulesAsync(warning.SubSystemId, warning.CustomerId);
return true;
}
/// <summary>
/// 查询预警规则列表
/// </summary>
/// <param name="warningId">预警规则id</param>
/// <param name="factorQuery"></param>
/// <returns></returns>
[HttpGet("GetWarningRuleList")]
public async Task<List<dynamic>> GetWarningRuleListAsync([Required] int warningId,
[FromServices] IBasicQuery<WarningFactorEntity> factorQuery)
{
var factors = await factorQuery.GetListAsync(x => x.WarningId == warningId, x => x.Type);
var rules = await _warningRepository.GetWarningRuleListAsync(warningId);
var dynamicList = ForewarningHelper.DynamicCalcRuleList(rules, factors);
return dynamicList;
}
/// <summary>
/// 修改预警规则时效
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("ModifyTimeSpans")]
public async Task<bool> ModifyTimeSpans(ModifyTimeSpanInput input)
{
if (input.Items.IsNullOrEmpty()) return false;
var warning = await _warningRepository.GetFirstAsync(x => x.Id == input.WarningId);
if (warning == null)
{
throw new BusinessException(message: "预警不存在");
}
var warningRules = ObjectMapper.Map<List<WarningTimeSpanDto>, List<WarningRuleEntity>>(input.Items);
await _warningRepository.ModifyTimeSpansAsync(warningRules);
// 清除缓存
await _forewarningCacheService.DeleteRulesAsync(warning.SubSystemId, warning.CustomerId);
return true;
}
}

View File

@ -0,0 +1,29 @@
using SJZY.Expand.ABP.Core;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
using SJZY.Track.Domain.Consts;
using SJZY.Track.Domain.Dtos.Forewarning;
using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.Services.ForewarningServices;
public class KpiAreaCacheService:IKpiAreaCacheService,ITransientDependency
{
IKpiAreaRepository _kpiAreaRepository;
const string GlobalKpiAreaLockKey = "KpiAreaCacheService_GlobalLock";
public KpiAreaCacheService(IKpiAreaRepository kpiAreaRepository)
{
_kpiAreaRepository = kpiAreaRepository;
}
private string GetKey(int systemId, string country)
{
return $"{GlobalConst.KpiAreaKeyPrefix}:{systemId}:{country}";
}
public Task GetKpiArea(int systemId, string country)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,141 @@
using SJZY.Expand.ABP.Core;
using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
using SJZY.Track.Domain.Consts;
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Application.Services.ForewarningServices;
[ApiExplorerSettings(GroupName = "预警服务")]
[Route("/ForewarningService")]
public class KpiAreaService : ApplicationService
{
private readonly IKpiAreaRepository _ruleAreaRepository;
private readonly IForewarningCacheService _forewarningCacheService;
public KpiAreaService(IKpiAreaRepository ruleAreaRepository, IForewarningCacheService forewarningCacheService)
{
_ruleAreaRepository = ruleAreaRepository;
_forewarningCacheService = forewarningCacheService;
}
/// <summary>
/// 创建分区
/// </summary>
/// <param name="input"></param>
/// <returns>分区id</returns>
[HttpPost("KpiArea/CreateKpiArea")]
public async ValueTask<UnifyResultDto> CreateKpiArea(CreateRuleAreaRequest input)
{
var ruleAreaEntity = ObjectMapper.Map<CreateRuleAreaRequest, RuleAreaEntity>(input);
var areaId = await _ruleAreaRepository.CreateAreaAsync(ruleAreaEntity);
return new UnifyResultDto(true, areaId, "创建成功");
}
/// <summary>
/// 添加区域信息
/// </summary>
/// <param name="input"></param>
/// <returns>区域id</returns>
[HttpPost("KpiArea/AddAreaInfo")]
public async ValueTask<UnifyResultDto> AddAreaInfoAsync(AddRuleAreaInfoRequest input)
{
var ruleAreaEntity = ObjectMapper.Map<AddRuleAreaInfoRequest, RuleAreaInfoEntity>(input);
var ruleInfoId = await _ruleAreaRepository.AddAreaInfoAsync(ruleAreaEntity);
await _forewarningCacheService.ClearRedisByAreaId(input.AreaId);
return new UnifyResultDto(true, ruleInfoId, "添加成功");
}
/// <summary>
/// 删除分区
/// </summary>
/// <param name="id">分区id</param>
/// <returns></returns>
[HttpDelete("KpiArea/DeleteKpiArea")]
public async ValueTask<UnifyResultDto> DeleteKpiArea(int id)
{
await _ruleAreaRepository.DeleteArea(id);
await _forewarningCacheService.ClearRedisByAreaId(id);
return new UnifyResultDto(true, "删除成功");
}
/// <summary>
/// 删除分区信息
/// </summary>
/// <param name="id">分区信息id</param>
/// <returns></returns>
[HttpDelete("KpiArea/DeleteKpiAreaInfo")]
public async ValueTask<UnifyResultDto> DeleteKpiAreaInfo(int id)
{
var info=await _ruleAreaRepository.GetAreaInfoById(id);
if (info == null) return new UnifyResultDto(false,"未找到该区域信息");
await _ruleAreaRepository.DeleteAreaInfo(id);
await _forewarningCacheService.ClearRedisByAreaId(info.AreaId);
return new UnifyResultDto(true, "删除成功");
}
/// <summary>
/// 更新区域信息
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("KpiArea/ModifyKpiArea")]
public async Task<UnifyResultDto> ModifyKpiArea(UpdateRuleAreaRequest input)
{
if (input.Id <= 0)
return new UnifyResultDto(false, "id不能为空");
var ruleArea = await _ruleAreaRepository.GetFirstAsync(val => val.Id == input.Id);
if (ruleArea == null)
return new UnifyResultDto(false, "未找到该区域");
ruleArea.Name = input.Name;
await _ruleAreaRepository.UpdateAsync(ruleArea);
return new UnifyResultDto(true, "更新成功");
}
/// <summary>
/// 更新区域信息
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("KpiArea/ModifyKpiAreaInfo")]
public async Task<UnifyResultDto> ModifyKpiAreaInfo(UpdateRuleAreaInfoRequest input)
{
if (input.Id <= 0)
return new UnifyResultDto(false, "id不能为空");
var areaInfo = await _ruleAreaRepository.GetAreaInfoById(input.Id);
if (areaInfo == null)
return new UnifyResultDto(false, "未找到该区域信息");
var ruleAreaEntity = ObjectMapper.Map(input, areaInfo);
await _ruleAreaRepository.UpdateAreaInfoAsync(ruleAreaEntity);
await _forewarningCacheService.ClearRedisByAreaId(areaInfo.AreaId);
await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache");
return new UnifyResultDto(true, "更新成功");
}
/// <summary>
/// 查询分区列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("KpiArea/GetKpiAreas")]
public async Task<UnifyPageResultDto<AreaDto>> GetKpiAreas(QueryAreaRequest input)
{
return await _ruleAreaRepository.GetAreas(input);
}
/// <summary>
/// 查询分区列表
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
[HttpPost("KpiArea/GetKpiAreaInfos")]
public async Task<UnifyPageResultDto<RuleAreaInfoDto>> GetKpiAreaInfos(QueryAreaInfoRequest query)
{
return await _ruleAreaRepository.GetAreaInfos(query);
}
}

View File

@ -101,7 +101,6 @@ public partial class OrderService
OrderId = x.Id,
OrderNo = x.OrderNo,
OrderType = x.OrderType,
CustomId = x.CustomId
}).ToArray(),
TrackCode = track.TrackCode,
TrackPlace = track.TrackPlace,

View File

@ -1,5 +1,6 @@
using DotNetCore.CAP;
using Microsoft.AspNetCore.Authorization;
using SJZY.Track.Application.CAP.EventHandlers;
using SJZY.Track.Application.Contract.Dtos.ProvideServices;
using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Domain.Aggregates.OrderAggregate;
@ -31,6 +32,7 @@ public class TrackPushService : ApplicationService
_capPublisher = capPublisher;
_trackPushRecordsRepository = trackPushRecordsRepository;
_requestCurrentUserService = requestCurrentUserService;
}
/// <summary>
@ -90,9 +92,23 @@ public class TrackPushService : ApplicationService
[HttpPost("Track/PushWarnForTask")]
public async Task<string> PushWarnForTaskAsync()
{
// todo:订单数据上来后一次性update是否会引发长时间X独占?
var count = await _orderRepository.PushWarnOrdersAsync();
return $"本次预警扫描了:{count}个订单";
int total = 0, pageSize = 1000,waitTime = 1000;
var dateTimes = Enumerable.Range(1, 12).Select(val => DateTime.Now.AddHours(-val)).ToList();
foreach (var dateTime in dateTimes)
{
var rows = 0;
var pageindex = 1;
do
{
rows = await _orderRepository.PushWarnOrdersAsync(pageSize,pageindex,dateTime);
total+=rows;
pageindex++;
await Task.Delay(waitTime);
}while (rows!= pageSize);
}
return $"本次预警扫描了:{total}个订单";
}
/// <summary>

View File

@ -73,7 +73,6 @@ public partial class TrackService : ApplicationService, ITrackService
OrderId = order.Id,
OrderNo = order.OrderNo,
OrderType = order.OrderType,
CustomId = order.CustomId
}
};
@event.OperationMan = name;
@ -111,18 +110,19 @@ public partial class TrackService : ApplicationService, ITrackService
var name = userInfo.Data.UserName;
foreach (var group in orders.GroupBy(x => x.SubSystemId))
foreach (var group in orders.GroupBy(x => new { x.SubSystemId, x.CustomId }))
{
// 新增/更新轨迹
var @event = new TrackUpsertEvent
{
SubSystemId = group.Key,
SubSystemId = group.Key.SubSystemId,
CustomId = group.Key.CustomId,
OrderIdentifiers = group.Select(order => new OrderIdentifier
{
OrderId = order.Id,
OrderNo = order.OrderNo,
OrderType = input.OrderType,
CustomId = order.CustomId
}).ToArray(),
IsAdd = input.IsAdd,
TrackCode = input.TrackCode,

View File

@ -0,0 +1,156 @@
using SJZY.Track.Application.Contract.Dtos.TrackNode;
using SJZY.Track.Application.Contract.Dtos.TrackStatus;
using SJZY.Track.Domain.Aggregates.TrackNodeAggregate;
using SJZY.Track.Domain.Aggregates.TrackStatusAggregate;
using SJZY.Track.Domain.Dtos.TrackNode;
using Volo.Abp.DependencyInjection;
namespace SJZY.Track.Application.Services.TrackNode
{
/// <summary>
/// 轨迹节点服务
/// </summary>
[ApiExplorerSettings(GroupName = "轨迹节点")]
[Route("/Track/Node")]
public class TrackNodeService : ApplicationService, ITransientDependency
{
private readonly ITrackStatusRepository _trackStatusRepository;
private readonly ITrackNodeRepository _trackNodeRepository;
public TrackNodeService(
ITrackNodeRepository trackNodeRepository, ITrackStatusRepository trackStatusRepository)
{
this._trackNodeRepository = trackNodeRepository;
_trackStatusRepository = trackStatusRepository;
}
/// <summary>
/// 轨迹节点编辑
/// </summary>
/// <returns></returns>
[HttpPost("Edit")]
public async Task<UnifyResultDto> EditAsync(EditTrackNodeInput input)
{
return input.Options switch
{
OptionType.新增 => await AddAsync(input),
OptionType.修改 => await UpdateAsync(input),
OptionType.删除 => await DeleteAsync(input),
_ => throw new BusinessException(message: "当前操作动作不合法")
};
}
/// <summary>
/// 获取分页轨迹节点
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("GetList")]
public async Task<UnifyPageResultDto<TrackNodeDto>> GetPageTrackNode(QueryTrackNodeRequest input)
{
var queryParam = ObjectMapper.Map<QueryTrackNodeRequest, TrackNodePageDto>(input);
var resultDto = await _trackNodeRepository.GetPageList(queryParam);
return resultDto;
}
/// <summary>
/// 获取详情
/// </summary>
/// <param name="nodeid">节点id</param>
/// <returns></returns>
[HttpPost("GetDetail")]
public async Task<UnifyResultDto> GetDetail(int nodeid)
{
var resultDto = await _trackNodeRepository.GetDetail(nodeid);
return new UnifyResultDto(true,resultDto);
}
/// <summary>
/// 获取分页轨迹字典,下拉框用的
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("GetTrackNodeOption")]
public async Task<UnifyPageResultDto<TrackNodeDicDto>> GetTrackNodeOption(QueryTrackNodeRequest input)
{
var queryParam = ObjectMapper.Map<QueryTrackNodeRequest, TrackNodePageDto>(input);
var resultDto = await _trackNodeRepository.GetPageDicList(queryParam);
return resultDto;
}
#region 私有方法
private async Task<UnifyResultDto> AddAsync(EditTrackNodeInput input)
{
#region 业务校验
if(string.IsNullOrEmpty(input.NodeName)|| string.IsNullOrEmpty(input.NodeEnName) || !input.SubSystemId.HasValue)
{
return new UnifyResultDto("200", "NodeName,NodeEnName,SubSystemId 必填", false);
}
var trackNodesEntitys = await _trackNodeRepository.GetListAsync(a =>
a.SubSystemId == input.SubSystemId && (a.NodeName == input.NodeName || a.NodeEnName == input.NodeEnName));
if (trackNodesEntitys.Any())
{
var trackNodeName = trackNodesEntitys.FirstOrDefault(a => a.NodeName == input.NodeName);
if (trackNodeName != null)
{
throw new BusinessException(message: "该子系统下节点名称已存在");
}
var trackNodeEnName = trackNodesEntitys.FirstOrDefault(a => a.NodeEnName == input.NodeEnName);
if (trackNodeEnName != null)
{
throw new BusinessException(message: "该子系统下节点英文名称已存在");
}
}
#endregion
var entity = ObjectMapper.Map<EditTrackNodeInput, TrackNodeEntity>(input);
var isTrue = await _trackNodeRepository.InsertAsync(entity);
return new UnifyResultDto("200", "新增成功", isTrue);
}
private async Task<UnifyResultDto> UpdateAsync(EditTrackNodeInput input)
{
#region 业务校验
if(string.IsNullOrEmpty(input.NodeName)|| string.IsNullOrEmpty(input.NodeEnName) )
{
return new UnifyResultDto("200", "NodeName,NodeEnName 必填", false);
}
var trackNodesEntitys = await _trackNodeRepository.GetFirstAsync(a => a.Id == input.Id);
if (trackNodesEntitys == null)
{
return new UnifyResultDto("200", "没有找到数据,更新失败", false);
}
#endregion
trackNodesEntitys.NodeEnName = input.NodeEnName;
trackNodesEntitys.NodeName = input.NodeName;
var isTrue = await _trackNodeRepository.UpdateAsync(trackNodesEntitys);
return new UnifyResultDto("200", "修改成功", isTrue);
}
private async Task<UnifyResultDto> DeleteAsync(EditTrackNodeInput input)
{
if(input.Id <=0 || !input.SubSystemId.HasValue)
{
return new UnifyResultDto("200", "SubSystemId 必填", false);
}
var trackStatus = await _trackStatusRepository.GetFirstAsync(val => val.Node == input.Id && val.SubSystemId == input.SubSystemId);
if(trackStatus!=null)
{
return new UnifyResultDto("200", "删除失败,该节点已被使用", false);
}
var rows = await _trackNodeRepository.DeleteById(input.Id);
return new UnifyResultDto("200", "删除成功", rows>0);
}
#endregion
}
}

View File

@ -48,7 +48,7 @@ public class TrackPushCacheService : ITrackPushCacheService, ITransientDependenc
var trackPushConfig = await _trackPushConfigRepository.GetFirstAsync(
x => x.SubSystemId == subSystemId && x.CustomId == customId, x => new
{
x.Id, x.IsAutoPush, x.IsAllPush, x.IsCanDelete, x.IsCanUpdate, x.SubSystemId, x.CustomId
x.Id, x.IsAutoPush, x.IsAllPush, x.IsCanDelete, x.IsCanUpdate, x.SubSystemId, x.CustomId,x.DateTimeFormatType,x.DateTimeFormat,x.TimezoneFormat
});
if (trackPushConfig == null)
{
@ -68,6 +68,9 @@ public class TrackPushCacheService : ITrackPushCacheService, ITransientDependenc
IsCanUpdate = trackPushConfig.IsCanUpdate,
IsAllPush = trackPushConfig.IsAllPush,
IsCanDelete = trackPushConfig.IsCanDelete,
DateTimeFormatType = trackPushConfig.DateTimeFormatType,
DateTimeFormat = trackPushConfig.DateTimeFormat,
TimezoneFormat = trackPushConfig.TimezoneFormat,
ServiceProviderCode = item.ServiceProviderCode,
OrderType = item.OrderType,
TrackCode = item.TrackCode,

View File

@ -99,7 +99,6 @@ namespace SJZY.Track.Application
options.SucceedMessageExpiredAfter = configuration.GetValue<int>("CAP:SucceedMessageExpiredAfter");
options.FallbackWindowLookbackSeconds =
configuration.GetValue<int>("CAP:FallbackWindowLookbackSeconds");
options.EnableSubscriberParallelExecute = true;
options.UseDashboard(dashboardOptions =>
{

View File

@ -0,0 +1,19 @@
using FluentValidation;
using SJZY.Track.Application.Contract.Dtos.Forewarning;
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Application.Validations.Forewarning;
public class CreateRuleFactorValidation : AbstractValidator<CreateWarningInput>
{
public CreateRuleFactorValidation()
{
RuleFor(x => x.WarningFactors).NotNull().WithMessage("因素列表不能为空");
RuleFor(x => x.TrackStatusSpans).NotNull().WithMessage("轨迹状态列表不能为空");
RuleFor(x => x.NotifyTemplate).NotEmpty().WithMessage("通知模板必填")
.MaximumLength(256).WithMessage("通知模板长度不能超过256");
// todo: more validation
}
}

View File

@ -0,0 +1,6 @@
namespace SJZY.Track.Application.Validations.Forewarning;
public class FactorItemDtoValidator
{
}

View File

@ -0,0 +1,14 @@
using FluentValidation;
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Application.Validations.Forewarning;
public class RuleTrackStateDtoValidator : AbstractValidator<WarningNitifyDto>
{
public RuleTrackStateDtoValidator()
{
RuleFor(x => x.StartTrackStatusId).GreaterThan(0).WithMessage("起始轨迹状态id必须大于0");
RuleFor(x => x.EndTrackStatusId).GreaterThan(0).WithMessage("终止轨迹状态id必须大于0");
RuleFor(x => x.NotifyUserId).NotEmpty().WithMessage("通知对象不能为空");
}
}

View File

@ -0,0 +1,16 @@
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate;
public class FactorItemModel
{
/// <summary>
/// Gets or sets the sort order.
/// </summary>
public int Sort { get; set; }
/// <summary>
/// Gets or sets the type of the rule factor.
/// </summary>
public RuleFactorType FactorType { get; set; }
}

View File

@ -0,0 +1,62 @@
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate;
public interface IWarningRepository : IBasicRepository<WarningEntity>
{
/// <summary>
/// 创建预警,include factors, notifies, rules
/// </summary>
/// <param name="warning"></param>
/// <returns></returns>
Task<int> CreateForewarningAsync(WarningEntity warning);
/// <summary>
/// 复制预警
/// </summary>
/// <param name="warningId"></param>
/// <returns></returns>
Task CopyForewarning(int warningId);
/// <summary>
/// 获取预警分页列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<UnifyPageResultDto<WarningPageOutput>> GetWarningPageListAsync(GetWarningPageInputVo input);
/// <summary>
/// 获取预警规则列表
/// </summary>
/// <param name="warningId">预警id</param>
/// <returns></returns>
Task<List<WarningRuleVo>> GetWarningRuleListAsync(int warningId);
/// <summary>
/// 修改预警
/// </summary>
/// <param name="warning"></param>
/// <returns></returns>
Task UpdateForewarningAsync(WarningEntity warning);
/// <summary>
/// 修改预警时效
/// </summary>
/// <param name="inputItems"></param>
/// <returns></returns>
Task<int> ModifyTimeSpansAsync(List<WarningRuleEntity> inputItems);
/// <summary>
/// 启用预警
/// </summary>
/// <param name="warning"></param>
/// <returns></returns>
Task<bool> TurnWarningAsync(WarningEntity warning);
/// <summary>
/// 获取预警,include factors, notifies
/// </summary>
/// <param name="warningId"></param>
/// <returns></returns>
Task<WarningEntity?> GetByIdAsync(int warningId);
}

View File

@ -0,0 +1,6 @@
namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate;
public class RuleAreaEntity
{
}

View File

@ -0,0 +1,77 @@
using SJZY.Track.Domain.Enums;
using Volo.Abp;
namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate;
[SugarTable("t_warning")]
public class WarningEntity: IFullAuditedBasicEntitys, IAggregateRoot
{
/// <summary>
/// 自增主键
/// </summary>
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
/// <summary>
/// 子系统id
/// </summary>
[SugarColumn(ColumnName = "SubSystemId")]
public int SubSystemId { get; set; }
/// <summary>
/// 客户id
/// </summary>
[SugarColumn(ColumnName = "CustomerId")]
public int CustomerId { get; set; }
/// <summary>
/// 规则模板名称,KPI名称
/// </summary>
[SugarColumn(ColumnName = "Name")]
public string Name { get; set; } = null!;
/// <summary>
/// 因素类型排序
/// </summary>
[SugarColumn(ColumnName = "FactorTypeSort")]
public string FactorTypeSort { get; set; } = null!;
/// <summary>
/// 是否启用,默认否
/// </summary>
[SugarColumn(ColumnName = "IsEnable")]
public bool IsEnable { get; set; }
/// <summary>
/// 预警规则
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<WarningRuleEntity> WarningRules { get; set; } = new();
/// <summary>
/// 预警因素
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<WarningFactorEntity> WarningFactors { get; set; } = new();
/// <summary>
/// 预警通知
/// </summary>
[SugarColumn(IsIgnore = true)]
public List<WarningNotifyEntity> WarningNotifies { get; set; } = new();
public void SetName(string name)
{
Name = name;
}
public void SetEnable(bool isEnable)
{
if (IsEnable == isEnable)
{
throw new BusinessException(message: $"预警已经是{(isEnable ? "启用" : "禁用")}状态");
}
IsEnable = isEnable;
}
}

View File

@ -0,0 +1,31 @@
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate;
[SugarTable("t_warning_factor")]
public class WarningFactorEntity : IFullAuditedBasicEntitys, IEntity
{
/// <summary>
/// 自增主键
/// </summary>
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
/// <summary>
/// 预警id
/// </summary>
[SugarColumn(ColumnName = "WarningId")]
public int WarningId { get; set; }
/// <summary>
/// 因素类型(1货物类型,2月份,3分区,4是否中转,5起运港口,6销售产品)
/// </summary>
[SugarColumn(ColumnName = "Type")]
public RuleFactorType Type { get; set; }
/// <summary>
/// 预警因素值(json数组字符串)
/// </summary>
[SugarColumn(ColumnName = "Value")]
public string Value { get; set; } = null!;
}

View File

@ -0,0 +1,49 @@
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate;
[SugarTable("t_warning_notify")]
public class WarningNotifyEntity : IFullAuditedBasicEntitys, IEntity
{
/// <summary>
/// 自增主键
/// </summary>
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
/// <summary>
/// 预警id
/// </summary>
[SugarColumn(ColumnName = "WarningId")]
public int WarningId { get; set; }
/// <summary>
/// 起始轨迹状态id
/// </summary>
[SugarColumn(ColumnName = "StartTrackStatusId", IsNullable = false)]
public int StartTrackStatusId { get; set; }
/// <summary>
/// 终止轨迹状态id
/// </summary>
[SugarColumn(ColumnName = "EndTrackStatusId", IsNullable = false)]
public int EndTrackStatusId { get; set; }
/// <summary>
/// 通知方式
/// </summary>
[SugarColumn(ColumnName = "NotifyMode")]
public NotifyMode NotifyMode { get; set; }
/// <summary>
/// 通知模板
/// </summary>
[SugarColumn(ColumnName = "NotifyTemplate", Length = 256)]
public string NotifyTemplate { get; set; } = null!;
/// <summary>
/// 通知人
/// </summary>
[SugarColumn(ColumnName = "NotifyUserId")]
public int NotifyUserId { get; set; }
}

View File

@ -0,0 +1,95 @@
namespace SJZY.Track.Domain.Aggregates.ForewarningAggregate;
[SugarTable("t_warning_rule")]
public class WarningRuleEntity : IFullAuditedBasicEntitys, IEntity
{
/// <summary>
/// 自增主键
/// </summary>
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
/// <summary>
/// 预警id
/// </summary>
[SugarColumn(ColumnName = "WarningId")]
public int WarningId { get; set; }
/// <summary>
/// 时效(小时)
/// </summary>
[SugarColumn(ColumnName = "TotalTimeSpan")]
public float TotalTimeSpan { get; set; }
/// <summary>
/// 提醒的时间(小时)
/// </summary>
[SugarColumn(ColumnName = "WarnTimeSpan")]
public float WarnTimeSpan { get; set; }
/// <summary>
/// 提醒次数
/// </summary>
[SugarColumn(ColumnName = "WarnCount")]
public int WarnCount { get; set; }
/// <summary>
/// 起始轨迹状态id
/// </summary>
[SugarColumn(ColumnName = "StartTrackStatusId")]
public int StartTrackStatusId { get; set; }
/// <summary>
/// 起始轨迹状态(冗余)
/// </summary>
[SugarColumn(ColumnName = "StartTrackStatusId")]
public string StartTrackStatus { get; set; } = null!;
/// <summary>
/// 终止轨迹状态id
/// </summary>
[SugarColumn(ColumnName = "EndTrackStatusId")]
public int EndTrackStatusId { get; set; }
/// <summary>
/// 终止轨迹状态(冗余)
/// </summary>
[SugarColumn(ColumnName = "EndTrackStatus")]
public string EndTrackStatus { get; set; } = null!;
/// <summary>
/// 货物类型
/// </summary>
[SugarColumn(ColumnName = "CargoType", Length = 64)]
public string? CargoType { get; set; }
/// <summary>
/// 月份
/// </summary>
[SugarColumn(ColumnName = "Months", Length = 25)]
public string? Months { get; set; }
/// <summary>
/// 区域
/// </summary>
[SugarColumn(ColumnName = "AreaId")]
public int? AreaId { get; set; }
/// <summary>
/// 是否中转
/// </summary>
[SugarColumn(ColumnName = "IsTransit")]
public bool? IsTransit { get; set; }
/// <summary>
/// 起运港口
/// </summary>
[SugarColumn(ColumnName = "LeavePort")]
public string? LeavePort { get; set; }
/// <summary>
/// 产品编码
/// </summary>
[SugarColumn(ColumnName = "ProductCode")]
public string? ProductCode { get; set; }
}

View File

@ -0,0 +1,6 @@
namespace SJZY.Track.Domain.Aggregates;
public class KPIAreaAggregate
{
}

View File

@ -0,0 +1,68 @@
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
public interface IKpiAreaRepository : IBasicRepository<RuleAreaEntity>
{
/// <summary>
/// 创建分区
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
ValueTask<int> CreateAreaAsync(RuleAreaEntity entity);
/// <summary>
/// 添加分区信息
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
ValueTask<int> AddAreaInfoAsync(RuleAreaInfoEntity entity);
/// <summary>
/// 删除分区信息
/// </summary>
/// <param name="areaInfoId">分区信息id</param>
/// <returns></returns>
Task<int> DeleteAreaInfo(int areaInfoId);
/// <summary>
/// 删除分区
/// </summary>
/// <param name="areaId">分区id</param>
/// <returns></returns>
ValueTask DeleteArea(int areaId);
/// <summary>
/// 更新区域信息
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
Task<int> UpdateAreaInfoAsync(RuleAreaInfoEntity entity);
/// <summary>
/// 获得分区
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
Task<UnifyPageResultDto<AreaDto>> GetAreas(QueryAreaRequest query);
/// <summary>
/// 获取分区信息
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
Task<UnifyPageResultDto<RuleAreaInfoDto>> GetAreaInfos(QueryAreaInfoRequest query);
/// <summary>
/// 获取分区信息通过id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<RuleAreaInfoEntity?> GetAreaInfoById(int id);
/// <summary>
/// 获取所有区域信息
/// </summary>
/// <returns></returns>
List<KpiAreaInfo>? GetAreaInfosByAreaId(int areaId);
}

View File

@ -0,0 +1,50 @@
namespace SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
[SugarTable("t_area", TableDescription = "KPI分区主表")]
public class RuleAreaEntity : IAggregateRoot
{
/// <summary>
/// 自增主键
/// </summary>
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "自增主键")]
public int Id { get; set; }
/// <summary>
/// 子系统id
/// </summary>
[SugarColumn(ColumnName = "SubSystemId", ColumnDescription = "子系统id")]
public int SubSystemId { get; set; }
/// <summary>
/// 分区名称KPI名称
/// </summary>
[SugarColumn(ColumnName = "Name", Length = 64, ColumnDescription = "分区名称KPI名称")]
public string Name { get; set; }
/// <summary>
/// 创建人
/// </summary>
[SugarColumn(ColumnName = "CreateUserId", ColumnDescription = "创建人")]
public int CreateUserId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[SugarColumn(ColumnName = "CreateTime", ColumnDescription = "创建时间")]
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新人
/// </summary>
[SugarColumn(ColumnName = "UpdateUserId", ColumnDescription = "更新人")]
public int UpdateUserId { get; set; }
/// <summary>
/// 更新时间
/// </summary>
[SugarColumn(ColumnName = "UpdateTime", ColumnDescription = "更新时间")]
public DateTime UpdateTime { get; set; }
[SugarColumn(IsIgnore = true)]
public List<RuleAreaInfoEntity> AreaInfos { get; set; }
}

View File

@ -0,0 +1,77 @@
namespace SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
[SugarTable("t_area_details", TableDescription = "kpi 区域子表")]
public class RuleAreaInfoEntity : IEntity
{
/// <summary>
/// 自增主键
/// </summary>
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "自增主键")]
public int Id { get; set; }
/// <summary>
/// t_rule_area 表id
/// </summary>
[SugarColumn(ColumnName = "AreaId", ColumnDescription = "t_rule_area 表id")]
public int AreaId { get; set; }
/// <summary>
/// 国家或地区二字码
/// </summary>
[SugarColumn(ColumnName = "CountryCode", Length = 128, ColumnDescription = "国家或地区二字码")]
public string CountryCode { get; set; }
/// <summary>
/// 国家或地区名称
/// </summary>
[SugarColumn(ColumnName = "CountryName", Length = 128, ColumnDescription = "国家或地区名称")]
public string CountryName { get; set; }
/// <summary>
/// 州或者省份
/// </summary>
[SugarColumn(ColumnName = "Province", Length = 128, IsNullable = true, ColumnDescription = "州或者省份")]
public string Province { get; set; }
/// <summary>
/// 城市
/// </summary>
[SugarColumn(ColumnName = "City", Length = 128, IsNullable = true, ColumnDescription = "城市")]
public string City { get; set; }
/// <summary>
/// 起始邮编
/// </summary>
[SugarColumn(ColumnName = "LeftPostcode", Length = 10, IsNullable = true, ColumnDescription = "起始邮编")]
public string LeftPostcode { get; set; }
/// <summary>
/// 截止邮编
/// </summary>
[SugarColumn(ColumnName = "RightPostcode", Length = 10, IsNullable = true, ColumnDescription = "截止邮编")]
public string RightPostcode { get; set; }
/// <summary>
/// 创建人
/// </summary>
[SugarColumn(ColumnName = "CreateUserId", ColumnDescription = "创建人")]
public int CreateUserId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[SugarColumn(ColumnName = "CreateTime", ColumnDescription = "创建时间")]
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新人
/// </summary>
[SugarColumn(ColumnName = "UpdateUserId", IsNullable = false, ColumnDescription = "更新人")]
public int UpdateUserId { get; set; }
/// <summary>
/// 更新时间
/// </summary>
[SugarColumn(ColumnName = "UpdateTime", IsNullable = false, ColumnDescription = "更新时间")]
public DateTime UpdateTime { get; set; }
}

View File

@ -29,7 +29,21 @@ public interface IOrderRepository : IBasicRepository<OrderEntity>
/// 更新推送预警单
/// </summary>
/// <returns></returns>
Task<int> PushWarnOrdersAsync();
Task<int> PushWarnOrdersAsync(int pageSize,DateTime createTime);
/// <summary>
/// 更新推送预警单
/// </summary>
/// <returns></returns>
Task<int> PushWarnOrdersAsync(int pageSize, int pageindex, DateTime createTime);
/// <summary>
/// 更新缺失标记,时区缺失和发生地缺失标记,1表示时区,2表示发生地,3表示两种
/// </summary>
/// <param name="ids">订单id</param>
/// <param name="newValue">值</param>
/// <returns></returns>
Task<int> UpdateBatchIdsAsync(List<int> ids, int newValue);
#region 订单关联

View File

@ -132,6 +132,48 @@ public class OrderEntity : IFullAuditedBasicEntitys, IAggregateRoot
[SugarColumn(ColumnName = "LeavePort")]
public string? LeavePort { get; set; }
/// <summary>
/// 货物类型
/// </summary>
[SugarColumn(ColumnName = "CargoType")]
public string? CargoType { get; set; }
/// <summary>
/// 是否中转
/// </summary>
[SugarColumn(ColumnName = "IsTransit")]
public bool? IsTransit { get; set; }
/// <summary>
/// 产品编码
/// </summary>
[SugarColumn(ColumnName = "ProductCode")]
public string? ProductCode { get; set; }
/// <summary>
/// 时区缺失和发生地缺失标记,1表示时区,2表示发生地,3表示两种
/// </summary>
[SugarColumn(ColumnName = "DeficiencyTag")]
public int? DeficiencyTag { get; set; }
/// <summary>
/// 到达邮编
/// </summary>
[SugarColumn(ColumnName = "ArrivalPostCode")]
public string? ArrivalPostCode { get; set; }
/// <summary>
/// 到达州省
/// </summary>
[SugarColumn(ColumnName = "ArrivalProvince")]
public string? ArrivalProvince { get; set; }
/// <summary>
/// 是否触发拉取,1表示触发
/// </summary>
[SugarColumn(ColumnName = "IsTriggerPull")]
public short? IsTriggerPull { get; set; }
[SugarColumn(IsIgnore = true)] public List<OrderFlightEntity> OrderFlights { get; set; }
[SugarColumn(IsIgnore = true)] public List<OrderTemplateEntity> OrderTemplates { get; set; }

View File

@ -0,0 +1,34 @@
using SJZY.Track.Domain.Dtos.TrackNode;
namespace SJZY.Track.Domain.Aggregates.TrackNodeAggregate;
public interface ITrackNodeRepository:IBasicRepository<TrackNodeEntity>
{
/// <summary>
/// 分页查询
/// </summary>
/// <param name="queryParam"></param>
/// <returns></returns>
Task<UnifyPageResultDto<TrackNodeDto>> GetPageList(TrackNodePageDto queryParam);
/// <summary>
/// 分页查询字典
/// </summary>
/// <param name="queryParam"></param>
/// <returns></returns>
Task<UnifyPageResultDto<TrackNodeDicDto>> GetPageDicList(TrackNodePageDto queryParam);
/// <summary>
/// 删除
/// </summary>
/// <param name="inputId"></param>
/// <returns></returns>
Task<int> DeleteById(long inputId);
/// <summary>
/// 获取详情
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<TrackNodeDto> GetDetail(long id);
}

View File

@ -0,0 +1,32 @@
namespace SJZY.Track.Domain.Aggregates.TrackNodeAggregate;
/// <summary>
/// 轨迹节点主表
/// </summary>
[SugarTable("t_track_node", TableDescription = "轨迹节点主表")]
public class TrackNodeEntity : IFullAuditedBasicEntitys, IAggregateRoot
{
/// <summary>
/// 自增主键
/// </summary>
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "自增主键")]
public long Id { get; set; }
/// <summary>
/// 子系统id
/// </summary>
[SugarColumn(ColumnName = "SubSystemId", ColumnDescription = "子系统id")]
public int SubSystemId { get; set; }
/// <summary>
/// 节点名称
/// </summary>
[SugarColumn(ColumnName = "NodeName", Length = 32, ColumnDescription = "节点名称")]
public string? NodeName { get; set; }
/// <summary>
/// 节点英文名称
/// </summary>
[SugarColumn(ColumnName = "NodeEnName", Length = 32, ColumnDescription = "节点英文名称")]
public string? NodeEnName { get; set; }
}

View File

@ -71,4 +71,10 @@ public class TrackPullContentConfigEntity : IFullAuditedBasicEntitys, IEntity
/// </summary>
[SugarColumn(ColumnName = "IsCompleted")]
public bool IsCompleted { get; set; }
/// <summary>
/// 轨迹时区发生地参数值
/// </summary>
[SugarColumn(ColumnName = "TrackZonePlaceValue")]
public int? TrackZonePlaceValue { get; set; }
}

View File

@ -89,4 +89,10 @@ public class TrackPushPullParamsConfigEntity : IFullAuditedBasicEntitys, IEntity
/// </summary>
[SugarColumn(ColumnName = "ExpandFieldsName")]
public string? ExpandFieldsName { get; set; }
/// <summary>
/// 轨迹内容代码
/// </summary>
[SugarColumn(ColumnName = "TrackContentCode")]
public string? TrackContentCode { get; set; }
}

View File

@ -1,4 +1,6 @@
namespace SJZY.Track.Domain.Aggregates.TrackPushAggregate;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Domain.Aggregates.TrackPushAggregate;
/// <summary>
/// 轨迹推送配置表
@ -53,4 +55,22 @@ public class TrackPushConfigEntity : IFullAuditedBasicEntitys, IAggregateRoot
/// </summary>
[SugarColumn(ColumnName = "IsAllPush")]
public bool IsAllPush { get; set; }
/// <summary>
/// 日期时间格式类型
/// </summary>
[SugarColumn(ColumnName = "DateTimeFormatType")]
public DateTimeFormatEnum DateTimeFormatType { get; set; }
/// <summary>
/// 日期时间格式模板
/// </summary>
[SugarColumn(ColumnName = "DateTimeFormat")]
public string? DateTimeFormat { get; set; }
/// <summary>
/// 时区格式格式模板
/// </summary>
[SugarColumn(ColumnName = "TimezoneFormat")]
public string? TimezoneFormat { get; set; }
}

View File

@ -25,7 +25,7 @@ public class TrackStatusEntity : IFullAuditedBasicEntitys, IAggregateRoot
public string EnStatus { get; set; } = null!;
/// <summary>
/// 所属节点(主数据枚举ServiceNode)
/// 所属节点(来自表 t_track_node) 之前是枚举,后续调整为表
/// </summary>
[SugarColumn(ColumnName = "Node")]
public int Node { get; set; }

View File

@ -11,4 +11,9 @@ public partial class GlobalConst
/// 轨迹推送记录 es索引名称(模板)
/// </summary>
public const string TrackPushRecordsIndex = "sjzy_track_push_records";
/// <summary>
/// 预警规则 es索引名称(模板)
/// </summary>
public const string RuleFactorIndex = "sjzy_rule_factor";
}

View File

@ -21,4 +21,44 @@ public partial class GlobalConst
/// 轨迹拉取配置信息redis key前缀
/// </summary>
public const string TrackPullContentKeyPrefix = "TrackPullContent";
/// <summary>
/// 预警通知redis key
/// </summary>
public const string ForewarningNotifyKey = "ForewarningNotify";
/// <summary>
/// 预警通知剩余次数redis key
/// </summary>
public const string ForewarningNotifyCntKey = "ForewarningNotifyCnt";
/// <summary>
/// kpi区域redis key前缀
/// </summary>
public const string KpiAreaKeyPrefix = "KpiArea";
/// <summary>
/// 城市时区redis key前缀
/// </summary>
public const string CityTimezoneKeyPrefix = "CityTimezone";
/// <summary>
/// 港口时区redis key前缀
/// </summary>
public const string PortTimezoneKeyPrefix = "PortTimezone";
/// <summary>
/// 发布订阅通知频道Key
/// </summary>
public const string PublishNotifyChannelKey = "PublishNotifyChannel";
/// <summary>
/// 轨迹区域reids key
/// </summary>
public const string TrackAreaKeyPrefix = "TrackArea";
/// <summary>
/// 轨迹预警数据redis key前缀
/// </summary>
public const string TrackWarningKeyPrefix = "TrackWarning";
}

View File

@ -0,0 +1,14 @@
namespace SJZY.Track.Domain.Dtos.Common;
public class CityTimezoneDto
{
/// <summary>
/// 城市英文名称
/// </summary>
public string? CityEnName { get; set; }
/// <summary>
/// 时区
/// </summary>
public string Timezone { get; set; }
}

View File

@ -0,0 +1,14 @@
namespace SJZY.Track.Domain.Dtos.Common;
public class PortTimezoneDto
{
/// <summary>
/// 城市英文名称
/// </summary>
public string? PortCode { get; set; }
/// <summary>
/// 时区
/// </summary>
public string Timezone { get; set; }
}

View File

@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class AddRuleAreaInfoRequest
{
/// <summary>
/// 国家二字码,必填项。
/// </summary>
[Required]
public string CountryCode { get; set; }
/// <summary>
/// 国家名称
/// </summary>
[Required]
public string CountryName { get; set; }
/// <summary>
/// 省份名称,可为空。
/// </summary>
public string? Province { get; set; }
/// <summary>
/// 城市名称,可为空。
/// </summary>
public string? City { get; set; }
/// <summary>
/// 左侧邮政编码,可为空。
/// </summary>
public string? LeftPostCode { get; set; }
/// <summary>
/// 右侧邮政编码,可为空。
/// </summary>
public string? RightPostCode { get; set; }
/// <summary>
/// 区域ID,必填项。
/// </summary>
[Required]
public int AreaId { get; set; }
}

View File

@ -0,0 +1,29 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class AreaDto
{
/// <summary>
/// 区域id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 子系统id
/// </summary>
public int SubSystemId { get; set; }
/// <summary>
/// 系统名称
/// </summary>
public string SystemName { get; set; }
/// <summary>
/// 区域名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
}

View File

@ -0,0 +1,45 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class RuleAreaInfoDto
{
/// <summary>
/// 分区信息id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 分区id
/// </summary>
public int AreaId { get; set; }
/// <summary>
/// 国家或地区 二字码
/// </summary>
public string CountryCode { get; set; }
/// <summary>
/// 国家名称
/// </summary>
public string CountryName { get; set; }
/// <summary>
/// 国家id
/// </summary>
public int CountryId { get; set; }
/// <summary>
/// 省份
/// </summary>
public string Province { get; set; }
/// <summary>
/// 城市
/// </summary>
public string City { get; set; }
/// <summary>
/// 开始邮编
/// </summary>
public string LeftPostcode { get; set; }
public string RightPostcode { get; set; }
}

View File

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class CreateRuleAreaRequest
{
/// <summary>
/// 系统id
/// </summary>
[Required]
public int SystemId { get; set; }
/// <summary>
/// 名称
/// </summary>
[Required]
public string Name { get; set; }
}

View File

@ -0,0 +1,131 @@
using System.Text;
using NUglify.Helpers;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class GetWarningNotifiesVo
{
/// <summary>
/// 规则 id
/// </summary>
public int RuleId { get; set; }
/// <summary>
/// 预警名称
/// </summary>
public string WarningName { get; set; } = null!;
/// <summary>
/// 时效(小时)
/// </summary>
public float TotalTimeSpan { get; set; }
/// <summary>
/// 提醒的时间(小时)
/// </summary>
public float WarnTimeSpan { get; set; }
/// <summary>
/// 提醒次数
/// </summary>
public int WarnCount { get; set; }
/// <summary>
/// 起始轨迹状态(冗余)
/// </summary>
public string StartTrackStatus { get; set; } = null!;
/// <summary>
/// 终止轨迹状态(冗余)
/// </summary>
public string EndTrackStatus { get; set; } = null!;
/// <summary>
/// 货物类型
/// </summary>
public string? CargoType { get; set; }
/// <summary>
/// 月份
/// </summary>
public string? Months { get; set; }
/// <summary>
/// 区域
/// </summary>
public string? AreaName { get; set; }
/// <summary>
/// 是否中转
/// </summary>
public bool? IsTransit { get; set; }
/// <summary>
/// 起运港口
/// </summary>
public string? LeavePort { get; set; }
/// <summary>
/// 产品编码
/// </summary>
public string? ProductCode { get; set; }
/// <summary>
/// 通知方式
/// </summary>
public NotifyMode NotifyMode { get; set; }
/// <summary>
/// 通知模板
/// </summary>
public string NotifyTemplate { get; set; } = null!;
/// <summary>
/// 通知人
/// </summary>
public int NotifyUserId { get; set; }
/// <summary>
/// 因素类型排序
/// </summary>
public string FactorTypeSort { get; set; } = null!;
public override string ToString()
{
// {起飞-落地+普货+1/2/3/4+美国 时效9,预警时效7,次数3}
var res = new StringBuilder();
res.Append('{');
res.Append($"{StartTrackStatus}-{EndTrackStatus}");
FactorTypeSort.Split(",").Select(Enum.Parse<RuleFactorType>).ForEach(factorType =>
{
switch (factorType)
{
case RuleFactorType.CargoType:
res.Append($"+{CargoType}");
break;
case RuleFactorType.Months:
res.Append($"+{Months}");
break;
case RuleFactorType.Area:
res.Append($"+{AreaName}");
break;
case RuleFactorType.IsTransit:
res.Append($"+{(IsTransit?? false? "中转":"直飞")}");
break;
case RuleFactorType.LeavePort:
res.Append($"+{LeavePort}");
break;
case RuleFactorType.ProductCode:
res.Append($"+{ProductCode}");
break;
default:
throw new ArgumentOutOfRangeException(nameof(factorType), factorType, null);
}
});
res.Append($" 时效{TotalTimeSpan},预警时效{WarnTimeSpan},次数{WarnCount}}}");
return res.ToString();
}
}

View File

@ -0,0 +1,14 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class GetWarningPageInputVo: PageInfoInput
{
/// <summary>
/// 子系统id
/// </summary>
public int? SystemId { get; set; }
/// <summary>
/// 关键字(客户or名称)
/// </summary>
public string? KeyWord { get; set; }
}

View File

@ -0,0 +1,19 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class GetWarningRuleListInput: PageInfoInput
{
/// <summary>
/// kpi 预警规则模板id
/// </summary>
public int RuleId { get; set; }
/// <summary>
/// 排序方式,不填不排序
/// </summary>
public string? SortField { get; set; }
/// <summary>
/// 排序方式
/// </summary>
public bool IsAsc { get; set; } = false;
}

View File

@ -0,0 +1,16 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public interface IForewarningFactorValue
{
public string? CargoType { get; set; }
public int? Month { get; set; }
public int? Area { get; set; }
public bool? IsTransit { get; set; }
public string? LeavePort { get; set; }
public string? ProductCode { get; set; }
}

View File

@ -0,0 +1,31 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class KpiAreaInfo
{
public int AreaId { get; set; }
/// <summary>
/// 国家或地区
/// </summary>
public string Country { get; set; } = null!;
/// <summary>
/// 省份
/// </summary>
public string? Province { get; set; }
/// <summary>
/// 城市
/// </summary>
public string? City { get; set; }
/// <summary>
/// 开始邮编
/// </summary>
public string? LeftPostcode { get; set; }
/// <summary>
/// 截止邮编
/// </summary>
public string? RightPostcode { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class QueryAreaInfoRequest: PageInfoInput
{
/// <summary>
/// 区域id
/// </summary>
public int AreaId { get; set; }
}

View File

@ -0,0 +1,14 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class QueryAreaRequest:PageInfoInput
{
/// <summary>
/// 分区名称
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 子系统id
/// </summary>
public int? SystemId { get; set; }
}

View File

@ -0,0 +1,3 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public record RuleAreaBaseInfo(int SystemId, int CustomerId);

View File

@ -0,0 +1,7 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class RuleCollectRequest
{
public string Name { get; set; }
}

View File

@ -0,0 +1,37 @@
using SJZY.Track.Domain.Aggregates.ForewarningAggregate;
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class RuleFactor
{
public int RuleId { get; set; }
public int SystemId { get; set; }
public int CustomerId { get; set; }
/// <summary>
/// Gets or sets the start track value.
/// </summary>
public int Start { get; set; }
/// <summary>
/// Gets or sets the end track value.
/// </summary>
public int End { get; set; }
/// <summary>
/// Gets or sets the user to notify.
/// </summary>
public string NotifyUser { get; set; }
/// <summary>
/// Gets or sets the notification template.
/// </summary>
public string NotifyTemplate { get; set; }
/// <summary>
/// 获取或设置因素项模型的列表。
/// </summary>
public List<FactorItemModel> FactorItems { get; set; }
}

View File

@ -0,0 +1,46 @@
using System.ComponentModel.DataAnnotations;
namespace SJZY.Track.Domain.Dtos.Forewarning;
/// <summary>
/// 更新规则区域信息请求
/// </summary>
public class UpdateRuleAreaInfoRequest
{
/// <summary>
/// 区域信息ID
/// </summary>
public int Id { get; set; }
/// <summary>
/// 国家二字码,必填项。
/// </summary>
[Required]
public string CountryCode { get; set; }
/// <summary>
/// 国家名称,必填项。
/// </summary>
[Required]
public string CountryName { get; set; }
/// <summary>
/// 省份名称,可为空。
/// </summary>
public string? Province { get; set; }
/// <summary>
/// 城市名称,可为空。
/// </summary>
public string? City { get; set; }
/// <summary>
/// 左侧邮政编码,可为空。
/// </summary>
public string? LeftPostCode { get; set; }
/// <summary>
/// 右侧邮政编码,可为空。
/// </summary>
public string? RightPostCode { get; set; }
}

View File

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class UpdateRuleAreaRequest
{
/// <summary>
/// 名称
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// 区域id
/// </summary>
public int Id { get; set; }
}

View File

@ -0,0 +1,17 @@
using Newtonsoft.Json.Linq;
using SJZY.Track.Domain.Enums;
namespace SJZY.Track.Domain.Dtos.Forewarning;
public class WarningFactorDto
{
/// <summary>
/// 因素类型
/// </summary>
public RuleFactorType FactorType { get; set; }
/// <summary>
/// 因素值
/// </summary>
public JArray FactorValue { get; set; } = new();
}

Some files were not shown because too many files have changed in this diff Show More