這裡想補充下上個文章,感覺有點不太行。因為每次設計新的表結構就要去更新一下,所以,乾脆隨著我要做的功能去展示我的表結構設計,最終再把所有的表結構包括sql語句統計出來,感覺這樣更新會方便很多~ 這個文章主要是發送郵件的功能。之前提過,我不是一下子把後端全部完成,然後再一下子搞定後端。所以我前後端是要 ...
這裡想補充下上個文章,感覺有點不太行。因為每次設計新的表結構就要去更新一下,所以,乾脆隨著我要做的功能去展示我的表結構設計,最終再把所有的表結構包括sql語句統計出來,感覺這樣更新會方便很多~
這個文章主要是發送郵件的功能。之前提過,我不是一下子把後端全部完成,然後再一下子搞定後端。所以我前後端是要一起做,然後去完善介面功能。
登錄註冊這個功能,90%網站都需要。可以不用,但是不能沒有。本篇文章主要是實現註冊的驗證碼發放功能(郵件or手機號)。
因為發簡訊得收費,咳咳,所以這裡先做的郵件發送驗證碼。這個在網路衝浪的時候要註冊某些網站賬號的時候一般都會遇上把?之前看有的朋友是後臺返回一張驗證碼的圖片,這個我後面應該也會做一下,但我覺得意義不是特別大。就跟死驗證一樣,這樣我怎麼判定你輸入的郵箱是否是正確真實可用的呢?我不想給資料庫搞一堆臟數據,麻煩
之前微軟是自帶一個郵箱發送的庫的,現在好像棄用了,雖然還可以用。但是微軟推薦了一個新的開源的第三方庫MailKit。使用起來相對來說,代碼減少了不少,感覺挺好用的,官網也有案例,所以不會細說,官網的很詳細,我也不想寫博客都是搬運官網的。我的代碼會加上自己的註釋。哪怕你不看官網,跟著我這個流程走就100%沒問題。
正文開始~
先展示下,驗證碼表的資料庫結構:註釋說明都有應該無需細說了
建表SQL
create table dbo.AutoCode ( Id INT identity(1, 1) not null /*編號*/, Code VARCHAR(20) not null /*驗證碼*/, Phone VARCHAR(20) null /*手機號*/, Email VARCHAR(100) null /*郵箱*/, ExpDate FLOAT not null /*過期時間*/, CreateDate DATETIME default getdate() null /*創建日期*/, UpdateDate DATETIME default getdate() null /*修改日期*/ ); alter table dbo.AutoCode add constraint PK_AutoCode_Id primary key (Id); EXEC sp_addextendedproperty 'MS_Description', '驗證碼表', 'user', dbo, 'table', AutoCode, NULL, NULL; EXEC sp_addextendedproperty 'MS_Description', '編號', 'user', dbo, 'table', AutoCode, 'column', Id; EXEC sp_addextendedproperty 'MS_Description', '驗證碼', 'user', dbo, 'table', AutoCode, 'column', Code; EXEC sp_addextendedproperty 'MS_Description', '手機號', 'user', dbo, 'table', AutoCode, 'column', Phone; EXEC sp_addextendedproperty 'MS_Description', '郵箱', 'user', dbo, 'table', AutoCode, 'column', Email; EXEC sp_addextendedproperty 'MS_Description', '過期時間 (當前時間+過期時間)的時間戳', 'user', dbo, 'table', AutoCode, 'column', ExpDate; EXEC sp_addextendedproperty 'MS_Description', '創建日期 預設為當前時間', 'user', dbo, 'table', AutoCode, 'column', CreateDate; EXEC sp_addextendedproperty 'MS_Description', '修改日期 預設為當前時間', 'user', dbo, 'table', AutoCode, 'column', UpdateDate;View Code
使用SQLsugar生成實體到FastEasy.Model類庫中。
這裡插一句吧,前面不是用T4模板搞了個生成倉儲模式4個類的代碼生成嗎,我可能目前對於T4還是不是很會,在T4模板使用第三方庫的時候會報一個找不到netstand程式集的問題,我目前很疑惑。然後看了下別人的代碼生成器,好像通過後臺管理系統就可以,那目前可能我弄的這個還不是很好,但是也極大的減少了一些工作量,以後我會再學習下這塊兒,也搞個更方便的代碼生成器。T4模板確實有一些局限性……
那我是又在base基類添加了一個方法,CreateModels。我這個資料庫空空如也,所以選擇了全部生成,當然也可以根據條件去篩選生成,甚至重命名,修改欄位名,諸如此類的。這是sqlsugar提供的,真的很強大,可以去學習一下。中秋的時候阿妮亞又更新了一個功能,可以把增刪改寫成一個方法,但我目前還是沒看,只是看到了介紹,這個以後再去看一下,nb~
代碼只有一句:第一個參數是生成的目錄路徑,第二個是命名空間public void CreateModels() { db.DbFirst.IsCreateAttribute().CreateClassFile(@"D:\FastEasy\API\FastEasy.Model\Models\", "FastEasy.Model.Models"); }
如何使用呢。因為是要繼承base基類,所以得有個實體,所以我就手動創建了一個空的base類。然後用T4代碼生成器分別生成了倉儲層和服務層的介面和實現類。主要是避免沒有泛型傳入,註入的時候報錯。然後直接調用這個方法就好了,base類在這裡沒有作用!只是避免註入解析的時候不報錯!
其實應該稍微感覺的出來這樣好像有點麻煩……無奈現在手裡活不夠多,經驗欠缺,暫時沒有想到別的思路,但我們有代碼生成器,終歸也麻煩不到哪去,一分鐘就搞定了
到這裡,我們生成了實體,然後通過T4代碼生成器,同樣根據資料庫生成了各自的倉儲層和服務層。這裡就不得不提一個弊端了。但是這隻能說目前的,就是要先生成實體,因為T4模板不需要運行就可以生成,所以沒實體的話,依賴註入會找不到實體而報錯,這樣就沒法運行程式生成實體了。除非就是說,咱們搞個控制台程式單獨運行下?反正上邊也提到了,這個T4我弄的確實不到位,只是最基礎的一些功能,還需繼續完善呀~~~
言歸正傳,這篇文章主要說的是發送郵件!
上面簡單提到了,我們使用的是微軟推薦的一個第三方庫:MailKit+MimeKit。NuGet管理器自行下載。
MailKit是用於向郵箱服務端發送消息的。
MimeKit則是創建郵件實例的(包括並不局限:發送人接收人郵件內容等……)。
再簡單點理解就是,通過MailKIt把MimeKit創建的郵件實例發送給郵箱服務端,然後根據你的郵件實例解析發送相應的郵件。似乎我這麼說有點繞嘴,但我是這麼理解的。甚至一開始我都很好奇,為什麼要分成兩個包安裝呀,或者你內置進去唄?不過他這些個庫不僅僅支持你發送郵件,好像還能下載郵件,刪除郵件,挺多功能的,確實np,但這些我好像也用不上,有興趣的同學就自行學習吧~
實現註冊發送郵件驗證碼的功能。其實很簡單。首先搞清楚,我們需要的情景是如何的:
- 用戶填寫郵箱
- 發送驗證碼到郵箱
- 正則匹配輸入的是否正確格式
- 生成隨機驗證碼
- 新增到資料庫
- 返回結果給前端成功還是失敗
正則也沒啥可說的,因為你百度郵箱正則就找到了匹配規則拿過來用就好了。隨機驗證碼這個更沒什麼可說的Random……當然你可以搞更複雜的,但我覺得我這樣應該足夠了。
我把這兩個方法寫在了Common類庫中。因為他們不涉及業務邏輯,是可公用的。因此做成靜態類方法直接調用就好。
/// <summary> /// 檢驗是否手機號 /// </summary> /// <param name="number">號碼</param> /// <returns></returns> public static bool IsPhone(string number) { string phoneRegex = @"^1\d{10}$"; return Regex.IsMatch(number, phoneRegex); } /// <summary> /// 檢驗是否郵箱 /// </summary> /// <param name="number">號碼</param> /// <returns></returns> public static bool IsEmail(string number) { string emailRegex = @"^\S+@\S+\.\S+$"; return Regex.IsMatch(number, emailRegex); }
/// <summary> /// 創建一個4位隨機驗證碼 /// </summary> /// <returns></returns> public static string CreateRandomCode() { int num = 4; const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // 根據需要修改字元集合 Random random = new(); char[] code = new char[num]; for (int i = 0; i < num; i++) { code[i] = chars[random.Next(chars.Length)]; } return new string(code); }
包括發送郵件的功能,也理所應當的放在Common類庫中。目前因為針對發送郵件的功能,可能封裝的方法自由度不高,我貼的代碼只做參考,可以根據自己需求完善 郵件實例是用MimeKit中的MimeMessage創建的。裡面包含了發送人,接收人,郵件內容等。原本我想傳遞這個類型的參數,但是官方的這個實例中,發送人和接收人是不能直接賦值的,要Add添加進去。這對我們來說還是有點麻煩的 ,主要是傳參太麻煩了。所以自己創建了一個新的實體類型Mime,主要用來傳遞參數。
using MimeKit; namespace FastEasy.Common.Extension.Mail { public class Mime { /// <summary> /// 主題 /// </summary> public string Subject { get; set; } /// <summary> /// 消息內容 /// </summary> public TextPart Body { get; set; } /// <summary> /// 發送人,預設為“官方” /// </summary> public MailboxAddress From { get; set; } = new MailboxAddress("神里凌華", "[email protected]"); /// <summary> /// 郵箱SMTP授權碼 /// </summary> public string Code { get; set; } = "XXXXX"; /// <summary> /// 接收人 /// </summary> public MailboxAddress To { get; set; } /// <summary> /// 465加密25不加密 /// </summary> public int Port { get; set; } = 465; } }
發送郵件方法:通過上面的註釋應該都可以看懂,所以不過多解釋廢話了
/// <summary> /// 發送郵件 /// </summary> /// <param name="mime">郵件消息體</param> /// <returns></returns> public static async Task<bool> SendMail(Mime mime) { //郵件實例 MimeMessage message = new() { Subject = mime.Subject, Body = mime.Body, }; message.To.Add(mime.To); message.From.Add(mime.From); try { using var client = new SmtpClient(); client.Connect("smtp.163.com", mime.Port, mime.Port == 465); client.Authenticate(mime.From.Address, mime.Code); await client.SendAsync(message); client.Disconnect(true); } catch { return false; } return true; }
由此看來,該功能並未涉及程式本身的業務邏輯,因此只要在API程式層調用相應的公共方法,然後調用基類的新增方法即可完成。 API程式層代碼:
/// <summary> /// 發送驗證碼 /// </summary> /// <param name="user">郵箱或者手機號</param> /// <returns></returns> [HttpPost] [SwaagerRoute(SwaggerVersion.FastEasy, "CreateCode")] public async Task<ResultDto> CreateCode(string user) { var result = new ResultDto(); var code = RandomCode.CreateRandomCode(); if (RegexMatch.IsPhone(user)) { result.State = false; result.Message = "暫不支持手機號註冊"; } else if (RegexMatch.IsEmail(user)) { var mime = new Mime { Body = new MimeKit.TextPart() { Text = code }, Subject = "驗證碼", To = new MimeKit.MailboxAddress(user, user), }; var autocode = new AutoCode { Code = code, CreateDate = DateTime.Now, Email = user, ExpDate = DateTimeOffset.Now.AddMinutes(10).ToUnixTimeSeconds(), UpdateDate = DateTime.Now }; if (await EmailKit.SendMail(mime) && await service.CreateEntityAsync(autocode)) { result.State = true; result.Message = "發送成功請查收"; } else { result.State = false; result.Message = "發送失敗"; } } else { result.State = false; result.Message = "請輸入正確的註冊號碼"; }; return result; }
我承認我廢話太多了,我本意想講的詳細點……看效果:
掰掰,中秋國慶快樂……我還沒買到票……