本篇分享的是NetCore建構的分散式郵件系統,主要採用NetCore的Api和控制台應用程式,由於此系統屬於公司的所以這裡只能分享設計圖和一些單純不設計業務的類別或方法;
為什麼要在公司中首例採用NetCore做開發,有些netcoreapi不是還不全面麼,您都敢嘗試?恐怕會有人這樣問我,我只能告訴你NetCore現在出2.0版本了,很多Framwork的常用封裝都已經有了,況且她主打的是MVC模式,能夠高效的開發系統,也有很多Core的Nuget包支援了,已經到達了幾乎可以放心大膽使用的地步,退一萬不說有些東西不支持那這又如何,可以採用接口的方式從其他地方對接過來也是一種不錯的處理方案。為了讓C#這門優秀的語言被廣泛應用,默默努力著。
#AspNetCore - MVC實戰系列目錄
#.NetCore上傳多重檔案的幾個範例
開源一個跨平台運行的服務外掛- TaskCore.Main#Form
##NET Core-學習筆記
#Asp.NetCore1.1版本沒了project .json,這樣來產生跨平台套件
正片環節- 分散式郵件系統設計圖表##分散式郵件系統說明其實由上圖可以知曉這裡我主要採用了Api+服務的模式,這也是現在網路公司經常採用的一種搭配預設;利用api接受請求插入待傳送郵件
佇列和入庫,然後透過部署多個NetCore跨平台服務(這裡服務指的是:控制台應用)
來做分散式處理操作,跨平台服務主要操作有:
. 郵件發送狀態的通知(如果需要通知子業務,那麼需要通知業務方郵件發送的狀態). 通知失敗處理(自動往綁定的責任人發送一封郵件)
. 填充隊列(如果待發郵件佇列或通知佇列資料不完整,需要修復佇列資料)
Api介面的統一驗證入口
###這裡我用最簡單的方式,###繼承###Controller封裝了一個父級的BaseController,來讓各個api的Controller基礎統一來做身份驗證;來看看重寫 ######public### ###override### ###void### On###Action###Executing(ActionExecutingContext context)### 的驗證程式碼:# ###########1 public override void OnActionExecuting(ActionExecutingContext context) 2 { 3 base.OnActionExecuting(context); 4 5 var moResponse = new MoBaseRp(); 6 try 7 { 8 9 #region 安全性验证 10 11 var key = "request"; 12 if (!context.ActionArguments.ContainsKey(key)) { moResponse.Msg = "请求方式不正确"; return; } 13 var request = context.ActionArguments[key]; 14 var baseRq = request as MoBaseRq; 15 //暂时不验证登录账号密码 16 if (string.IsNullOrWhiteSpace(baseRq.UserName) || string.IsNullOrWhiteSpace(baseRq.UserPwd)) { moResponse.Msg = "登录账号或密码不能为空"; return; } 17 else if (baseRq.AccId <= 0) { moResponse.Msg = "发送者Id无效"; return; } 18 else if (string.IsNullOrWhiteSpace(baseRq.FuncName)) { moResponse.Msg = "业务方法名不正确"; return; } 19 20 //token验证 21 var strToken = PublicClass._Md5($"{baseRq.UserName}{baseRq.AccId}", ""); 22 if (!strToken.Equals(baseRq.Token, StringComparison.OrdinalIgnoreCase)) { moResponse.Msg = "Token验证失败"; return; } 23 24 //验证发送者Id 25 if (string.IsNullOrWhiteSpace(baseRq.Ip)) 26 { 27 var account = _db.EmailAccount.SingleOrDefault(b => b.Id == baseRq.AccId); 28 if (account == null) { moResponse.Msg = "发送者Id无效。"; return; } 29 else 30 { 31 if (account.Status != (int)EnumHelper.EmStatus.启用) 32 { 33 moResponse.Msg = "发送者Id已禁用"; return; 34 } 35 36 //验证ip 37 var ipArr = account.AllowIps.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 38 //当前请求的Ip 39 var nowIp = this.GetUserIp(); 40 baseRq.Ip = nowIp; 41 //默认*为所有ip , 匹配ip 42 if (!ipArr.Any(b => b.Equals("*")) && !ipArr.Any(b => b.Equals(nowIp))) 43 { 44 moResponse.Msg = "请求IP为授权"; return; 45 } 46 } 47 } 48 else 49 { 50 var account = _db.EmailAccount.SingleOrDefault(b => b.Id == baseRq.AccId && b.AllowIps.Any(bb => bb.Equals(baseRq.Ip))); 51 if (account == null) { moResponse.Msg = "发送者未授权"; return; } 52 else if (account.Status != (int)EnumHelper.EmStatus.启用) 53 { 54 moResponse.Msg = "发送者Id已禁用"; return; 55 } 56 } 57 58 //内容非空,格式验证 59 if (!context.ModelState.IsValid) 60 { 61 var values = context.ModelState.Values.Where(b => b.Errors.Count > 0); 62 if (values.Count() > 0) 63 { 64 moResponse.Msg = values.First().Errors.First().ErrorMessage; 65 return; 66 } 67 } 68 69 #endregion 70 71 moResponse.Status = 1; 72 } 73 catch (Exception ex) 74 { 75 moResponse.Msg = "O No请求信息错误"; 76 } 77 finally 78 { 79 if (moResponse.Status == 0) { context.Result = Json(moResponse); } 80 } 81 }###郵件請求父類別實體:###
1 /// <summary> 2 /// 邮件请求父类 3 /// </summary> 4 public class MoBaseRq 5 { 6 7 public string UserName { get; set; } 8 9 public string UserPwd { get; set; } 10 11 /// <summary> 12 /// 验证token(Md5(账号+配置发送者账号信息的Id+Ip)) 必填 13 /// </summary> 14 public string Token { get; set; } 15 16 /// <summary> 17 /// 配置发送者账号信息的Id 必填 18 /// </summary> 19 public int AccId { get; set; } 20 21 /// <summary> 22 /// 业务方法名称 23 /// </summary> 24 public string FuncName { get; set; } 25 26 /// <summary> 27 /// 请求者Ip,如果客户端没赋值,默认服务端获取 28 /// </summary> 29 public string Ip { get; set; } 30 31 }
此邮件系统使用到了第三方包,这也能够看出有很多朋友正为开源,便利,NetCore的推广努力着;
首先看看MailKit(邮件发送)包,通过安装下载命令: Install-Package MailKit 能够下载最新包,然后你不需要做太花哨的分装,只需要正对于邮件发送的服务器,端口,账号,密码做一些设置基本就行了,如果可以您可以直接使用我的代码:
1 /// 631fb227578dfffda61e1fa4d04b7d25 2 /// 发送邮件 3 /// 039f3e95db2a684c7b74365531eb6044 4 /// 99b66721bb716c6eb6248858e6afabfd8bb7487ae6a16a43571bc14c7fcf93c2 5 /// 0650a652733f77474d453f9eb9a48d388bb7487ae6a16a43571bc14c7fcf93c2 6 /// 1c385df0e091b792f8bbe6e914c8e6778bb7487ae6a16a43571bc14c7fcf93c2 7 /// 77f0c99f65057d8cbdf7a816a81405a98bb7487ae6a16a43571bc14c7fcf93c2 8 /// 1e8c6117c440c6fca484f2c6d869a8108bb7487ae6a16a43571bc14c7fcf93c2 9 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 10 public static bool _SendEmail( 11 Dictionary1c31b2bed3c3ee28739bab1dbc9f292f dicToEmail, 12 string title, string content, 13 string name = "爱留图网", string fromEmail = "841202396@qq.com", 14 string host = "smtp.qq.com", int port = 587, 15 string userName = "841202396@qq.com", string userPwd = "123123") 16 { 17 var isOk = false; 18 try 19 { 20 if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(content)) { return isOk; } 21 22 //设置基本信息 23 var message = new MimeMessage(); 24 message.From.Add(new MailboxAddress(name, fromEmail)); 25 foreach (var item in dicToEmail.Keys) 26 { 27 message.To.Add(new MailboxAddress(item, dicToEmail[item])); 28 } 29 message.Subject = title; 30 message.Body = new TextPart("html") 31 { 32 Text = content 33 }; 34 35 //链接发送 36 using (var client = new SmtpClient()) 37 { 38 // For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS) 39 client.ServerCertificateValidationCallback = (s, c, h, e) => true; 40 41 //采用qq邮箱服务器发送邮件 42 client.Connect(host, port, false); 43 44 // Note: since we don't have an OAuth2 token, disable 45 // the XOAUTH2 authentication mechanism. 46 client.AuthenticationMechanisms.Remove("XOAUTH2"); 47 48 //qq邮箱,密码(安全设置短信获取后的密码) ufiaszkkulbabejh 49 client.Authenticate(userName, userPwd); 50 51 client.Send(message); 52 client.Disconnect(true); 53 } 54 isOk = true; 55 } 56 catch (Exception ex) 57 { 58 59 } 60 return isOk; 61 }
Redis方面的操作包StackExchange.Redis,现在NetCore支持很多数据库驱动(例如:Sqlserver,mysql,postgressql,db2等)这么用可以参考下这篇文章AspNetCore - MVC实战系列(一)之Sqlserver表映射实体模型,不仅如此还支持很多缓存服务(如:Memorycach,Redis),这里讲到的就是Redis,我利用Redis的list的队列特性来做分布式任务存储,尽管目前我用到的只有一个主Redis服务还没有业务场景需要用到主从复制等功能;这里分享的代码是基于StackExchange.Redis基础上封装对于string,list的操作:
1 public class StackRedis : IDisposable 2 { 3 #region 配置属性 基于 StackExchange.Redis 封装 4 //连接串 (注:IP:端口,属性=,属性=) 5 public string _ConnectionString = "127.0.0.1:6377,password=shenniubuxing3"; 6 //操作的库(注:默认0库) 7 public int _Db = 0; 8 #endregion 9 10 #region 管理器对象 11 12 /// 631fb227578dfffda61e1fa4d04b7d25 13 /// 获取redis操作类对象 14 /// 039f3e95db2a684c7b74365531eb6044 15 private static StackRedis _StackRedis; 16 private static object _locker_StackRedis = new object(); 17 public static StackRedis Current 18 { 19 get 20 { 21 if (_StackRedis == null) 22 { 23 lock (_locker_StackRedis) 24 { 25 _StackRedis = _StackRedis ?? new StackRedis(); 26 return _StackRedis; 27 } 28 } 29 30 return _StackRedis; 31 } 32 } 33 34 /// 631fb227578dfffda61e1fa4d04b7d25 35 /// 获取并发链接管理器对象 36 /// 039f3e95db2a684c7b74365531eb6044 37 private static ConnectionMultiplexer _redis; 38 private static object _locker = new object(); 39 public ConnectionMultiplexer Manager 40 { 41 get 42 { 43 if (_redis == null) 44 { 45 lock (_locker) 46 { 47 _redis = _redis ?? GetManager(this._ConnectionString); 48 return _redis; 49 } 50 } 51 52 return _redis; 53 } 54 } 55 56 /// 631fb227578dfffda61e1fa4d04b7d25 57 /// 获取链接管理器 58 /// 039f3e95db2a684c7b74365531eb6044 59 /// 907b64d6f55b544ebfbd0f523985dc218bb7487ae6a16a43571bc14c7fcf93c2 60 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 61 public ConnectionMultiplexer GetManager(string connectionString) 62 { 63 return ConnectionMultiplexer.Connect(connectionString); 64 } 65 66 /// 631fb227578dfffda61e1fa4d04b7d25 67 /// 获取操作数据库对象 68 /// 039f3e95db2a684c7b74365531eb6044 69 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 70 public IDatabase GetDb() 71 { 72 return Manager.GetDatabase(_Db); 73 } 74 #endregion 75 76 #region 操作方法 77 78 #region string 操作 79 80 /// 631fb227578dfffda61e1fa4d04b7d25 81 /// 根据Key移除 82 /// 039f3e95db2a684c7b74365531eb6044 83 /// 4b9508bee1b4e18a78c442a485ff2a938bb7487ae6a16a43571bc14c7fcf93c2 84 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 85 public async Task9eac9cfd9e022188a134e2cbc39820d5 Remove(string key) 86 { 87 var db = this.GetDb(); 88 89 return await db.KeyDeleteAsync(key); 90 } 91 92 /// 631fb227578dfffda61e1fa4d04b7d25 93 /// 根据key获取string结果 94 /// 039f3e95db2a684c7b74365531eb6044 95 /// 4b9508bee1b4e18a78c442a485ff2a938bb7487ae6a16a43571bc14c7fcf93c2 96 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 97 public async Task98c455a79ddfebb79781bff588e7b37e Get(string key) 98 { 99 var db = this.GetDb(); 100 return await db.StringGetAsync(key); 101 } 102 103 /// 631fb227578dfffda61e1fa4d04b7d25 104 /// 根据key获取string中的对象 105 /// 039f3e95db2a684c7b74365531eb6044 106 /// d65706098b2250eee2f48a758c9680c88ed7e392d75469626ca6a252f320a704 107 /// 4b9508bee1b4e18a78c442a485ff2a938bb7487ae6a16a43571bc14c7fcf93c2 108 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 109 public async Task8742468051c85b06f0a0af9e3e506b5c Get8742468051c85b06f0a0af9e3e506b5c(string key) 110 { 111 var t = default(T); 112 try 113 { 114 var _str = await this.Get(key); 115 if (string.IsNullOrWhiteSpace(_str)) { return t; } 116 117 t = JsonConvert.DeserializeObject8742468051c85b06f0a0af9e3e506b5c(_str); 118 } 119 catch (Exception ex) { } 120 return t; 121 } 122 123 /// 631fb227578dfffda61e1fa4d04b7d25 124 /// 存储string数据 125 /// 039f3e95db2a684c7b74365531eb6044 126 /// 4b9508bee1b4e18a78c442a485ff2a938bb7487ae6a16a43571bc14c7fcf93c2 127 /// baf7518bd06a20614696879534a64e8e8bb7487ae6a16a43571bc14c7fcf93c2 128 /// 52d2b40b4cb5f9a554e9da01ad3fff218bb7487ae6a16a43571bc14c7fcf93c2 129 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 130 public async Task9eac9cfd9e022188a134e2cbc39820d5 Set(string key, string value, int expireMinutes = 0) 131 { 132 var db = this.GetDb(); 133 if (expireMinutes > 0) 134 { 135 return db.StringSet(key, value, TimeSpan.FromMinutes(expireMinutes)); 136 } 137 return await db.StringSetAsync(key, value); 138 } 139 140 /// 631fb227578dfffda61e1fa4d04b7d25 141 /// 存储对象数据到string 142 /// 039f3e95db2a684c7b74365531eb6044 143 /// d65706098b2250eee2f48a758c9680c88ed7e392d75469626ca6a252f320a704 144 /// 4b9508bee1b4e18a78c442a485ff2a938bb7487ae6a16a43571bc14c7fcf93c2 145 /// baf7518bd06a20614696879534a64e8e8bb7487ae6a16a43571bc14c7fcf93c2 146 /// 52d2b40b4cb5f9a554e9da01ad3fff218bb7487ae6a16a43571bc14c7fcf93c2 147 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 148 public async Task9eac9cfd9e022188a134e2cbc39820d5 Set8742468051c85b06f0a0af9e3e506b5c(string key, T value, int expireMinutes = 0) 149 { 150 try 151 { 152 var jsonOption = new JsonSerializerSettings() 153 { 154 ReferenceLoopHandling = ReferenceLoopHandling.Ignore 155 }; 156 var _str = JsonConvert.SerializeObject(value, jsonOption); 157 if (string.IsNullOrWhiteSpace(_str)) { return false; } 158 159 return await this.Set(key, _str, expireMinutes); 160 } 161 catch (Exception ex) { } 162 return false; 163 } 164 #endregion 165 166 #region List操作(注:可以当做队列使用) 167 168 /// 631fb227578dfffda61e1fa4d04b7d25 169 /// list长度 170 /// 039f3e95db2a684c7b74365531eb6044 171 /// d65706098b2250eee2f48a758c9680c88ed7e392d75469626ca6a252f320a704 172 /// 4b9508bee1b4e18a78c442a485ff2a938bb7487ae6a16a43571bc14c7fcf93c2 173 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 174 public async Task4db15037c2d45d75b28ec2b6a696f099 GetListLen8742468051c85b06f0a0af9e3e506b5c(string key) 175 { 176 try 177 { 178 var db = this.GetDb(); 179 return await db.ListLengthAsync(key); 180 } 181 catch (Exception ex) { } 182 return 0; 183 } 184 185 /// 631fb227578dfffda61e1fa4d04b7d25 186 /// 获取队列出口数据并移除 187 /// 039f3e95db2a684c7b74365531eb6044 188 /// d65706098b2250eee2f48a758c9680c88ed7e392d75469626ca6a252f320a704 189 /// 4b9508bee1b4e18a78c442a485ff2a938bb7487ae6a16a43571bc14c7fcf93c2 190 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 191 public async Task8742468051c85b06f0a0af9e3e506b5c GetListAndPop8742468051c85b06f0a0af9e3e506b5c(string key) 192 { 193 var t = default(T); 194 try 195 { 196 var db = this.GetDb(); 197 var _str = await db.ListRightPopAsync(key); 198 if (string.IsNullOrWhiteSpace(_str)) { return t; } 199 t = JsonConvert.DeserializeObject8742468051c85b06f0a0af9e3e506b5c(_str); 200 } 201 catch (Exception ex) { } 202 return t; 203 } 204 205 /// 631fb227578dfffda61e1fa4d04b7d25 206 /// 集合对象添加到list左边 207 /// 039f3e95db2a684c7b74365531eb6044 208 /// d65706098b2250eee2f48a758c9680c88ed7e392d75469626ca6a252f320a704 209 /// 4b9508bee1b4e18a78c442a485ff2a938bb7487ae6a16a43571bc14c7fcf93c2 210 /// 3c7bf1722eecc8ca7bc30043734f11978bb7487ae6a16a43571bc14c7fcf93c2 211 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 212 public async Task4db15037c2d45d75b28ec2b6a696f099 SetLists8742468051c85b06f0a0af9e3e506b5c(string key, List8742468051c85b06f0a0af9e3e506b5c values) 213 { 214 var result = 0L; 215 try 216 { 217 var jsonOption = new JsonSerializerSettings() 218 { 219 ReferenceLoopHandling = ReferenceLoopHandling.Ignore 220 }; 221 var db = this.GetDb(); 222 foreach (var item in values) 223 { 224 var _str = JsonConvert.SerializeObject(item, jsonOption); 225 result += await db.ListLeftPushAsync(key, _str); 226 } 227 return result; 228 } 229 catch (Exception ex) { } 230 return result; 231 } 232 233 /// 631fb227578dfffda61e1fa4d04b7d25 234 /// 单个对象添加到list左边 235 /// 039f3e95db2a684c7b74365531eb6044 236 /// d65706098b2250eee2f48a758c9680c88ed7e392d75469626ca6a252f320a704 237 /// 4b9508bee1b4e18a78c442a485ff2a938bb7487ae6a16a43571bc14c7fcf93c2 238 /// baf7518bd06a20614696879534a64e8e8bb7487ae6a16a43571bc14c7fcf93c2 239 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 240 public async Task4db15037c2d45d75b28ec2b6a696f099 SetList8742468051c85b06f0a0af9e3e506b5c(string key, T value) 241 { 242 var result = 0L; 243 try 244 { 245 result = await this.SetLists(key, new List8742468051c85b06f0a0af9e3e506b5c { value }); 246 } 247 catch (Exception ex) { } 248 return result; 249 } 250 251 252 #endregion 253 254 #region 额外扩展 255 256 /// 631fb227578dfffda61e1fa4d04b7d25 257 /// 手动回收管理器对象 258 /// 039f3e95db2a684c7b74365531eb6044 259 public void Dispose() 260 { 261 this.Dispose(_redis); 262 } 263 264 public void Dispose(ConnectionMultiplexer con) 265 { 266 if (con != null) 267 { 268 con.Close(); 269 con.Dispose(); 270 } 271 } 272 273 #endregion 274 275 #endregion 276 }
用到Redis的那些操作就添加哪些就行了,也不用太花哨能用就行;
这小节的内容最重要,由于之前有相关的文章,这里就不用再赘述了,来这里看看:Asp.NetCore1.1版本没了project.json,这样来生成跨平台包
以上是.Net Core分散式郵件系統的詳細內容。更多資訊請關注PHP中文網其他相關文章!