nopCommerce 3.9 大波浪系列 之 事件机制(生产者、消费者)
一.nop事件机制简介
应用场景:客户支付成功后,需要发送短信、邮件告知客户订单支付成功(短信、邮件由不同模块实现)
实现方法: 1.定义支付成功OrderPaidEvent事件。
2.定义短信,邮箱两个消费者共同监听OrderPaidEvent事件,并实现相关业务。
3.当客户支付成功后生产者发送OrderPaidEvent事件。
4.消费者接收到OrderPaidEvent事件后,短信和邮箱消费者分别执行自己的业务。
nop事件机制使用到“生产者/消费者”模式。生产者只负责发布事件,并不需要关心谁来处理,相反消费者只用来处理事件。那生产者和消费者是如何进行关联的呢?nop实现是非常简单的,通过泛型来定义一个事件类,如果生产者和消费者都使用同一个事件类,那么就关联到一起了称之为订阅。负责实现事件机制的部分称之为缓冲区,缓冲区的作用是通过解耦的方式实现消息机制。生产者和消费者是一对多的关系。下图简单介绍下生产者消费者关系。
二.nop事件相关接口
生产者接口:Nop.Services.Events.IEventPublisher
消费者接口:Nop.Services.Events.IConsumer<T>
事件订阅接口:Nop.Services.Events.ISubscriptionService
IEventPublisher接口Publish<T>(T eventMessage)方法用于发布事件(生产者)。
IConsumer<T>接口HandleEvent(T eventMessage)方法用于处理事件(消费者)。
两者之间的关系由T泛型来关联,称之为事件,简单的说T类型相同则两者关联订阅成功。
ISubscriptionService接口GetSubscriptions<T>()方法返回IList<IConsumer<T>>集合,该集合保存了消费者。
接口实现如下图:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System; 2 using System.Linq; 3 using Nop.Core.Infrastructure; 4 using Nop.Core.Plugins; 5 using Nop.Services.Logging; 6 7 namespace Nop.Services.Events 8 { 9 /// <summary> 10 /// Evnt publisher 11 /// </summary> 12 public class EventPublisher : IEventPublisher 13 { 14 private readonly ISubscriptionService _subscriptionService; 15 16 /// <summary> 17 /// Ctor 18 /// </summary> 19 /// <param name="subscriptionService"></param> 20 public EventPublisher(ISubscriptionService subscriptionService) 21 { 22 _subscriptionService = subscriptionService; 23 } 24 25 /// <summary> 26 /// Publish to cunsumer 27 /// </summary> 28 /// <typeparam name="T">Type</typeparam> 29 /// <param name="x">Event consumer</param> 30 /// <param name="eventMessage">Event message</param> 31 protected virtual void PublishToConsumer<T>(IConsumer<T> x, T eventMessage) 32 { 33 //Ignore not installed plugins 34 var plugin = FindPlugin(x.GetType()); 35 if (plugin != null && !plugin.Installed) 36 return; 37 38 try 39 { 40 //消费者处理方法 41 x.HandleEvent(eventMessage); 42 } 43 catch (Exception exc) 44 { 45 //log error 46 var logger = EngineContext.Current.Resolve<ILogger>(); 47 //we put in to nested try-catch to prevent possible cyclic (if some error occurs) 48 try 49 { 50 logger.Error(exc.Message, exc); 51 } 52 catch (Exception) 53 { 54 //do nothing 55 } 56 } 57 } 58 59 /// <summary> 60 /// Find a plugin descriptor by some type which is located into its assembly 61 /// </summary> 62 /// <param name="providerType">Provider type</param> 63 /// <returns>Plugin descriptor</returns> 64 protected virtual PluginDescriptor FindPlugin(Type providerType) 65 { 66 if (providerType == null) 67 throw new ArgumentNullException("providerType"); 68 69 if (PluginManager.ReferencedPlugins == null) 70 return null; 71 72 foreach (var plugin in PluginManager.ReferencedPlugins) 73 { 74 if (plugin.ReferencedAssembly == null) 75 continue; 76 77 if (plugin.ReferencedAssembly.FullName == providerType.Assembly.FullName) 78 return plugin; 79 } 80 81 return null; 82 } 83 84 /// <summary> 85 /// 发送事件 86 /// </summary> 87 /// <typeparam name="T">Type</typeparam> 88 /// <param name="eventMessage">Event message</param> 89 public virtual void Publish<T>(T eventMessage) 90 { 91 var subscriptions = _subscriptionService.GetSubscriptions<T>();//获取订阅消费者 92 subscriptions.ToList().ForEach(x => PublishToConsumer(x, eventMessage)); 93 } 94 95 } 96 } 97
1 using System.Collections.Generic; 2 using Nop.Core.Infrastructure; 3 4 namespace Nop.Services.Events 5 { 6 /// <summary> 7 /// 事件订阅服务 8 /// </summary> 9 public class SubscriptionService : ISubscriptionService 10 { 11 /// <summary> 12 /// 获取事件订阅 13 /// </summary> 14 /// <typeparam name="T">Type</typeparam> 15 /// <returns>Event consumers</returns> 16 public IList<IConsumer<T>> GetSubscriptions<T>() 17 { 18 return EngineContext.Current.ResolveAll<IConsumer<T>>(); 19 } 20 } 21 } 22
二.消费者IConsermer<T>注册
应用启动时Nop.Web.Framework.DependencyRegistrar中将所有实现IConsumer<T>接口的类注册到ioc容器中。
通过EngineContext.Current.ResolveAll<IConsumer<T>>(),就可以获取到某个消息(T)的订阅了。
1 //注册事件消费者 2 var consumers = typeFinder.FindClassesOfType(typeof(IConsumer<>)).ToList(); 3 foreach (var consumer in consumers) 4 { 5 builder.RegisterType(consumer) 6 .As(consumer.FindInterfaces((type, criteria) => 7 { 8 var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition()); 9 return isMatch; 10 }, typeof(IConsumer<>))) 11 .InstancePerLifetimeScope(); 12 }
三.创建消费者
结合上边提到的应用场景,我们创建订阅OrderPaidEvent事件来处理短信通知的消费者。
创建OrderPaidSMSEventConsumer类
1 using System; 2 using Nop.Core; 3 using Nop.Core.Domain.Orders; 4 using Nop.Core.Plugins; 5 using Nop.Services.Events; 6 using Nop.Services.Orders; 7 8 namespace Nop.Plugin.SMS 9 { 10 public class OrderPaidSMSEventConsumer : IConsumer<OrderPaidEvent> 11 { 12 13 private readonly IOrderService _orderService; 14 15 public OrderPaidSMSEventConsumer( 16 IOrderService orderService, 17 IStoreContext storeContext) 18 { 19 this._orderService = orderService; 20 this._storeContext = storeContext; 21 } 22 23 /// <summary> 24 /// 事件处理. 25 /// </summary> 26 /// <param name="eventMessage">The event message.</param> 27 public void HandleEvent(OrderPaidEvent eventMessage) 28 { 29 30 var order = eventMessage.Order;//获取订单 31 32 //发送短息通知代码 33 //.................... 34 } 35 } 36 }
OrderPaidSMSEventConsumer类继承 IConsumer<OrderPaidEvent>,OrderPaidEvent就是事件类,维护生产者与消费者之间的订阅关系。事件类名称可以自定义,代表了一个事件。
接下来我们再创建一个邮件处理的消费者OrderPaidEmailEventConsumer类,同样继承了ICnsumer<OrderPaidEvent>,说明我们订阅的是也是OrderPaidEvent事件。
1 using System; 2 using Nop.Core; 3 using Nop.Core.Domain.Orders; 4 using Nop.Core.Plugins; 5 using Nop.Services.Events; 6 using Nop.Services.Orders; 7 8 namespace Nop.Plugin.Email 9 { 10 public class OrderPaidEmailEventConsumer : IConsumer<OrderPaidEvent> 11 { 12 13 private readonly IOrderService _orderService; 14 private readonly IStoreContext _storeContext; 15 16 public OrderPaidEmailEventConsumer( 17 IOrderService orderService, 18 IStoreContext storeContext) 19 { 20 21 this._orderService = orderService; 22 this._storeContext = storeContext; 23 } 24 25 /// <summary> 26 /// 邮件处理 27 /// </summary> 28 /// <param name="eventMessage">The event message.</param> 29 public void HandleEvent(OrderPaidEvent eventMessage) 30 { 31 32 33 var order = eventMessage.Order; 34 35 //发送邮件通知客户 36 //............................ 37 } 38 } 39 }
四.生产消息
我们已经创建了两个订阅了OrderPaidEvent事件的消费者,现在我们看看当客户支付完成时我们是如何通知消费者的。
Nop.Services.OrderProcessingService类中
_eventPublisher.Publish(new OrderPaidEvent(order))方法发送了OrderPaidEvent事件。这时候上边订阅OrderPaidEvent事件的消费(短信、邮件)就会处理消息了。
五.nop中常用的事件整理
消费者,主要还是处理缓存
Nop.Web.Infrastructure.Cache.ModelCacheEventConsumer:前台模型相关
Nop.Admin.Infrastructure.Cache.ModelCacheEventConsumer:后台模型相关
Nop.Services.Discounts.Cache.DiscountEventConsumer:折扣相关
Nop.Services.Catalog.Cache.PriceCacheEventConsumer:价格相关
Nop.Services.Customers.Cache.CustomerCacheEventConsumer:密码修改
生产者,下图总结了nop 3.9 源码中自带的事件及所在的类,大部分是未实现对应的消费者。
nop只是在相关的地方留下事件位置,方便我们二次开发的时候进行扩展。
六.总结
1.生产者需要继承IEventPublisher接口。
2.消费者需要继承IConsumer<T>接口。
3.消费者通过事件类订阅到生产者,订阅实现参见ISubscriptionService接口。
nop事件机制实现很简单,有兴趣的朋友可以用RabbitMQ进行消息的扩展。
文中有错误的理解和不正确的观点,请留言,一起交流共同进步。
本文地址:http://www.cnblogs.com/yaoshangjin/p/7234522.html
本文为大波浪原创、转载请注明出处。