nopCommerce 3.9 大波浪系列 之 事件机制(生产者、消费者)

一.nop事件机制简介

应用场景:客户支付成功后,需要发送短信、邮件告知客户订单支付成功(短信、邮件由不同模块实现)

实现方法: 1.定义支付成功OrderPaidEvent事件。

               2.定义短信,邮箱两个消费者共同监听OrderPaidEvent事件,并实现相关业务。

               3.当客户支付成功后生产者发送OrderPaidEvent事件。

               4.消费者接收到OrderPaidEvent事件后,短信和邮箱消费者分别执行自己的业务。

     nop事件机制使用到“生产者/消费者”模式。生产者只负责发布事件,并不需要关心谁来处理,相反消费者只用来处理事件。那生产者和消费者是如何进行关联的呢?nop实现是非常简单的,通过泛型来定义一个事件类,如果生产者和消费者都使用同一个事件类,那么就关联到一起了称之为订阅。负责实现事件机制的部分称之为缓冲区,缓冲区的作用是通过解耦的方式实现消息机制。生产者和消费者是一对多的关系。下图简单介绍下生产者消费者关系。

image

二.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>>集合,该集合保存了消费者。

接口实现如下图:

image

  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 
EventPublisher
  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事件的消费(短信、邮件)就会处理消息了。

image

五.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只是在相关的地方留下事件位置,方便我们二次开发的时候进行扩展。

image

六.总结

1.生产者需要继承IEventPublisher接口。

2.消费者需要继承IConsumer<T>接口。

3.消费者通过事件类订阅到生产者,订阅实现参见ISubscriptionService接口。

nop事件机制实现很简单,有兴趣的朋友可以用RabbitMQ进行消息的扩展。

文中有错误的理解和不正确的观点,请留言,一起交流共同进步。

本文地址:http://www.cnblogs.com/yaoshangjin/p/7234522.html

本文为大波浪原创、转载请注明出处。

如果您认为这篇文章还不错或者有所收获,可以点击下方的【关注】按钮,因为你的支持是我继续写作,分享的最大动力!
作者:大波浪
声明: 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果您发现博客中出现了错误,或者有更好的建议、想法,请及时与我联系!!如果想找我私下交流,可以私信或者加我QQ。
posted @ 2017-07-25 15:47  大波浪  阅读(2174)  评论(7编辑  收藏  举报