mirror of https://github.com/abpframework/abp.git
10 changed files with 191 additions and 102 deletions
@ -1,8 +0,0 @@ |
|||||
namespace Volo.Abp.BackgroundJobs.RabbitMQ |
|
||||
{ |
|
||||
public interface IJobListener |
|
||||
{ |
|
||||
void Start(); |
|
||||
void Stop(); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,12 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.RabbitMQ |
||||
|
{ |
||||
|
public interface IJobQueue<TArgs> : IDisposable |
||||
|
{ |
||||
|
Task<string> Enqueue(TArgs args); |
||||
|
|
||||
|
Task StartAsync(); |
||||
|
} |
||||
|
} |
||||
@ -1,62 +0,0 @@ |
|||||
using System; |
|
||||
using RabbitMQ.Client; |
|
||||
using RabbitMQ.Client.Events; |
|
||||
using Volo.Abp.RabbitMQ; |
|
||||
|
|
||||
namespace Volo.Abp.BackgroundJobs.RabbitMQ |
|
||||
{ |
|
||||
public class JobListener<TJob> : IJobListener |
|
||||
{ |
|
||||
protected string JobName { get; } |
|
||||
protected IBackgroundJobExecuter JobExecuter { get; } |
|
||||
protected IChannelPool ChannelPool { get; } |
|
||||
protected IRabbitMqSerializer Serializer { get; } |
|
||||
protected Type ArgsType { get; } |
|
||||
|
|
||||
protected IChannelAccessor ChannelAccessor { get; private set; } |
|
||||
protected EventingBasicConsumer Consumer { get; private set; } |
|
||||
|
|
||||
public JobListener( |
|
||||
IChannelPool channelPool, |
|
||||
IBackgroundJobExecuter jobExecuter, |
|
||||
IRabbitMqSerializer serializer) |
|
||||
{ |
|
||||
ChannelPool = channelPool; |
|
||||
JobExecuter = jobExecuter; |
|
||||
Serializer = serializer; |
|
||||
ArgsType = BackgroundJobArgsHelper.GetJobArgsType(typeof(TJob)); |
|
||||
JobName = BackgroundJobNameAttribute.GetName(ArgsType); |
|
||||
} |
|
||||
|
|
||||
public void Start() |
|
||||
{ |
|
||||
var queueName = "BackgroundJobs." + JobName; //TODO: Make prefix optional
|
|
||||
|
|
||||
ChannelAccessor = ChannelPool.Acquire(queueName); |
|
||||
|
|
||||
//TODO: How to ensure that queue is created!
|
|
||||
|
|
||||
Consumer = new EventingBasicConsumer(ChannelAccessor.Channel); |
|
||||
Consumer.Received += MessageReceived; |
|
||||
|
|
||||
//TODO: What BasicConsume returns?
|
|
||||
ChannelAccessor.Channel.BasicConsume( |
|
||||
queue: queueName, |
|
||||
autoAck: false, |
|
||||
consumer: Consumer |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
private void MessageReceived(object sender, BasicDeliverEventArgs e) |
|
||||
{ |
|
||||
var context = new JobExecutionContext(typeof(TJob), Serializer.Deserialize(e.Body, ArgsType)); |
|
||||
JobExecuter.Execute(context); |
|
||||
//TODO: How to ACK on success or Reject on failure?
|
|
||||
} |
|
||||
|
|
||||
public void Stop() |
|
||||
{ |
|
||||
ChannelAccessor.Dispose(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,153 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Nito.AsyncEx; |
||||
|
using RabbitMQ.Client; |
||||
|
using RabbitMQ.Client.Events; |
||||
|
using Volo.Abp.RabbitMQ; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.RabbitMQ |
||||
|
{ |
||||
|
public class JobQueue<TArgs> : IJobQueue<TArgs> |
||||
|
{ |
||||
|
protected Type JobType { get; } |
||||
|
protected string JobName { get; } |
||||
|
protected string QueueName { get; } |
||||
|
|
||||
|
protected IChannelAccessor ChannelAccessor { get; private set; } |
||||
|
protected EventingBasicConsumer Consumer { get; private set; } |
||||
|
|
||||
|
protected IChannelPool ChannelPool { get; } |
||||
|
protected AbpRabbitMqOptions RabbitMqOptions { get; } |
||||
|
protected BackgroundJobOptions BackgroundJobOptions { get; } |
||||
|
protected IRabbitMqSerializer Serializer { get; } |
||||
|
protected IBackgroundJobExecuter JobExecuter { get; } |
||||
|
|
||||
|
protected AsyncLock SyncObj = new AsyncLock(); |
||||
|
protected bool IsDiposed { get; private set; } |
||||
|
|
||||
|
public JobQueue( |
||||
|
IChannelPool channelPool, |
||||
|
IRabbitMqSerializer serializer, |
||||
|
IOptions<AbpRabbitMqOptions> options, |
||||
|
IBackgroundJobExecuter jobExecuter, |
||||
|
IOptions<BackgroundJobOptions> backgroundJobOptions) |
||||
|
{ |
||||
|
Serializer = serializer; |
||||
|
JobExecuter = jobExecuter; |
||||
|
BackgroundJobOptions = backgroundJobOptions.Value; |
||||
|
ChannelPool = channelPool; |
||||
|
RabbitMqOptions = options.Value; |
||||
|
|
||||
|
JobName = BackgroundJobNameAttribute.GetName<TArgs>(); |
||||
|
JobType = BackgroundJobOptions.GetJobType(JobName); |
||||
|
QueueName = "BackgroundJobs." + JobName; //TODO: Make prefix optional
|
||||
|
} |
||||
|
|
||||
|
public virtual async Task<string> Enqueue(TArgs args) |
||||
|
{ |
||||
|
CheckDisposed(); |
||||
|
|
||||
|
using (await SyncObj.LockAsync()) |
||||
|
{ |
||||
|
await EnsureInitializedAsync(); |
||||
|
|
||||
|
await PublishAsync(args); |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task StartAsync() |
||||
|
{ |
||||
|
if (!BackgroundJobOptions.IsJobExecutionEnabled) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
return EnsureInitializedAsync(); |
||||
|
} |
||||
|
|
||||
|
public virtual void Dispose() |
||||
|
{ |
||||
|
if (IsDiposed) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
IsDiposed = true; |
||||
|
|
||||
|
ChannelAccessor?.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public virtual Task EnsureInitializedAsync() |
||||
|
{ |
||||
|
if (ChannelAccessor != null) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
ChannelAccessor = ChannelPool.Acquire(QueueName); |
||||
|
|
||||
|
var queueOptions = RabbitMqOptions.Queues.GetOrDefault(QueueName) |
||||
|
?? new QueueOptions(QueueName); |
||||
|
|
||||
|
queueOptions.Declare(ChannelAccessor.Channel); |
||||
|
|
||||
|
if (BackgroundJobOptions.IsJobExecutionEnabled) |
||||
|
{ |
||||
|
Consumer = new EventingBasicConsumer(ChannelAccessor.Channel); |
||||
|
Consumer.Received += MessageReceived; |
||||
|
|
||||
|
//TODO: What BasicConsume returns?
|
||||
|
ChannelAccessor.Channel.BasicConsume( |
||||
|
queue: QueueName, |
||||
|
autoAck: false, |
||||
|
consumer: Consumer |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
protected virtual Task PublishAsync(TArgs args) |
||||
|
{ |
||||
|
ChannelAccessor.Channel.BasicPublish( |
||||
|
exchange: "", |
||||
|
routingKey: QueueName, |
||||
|
basicProperties: CreateBasicPropertiesToPublish(), |
||||
|
body: Serializer.Serialize(args) |
||||
|
); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
protected virtual IBasicProperties CreateBasicPropertiesToPublish() |
||||
|
{ |
||||
|
var properties = ChannelAccessor.Channel.CreateBasicProperties(); |
||||
|
properties.Persistent = true; |
||||
|
return properties; |
||||
|
} |
||||
|
|
||||
|
protected virtual void MessageReceived(object sender, BasicDeliverEventArgs ea) |
||||
|
{ |
||||
|
var context = new JobExecutionContext( |
||||
|
JobType, |
||||
|
Serializer.Deserialize(ea.Body, typeof(TArgs)) |
||||
|
); |
||||
|
|
||||
|
JobExecuter.Execute(context); |
||||
|
|
||||
|
//TODO: How to ACK on success or Reject on failure?
|
||||
|
} |
||||
|
|
||||
|
private void CheckDisposed() |
||||
|
{ |
||||
|
if (IsDiposed) |
||||
|
{ |
||||
|
throw new AbpException("This object is disposed!"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue