在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.pswp.cn/diannao/39258.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39258.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/39258.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

vue中路由來回切換頁面直接卡死

今天發現一個很嚴重的問題&#xff0c;項目好不容易做好了&#xff0c;結果頁面多了&#xff0c;切換之后卡死。頁面所有的交互效果都失效了。 排查了許久的錯誤原因最后發現原來是路由名稱重復了。 如上圖當頁面跳轉到riskdetails詳細頁面之后&#xff0c;框架則被這個詳情頁…

隨機森林R語言預測工具

隨機森林&#xff08;Random Forest&#xff09;是一種基于決策樹的集成學習方法&#xff0c;它通過構建多個決策樹并集成它們的預測結果來提高預測的準確性。在R語言中&#xff0c;我們可以使用randomForest包來構建和訓練隨機森林模型。以下是對隨機森林的詳細介紹以及使用R語…

java高仿真數據生成器-需要的拿去

java高仿真數據生成器源碼-需要的拿去 nit-random-tools 介紹&#xff1a;高仿真數據生成器 逆天開源 java 證號碼, 姓名&#xff0c;職業, 日期&#xff0c;手機號 生成器 功能列表 編號功能描述class1號 生成器NitIdcardGenerator2姓名 生成器NitChineseNameGenerator3職…

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…

目標檢測算法的技術革新與應用案例

引言 目標檢測作為計算機視覺領域中的一項關鍵技術&#xff0c;近年來取得了顯著進展。從傳統的基于特征的方法到如今的深度學習算法&#xff0c;目標檢測技術在準確性、速度和魯棒性上均實現了大幅提升。本文將深入探討目標檢測算法的技術原理、發展歷程、最新進展以及實際應…

HarmonyOS--開發者證書考試地址

初級證書&#xff1a;華為開發者學堂 高級證書&#xff1a;華為開發者學堂 對應課程&#xff1a;華為開發者學堂

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;信息安全已成為企業生存與發展的基石。隨著網絡攻擊手段的不斷演變和復雜度的提升&#xff0c;信息系統等級保護&#xff08;簡稱“等保”&#xff09;作為國家信息安全保障體系的重要組成部分&#xff0c;其重要性日益凸顯。《等保測評實戰指南&a…

私有云統一多云管理平臺主要服務內容

私有云統一多云管理平臺&#xff0c;作為企業IT架構現代化的關鍵組成部分&#xff0c;旨在為企業提供高效、靈活、安全的云計算資源管理解決方案。這類平臺通過整合和優化不同云環境(包括私有云、公有云、混合云)的管理&#xff0c;幫助企業打破云孤島&#xff0c;實現資源的統…

clickhouse-client 數據導入導出

ClickHouse提供了clickhouse-client客戶端可用于數據的快速導入導出 官方文檔&#xff1a; Inserting Data from a File JSONL 格式 導出 clickhouse-client -h 127.0.0.1 --port 9000 -u default --password XXX -d default \--query "SELECT * from default.doc_typ…

【游戲引擎之路】登神長階(五)

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; …

Oracle PL / SQL包

在實踐中&#xff0c;您很少創建獨立的存儲函數或過程。 相反&#xff0c;你會使用一個包。 包可以一起組織相關的功能和過程&#xff0c;例如創建庫&#xff0c;但在PL / SQL中&#xff0c;庫被稱為包。 PL / SQL包有兩個部分&#xff1a; 包規格包裝體 包規范是包的公共…

使用fabric8操作k8s

文章目錄 一、引入fabric包二、認證1、使用config文件認證2、使用oauthtoken認證 三、pod的查詢和遍歷四、命名空間的創建和刪除五、deployment的創建和刪除部分參數說明1、resourceRequirements2、containerPorts3、envVarList4、volumeMounts和volumeList5、nodeAffinity 六、…

「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;其…

【Arm技術日:為AI終端準備了哪些新基石?】

過去一年&#xff0c;移動終端設備的長足進步令人贊嘆&#xff0c;例如人工智能 (AI) 從手機到筆記本電腦的巨大創新&#xff0c;并誕生了“新一代 AI 手機”和 AIPC。據IDC預測&#xff0c;2024年全球新一代AI手機的出貨量將達到1.7億部&#xff0c;占智能手機市場總量的近15%…