feat: 轨迹拉取定时任务

This commit is contained in:
xiaolipro 2026-03-05 17:59:34 +08:00
parent cb8edf4cbb
commit df0e275a4b
7 changed files with 76 additions and 54 deletions

View File

@ -4,5 +4,8 @@
"Default": "Debug", "Default": "Debug",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
},
"FeiShuNotice": {
"EnableFeiShuExceptionSubscribe": false
} }
} }

View File

@ -13,11 +13,16 @@ public class TrackTaskComponentHostedService : IHostedService
private readonly IOptionsMonitor<TrackAppOptions> _optionsMonitor; private readonly IOptionsMonitor<TrackAppOptions> _optionsMonitor;
private TrackAppOptions? _oldOptions; private TrackAppOptions? _oldOptions;
private readonly ILogger<TrackTaskComponentHostedService> _logger; private readonly ILogger<TrackTaskComponentHostedService> _logger;
private readonly IRecurringJobManager _recurringJobManager;
public TrackTaskComponentHostedService(IOptionsMonitor<TrackAppOptions> optionsMonitor, ILogger<TrackTaskComponentHostedService> logger) public TrackTaskComponentHostedService(
IOptionsMonitor<TrackAppOptions> optionsMonitor,
ILogger<TrackTaskComponentHostedService> logger,
IRecurringJobManager recurringJobManager)
{ {
_optionsMonitor = optionsMonitor; _optionsMonitor = optionsMonitor;
_logger = logger; _logger = logger;
_recurringJobManager = recurringJobManager;
var onChanged = DebounceHelper.WrapMethod(TimeSpan.FromSeconds(1), () => { ReloadConfig(optionsMonitor); }); var onChanged = DebounceHelper.WrapMethod(TimeSpan.FromSeconds(1), () => { ReloadConfig(optionsMonitor); });
optionsMonitor.OnChange(_ => onChanged.Debounce()); optionsMonitor.OnChange(_ => onChanged.Debounce());
} }
@ -43,7 +48,7 @@ public class TrackTaskComponentHostedService : IHostedService
if ((_oldOptions?.TrackPullTaskMinuteInterval ?? 0) != newVal.TrackPullTaskMinuteInterval) if ((_oldOptions?.TrackPullTaskMinuteInterval ?? 0) != newVal.TrackPullTaskMinuteInterval)
{ {
var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.TrackPullTaskLevel]; var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.TrackPullTaskLevel];
RecurringJob.AddOrUpdate<TrackPullTask>("Execute_TrackPullTask", _recurringJobManager.AddOrUpdate<TrackPullTask>("Execute_TrackPullTask",
q, q,
x => x.RefreshForTaskAsync(), x => x.RefreshForTaskAsync(),
$"*/{newVal.TrackPullTaskMinuteInterval} * * * *"); $"*/{newVal.TrackPullTaskMinuteInterval} * * * *");

View File

@ -17,11 +17,16 @@ public class WarningTaskComponentHostedService : IHostedService
private readonly IOptionsMonitor<TrackWarningTaskOptions> _optionsMonitor; private readonly IOptionsMonitor<TrackWarningTaskOptions> _optionsMonitor;
private TrackWarningTaskOptions? _oldOptions; private TrackWarningTaskOptions? _oldOptions;
private readonly ILogger<WarningTaskComponentHostedService> _logger; private readonly ILogger<WarningTaskComponentHostedService> _logger;
private readonly IRecurringJobManager _recurringJobManager;
public WarningTaskComponentHostedService(IOptionsMonitor<TrackWarningTaskOptions> optionsMonitor, ILogger<WarningTaskComponentHostedService> logger) public WarningTaskComponentHostedService(
IOptionsMonitor<TrackWarningTaskOptions> optionsMonitor,
ILogger<WarningTaskComponentHostedService> logger,
IRecurringJobManager recurringJobManager)
{ {
_optionsMonitor = optionsMonitor; _optionsMonitor = optionsMonitor;
_logger = logger; _logger = logger;
_recurringJobManager = recurringJobManager;
// 防抖配置变更包装 // 防抖配置变更包装
var onChanged = DebounceHelper.WrapMethod(TimeSpan.FromSeconds(1), () => { ReloadConfig(optionsMonitor); }); var onChanged = DebounceHelper.WrapMethod(TimeSpan.FromSeconds(1), () => { ReloadConfig(optionsMonitor); });
optionsMonitor.OnChange(_ => onChanged.Debounce()); optionsMonitor.OnChange(_ => onChanged.Debounce());
@ -50,7 +55,7 @@ public class WarningTaskComponentHostedService : IHostedService
{ {
var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.ScanWarningNotifyTaskLevel]; var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.ScanWarningNotifyTaskLevel];
// 预警通知任务扫描 // 预警通知任务扫描
RecurringJob.AddOrUpdate<IWarningTaskScanner>("Scan_WarningNotifyTask", _recurringJobManager.AddOrUpdate<IWarningTaskScanner>("Scan_WarningNotifyTask",
q, q,
x => x.ScanNotifyTaskAndSchedulingAsync(), x => x.ScanNotifyTaskAndSchedulingAsync(),
$"0 */{newVal.ScanWarningNotifyTaskHourInterval} * * *"); $"0 */{newVal.ScanWarningNotifyTaskHourInterval} * * *");
@ -60,7 +65,7 @@ public class WarningTaskComponentHostedService : IHostedService
{ {
var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.ScanReturnWarningRetryPushTaskLevel]; var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.ScanReturnWarningRetryPushTaskLevel];
// 回传预警通知重试推送任务扫描 // 回传预警通知重试推送任务扫描
RecurringJob.AddOrUpdate<IWarningTaskScanner>("Scan_ReturnWarningRetryPushTask", _recurringJobManager.AddOrUpdate<IWarningTaskScanner>("Scan_ReturnWarningRetryPushTask",
q, q,
x => x.ScanReturnRetryPushTaskAndSchedulingAsync(), x => x.ScanReturnRetryPushTaskAndSchedulingAsync(),
$"*/{newVal.ScanReturnWarningRetryPushTaskMinuteInterval} * * * *"); $"*/{newVal.ScanReturnWarningRetryPushTaskMinuteInterval} * * * *");
@ -70,7 +75,7 @@ public class WarningTaskComponentHostedService : IHostedService
{ {
var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.ScanTransportWarningDetailsGenerateTaskLevel]; var q = GlobalConst.TaskQueueLevelDict[_optionsMonitor.CurrentValue.ScanTransportWarningDetailsGenerateTaskLevel];
// 运输预警明细生成任务扫描 // 运输预警明细生成任务扫描
RecurringJob.AddOrUpdate<IWarningTaskScanner>("Scan_TransportWarningDetailsGenerateTask", _recurringJobManager.AddOrUpdate<IWarningTaskScanner>("Scan_TransportWarningDetailsGenerateTask",
q, q,
x => x.ScanTransportDetailsGenerateTaskAndSchedulingAsync(), x => x.ScanTransportDetailsGenerateTaskAndSchedulingAsync(),
$"*/{newVal.ScanTransportWarningDetailsGenerateTaskMinuteInterval} * * * *"); $"*/{newVal.ScanTransportWarningDetailsGenerateTaskMinuteInterval} * * * *");

View File

@ -31,4 +31,9 @@ public class TrackAppOptions
/// 拉取优先级 /// 拉取优先级
/// </summary> /// </summary>
public int TrackPullTaskLevel { get; set; } public int TrackPullTaskLevel { get; set; }
/// <summary>
/// Hangfire 工作线程数
/// </summary>
public int HangfireWorkerCount { get; set; }
} }

View File

@ -3,6 +3,7 @@ using SqlSugar;
using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.OrderAggregate;
using TrackCenter.Domain.Aggregates.TrackContentAggregate; using TrackCenter.Domain.Aggregates.TrackContentAggregate;
using TrackCenter.Domain.Aggregates.TrackFlagAggregate; using TrackCenter.Domain.Aggregates.TrackFlagAggregate;
using TrackCenter.Domain.Aggregates.TrackPullAggregate;
using TrackCenter.Domain.Aggregates.TrackStatusAggregate; using TrackCenter.Domain.Aggregates.TrackStatusAggregate;
using TrackCenter.Domain.Dtos.Order; using TrackCenter.Domain.Dtos.Order;
using TrackCenter.Domain.Dtos.TrackContents; using TrackCenter.Domain.Dtos.TrackContents;
@ -51,14 +52,17 @@ public class ProvideQueries : SqlSugarRootlessRepository<TrackCenterDbContext>,
{ {
return await Context.Queryable<OrderEntity>() return await Context.Queryable<OrderEntity>()
.InnerJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId) .InnerJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId)
.LeftJoin<VAppEntity>((a, b, c) => a.AppKey == c.AppKey) .LeftJoin<TrackPullConfigEntity>((a, b, c) =>
a.AppKey == c.AppKey && a.ServiceProviderCode == c.ServiceProviderCode)
.LeftJoin<TrackPullPriorityEntity>((a, b, c, d) => c.Id == d.TrackPullConfigId && a.Customer == d.Customer)
.WhereIF(!appKey.IsNullOrWhiteSpace(), a => a.AppKey == appKey) .WhereIF(!appKey.IsNullOrWhiteSpace(), a => a.AppKey == appKey)
.Where((a, b) => !a.IsCompleted && orderNos.Contains(a.OrderNo) .Where((a, b) => !a.IsCompleted && orderNos.Contains(a.OrderNo)
&& b.StartTime <= DateTime.Now && b.EndTime >= DateTime.Now) && b.StartTime <= DateTime.Now && b.EndTime >= DateTime.Now)
.Select((a, b, c) => new OrderWithServiceProviderVO .Select((a, b, c, d) => new OrderWithServiceProviderVO
{ {
//优先使用第三方的订单号,为空时才使用主单号 OrderNo = d.OrderNoType == TrackOrderNoType.客户单号 ? a.OrderNo :
OrderNo = string.IsNullOrWhiteSpace(b.OrderNo) ? a.OrderNo : b.OrderNo, d.OrderNoType == TrackOrderNoType.跟踪单号 ? a.TrackNo :
d.OrderNoType == TrackOrderNoType.其他单号 ? a.ThirdPartyOrderNumber : a.OrderNo,
OrderType = a.OrderType, OrderType = a.OrderType,
AppKey = a.AppKey, AppKey = a.AppKey,
ServiceProviderCode = b.ServiceProviderCode, ServiceProviderCode = b.ServiceProviderCode,
@ -81,9 +85,9 @@ public class ProvideQueries : SqlSugarRootlessRepository<TrackCenterDbContext>,
.WhereIF(config.Customer != GlobalConst.All, a => a.Customer == config.Customer) .WhereIF(config.Customer != GlobalConst.All, a => a.Customer == config.Customer)
.Select((a, b) => new OrderWithServiceProviderVO .Select((a, b) => new OrderWithServiceProviderVO
{ {
//优先使用第三方的订单号,为空时才使用主单号
OrderNo = config.OrderNoType == TrackOrderNoType.客户单号 ? a.OrderNo : OrderNo = config.OrderNoType == TrackOrderNoType.客户单号 ? a.OrderNo :
config.OrderNoType == TrackOrderNoType.跟踪单号 ? a.TrackNo : a.ThirdPartyOrderNumber, config.OrderNoType == TrackOrderNoType.跟踪单号 ? a.TrackNo :
config.OrderNoType == TrackOrderNoType.其他单号 ? a.ThirdPartyOrderNumber : a.OrderNo,
OrderType = a.OrderType, OrderType = a.OrderType,
AppKey = a.AppKey, AppKey = a.AppKey,
ServiceProviderCode = b.ServiceProviderCode ServiceProviderCode = b.ServiceProviderCode

View File

@ -1,6 +1,10 @@
using Hangfire;
using Hangfire.Common;
using Hangfire.States;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Fake.UnitOfWork;
using TrackCenter.Application.Dtos.TrackPull; using TrackCenter.Application.Dtos.TrackPull;
using TrackCenter.Application.Helpers; using TrackCenter.Application.Helpers;
using TrackCenter.Application.Options; using TrackCenter.Application.Options;
@ -16,27 +20,25 @@ namespace TrackCenter.Application.Services.TimerTaskss;
/// 轨迹拉取定时任务 /// 轨迹拉取定时任务
/// </summary> /// </summary>
[ApiExplorerSettings(GroupName = "定时任务")] [ApiExplorerSettings(GroupName = "定时任务")]
public class TrackPullTask : ApplicationServiceBase, ISingletonDependency public class TrackPullTask(
IBackgroundJobClient backgroundJobClient,
ITrackPullConfigRepository trackPullConfigRepository,
IOptionsMonitor<TrackAppOptions> trackAppOptions,
TrackPullCacheService trackPullCacheService,
ProvideQueries provideQueries,
InterfaceForwardService interfaceForwardService,
IOptions<InterfaceRelayParamsConfigOptions> interfaceOptions)
: ApplicationServiceBase
{ {
private readonly IServiceScopeFactory _serviceScopeFactory;
public TrackPullTask(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
/// <summary> /// <summary>
/// 定时任务刷新轨迹 /// 定时任务刷新轨迹
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[AllowAnonymous] [DisableConcurrentExecution(timeoutInSeconds: 1800)]
[UnitOfWork]
[HttpPost("/TimerTask/Refresh")] [HttpPost("/TimerTask/Refresh")]
public async Task<bool> RefreshForTaskAsync() public virtual async Task<bool> RefreshForTaskAsync()
{ {
using var serviceScope = _serviceScopeFactory.CreateScope();
var trackPullConfigRepository = serviceScope.ServiceProvider.GetRequiredService<ITrackPullConfigRepository>();
var configs = await trackPullConfigRepository.AsQueryable() var configs = await trackPullConfigRepository.AsQueryable()
.LeftJoin<TrackPullPriorityEntity>((a, b) => a.Id == b.TrackPullConfigId) .LeftJoin<TrackPullPriorityEntity>((a, b) => a.Id == b.TrackPullConfigId)
.Select((a, b) => new TrackPullConfigVo .Select((a, b) => new TrackPullConfigVo
@ -51,18 +53,22 @@ public class TrackPullTask : ApplicationServiceBase, ISingletonDependency
}) })
.ToListAsync(); .ToListAsync();
if (configs.Count == 0) return true;
var queue = GlobalConst.TaskQueueLevelDict[trackAppOptions.CurrentValue.TrackPullTaskLevel];
foreach (var config in configs) foreach (var config in configs)
{ {
await RefreshForConfigAsync(serviceScope, config); backgroundJobClient.Create(
Job.FromExpression<TrackPullTask>(x => x.RefreshForConfigAsync(config)),
new EnqueuedState(queue));
} }
await Task.Delay(1000); // life-time possible dispose
return true; return true;
} }
private async Task RefreshForConfigAsync(IServiceScope serviceScope, TrackPullConfigVo config)
[UnitOfWork]
public virtual async Task RefreshForConfigAsync(TrackPullConfigVo config)
{ {
var trackPullCacheService = LazyServiceProvider.GetRequiredService<TrackPullCacheService>();
var redisKey = trackPullCacheService.TrackPullKey(config); var redisKey = trackPullCacheService.TrackPullKey(config);
if (await RedisHelper.Client.ExistsAsync(redisKey)) if (await RedisHelper.Client.ExistsAsync(redisKey))
{ {
@ -71,26 +77,19 @@ public class TrackPullTask : ApplicationServiceBase, ISingletonDependency
await RedisHelper.Client.SetExAsync(redisKey, config.PullInterval * 60, 1); await RedisHelper.Client.SetExAsync(redisKey, config.PullInterval * 60, 1);
_ = Task.Run(async () =>
{
try try
{ {
var provideQueries = serviceScope.ServiceProvider.GetRequiredService<ProvideQueries>(); await DoRefreshAsync(config, provideQueries, interfaceForwardService, interfaceOptions.Value);
var interfaceForward = serviceScope.ServiceProvider
.GetRequiredService<InterfaceForwardService>();
var interfaceOptions = serviceScope.ServiceProvider
.GetRequiredService<IOptions<InterfaceRelayParamsConfigOptions>>();
await DoRefreshAsync(config, provideQueries, interfaceForward, interfaceOptions.Value);
} }
catch (Exception e) catch (Exception e)
{ {
await RedisHelper.Client.DelAsync(redisKey); await RedisHelper.Client.DelAsync(redisKey);
Log.Error(e.ToString()); Log.Error(e.ToString());
throw;
} }
});
} }
private static async Task DoRefreshAsync(TrackPullConfigVo config, ProvideQueries provideQueries, protected async Task DoRefreshAsync(TrackPullConfigVo config, ProvideQueries provideQueries,
InterfaceForwardService interfaceForward, InterfaceForwardService interfaceForward,
InterfaceRelayParamsConfigOptions interfaceOptionsValue) InterfaceRelayParamsConfigOptions interfaceOptionsValue)
{ {

View File

@ -4,7 +4,6 @@ using DotNetCore.CAP;
using DotNetCore.CAP.Internal; using DotNetCore.CAP.Internal;
using DotNetCore.CAP.Messages; using DotNetCore.CAP.Messages;
using Fake; using Fake;
using Fake.AspNetCore;
using Fake.EventBus.RabbitMQ; using Fake.EventBus.RabbitMQ;
using Fake.Modularity; using Fake.Modularity;
using Fake.ObjectMapping.AutoMapper; using Fake.ObjectMapping.AutoMapper;
@ -14,17 +13,13 @@ using Hangfire;
using Hangfire.Redis.StackExchange; using Hangfire.Redis.StackExchange;
using InterfaceForward.Domain.Shared.Helpers; using InterfaceForward.Domain.Shared.Helpers;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Logging;
using RabbitMQ.Client; using RabbitMQ.Client;
using Savorboard.CAP.InMemoryMessageQueue; using Savorboard.CAP.InMemoryMessageQueue;
using TrackCenter.Application.Kafka; using TrackCenter.Application.Kafka;
using TrackCenter.Application.CAP; using TrackCenter.Application.CAP;
using TrackCenter.Application.Hangfire;
using TrackCenter.Application.HostServices; using TrackCenter.Application.HostServices;
using TrackCenter.Application.Options; using TrackCenter.Application.Options;
using TrackCenter.Application.Rabbit; using TrackCenter.Application.Rabbit;
using TrackCenter.Application.Services.TimerTaskss;
namespace TrackCenter.Application; namespace TrackCenter.Application;
@ -188,6 +183,12 @@ public class TrackCenterApplicationModule : FakeModule
o.UseRedisStorage(connectionString, options) o.UseRedisStorage(connectionString, options)
.WithJobExpirationTimeout(TimeSpan.FromHours(1)); // 终止态任务只保留1小时 .WithJobExpirationTimeout(TimeSpan.FromHours(1)); // 终止态任务只保留1小时
}); });
var hangfireWorkerCount = configuration.GetValue<int>("TrackApp:HangfireWorkerCount");
if (hangfireWorkerCount <= 0)
{
hangfireWorkerCount = Math.Max(4, Environment.ProcessorCount);
}
context.Services.AddHangfireServer(o => context.Services.AddHangfireServer(o =>
{ {
// 索引越小优先级越高 // 索引越小优先级越高
@ -200,7 +201,7 @@ public class TrackCenterApplicationModule : FakeModule
]; ];
o.ServerName = Dns.GetHostName(); o.ServerName = Dns.GetHostName();
o.ShutdownTimeout = TimeSpan.FromSeconds(10); o.ShutdownTimeout = TimeSpan.FromSeconds(10);
o.WorkerCount = 1; o.WorkerCount = hangfireWorkerCount;
}); });
// 注册轨迹业务定时任务服务 // 注册轨迹业务定时任务服务