首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

基于事件总线EventBus实现邮件推送功能

编程知识
2024年08月26日 19:59

       有时候,有人给我的网站留了言,但是我必须要打开我的网站(https://www.xiandanplay.com/)才知道,所以我便决定给网站增加一个邮件推送的功能,好让我第一时间知道。于是乎,按照我自己的思路,同时为了去学习了解rabbitmq以及EventBus概念,我便设计了一套邮件推送的功能,这里分享出来,可能方案不是很好,大家不喜勿喷。

什么是事件总线   

    事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。

    关于这个概念,网上有很多讲解的,这里我推荐一个讲的比较好的(事件总线知多少)

什么是RabbitMQ

   RabbitMQ这个就不用说了,想必到家都知道。

粗糙流程图

简单来解释就是:

      1、定义一个事件抽象类

public abstract class EventData
    {
        /// <summary>
        /// 唯一标识
        /// </summary>
        public string Unique { get; set; }
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool Success { get; set; }
        /// <summary>
        /// 结果
        /// </summary>
        public string Result { get; set; }
    }

 2、定义一个事件处理抽象类,以及对应的一个队列消息执行的一个记录

public abstract class EventHandler<T> where T : EventData
    {
        public async Task Handler(T eventData)
        {
            await BeginHandler(eventData.Unique);
            eventData = await ProcessingHandler(eventData);
            if (eventData.Success)
                await FinishHandler(eventData);
        }
        /// <summary>
        ///  开始处理
        /// </summary>
        /// <param name="unique"></param>
        /// <returns></returns>
        protected abstract Task BeginHandler(string unique);
        /// <summary>
        /// 处理中
        /// </summary>
        /// <param name="eventData"></param>
        /// <returns></returns>
        protected abstract Task<T> ProcessingHandler(T eventData);
        /// <summary>
        /// 处理完成
        /// </summary>
        /// <param name="eventData"></param>
        /// <returns></returns>
        protected abstract Task FinishHandler(T eventData);
    }
   
   [Table("Sys_TaskRecord")]
    public class TaskRecord : Entity<long>
    {
        /// <summary>
        /// 任务类型
        /// </summary>
        public TaskRecordType TaskType { get; set; }
        /// <summary>
        /// 任务状态
        /// </summary>
        public int TaskStatu { get; set; }
        /// <summary>
        /// 任务值
        /// </summary>
        public string TaskValue { get; set; }
        /// <summary>
        /// 任务结果
        /// </summary>
        public string TaskResult { get; set; }
        /// <summary>
        /// 任务开始时间
        /// </summary>
        public DateTime TaskStartTime { get; set; }
        /// <summary>
        /// 任务完成时间
        /// </summary>
        public DateTime? TaskFinishTime { get; set; }
        /// <summary>
        /// 任务最后更新时间
        /// </summary>
        public DateTime? LastUpdateTime { get; set; }
        /// <summary>
        /// 任务名称
        /// </summary>
        public string TaskName { get; set; }
        /// <summary>
        /// 附加数据
        /// </summary>
        public string AdditionalData { get; set; }
    }

   3、定义一个邮件事件消息类,继承自EventData,以及一个邮件处理的Hanler继承自EventHandler

 public class EmailEventData:EventData
    {
        /// <summary>
        /// 邮件内容
        /// </summary>
        public string Body { get; set; }
        /// <summary>
        /// 接收者
        /// </summary>
        public string Reciver { get; set; }
    }

 public class CreateEmailHandler<T> : Core.EventBus.EventHandler<T> where T : EventData
    {
        private IEmailService emailService;
        private IUnitOfWork unitOfWork;
        private ITaskRecordService taskRecordService;
        public CreateEmailHandler(IEmailService emailService, IUnitOfWork unitOfWork, ITaskRecordService taskRecordService)
        {
            this.emailService = emailService;
            this.unitOfWork = unitOfWork;
            this.taskRecordService = taskRecordService;
        }
        protected override async Task BeginHandler(string unique)
        {
            await taskRecordService.UpdateRecordStatu(Convert.ToInt64(unique), (int)MqMessageStatu.Processing);
            await unitOfWork.CommitAsync();
        }

        protected override async Task<T> ProcessingHandler(T eventData)
        {
            try
            {
                EmailEventData emailEventData = eventData as EmailEventData;
                await emailService.SendEmail(emailEventData.Reciver, emailEventData.Reciver, emailEventData.Body, "[闲蛋]收到一条留言");
                eventData.Success = true;
            }
            catch (Exception ex)
            {
                await taskRecordService.UpdateRecordFailStatu(Convert.ToInt64(eventData.Unique), (int)MqMessageStatu.Fail,ex.Message);
                await unitOfWork.CommitAsync();
                eventData.Success = false;
            }
            return eventData;

        }

        protected override async Task FinishHandler(T eventData)
        {
            await taskRecordService.UpdateRecordSuccessStatu(Convert.ToInt64(eventData.Unique), (int)MqMessageStatu.Finish,"");
            await unitOfWork.CommitAsync();
        }

   4、接着就是如何把事件消息和事件Hanler关联起来,那么我这里思路就是把EmailEventData的类型和CreateEmailHandler的类型先注册到字典里面,这样我就可以根据EmailEventData找到对应的处理程序了,找类型还不够,如何创建实例呢,这里就还需要把CreateEmailHandler注册到DI容器里面,这样就可以根据容器获取对象了,如下

  public void AddSub<T, TH>()
             where T : EventData
             where TH : EventHandler<T>
        {
            Type eventDataType = typeof(T);
            Type handlerType = typeof(TH);
            if (!eventhandlers.ContainsKey(typeof(T)))
                eventhandlers.TryAdd(eventDataType, handlerType);
            _serviceDescriptors.AddScoped(handlerType);
        }
-------------------------------------------------------------------------------------------------------------------
 public Type FindEventType(string eventName)
        {
            if (!eventTypes.ContainsKey(eventName))
                throw new ArgumentException(string.Format("eventTypes不存在类名{0}的key", eventName));
            return eventTypes[eventName];
        }
------------------------------------------------------------------------------------------------------------------------------------------------------------
  public object FindHandlerType(Type eventDataType)
        {
            if (!eventhandlers.ContainsKey(eventDataType))
                throw new ArgumentException(string.Format("eventhandlers不存在类型{0}的key", eventDataType.FullName));
            var obj = _buildServiceProvider(_serviceDescriptors).GetService(eventhandlers[eventDataType]);
            return obj;
        }
----------------------------------------------------------------------------------------------------------------------------------
 private static IServiceCollection AddEventBusService(this IServiceCollection services)
        {
            string exchangeName = ConfigureProvider.configuration.GetSection("EventBusOption:ExchangeName").Value;
            services.AddEventBus(Assembly.Load("XianDan.Application").GetTypes())
                .AddSubscribe<EmailEventData, CreateEmailHandler<EmailEventData>>(exchangeName, ExchangeType.Direct, BizKey.EmailQueueName);
            return services;
        }

   5、发送消息,这里代码简单,就是简单的发送消息,这里用eventData.GetType().Name作为消息的RoutingKey,这样消费这就可以根据这个key调用FindEventType,然后找到对应的处理程序了

 using (IModel channel = connection.CreateModel())
{
     string routeKey = eventData.GetType().Name;
     string message = JsonConvert.SerializeObject(eventData);
     byte[] body = Encoding.UTF8.GetBytes(message);
     channel.ExchangeDeclare(exchangeName, exchangeType, true, false, null);
     channel.QueueDeclare(queueName, true, false, false, null);
     channel.BasicPublish(exchangeName, routeKey, null, body);
}

  

6、订阅消息,核心的是这一段

  Type eventType = _eventBusManager.FindEventType(eventName);
  var eventData = (T)JsonConvert.DeserializeObject(body, eventType);
  EventHandler<T> eventHandler = _eventBusManager.FindHandlerType(eventType)  as       EventHandler<T>;

 public void Subscribe<T, TH>(string exchangeName, string exchangeType, string queueName)
            where T : EventData
            where TH : EventHandler<T>
        {
            try
            {
                _eventBusManager.AddSub<T, TH>();
                IModel channel = connection.CreateModel();
                channel.QueueDeclare(queueName, true, false, false, null);
                channel.ExchangeDeclare(exchangeName, exchangeType, true, false, null);
                channel.QueueBind(queueName, exchangeName, typeof(T).Name, null);
                var consumer = new EventingBasicConsumer(channel);
                consumer.Received += async (model, ea) =>
                {
                    string eventName = ea.RoutingKey;
                    byte[] resp = ea.Body.ToArray();
                    string body = Encoding.UTF8.GetString(resp);
                    try
                    {
                        Type eventType = _eventBusManager.FindEventType(eventName);
                        var eventData = (T)JsonConvert.DeserializeObject(body, eventType);
                        EventHandler<T> eventHandler = _eventBusManager.FindHandlerType(eventType) as EventHandler<T>;
                        await eventHandler.Handler(eventData);
                    }
                    catch (Exception ex)
                    {
                        LogUtils.LogError(ex, "EventBusRabbitMQ", ex.Message);
                    }
                    finally
                    {
                        channel.BasicAck(ea.DeliveryTag, false);
                    }
                  

                };
                channel.BasicConsume(queueName, autoAck: false, consumer: consumer);
            }
            catch (Exception ex)
            {
                LogUtils.LogError(ex, "EventBusRabbitMQ.Subscribe", ex.Message);
            }

        }

    注意,这里我使用的时候有个小坑,就是最开始是用using包裹这个IModel channel = connection.CreateModel();导致最后程序启动后无法收到消息,然后去rabbitmq的管理界面发现没有channel连接,队列也没有消费者,最后发现可能是using执行完后就释放掉了,把using去掉就好了。

   好了,到此,我的思路大概讲完了,现在我的网站留言也可以收到邮件了,那么多测试邮件,哈哈哈哈哈

 

  大家感兴趣的话可以去我的网站(https://www.xiandanplay.com/)踩一踩,互加友链也可以的,谢谢大家,不喜勿喷喽!

From:https://www.cnblogs.com/MrHanBlog/p/18381572
本文地址: http://www.shuzixingkong.net/article/1466
0评论
提交 加载更多评论
其他文章 ArcGIS创建渔网工具的使用方法
本文介绍在ArcMap软件中,通过“Create Fishnet”工具创建渔网,从而获得指定大小的矢量格网数据的方法~
ArcGIS创建渔网工具的使用方法 ArcGIS创建渔网工具的使用方法 ArcGIS创建渔网工具的使用方法
知道泛型擦除会造成多态的冲突吗?
类型擦除会造成多态的冲突,而JVM的解决方法就是泛型的桥接方法。 举例 现在有这样一个泛型类: class Pair&lt;T&gt; { private T value; public T getValue() { return value; } public void setValue(T va
学习真DDD的最佳路径
本文书接上回《DDD是软件工程的第一性原理?》,关注公众号(老肖想当外语大佬)获取信息: 最新文章更新; DDD框架源码(.NET、Java双平台); 加群畅聊,建模分析、技术实现交流; 视频和直播在B站。 假DDD的特征 在开始之前,考虑到目前关于DDD的资料非常多且杂,我们需要具备分辨的能力,确
学习真DDD的最佳路径 学习真DDD的最佳路径 学习真DDD的最佳路径
数据库服务器运维最佳实践
本文简要介绍了数据库服务器运维的最佳实践涵盖了多个方面,包括硬件选择、系统配置、性能优化、安全管理、数据备份与恢复、高可用性和灾难恢复等。
Flask细说
Flask框架 简介 特点: 微框架,间接,给开发者提供很大的扩展性 Flask和相应的插件写得很好,用起来很爽。 开发效率非常高,比如使用 SQLAlchemy 的 ORM 操作数据库可以节省开发者大量书写 sql 的时 间。 文档地址 中文文档(http://docs.jinkan.org/do
Flask细说 Flask细说 Flask细说
详细分析平衡树-红黑树的平衡修正 图文详解(附代码) (万字长文)
目录红黑树简述性质/规则主要规则:推导性质:红黑树的基本实现struct RBTreeNodeclass RBTree红黑树的插入红黑树插入修正前言什么时候需要变色:变色的基础:为什么需要旋转与变色变色:旋转需要修正的所有情况先认识最简单的情况1. 叔叔是红色结点注意:2.没有叔叔结点3. 叔叔是黑
详细分析平衡树-红黑树的平衡修正 图文详解(附代码) (万字长文) 详细分析平衡树-红黑树的平衡修正 图文详解(附代码) (万字长文) 详细分析平衡树-红黑树的平衡修正 图文详解(附代码) (万字长文)
Tomcat的配置文件中有哪些关键的配置项,它们分别有什么作用?
Tomcat的配置文件主要包括server.xml和web.xml,它们位于Tomcat安装目录下的conf文件夹中。今天的内容重点介绍 server.xml 文件的配置,V 哥会结合一些业务场景来介绍,希望可以帮助到你,以下是一些关键的配置项及其作用: server.xml中的配置项: &lt;S
网卡-热点搜索不到或者无法连接问题
大屏设置网卡开启热点后,经常收到反馈,手机端无法搜索到大屏热点、或者手机连接大屏热点失败 这类问题一般有以下几类情况: 1. 物理网卡IP与热点网卡IP相同 2. 热点网卡IP,非正常热点IP(192.168.137.X) 热点IP我们一般定为192.168.137.X,192.168.137.X是