在C#中使用RabbitMQ做个简单的发送邮件小项目 _

前言

好久没有做项目了,这次做一个发送邮件的小项目。发邮件是一个比较耗时的操作,之前在我的个人博客里面回复评论和友链申请是会通过发送邮件来通知对方的,不过当时只是简单的进行了异步操作。那么这次来使用RabbitMQ去统一发送邮件,我的想法是通过调用邮件发送接口,将请求发送到队列。然后在队列中接收并执行邮件发送操作。本文采用简单的点对点模式:

在点对点模式中,只会有一个消费者进行消费。

架构图

image

简单描述下项目结构。项目主要分为生产者、RabbitMQ、消费者这3个对象。

  • 生产者(Publisher):负责将邮件发送请求发送到RabbitMQ的队列中。

  • RabbitMQ服务器:作为消息中间件,用于接收并存储生产者发送的消息。

  • 消费者(Consumer):从RabbitMQ的队列中接收邮件发送请求,并执行实际的邮件发送操作。

项目结构

  • RabbitMQEmailProject

  • EamilApiProject 生产者

  • Controllers 控制器

  • Service 服务

  • RabiitMQClient 消费者

  • Program 主程序

  • Model 实体类

开始编码(一阶段)

首先我们先简单的将生产者和消费者代码完成,让生产者能够发送消息,消费者能够接受并处理消息。代码有点多,不过注释也多很容易看懂。给生产者和消费者都安装上用于处理RabiitMQ连接的Nuget包:

dotnet add package RabbitMQ.Client

生产者

EamilApiProject

配置文件

appsetting.json

"RabbitMQ": {  "Hostname": "localhost",  "Port": "5672",  "Username": "guest",  "Password": "guest"  
}

控制器

[ApiController]  
[Route("[controller]")]  
public class SendEmailController : ControllerBase  
{  private readonly EmailService _emailService;  public SendEmailController(EmailService emailService)  {       _emailService = emailService;  }  [HttpPost(Name = "SendEmail")]  public IActionResult Post([FromBody] EmailDto emailRequest)  {        _emailService.SendEamil(emailRequest);  return Ok("邮件已发送");  }
}

服务

RabbitMQ连接服务

public class RabbitMqConnectionFactory :IDisposable  
{  private readonly RabbitMqSettings _settings;  private IConnection _connection;  public RabbitMqConnectionFactory (IOptions<RabbitMqSettings> settings)  {       _settings = settings.Value;  }  public IModel CreateChannel()  {        if (_connection == null || _connection.IsOpen == false)  {            var factory = new ConnectionFactory()  {  HostName = _settings.Hostname,  UserName = _settings.Username,  Password = _settings.Password  };  _connection = factory.CreateConnection();  }  return _connection.CreateModel();  }  public void Dispose()  {        if (_connection != null)  {            if (_connection.IsOpen)  {               _connection.Close();  }            _connection.Dispose();  }    }
}

发送邮件服务

public class EmailService
{private readonly RabbitMqConnectionFactory _connectionFactory;public EmailService(RabbitMqConnectionFactory connectionFactory){_connectionFactory = connectionFactory;}public void SendEamil(EmailDto emailDto){using var channel = _connectionFactory.CreateChannel();var properties = channel.CreateBasicProperties();properties.Persistent = true;//消息持久化var message = JsonConvert.SerializeObject(emailDto);var body = Encoding.UTF8.GetBytes(message);channel.BasicPublish( string.Empty, "email_queue", properties, body);}
}

注册服务

builder.Services.Configure<RabbitMqSettings>(builder.Configuration.GetSection("RabbitMQ"));
builder.Services.AddSingleton<RabbitMqConnectionFactory >();
builder.Services.AddTransient<EmailService>();

实体

Model

public class EmailDto  
{  /// <summary>  /// 邮箱地址  /// </summary>  public string Email { get; set; }  /// <summary>  /// 主题  /// </summary>  public string Subject { get; set; }  /// <summary>  /// 内容  /// </summary>  public string Body { get; set; }  
}

public class RabbitMqSettings  
{  public string Hostname { get; set; }  public string Port { get; set; }  public string Username { get; set; }  public string Password { get; set; }  
}

消费者

RabiitMQClient

static void Main(string[] args)  
{  var factory = new ConnectionFactory { HostName = "localhost", Port = 5672, UserName = "guest", Password = "guest" };  using var connection = factory.CreateConnection();  using var channel = connection.CreateModel();  channel.QueueDeclare(queue: "email_queue",  durable: true,//是否持久化  exclusive: false,//是否排他  autoDelete: false,//是否自动删除  arguments: null);//参数  //这里可以设置prefetchCount的值,表示一次从队列中取多少条消息,默认是1,可以根据需要设置  //这里设置了prefetchCount为1,表示每次只取一条消息,然后处理完后再确认收到,这样可以保证消息的顺序性  //global是否全局  channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);  Console.WriteLine(" [*] 正在等待消息...");  //创建消费者  var consumer = new EventingBasicConsumer(channel);  //注册事件处理方法  consumer.Received += (model, ea) =>  {  byte[] body = ea.Body.ToArray();  var message = Encoding.UTF8.GetString(body);  var email = JsonConvert.DeserializeObject<EmailDto>(message);  Console.WriteLine(" [x] 发送邮件 {0}", email.Email);  //处理完消息后,确认收到  //multiple是否批量确认  channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);  };    //开始消费  //queue队列名  //autoAck是否自动确认,false表示手动确认  //consumer消费者  channel.BasicConsume(queue: "email_queue",  autoAck: false,  consumer: consumer);  Console.WriteLine(" 按任意键退出");  Console.ReadLine();  
}	

一阶段测试效果

一阶段就是消费者和生产者能正常运行。

image

image

可以看到生产者发送邮件之后,消费者能够正常消费请求。那么开始二阶段,将邮件发送代码完成,并实现能够通过队列处理邮件发送。对于邮件发送失败就简单的做下处理,相对较好的解决方案就是使用死信队列,将发送失败的消息放到死信队列处理。

简单的创建一个用于发送邮件的类,这里使用MailKit库发送邮件。

public class EmailService  
{  private readonly SmtpClient client;  public EmailService(SmtpClient client)  {  this.client = client;  }  public async Task SendEmailAsync(string from, string to, string subject, string body)  {try{await client.ConnectAsync("smtp.163.com", 465, SecureSocketOptions.SslOnConnect); // 认证  await client.AuthenticateAsync("zy1767992919@163.com", "");  // 创建一个邮件消息  var message = new MimeMessage(); message.From.Add(new MailboxAddress("发件人名称", from));  message.To.Add(new MailboxAddress("收件人名称", to));  message.Subject = subject;  // 设置邮件正文  message.Body = new TextPart("html")  {  Text = body  };  // 发送邮件  var response =await client.SendAsync(message);  // 断开连接  await client.DisconnectAsync(true);  }catch (Exception ex){// 断开连接  await client.DisconnectAsync(true);  throw new EmailServiceException("邮件发送失败", ex);  }}  
}  public class EmailServiceFactory  
{  public EmailService CreateEmailService()  {  var client = new SmtpClient();  return new EmailService(client);  }  
}  
public class EmailServiceException : Exception  
{  public EmailServiceException(string message) : base(message)  {  }  public EmailServiceException(string message, Exception innerException) : base(message, innerException)  {  }  
}  

接下来我们在消费者中调用邮件发送方法即可,如果不使用死信队列,我们只需要在事件处理代码加上邮件发送逻辑就行了。

consumer.Received += async (model, ea) =>
{byte[] body = ea.Body.ToArray();var message = Encoding.UTF8.GetString(body);var email = JsonConvert.DeserializeObject<EmailDto>(message);// 创建一个EmailServiceFactory实例var emailServiceFactory = new EmailServiceFactory();  // 使用EmailServiceFactory创建一个EmailService实例  var emailService = emailServiceFactory.CreateEmailService();  // 调用EmailService的SendEmailAsync方法来发送电子邮件  string from = "zy1767992919@163.com"; // 发件人地址  string to = email.Email; // 收件人地址  string subject = email.Subject; // 邮件主题  string emailbody = email.Body; // 邮件正文  try  {  await emailService.SendEmailAsync(from, to, subject, emailbody);  Console.WriteLine(" [x] 发送邮件 {0}", email.Email);}  catch (Exception ex)  {  Console.WriteLine(" [x] 发送邮件失败 " + ex.Message);  //这里可以记录日志//可以使用BasicNack方法,重新回到队列,重新消费}  //处理完消息后,确认收到//multiple是否批量确认channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
};

在上面中可以将发送失败的邮件重新放队列,多试几次,这里就不做多余的介绍了。

完成效果展示

一封正确的邮件

ok,现在展示邮件发送Demo的完整展示。首先我们来写一个正确的邮箱地址进行发送:

image

image

image

可以看到当我们发送请求之后,消费者正常消费了这条请求,同时邮件发送服务也正常执行。

多条发送邮件请求

那么接下来,我们通过Api测试工具,一次性发送多条邮件请求。其中包含正确的邮箱地址、错误的邮箱地址,看看消费者能不能正常消费呢~这里简单的发送3条请求,2封正确的邮件地址,一封错误的,看看2封正常邮件地址的能不能正常发送出去。

这里有个问题,如果我填的邮件格式是正确的但是这个邮件地址是不存在的,他是能正常发送过去的,然后会被邮箱服务器退回来,这里不知道该怎么判断是否发送成功。所以我这的错误地址是格式就不对的邮件地址,用来模拟因为网络原因或者其他原因导致的邮件发送不成功。

image

image

image

image

可以看到3条请求都成功了,并且消费者接收到并正确消费了。2条正确邮件也收到了,1条错误的邮件也捕获到了。

总结

本文通过使用RabiitMQ点对点模式来完成一个发送邮件的小项目,通过队列去处理邮件发送。通过RabbitMQ.Client库去连接RabbitMQ服务器。使用MailKit库发送邮件。通过使用RabbitMQ来避免邮件发送请求时间长的问题,同时能在消费者中重试、记录发送失败的邮件,来统一发送、统一处理。不足点就是被退回的邮件不知道该如何处理。可优化点:

  • 可以使用WorkQueues工作队列队列模式将消息分发给多个消费者,适用于消息量较大的情况。

  • 可以使用死信队列处理发送失败的邮件

文章转载自:妙妙屋(zy)

原文链接:https://www.cnblogs.com/ZYPLJ/p/18279034

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/39258.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue中路由来回切换页面直接卡死

今天发现一个很严重的问题&#xff0c;项目好不容易做好了&#xff0c;结果页面多了&#xff0c;切换之后卡死。页面所有的交互效果都失效了。 排查了许久的错误原因最后发现原来是路由名称重复了。 如上图当页面跳转到riskdetails详细页面之后&#xff0c;框架则被这个详情页…

node.lib下载失败,手动下载并配置

在无网络环境&#xff0c;或者网络不好的环境&#xff0c;node.lib会下载失败&#xff0c;此时可手动下载并进行配置。 我们以 node16.17.0 为例&#xff1a; 下载地址 分别下载node.lib和headers https://registry.npmmirror.com/-/binary/node/v16.17.0/win-x64/node.lib…

Linux rpm与yum

一、rpm包管理 rpm用于互联网下载包的打包及安装工具&#xff0c;它包含在某些Linux分发版中。它生成具有.RPM扩展名的文件。RPM是RedHat Package Manager (RedHat软件包管理工具&#xff09;的缩写&#xff0c;类似windows的setup.exe&#xff0c;这一文件格式名称虽然打上了R…

办理北京公司注销流程和步骤说明

公司的生命周期是多变的&#xff0c;有时候&#xff0c;业务可能会结束或者出现其他原因&#xff0c;需要注销公司。注销公司是一个复杂的法律过程&#xff0c;需要遵循一系列的步骤和提交特定的材料。下面我们将详细介绍北京注销公司的流程以及需要准备的材料&#xff0c;以帮…

私有云统一多云管理平台主要服务内容

私有云统一多云管理平台&#xff0c;作为企业IT架构现代化的关键组成部分&#xff0c;旨在为企业提供高效、灵活、安全的云计算资源管理解决方案。这类平台通过整合和优化不同云环境(包括私有云、公有云、混合云)的管理&#xff0c;帮助企业打破云孤岛&#xff0c;实现资源的统…

【游戏引擎之路】登神长阶(五)

5月20日-6月4日&#xff1a;攻克2D物理引擎。 6月4日-6月13日&#xff1a;攻克《3D数学基础》。 6月13日-6月20日&#xff1a;攻克《3D图形教程》。 6月21日-6月22日&#xff1a;攻克《Raycasting游戏教程》。 6月23日-6月30日&#xff1a;攻克《Windows游戏编程大师技巧》。 …

【Qwen2部署实战】Qwen2初体验:用Transformers打造智能聊天机器人

系列篇章&#x1f4a5; No.文章1【Qwen部署实战】探索Qwen-7B-Chat&#xff1a;阿里云大型语言模型的对话实践2【Qwen2部署实战】Qwen2初体验&#xff1a;用Transformers打造智能聊天机器人3【Qwen2部署实战】探索Qwen2-7B&#xff1a;通过FastApi框架实现API的部署与调用4【Q…

从任意用户注册到任意密码重置

写在最前面一句话 To be or not to be ,it‘s a question . 哎呀&#xff0c;放错台词了&#xff0c;应该是 true or false , 在最近的测试中遇到了一个很有趣的点 “将 false 改为true ”就可以成功绕过验证码了。 T rue or false &#xff1f;&#xff1f;&#xff1f; …

「51媒体」企业举行新闻发布会,如何邀请媒体到场报道

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体宣传加速季&#xff0c;100万补贴享不停&#xff0c;一手媒体资源&#xff0c;全国100城线下落地执行。详情请联系胡老师。 企业举行新闻发布会时&#xff0c;邀请媒体到场报道是一个…

MySQL常用操作命令大全

文章目录 一、连接与断开数据库1.1 连接数据库1.2 选择数据库1.3 断开数据库 二、数据库操作2.1 创建数据库2.2 查看数据库列表2.3 删除数据库 三、表操作3.1 创建表3.2 查看表结构3.3 修改表结构3.3.1 添加列3.3.2 删除列3.3.3 修改列数据类型 3.4 删除表 四、数据操作4.1 插入…

day62--若依框架(基础应用篇)

若依搭建 若依版本 官方 若依官方针对不同开发需求提供了多个版本的框架&#xff0c;每个版本都有其独特的特点和适用场景&#xff1a; 前后端混合版本&#xff1a;RuoYi结合了SpringBoot和Bootstrap的前端开发框架&#xff0c;适合快速构建传统的Web应用程序&#xff0c;其…

Qt加载SVG矢量图片,放大缩小图片质量不发生变化。

前言&#xff1a; 首先简单描述下SVG: SVG 意为可缩放矢量图形&#xff08;Scalable Vector Graphics&#xff09;。 SVG 使用 XML 格式定义图像。 给界面或者按钮上显示一个图标或背景图片&#xff0c;日常使用.png格式的文件完全够用&#xff0c;但是有些使用场景需要把图…

QChartView显示实时更新的温度曲线图(二)

文章目录 参考图说明1. 项目结构2. TempChartView.pro3. main.cpp4. TemperatureSeries.qml5. main.qml详细说明 参考图 说明 Qt Charts 提供了一系列使用图表功能的简单方法。它使用Qt Graphics View Framework 图形视图框架&#xff0c;因此可以很容易集成到用户界面。可以使…

基于小波分析的纹理和颜色反射对称性检测(MATLAB R2018A)

对称物体在自然图像和合成图像中普遍存在。作为对称物体最重要的全局特征之一&#xff0c;对称性检测长期以来都是计算机视觉领域的研究热点&#xff0c;并在图片的语义提取、图像语义理解以及情感识别等任务上具有广泛的应用。对称物体的检测技术&#xff0c;就是将图片中所蕴…

【前端】HTML+CSS复习记录【3】

文章目录 前言一、from&#xff08;表单&#xff09;二、style属性1、标签中直接定义&#xff08;内联样式&#xff09;2、定义在head中3、外部链接引用 四、 class 选择器系列文章目录 前言 长时间未使用HTML编程&#xff0c;前端知识感觉忘得差不多了。通过梳理知识点&#…

qq文件传输助手在哪里?详细图文教程告诉你(2024新版)

QQ作为一款功能强大的社交软件&#xff0c;不仅提供了聊天、语音、视频等多种通讯方式&#xff0c;还内置了文件传输助手这一实用工具。通过文件传输助手&#xff0c;用户可以在不同设备之间轻松传输文件&#xff0c;实现跨平台的便捷操作。 那么&#xff0c;qq文件传输助手在…

【@AutoWired和@Resource的区别】

AutoWired和Resource的区别 这两个我们在项目中&#xff0c;经常去使用。很少有人知道他们有什么区别。下面我们将从 来源依赖查找顺序支持的参数依赖注入的用法支持 这四个方面来说明他们俩个的区别 来源 Autowired: 这是Spring框架自带的注解&#xff0c;用于实现自动依…

绝区零 Mac 下载安装详细教程(MacOS IPA 砸壳包 playCover 完美运行)

绝区零 7.4 号开始公测&#xff0c;但刚刚就可以开始下载了&#xff0c;我也是第一时间就迫不及待的安装到了我的 Mac 电脑上&#xff0c;感兴趣的朋友可以跟我一起安装试试 我这里是通过 playCover 的形式在 Mac 上安装运行的&#xff0c;根据之前原神的经验所以这次还是同样…

惠海 H6912 升压恒流芯片IC 支持2.6-40V升12V24V36V48V60V100V 10A 摄影灯 太阳能灯 UV灯 杀菌灯

1.产品描述 H6912是一款外围电路简洁的宽调光比升压调光LED恒流驱动器&#xff0c;可适用于2.6-40V输入 电压范围的LED恒流照明领域。H6912可以实现高精度的恒流效果&#xff0c;输出电流恒流精度≤士3%&#xff0c;电压工作范围为2.6-40V.可以轻松满足锂电池及中低压的应用需…

嵌入式Linux系统编程 — 6.1 信号的基本概念

目录 1 信号的概念和作用 1.1 什么是信号 1.2 信号的目的 1.3 信号如何处理 2 信号的分类 2.1 可靠信号与不可靠信号 2.2 实时信号与非实时信号 3 常见信号与默认行为 3.1 信号本质上是 int 类型数字编号 3.2 常见信号 1 信号的概念和作用 1.1 什么是信号 信号是一…