- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
好久没有做项目了,这次做一个发送邮件的小项目。发邮件是一个比较耗时的操作,之前在我的个人博客里面回复评论和友链申请是会通过发送邮件来通知对方的,不过当时只是简单的进行了异步操作。 那么这次来使用RabbitMQ去统一发送邮件,我的想法是通过调用邮件发送接口,将请求发送到队列。然后在队列中接收并执行邮件发送操作。 本文采用简单的点对点模式:
在点对点模式中,只会有一个消费者进行消费.
对于常用的RabbitMQ队列模式不了解的可以查看往期文章:
简单描述下项目结构。项目主要分为生产者、RabbitMQ、消费者这3个对象.
首先我们先简单的将生产者和消费者代码完成,让生产者能够发送消息,消费者能够接受并处理消息。代码有点多,不过注释也多很容易看懂。 给生产者和消费者都安装上用于处理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();
}
一阶段就是消费者和生产者能正常运行.
可以看到生产者发送邮件之后,消费者能够正常消费请求。那么开始二阶段,将邮件发送代码完成,并实现能够通过队列处理邮件发送。 对于邮件发送失败就简单的做下处理,相对较好的解决方案就是使用死信队列,将发送失败的消息放到死信队列处理,我这里就不用死信队列,对于死信队列感兴趣的可以查看往期文章:
简单的创建一个用于发送邮件的类,这里使用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的完整展示。 首先我们来写一个正确的邮箱地址进行发送:
可以看到当我们发送请求之后,消费者正常消费了这条请求,同时邮件发送服务也正常执行.
那么接下来,我们通过Api测试工具,一次性发送多条邮件请求。其中包含正确的邮箱地址、错误的邮箱地址,看看消费者能不能正常消费呢~ 这里简单的发送3条请求,2封正确的邮件地址,一封错误的,看看2封正常邮件地址的能不能正常发送出去.
这里有个问题,如果我填的邮件格式是正确的但是这个邮件地址是不存在的,他是能正常发送过去的,然后会被邮箱服务器退回来,这里不知道该怎么判断是否发送成功。所以我这的错误地址是格式就不对的邮件地址,用来模拟因为网络原因或者其他原因导致的邮件发送不成功.
可以看到3条请求都成功了,并且消费者接收到并正确消费了。2条正确邮件也收到了,1条错误的邮件也捕获到了.
本文通过使用RabiitMQ点对点模式来完成一个发送邮件的小项目,通过队列去处理邮件发送。 通过RabbitMQ.Client库去连接RabbitMQ服务器。 使用MailKit库发送邮件。 通过使用RabbitMQ来避免邮件发送请求时间长的问题,同时能在消费者中重试、记录发送失败的邮件,来统一发送、统一处理。 不足点就是被退回的邮件不知道该如何处理。 可优化点:
WorkQueues
工作队列队列模式将消息分发给多个消费者,适用于消息量较大的情况。最后此篇关于在C#中使用RabbitMQ做个简单的发送邮件小项目的文章就讲到这里了,如果你想了解更多关于在C#中使用RabbitMQ做个简单的发送邮件小项目的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个关于 JavaScript 语法的问题。实际上,我在自学 MEAN 堆栈教程时想出了编码(https://thinkster.io/mean-stack-tutorial#adding-aut
在我的书中它使用了这样的东西: for($ARGV[0]) { Expression && do { print "..."; last; }; ... } for 循环不完整吗?另外,do 的意义何
我已经编写了读取开关状态的代码,如果按 3 次 # 则退出。 void allkeypadTest(void) { static uint8_t modeKeyCount=0; do
因此,对于上周我必须做的作业,我必须使用 4 个 do-while 循环和 if 语句在 Java 中制作一个猜谜游戏。我无法成功完成它,类(class)已经继续,没有为我提供任何帮助。如果有人可以查
int i=1,j=0,n=10,k; do{ j+=i; i<<1; printf("%d\n",i); // printf("%d\n",12<<1); }while
此代码用于基本杂货计算器的按钮。当我按下按钮时,一个输入对话框会显示您输入商品价格的位置。我遇到的问题是我无法弄清楚如何获得 do ... while 循环以使输入对话框在输入后弹出。 我希望它始终恢
当我在循环中修改字符串或另一个变量时,它的条件是否每次都重新计算?或者在循环开始前一次 std::string a("aa"); do { a = "aaaa"; } while(a.size<10)
我刚刚写了这个,但我找不到问题。我使用代码块并编写了这个问题 error: expected 'while' before '{' token === Build finished: 1 errors
do { printf("Enter number (0-6): ", ""); scanf("%d", &Num); }while(Num >= 0 && Num 表示“超过”,<表
我有一个包含 10 个项目的 vector (为简单起见,所有项目都属于同一类,称其为“a”)。我想要做的是检查“A”不是 a) 隐藏墙壁或 b) 隐藏另一个“A”。我有一个碰撞函数可以做到这一点。
嗨,这是我的第二个问题。我有下表 |-----|-------|------|------| |._id.|..INFO.|.DONE.|.LAST.| |..1..|...A...|...N..|.
这个问题在这里已经有了答案: 关闭 12 年前。 Possible Duplicates: Why are there sometimes meaningless do/while and if/e
来自 wikibook在 F# 上有一小部分它说: What does let! do?# let! runs an async object on its own thread, then it i
我在 Real World Haskell 书中遇到了以下函数: namesMatching pat | not (isPattern pat) = do exists do
我有一个类似于下面的用例,我创建了多个图并使用 gridExtra 将它们排列到一些页面布局中,最后使用 ggsave 将其保存为 PDF : p1 % mutate(label2
当我使用具有 for 循环的嵌套 let 语句时,如果没有 (do (html5 ..)),我将无法运行内部 [:tr]。 (defpartial column-settings-layout [&
执行 vagrant up 时出现此错误: anr@anr-Lenovo-G505s ~ $ vagrant up Bringing machine 'default' up with 'virtua
# ################################################# # Subroutine to add data to the table Blas
我想创建一个检查特定日期格式的读取主机。此外,目标是检查用户输入是否正确,如果不正确,则提示应再次弹出。 当我刚接触编程时,发现了这段代码,这似乎很合适。我仍然在努力“直到” do {
我关注这个tutorial在谷歌云机器学习引擎上进行培训。我一步一步地跟着它,但是在将 ml 作业提交到云时我遇到了错误。我运行了这个命令。 sam@sam-VirtualBox:~/models/r
我是一名优秀的程序员,十分优秀!