diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs index b45d318bb6..1ccdfca88a 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs @@ -19,18 +19,28 @@ public class AbpBackgroundJobsTickerQModule : AbpModule public override void OnApplicationInitialization(ApplicationInitializationContext context) { var abpBackgroundJobOptions = context.ServiceProvider.GetRequiredService>(); - var tickerFunctionDelegateDict = new Dictionary(); + var tickerFunctionDelegates = new Dictionary(); var requestTypes = new Dictionary(); foreach (var jobConfiguration in abpBackgroundJobOptions.Value.GetJobs()) { var genericMethod = GetTickerFunctionDelegateMethod.MakeGenericMethod(jobConfiguration.ArgsType); var tickerFunctionDelegate = (TickerFunctionDelegate)genericMethod.Invoke(null, [jobConfiguration.ArgsType])!; - tickerFunctionDelegateDict.TryAdd(jobConfiguration.JobName, (string.Empty, TickerTaskPriority.Normal, tickerFunctionDelegate)); + tickerFunctionDelegates.TryAdd(jobConfiguration.JobName, (string.Empty, TickerTaskPriority.Normal, tickerFunctionDelegate)); requestTypes.TryAdd(jobConfiguration.JobName, (jobConfiguration.ArgsType.FullName, jobConfiguration.ArgsType)!); } - TickerFunctionProvider.RegisterFunctions(tickerFunctionDelegateDict); - TickerFunctionProvider.RegisterRequestType(requestTypes); + PreConfigure(options => + { + foreach (var functionDelegate in tickerFunctionDelegates) + { + options.Functions.TryAdd(functionDelegate.Key, functionDelegate.Value); + } + + foreach (var requestType in requestTypes) + { + options.RequestTypes.TryAdd(requestType.Key, requestType.Value); + } + }); } private static TickerFunctionDelegate GetTickerFunctionDelegate(Type argsType) diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQBackgroundWorkerManager.cs new file mode 100644 index 0000000000..bb2ea865b4 --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQBackgroundWorkerManager.cs @@ -0,0 +1,96 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using TickerQ.Utilities.Enums; +using Volo.Abp.DependencyInjection; +using Volo.Abp.TickerQ; + +namespace Volo.Abp.BackgroundWorkers.TickerQ; + +[Dependency(ReplaceServices = true)] +[ExposeServices(typeof(IBackgroundWorkerManager), typeof(TickerQBackgroundWorkerManager))] +public class TickerQBackgroundWorkerManager : BackgroundWorkerManager, ISingletonDependency +{ + protected IObjectAccessor ObjectAccessor { get; } + + public TickerQBackgroundWorkerManager(IObjectAccessor objectAccessor) + { + ObjectAccessor = objectAccessor; + } + + public override async Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default) + { + if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase) + { + int? period = null; + string? cronExpression = null; + + if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase) + { + period = asyncPeriodicBackgroundWorkerBase.Period; + cronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression; + } + else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase) + { + period = periodicBackgroundWorkerBase.Period; + cronExpression = periodicBackgroundWorkerBase.CronExpression; + } + + if (period == null && cronExpression.IsNullOrWhiteSpace()) + { + throw new AbpException($"Both 'Period' and 'CronExpression' are not set for {worker.GetType().FullName}. You must set at least one of them."); + } + + if (period != null && cronExpression.IsNullOrWhiteSpace()) + { + cronExpression = GetCron(period.Value); + } + + ObjectAccessor.Value!.PreConfigure(options => + { + var name = BackgroundWorkerNameAttribute.GetNameOrNull(worker.GetType()) ?? worker.GetType().FullName; + options.Functions.TryAdd(name!, (cronExpression!, TickerTaskPriority.Normal, async (tickerQCancellationToken, serviceProvider, tickerFunctionContext) => + { + var workerInvoker = new TickerQPeriodicBackgroundWorkerInvoker(worker, serviceProvider); + await workerInvoker.DoWorkAsync(tickerFunctionContext, tickerQCancellationToken); + })); + }); + } + + await base.AddAsync(worker, cancellationToken); + } + + protected virtual string GetCron(int period) + { + var time = TimeSpan.FromMilliseconds(period); + if (time.TotalMinutes < 1) + { + // Less than 1 minute — 5-field cron doesn't support seconds, so run every minute + return "* * * * *"; + } + + if (time.TotalMinutes < 60) + { + // Run every N minutes + var minutes = (int)Math.Round(time.TotalMinutes); + return $"*/{minutes} * * * *"; + } + + if (time.TotalHours < 24) + { + // Run every N hours + var hours = (int)Math.Round(time.TotalHours); + return $"0 */{hours} * * *"; + } + + if (time.TotalDays <= 31) + { + // Run every N days + var days = (int)Math.Round(time.TotalDays); + return $"0 0 */{days} * *"; + } + + throw new AbpException($"Cannot convert period: {period} to cron expression."); + } +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQPeriodicBackgroundWorkerInvoker.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQPeriodicBackgroundWorkerInvoker.cs new file mode 100644 index 0000000000..ae52032b9a --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQPeriodicBackgroundWorkerInvoker.cs @@ -0,0 +1,40 @@ +using System; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using TickerQ.Utilities.Models; + +namespace Volo.Abp.BackgroundWorkers.TickerQ; + +//TODO: Use lambda expression to improve performance. +public class TickerQPeriodicBackgroundWorkerInvoker +{ + private readonly MethodInfo _doWorkAsyncMethod; + private readonly MethodInfo _doWorkMethod; + + protected IBackgroundWorker Worker { get; } + protected IServiceProvider ServiceProvider { get; } + + public TickerQPeriodicBackgroundWorkerInvoker(IBackgroundWorker worker, IServiceProvider serviceProvider) + { + _doWorkAsyncMethod = worker.GetType().GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic)!; + _doWorkMethod = worker.GetType().GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic)!; + + Worker = worker; + ServiceProvider = serviceProvider; + } + + public virtual async Task DoWorkAsync(TickerFunctionContext context, CancellationToken cancellationToken = default) + { + var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider); + switch (Worker) + { + case AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorker: + await (Task)(_doWorkAsyncMethod.Invoke(asyncPeriodicBackgroundWorker, new object[] { workerContext })!); + break; + case PeriodicBackgroundWorkerBase periodicBackgroundWorker: + _doWorkMethod.Invoke(periodicBackgroundWorker, new object[] { workerContext }); + break; + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs index 6811e2a9a1..578b697b8b 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs @@ -45,7 +45,7 @@ public abstract class AbpApplicationBase : IAbpApplication StartupModuleType = startupModuleType; Services = services; - + services.AddObjectAccessor(services); services.TryAddObjectAccessor(); var options = new AbpApplicationCreationOptions(services); diff --git a/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs index fe5b122ba1..1d6965741f 100644 --- a/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs +++ b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using TickerQ.DependencyInjection; +using TickerQ.Utilities; +using Volo.Abp.DependencyInjection; using Volo.Abp.Modularity; namespace Volo.Abp.TickerQ; @@ -12,6 +14,18 @@ public class AbpTickerQModule : AbpModule { options.SetInstanceIdentifier(context.Services.GetApplicationName()); }); + } + + public override void OnPostApplicationInitialization(ApplicationInitializationContext context) + { + var serviceCollection = context.ServiceProvider.GetRequiredService>(); + if (serviceCollection.Value == null) + { + return; + } + var tickerQ = serviceCollection.Value.ExecutePreConfiguredActions(); + TickerFunctionProvider.RegisterFunctions(tickerQ.Functions); + TickerFunctionProvider.RegisterRequestType(tickerQ.RequestTypes); } } diff --git a/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQOptions.cs b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQOptions.cs new file mode 100644 index 0000000000..01463ea8b5 --- /dev/null +++ b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQOptions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using TickerQ.Utilities; +using TickerQ.Utilities.Enums; + +namespace Volo.Abp.TickerQ; + +public class AbpTickerQOptions +{ + public Dictionary Functions { get;} + + public Dictionary RequestTypes { get; } + + public AbpTickerQOptions() + { + Functions = new Dictionary(); + RequestTypes = new Dictionary(); + } +}