基于事件总线EventBus实现邮件推送功能
有时候,有人给我的网站留了言,但是我必须要打开我的网站(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/)踩一踩,互加友链也可以的,谢谢大家,不喜勿喷喽!
基于事件总线EventBus实现邮件推送功能的更多相关文章
- NET Core2基于RabbitMQ对Web前端实现推送功能
NET Core2基于RabbitMQ对Web前端实现推送功能 https://www.cnblogs.com/Andre/p/10012329.html 在我们很多的Web应用中会遇到需要从后端将指 ...
- ASP.NET Core2基于RabbitMQ对Web前端实现推送功能
在我们很多的Web应用中会遇到需要从后端将指定的数据或消息实时推送到前端,通常的做法是前端写个脚本定时到后端获取,或者借助WebSocket技术实现前后端实时通讯.因定时刷新的方法弊端很多(已不再采用 ...
- SpringBoot邮件推送功能
鞠躬,道歉 抱歉,迟到了近一年的更新,这一年挺忙的,发生了很多事情,就厚脸皮拖更了,抱歉. 现在状态回来了,打算分享下近期学到的东西,这一年期间学到的东西可能会随意更新,其实也就是玩了下C# + un ...
- 基于ASP.NET Core 5.0使用RabbitMQ消息队列实现事件总线(EventBus)
文章阅读请前先参考看一下 https://www.cnblogs.com/hudean/p/13858285.html 安装RabbitMQ消息队列软件与了解C#中如何使用RabbitMQ 和 htt ...
- 记一次邮件推送的坑,c#基于smtp使用腾讯企业邮箱发送邮件总是失败的原因
今天在弄企业邮箱推送的东西,原版代码是这样的 public void SendEmail(string title, string content) { try { MailMessage mailM ...
- HTML5服务器端推送事件 解决PHP微信墙推送问题
问题描述 以前的文章中<PHP微信墙制作,开源>已经用PHP搭建了一个微信墙获取信息的服务器,然后我就在想推送技术应该怎么解决,上一篇已经用了.NET 的signalr做了一个微信墙,PH ...
- 事件总线EventBus
什么是事件总线管理? 将事件放到队列里,用于管理和分发: 保证应用的各个部分之间高效的通信及数据,事件分发: 模块间解耦: 什么是EventBus? EventBus是发布/订阅的事件总线.Event ...
- jenkins持续集成Allure生成报表+邮件推送
本次基于<jenkins 生成HTML报表,邮件推送>的基础上将生成HTML报表修改为Allure生成报表,可以参考官方文档:https://docs.qameta.io/allure/# ...
- 解决.NET Core中MailKit无法使用阿里云邮件推送服务的问题
在博问中(.net core怎么实现邮件发送)知道了MailKit无法使用阿里云邮件推送服务发送邮件的问题,自已实测也遇到同样的问题,而用自己搭建的邮件服务器没这个问题. 于是,向阿里云提交了工单.. ...
- Laravel 下结合阿里云邮件推送服务
最近在学习laravel做项目开发,遇到注册用户推送邮件的问题,之前用java做的时候是自己代码写的,也就是用ECS推送邮件,但是现在转php的laravel了就打算用php的邮件发送功能来推送邮件, ...
随机推荐
- 【论文阅读】TRO2022: A Two-Stage Optimization-Based Motion Planner for Safe Urban Driving
TRO2022: A Two-Stage Optimization-Based Motion Planner for Safe Urban Driving Summary: 探讨planning过程中 ...
- 使用kafka作为生产者生产数据_到_hbase
配置文件: agent.sources = r1agent.sinks = k1agent.channels = c1 ## sources configagent.sources.r1.type = ...
- Win10 下安装使用easyocr图片识别工具
[前言] 最近在做图像识别相关的工作,找到了一个名为EasyOCR的pythoh 库. 使用过程中出现了一些问题,现做简单记录. [正文] 1. 安装EasyOCR 我用了最简单的方法:pip3 in ...
- 拟合算法与matlab工具包实现
与插值问题不同,在拟合问题中不需要曲线一定经过给定的点.拟合问题的目标是寻求一个函数(曲线),使得该曲线在某种准则下与所有的数据点最为接近,即曲线拟合的最好(最小化损失函数) 目录 一.插值和拟合的区 ...
- 文件系统(九):一文看懂yaffs2文件系统原理
liwen01 2024.07.07 前言 yaffs 是专为nand flash 设计的一款文件系统,与jffs 类似,都是属于日志结构文件系统.与jffs 不同的是,yaffs 文件系统利用了na ...
- 解决方案 | cad选择集找出包含特定字符串的多行文本
代码如下: 1 # 选择文本中出现特定单词的多行文字 2 # 下面的代码将选择条件定义为文本字符串中出现"The"的任意选项.此示例还演示了选择方法的用法:MtextSelectB ...
- oeasy教您玩转python - 007 - # 字符本质
字符本质 回忆上次内容 hello world 不是从来就有的 来自于unix和c 虽然我们今天有各种先进的学习手段 最早的高级语言学习是从最早的那张打字机用纸的手写代码起源的 所以输出用的是 p ...
- 从零开始写 Docker(十九)---增加 cgroup v2 支持
本文为从零开始写 Docker 系列第十九篇,添加对 cgroup v2 的支持. 完整代码见:https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 d ...
- 在Django REST framework (DRF) 中,`request.query_params` 和 `request.data` 区别
在Django REST framework (DRF) 中,request.query_params 和 request.data 都是用来获取请求中的数据,但是它们之间有一些关键的区别: requ ...
- 基于EasyTcp4Net开发一个功能较为完善的去持久化聊天软件
之前自己写了一篇介绍TCP的一些常用的功能介绍和特征,并且用代码做了示例,最终开发了一个EasyTcp4Net的TCP工具库,其最大的特色就是使用了微软提供的高性能库中的一些数据结构来处理TCP数据. ...