AspNetCore-MVC實戰系列(二)之通過繫結郵箱找回密碼

神牛003發表於2017-04-28

AspNetCore - MVC實戰系列目錄

. 愛留圖網站誕生

. git原始碼:https://github.com/shenniubuxing3/LovePicture.Web

. AspNetCore - MVC實戰系列(一)之Sqlserver表對映實體模型

. AspNetCore-MVC實戰系列(二)之通過繫結郵箱找回密碼

AspNetCore-MVC實戰系列(三)之個人中心

AspNetCore-MVC實戰系列(四)之賬戶設定

開篇嘮嗑

本篇內容寫在5.1假期前夕,主要是讓大家能在節假日休息充點的時候能有好的乾貨例子,到目前為止netcore方面的實戰例子分享即將進入正軌,謝謝各位朋友多多支援;最近工作安排的新專案即將開始,專案前期就我一人搭建,讓我猶豫的是對於公司這個內部系統並且是初建的專案用什麼開發方式好呢,最初考慮的是mvc5但是又想了下如果這樣還不如直接使用NetCore1.1的MVC呢,因為現在這版本基本也算穩定了可以試試水,可是又有顧慮是mvc專案在上線的時候會影響到其他人的使用(前期不考慮nginx分發),然後目光又轉向aspx網站的方式,不得不說這種方式在釋出上的確有優勢,尤其是在沒有分散式的前提下;好吧目前還在考慮中,希望能得到各位朋友的建議。。。

註冊模組

首先,這裡講解的內容對應的實體和表結構是基於上一篇文章建立的專案這裡就不多說了;對於一個註冊功能來說,通常需要的屬性是:賬號,密碼,確認密碼,驗證碼(可省略),甚至有些快捷的註冊方式就是通過手機號來註冊,當然咋們沒有簡訊通道的功能不能發簡訊,所以採用前者,先來看下Action中設計程式碼:

Register的get路由Action

1  // GET: Member/Create
2         public IActionResult Register()
3         {
4             return View();
5         }

Register的post提交註冊資訊的Action

 1 [HttpPost]
 2         [ValidateAntiForgeryToken]
 3         public async Task<IActionResult> Register([Bind("UserName,UserPwd,ComfirmPwd")] MoRegisterUser loginUser)
 4         {
 5             if (ModelState.IsValid)
 6             {
 7                 #region 驗證
 8                 if (_context.ToUserInfo.Any(b => b.UserName.ToUpper() == loginUser.UserName.Trim().ToUpper()))
 9                 {
10                     this.MsgBox("已經存在相同的賬號!");
11                     return View(loginUser);
12                 }
13                 #endregion
14 
15                 #region 入庫
16 
17                 ToUserInfo userInfo = new ToUserInfo();
18 
19                 userInfo.UserName = loginUser.UserName.Trim();
20                 userInfo.UserPwd = PublicClass._Md5(loginUser.UserPwd.Trim());
21                 userInfo.NickName = userInfo.UserName;
22                 userInfo.Status = (int)EnumHelper.EmUserStatus.啟用;
23                 userInfo.CreateTime = DateTime.Now;
24                 userInfo.LevelNum = (int)EmLevelNum.註冊;
25 
26                 userInfo.Ips = this.GetUserIp();
27                 userInfo.HeadPhoto = "/images/ailiutu_user.png";
28                 userInfo.Sex = false;
29 
30                 _context.Add(userInfo);
31                 var result = await _context.SaveChangesAsync();
32                 if (result > 0)
33                 {
34                     var moUserInfo = new MoUserInfo
35                     {
36                         Id = userInfo.Id,
37                         UserName = userInfo.UserName,
38                         NickName = userInfo.NickName,
39                         Addr = userInfo.Addr,
40                         Birthday = userInfo.Birthday,
41 
42                         Blog = userInfo.Blog,
43                         CreateTime = userInfo.CreateTime,
44                         Email = userInfo.Email,
45                         HeadPhoto = userInfo.HeadPhoto,
46                         Introduce = userInfo.Introduce,
47 
48                         Ips = userInfo.Ips,
49                         LevelNum = userInfo.LevelNum,
50                         Sex = userInfo.Sex,
51                         Tel = userInfo.Tel,
52                         Status = userInfo.Status,
53 
54                         LoginTime = DateTime.Now
55                     };
56                     HttpContext.Session.Set<MoUserInfo>(HttpContext.Session.SessionKey(), moUserInfo);
57 
58                     if (!string.IsNullOrWhiteSpace(moUserInfo.Ips))
59                     {
60                         _context.ToUserLog.Add(new ToUserLog
61                         {
62                             CodeId = (int)EmLogCode.登入,
63                             CreateTime = DateTime.Now,
64                             Des = $"IP:{moUserInfo.Ips},登入時間:{moUserInfo.LoginTime.ToString("yyyy-MM-dd HH:mm")}",
65                             UserId = userInfo.Id
66                         });
67                     }
68 
69                     _context.ToUserLog.Add(new ToUserLog
70                     {
71                         CodeId = (int)EmLogCode.積分,
72                         CreateTime = DateTime.Now,
73                         Des = $"【註冊】  +{(int)EmLevelNum.註冊}",
74                         UserId = userInfo.Id
75                     });
76                     await _context.SaveChangesAsync();
77 
78                     return RedirectToAction(nameof(HomeController.Index), "home");
79                 }
80                 #endregion
81 
82                 this.MsgBox("註冊失敗,請稍後重試。");
83                 return View(loginUser);
84             }
85             return View(loginUser);
86         }

通過Post的Action能夠看出註冊處理的Action主要操作步驟有以下幾點:

. ModelState.IsValid驗證提交的資訊是否滿足model規則設定

. Linq的Any()方法驗證是否存在相同賬號

. _context.Add()入庫註冊資訊

. HttpContext.Session.Set的擴充套件方法設定登陸的session

. 記錄登陸記錄和登陸增加的積分資訊

對於一個簡單的註冊基本就是這樣的流程,我們來看看提交註冊時的模型實體:

 1  /// <summary>
 2     /// 註冊實體
 3     /// </summary>
 4     public class MoRegisterUser
 5     {
 6         [Required(AllowEmptyStrings = false, ErrorMessage = "賬號長度範圍6-30字元!")]
 7         [Display(Prompt = "郵箱/手機號/6-30字元")]
 8         [RegularExpression(@"[^\s]{6,30}", ErrorMessage = "賬號長度範圍6-30字元。")]
 9         public string UserName { get; set; }
10 
11         [Required(AllowEmptyStrings = false, ErrorMessage = "密碼長度範圍6-20字元!")]
12         [DataType(DataType.Password)]
13         [Display(Prompt = "密碼長度範圍6-20字元!")]
14         [RegularExpression(@"[^\s]{6,20}", ErrorMessage = "密碼長度範圍6-20字元。")]
15         public string UserPwd { get; set; }
16 
17         [Compare("UserPwd", ErrorMessage = "密碼與確認密碼不相同!")]
18         [DataType(DataType.Password)]
19         [Display(Prompt = "必須與密碼相同")]
20         public string ComfirmPwd { get; set; }
21     }

這裡自定義的註冊模型,設定了DataAnnotations,以此來快速設定驗證,不用再每個都用js寫了,mvc框架幫你做了這些;下面看看View的程式碼:

 1 @model LovePicture.Model.MoClass.MoRegisterUser
 2 
 3 @{
 4     ViewData["Title"] = "註冊";
 5 }
 6 
 7 <h3><span class="glyphicon glyphicon-flag" aria-hidden="true"></span> 註冊</h3>
 8 <form name="form_submit" asp-action="Register">
 9     <div class="form-horizontal">
10         <h4>  愛留圖:歡迎您成為我們的一份子,讓我們一起留存珍惜的圖片吧。</h4>
11         <hr />
12         <div asp-validation-summary="ModelOnly" class="text-danger"></div>
13         <div class="form-group">
14             <label asp-for="UserName" class="col-md-2 control-label">賬號</label>
15             <div class="col-md-10">
16                 <input asp-for="UserName" required="required" class="form-control" />
17                 <span asp-validation-for="UserName" class="text-danger"></span>
18             </div>
19         </div>
20         <div class="form-group">
21             <label asp-for="UserPwd" class="col-md-2 control-label">密碼</label>
22             <div class="col-md-10">
23                 <input asp-for="UserPwd" required="required" class="form-control" />
24                 <span asp-validation-for="UserPwd" class="text-danger"></span>
25             </div>
26         </div>
27         <div class="form-group">
28             <label asp-for="ComfirmPwd" class="col-md-2 control-label">確認密碼</label>
29             <div class="col-md-10">
30                 <input asp-for="ComfirmPwd" class="form-control" />
31                 <span asp-validation-for="ComfirmPwd" class="text-danger"></span>
32             </div>
33         </div>
34 
35         <div class="form-group">
36             <div class="col-md-offset-2 col-md-10">
37                 <input type="button" value="注 冊" name="btnSubmit" class="btn btn-default" />
38                 <span id="msgbox" style="color:red">@ViewData["msgbox"]</span>
39             </div>
40         </div>
41     </div>
42 </form>
43 <br />
44 <div>
45      <a href="/member/login">有賬號去登入</a> | <a href="/member/ForgetPassword">忘記密碼?</a>
46 </div>
47 <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
48 <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
View Code

對於mvc模型註解的方式在前端需要引入這兩個js檔案

<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

View內容注意點在於我試圖中的Button按鈕是不是submit形式,這樣做的理由是,當您註冊的Action業務過多時,使用者點選註冊按鈕提交資料,這個時候如果無法快速響應資訊給使用者,那麼使用者可能多次點選,因此就有個需求是需要吧註冊提交按鈕置灰或者影藏點,這裡我為了方便把全站點的提交按鈕都弄成統一name的按鈕了,最後用js來提交submit(註冊效果):

 1  bindSubmitBtn: function () {
 2             $("input[name='btnSubmit']").on("click", function () {
 3 
 4                 var _btn = $(this);
 5                 _btn.addClass("hide");
 6                 var _msg = $("#msgbox");
 7                 _msg.html("提交中,請稍後...");
 8 
 9                 var _form = $("form[name='form_submit']");
10                 if (_form.valid()) {
11                     _form.submit();
12                 } else {
13                     _btn.removeClass("hide");
14                     _msg.html("");
15                 }
16             });
17         }

登入模組

從程式碼上來說登入和註冊相差不大,功能上登入模組主要用來驗證登陸使用者是否存在,分配唯一sessionid,如果有跳轉地址還需要在登陸成功後執行跳回原訪問地址;

Login的Get方式Action

 1  // GET: Member
 2         public IActionResult Login(string returnUrl = null)
 3         {
 4             //獲取session
 5             var userInfo = HttpContext.Session.Get<MoUserInfo>(HttpContext.Session.SessionKey());
 6             if (userInfo != null)
 7             {
 8                 if (string.IsNullOrWhiteSpace(returnUrl)) { return RedirectToAction(nameof(HomeController.Index), "Home"); }
 9                 else { Redirect(returnUrl); }
10             }
11             this.MsgBox(returnUrl, "returnUrl");
12             return View();
13         }

Login的Post登入方式Action

 1 [HttpPost]
 2         [ValidateAntiForgeryToken]
 3         public async Task<IActionResult> Login([Bind("UserName,UserPwd,ReturnUrl")] MoLoginUser loginUser)
 4         {
 5             if (ModelState.IsValid)
 6             {
 7                 #region 驗證
 8                 var md5Pwd = PublicClass._Md5(loginUser.UserPwd.Trim());
 9                 var userInfo = await _context.ToUserInfo.SingleOrDefaultAsync(b =>
10                         b.UserName.Equals(loginUser.UserName, StringComparison.CurrentCultureIgnoreCase) &&
11                         b.UserPwd.Equals(md5Pwd));
12                 if (userInfo == null)
13                 {
14                     this.MsgBox("賬號或密碼錯誤!");
15                     return View(loginUser);
16                 }
17                 else if (userInfo.Status == (int)EnumHelper.EmUserStatus.禁用)
18                 {
19                     this.MsgBox("該賬號已被禁用,或許你可以嘗試重新註冊一個賬號!");
20                     return View(loginUser);
21                 }
22                 #endregion
23 
24                 #region 更新登入資訊
25                 userInfo.Ips = this.GetUserIp();
26                 userInfo.LoginTime = DateTime.Now;
27                 userInfo.LevelNum += (int)EmLevelNum.登入;
28 
29                 //記錄session
30                 var moUserInfo = new MoUserInfo
31                 {
32                     Id = userInfo.Id,
33                     UserName = userInfo.UserName,
34                     NickName = userInfo.NickName,
35                     Addr = userInfo.Addr,
36                     Birthday = userInfo.Birthday,
37 
38                     Blog = userInfo.Blog,
39                     CreateTime = userInfo.CreateTime,
40                     Email = userInfo.Email,
41                     HeadPhoto = userInfo.HeadPhoto,
42                     Introduce = userInfo.Introduce,
43 
44                     Ips = userInfo.Ips,
45                     LevelNum = userInfo.LevelNum,
46                     Sex = userInfo.Sex,
47                     Tel = userInfo.Tel,
48                     Status = userInfo.Status,
49 
50                     LoginTime = Convert.ToDateTime(userInfo.LoginTime)
51                 };
52                 HttpContext.Session.Set<MoUserInfo>(HttpContext.Session.SessionKey(), moUserInfo);
53 
54                 if (!string.IsNullOrWhiteSpace(moUserInfo.Ips))
55                 {
56                     _context.ToUserLog.Add(new ToUserLog
57                     {
58                         CodeId = (int)EmLogCode.登入,
59                         CreateTime = DateTime.Now,
60                         Des = $"IP:{moUserInfo.Ips},登入時間:{moUserInfo.LoginTime.ToString("yyyy-MM-dd HH:mm")}",
61                         UserId = userInfo.Id
62                     });
63                 }
64 
65                 _context.ToUserLog.Add(new ToUserLog
66                 {
67                     CodeId = (int)EmLogCode.積分,
68                     CreateTime = DateTime.Now,
69                     Des = $"【登入】  +{(int)EmLevelNum.登入}",
70                     UserId = userInfo.Id
71                 });
72 
73                 await _context.SaveChangesAsync();
74 
75                 if (string.IsNullOrWhiteSpace(loginUser.ReturnUrl)) { return RedirectToAction(nameof(HomeController.Index), "Home"); }
76                 else { return Redirect(loginUser.ReturnUrl); }
77                 #endregion
78             }
79             return View(loginUser);
80         }

這裡模仿微軟官方例項給出的樣子,把登陸和註冊實體模型分開了,因為登陸模型不需要什麼重複密碼,並且還有其他的屬性如:回撥地址,驗證碼等:

 1 /// <summary>
 2     /// 登入實體
 3     /// </summary>
 4     public class MoLoginUser
 5     {
 6         [Required(AllowEmptyStrings = false, ErrorMessage = "賬號長度範圍6-30字元!")]
 7         [Display(Prompt = "郵箱/手機號/6-30字元")]
 8         [RegularExpression(@"[^\s]{6,30}", ErrorMessage = "賬號長度範圍6-30字元。")]
 9         public string UserName { get; set; }
10 
11         [Required(AllowEmptyStrings = false, ErrorMessage = "密碼長度範圍6-20字元!")]
12         [DataType(DataType.Password)]
13         [Display(Prompt = "密碼長度範圍6-20字元!")]
14         [RegularExpression(@"[^\s]{6,20}", ErrorMessage = "密碼長度範圍6-20字元。")]
15         public string UserPwd { get; set; }
16 
17         /// <summary>
18         /// 回跳地址
19         /// </summary>
20         public string ReturnUrl { get; set; }
21     }

同理對於登入的view設計也和註冊差不多,只是不同網站對於安全設定可能會增加一些驗證碼,或其他的驗證方式而已,如下登入View程式碼:

 1 @model LovePicture.Model.MoClass.MoLoginUser
 2 
 3 @{
 4     ViewData["Title"] = "登入";
 5 }
 6 
 7 <h3><span class="glyphicon glyphicon-send" aria-hidden="true"></span> 登入</h3>
 8 <form name="form_submit" asp-action="Login">
 9     <div class="form-horizontal">
10         <h4>愛留圖:即刻登入,讓我們一起留存珍惜的圖片吧。</h4>
11         <hr />
12         <div asp-validation-summary="ModelOnly" class="text-danger"></div>
13         <div class="form-group">
14             <label asp-for="UserName" class="col-md-2 control-label">賬號</label>
15             <div class="col-md-10">
16                 <input asp-for="UserName" required="required" class="form-control" />
17                 <span asp-validation-for="UserName" class="text-danger"></span>
18             </div>
19         </div>
20         <div class="form-group">
21             <label asp-for="UserPwd" class="col-md-2 control-label">密碼</label>
22             <div class="col-md-10">
23                 <input asp-for="UserPwd" required="required" class="form-control" />
24                 <span asp-validation-for="UserPwd" class="text-danger"></span>
25             </div>
26         </div>
27 
28         <div class="form-group">
29             <div class="col-md-offset-2 col-md-10">
30                 <input type="button" value="登 錄" name="btnSubmit" class="btn btn-default" /> <a href="/member/register" title="沒賬號來這裡註冊吧">沒賬號這裡註冊</a>
31                 <span id="msgbox" style="color:red">@ViewData["msgbox"]</span>
32             </div>
33         </div>
34     </div>
35     <input type="hidden" name="ReturnUrl" value="@ViewData["returnUrl"]" />
36 </form>
37 <br />
38 <div>
39     <a href="/member/register">沒賬號這裡註冊</a> | <a href="/member/ForgetPassword">忘記密碼?</a>
40 </div>
41 <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
42 <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
View Code

郵箱找回密碼

來到這裡才真正進入今天的主題,對於一些有安全設定的網站來說通常有類似於通過繫結郵箱找回密碼的功能,這裡愛留圖目前也做了這樣的設定:

如果是繫結郵箱的使用者,可以通過找回密碼-通過繫結郵箱找回密碼的功能來獲取重新設定密碼的許可權;首先我們需要一個忘記密碼的介面,好讓使用者填寫自己之前繫結好的郵箱,並且提交傳送找回郵箱的郵件操作,因此有了如下Ation程式碼:

ForgetPassword忘記密碼get的Action

1   public IActionResult ForgetPassword()
2         {
3             return View();
4         }

ForgetPassword忘記密碼post提交傳送郵件的Action

 1 /// <summary>
 2         /// 提交忘記密碼內容
 3         /// </summary>
 4         /// <param name="email"></param>
 5         /// <returns></returns>
 6         [HttpPost]
 7         [ValidateAntiForgeryToken]
 8         public async Task<IActionResult> ForgetPassword(string email)
 9         {
10             if (string.IsNullOrWhiteSpace(email)) { this.MsgBox("郵箱必填!"); return View(); }
11 
12             email = email.Trim().ToLower();
13             if (email.Length >= 50 || email.Length <= 3)
14             {
15                 this.MsgBox("郵箱長度不符!"); return View();
16             }
17             else if (!email.Contains("@"))
18             {
19                 this.MsgBox("郵箱格式不正確!"); return View();
20             }
21             var user = await _context.ToUserInfo.SingleOrDefaultAsync(b => b.Email.ToLower() == email);
22             if (user == null) { this.MsgBox("不存在該繫結郵箱的賬號!"); return View(); }
23             else if (user.Status == (int)EnumHelper.EmUserStatus.禁用)
24             {
25                 this.MsgBox("該繫結郵箱的賬號已被禁用,可以通過傳送郵件至:841202396@qq.com聯絡客服!"); return View();
26             }
27 
28             var timeOut = 10;
29             var now = DateTime.Now.AddMinutes(timeOut);
30             var expires = now.ToString("yyyy-MM-dd HH:mm");
31             var token = PublicClass._Md5($"{expires}-{email}-{Request.Host.Host}");
32             var appUrl = $"http://{Request.Host.Host}:{Request.Host.Port}";
33             var comfirmUrl = $"{appUrl}/member/confirmpassword?expire={expires}&token={token}&email={email}&t=0.{now.ToString("ssfff")}";
34 
35             //讀取模板
36             var tpl = await PublicClass._GetHtmlTpl(EnumHelper.EmEmailTpl.MsgBox, _selfSetting.EmailTplPath);
37             if (string.IsNullOrWhiteSpace(tpl)) { this.MsgBox("傳送繫結郵件失敗,請稍後重試。"); return View(); }
38 
39             tpl = tpl.Replace("{name}", "尊敬的使用者").
40               Replace("{content}", $"您正在使用<a href='{appUrl}'>愛留圖網</a>郵箱重置密碼功能,請點選以下連結確認繫結郵箱<a href='{comfirmUrl}'>{comfirmUrl}</a>;注意該地址有效時間{timeOut}分鐘。");
41             //傳送
42             var isOk = PublicClass._SendEmail(
43                new Dictionary<string, string> {
44                  { "尊敬的使用者",email}
45                },
46                "愛留圖 - 重置密碼",
47                tpl);
48 
49             this.MsgBox(isOk ? "已給您郵箱傳送了重置密碼郵件,請收件後點選重置密碼連結地址。" : "傳送繫結郵件失敗,請稍後重試!");
50 
51             return View();
52         }

這裡值得關注的是咋們構造了一個修改密碼的確認連結comfirmUrl,這裡我簡單設定的有連結超時時間,token加密的資訊,這些資訊主要用來在後面使用者點選此連結的時候跳轉到重置密碼的路由,當然我這裡是簡單設定驗證製作出來的重置密碼的url,大的入口網站可不會像這樣如此簡單構造連結和token的,這裡經供參考吧;這裡涉及到需要傳送郵件,我採用的是netcore第三方元件MailKit(就目前為止個人感覺很好用,能相容很多郵件的方式),這裡我用的是qq郵箱伺服器:

 1 /// <summary>
 2         /// 傳送郵件
 3         /// </summary>
 4         /// <param name="dicToEmail"></param>
 5         /// <param name="title"></param>
 6         /// <param name="content"></param>
 7         /// <param name="name"></param>
 8         /// <param name="fromEmail"></param>
 9         /// <returns></returns>
10         public static bool _SendEmail(
11             Dictionary<string, string> dicToEmail,
12             string title, string content,
13             string name = "愛留圖網", string fromEmail = "841202396@qq.com")
14         {
15             var isOk = false;
16             try
17             {
18                 if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(content)) { return isOk; }
19 
20                 //設定基本資訊
21                 var message = new MimeMessage();
22                 message.From.Add(new MailboxAddress(name, fromEmail));
23                 foreach (var item in dicToEmail.Keys)
24                 {
25                     message.To.Add(new MailboxAddress(item, dicToEmail[item]));
26                 }
27                 message.Subject = title;
28                 message.Body = new TextPart("html")
29                 {
30                     Text = content
31                 };
32 
33                 //連結傳送
34                 using (var client = new SmtpClient())
35                 {
36                     // For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS)
37                     client.ServerCertificateValidationCallback = (s, c, h, e) => true;
38 
39                     client.Connect("smtp.qq.com", 587, false); //這裡是qq郵箱
40 
41                     // Note: since we don't have an OAuth2 token, disable
42                     // the XOAUTH2 authentication mechanism.
43                     client.AuthenticationMechanisms.Remove("XOAUTH2");
44 
45                     // Note: only needed if the SMTP server requires authentication
46                     client.Authenticate("您郵箱", "你的郵箱密碼");
47 
48                     client.Send(message);
49                     client.Disconnect(true);
50                 }
51                 isOk = true;
52             }
53             catch (Exception ex)
54             {
55 
56             }
57             return isOk;
58         }

通過引數 message.Body = new TextPart("html") 來設定允許郵箱傳送html,為了我修改郵件模板方便我這裡自定義了郵件模板SettingEmail.html,主要是一些樣式的設定和內容部分的設定:

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="utf-8" />
 5     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 6     <title>愛留圖 - 繫結郵箱模板</title>
 7 </head>
 8 <body>
 9     <h3>{name} 您好:</h3>
10     <div style='padding-top:20px;width:100%'>{content}</div>
11     <div style='padding-top:30px;width:100%'>溫馨提示:如果您不是<a href='http://www.lovexins.com:9527'>愛留圖</a>使用者,請勿點選。</div>
12     <div style='padding-top:50px;width:100%'>
13         <a href='http://www.lovexins.com:9527'>
14             <img style="width:50px;height:50px" src="http://www.lovexins.com:9527/images/ailiutu_user.png" />
15             <br />
16             愛留圖網
17         </a>
18     </div>
19     <div style='padding-top:5px;width:100%;color:#ccc;border-top:1px solid #ccc'>此為系統郵件請勿回覆</div>
20 </body>
21 </html>
View Code

有了html模板,咋們還需要把這個檔案讀取出來,加入到傳送郵件內容中,讀取html模板:

 1 public static async Task<string> _GetHtmlTpl(EnumHelper.EmEmailTpl tpl, string folderPath = @"D:\F\學習\vs2017\netcore\LovePicture.Web\wwwroot\tpl")
 2         {
 3             var content = string.Empty;
 4             if (string.IsNullOrWhiteSpace(folderPath)) { return content; }
 5 
 6             var path = $"{folderPath}/{tpl}.html";
 7             try
 8             {
 9                 using (var stream = File.OpenRead(path))
10                 {
11                     using (var reader = new StreamReader(stream))
12                     {
13                         content = await reader.ReadToEndAsync();
14                     }
15                 }
16             }
17             catch (Exception ex)
18             {
19                 throw new Exception(ex.Message);
20             }
21             return content;
22         }

好的到這裡,通過使用者輸入的繫結郵箱,然後傳送郵件的功能就完成了,來看看效果吧:

怎麼樣是不是感覺瞬間逼格提升了很多呢哈哈,電子郵件的推廣和用途很多公司都太小看了,我們公司也是哎,什麼時候您公司能夠重視並且能夠像jd那樣常常個你發推送郵件,那麼估計你離成功不遠了嘿嘿;

接受重置密碼通知

來繼續咋們的講解,當使用者點選重置密碼的連結後,我們需要一個接受的地址,這裡我的Action取名為ConfirmPassword,接受的引數和我構造重置密碼確認連結時候差不多:

 1 /// <summary>
 2         /// 接受重置密碼通知
 3         /// </summary>
 4         /// <returns></returns>
 5         public IActionResult ConfirmPassword(string expire, string token, string email, string t)
 6         {
 7             if (string.IsNullOrWhiteSpace(expire) || string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(email) || !email.Contains("@") || string.IsNullOrWhiteSpace(t))
 8             {
 9                 return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "無效的請求。" });
10             }
11             else if (t.Length != 7)
12             {
13                 return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "無效的請求。" });
14             }
15 
16             email = email.Trim().ToLower();
17             if (!DateTime.TryParse(expire, out var expires)) { return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "無效的請求!" }); }
18             else if (expires.AddMinutes(30) < DateTime.Now)
19             {
20                 return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "請求已過期,重新操作!" });
21             }
22 
23             var compareToken = PublicClass._Md5($"{expire}-{email}-{Request.Host.Host}");
24             if (!token.Equals(compareToken)) { return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "驗證失敗,無效的請求!" }); }
25 
26             var user = _context.ToUserInfo.SingleOrDefault(b => b.Email.ToLower() == email);
27             if (user == null) { return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "不存在該繫結郵箱的賬號!" }); }
28             else if (user.Status == (int)EnumHelper.EmUserStatus.禁用)
29             {
30                 return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "該繫結郵箱的賬號已被禁用,可以通過傳送郵件至:841202396@qq.com聯絡客服!" });
31             }
32 
33             var key = $"checkConfirmPwd{email}";
34             if (!_cache.TryGetValue<MoUserInfo>(key, out var result))
35             {
36                 _cache.Set<MoUserInfo>(key, new MoUserInfo { Id = user.Id, Email = email }, TimeSpan.FromMinutes(10));
37             }
38 
39             return View(new MoRegisterUser { UserName = email });
40         }

通過各種安全性的驗證(如:連結過期,不存在此使用者,token驗證失敗等),最終會跳轉到填寫重置密碼介面:

重置密碼的介面和提交後的處理方式,基本和登陸,註冊差不多,所以這裡我直接貼出提交重置密碼的Action程式碼:

 1 /// <summary>
 2         /// 提交重置的密碼
 3         /// </summary>
 4         /// <param name="user"></param>
 5         /// <returns></returns>
 6         [HttpPost]
 7         [ValidateAntiForgeryToken]
 8         public async Task<IActionResult> ConfirmPassword([Bind("UserName", "UserPwd", "ComfirmPwd")]MoRegisterUser registUser)
 9         {
10             if (ModelState.IsValid)
11             {
12                 if (string.IsNullOrWhiteSpace(registUser.UserPwd))
13                 {
14                     this.MsgBox("密碼不能為空!");
15                     return View(registUser);
16                 }
17                 else if (string.IsNullOrWhiteSpace(registUser.ComfirmPwd))
18                 {
19                     this.MsgBox("確認密碼不能為空!");
20                     return View(registUser);
21                 }
22                 else if (registUser.UserPwd != registUser.ComfirmPwd)
23                 {
24                     this.MsgBox("密碼和確認密碼不相同!");
25                     return View(registUser);
26                 }
27 
28                 var key = $"checkConfirmPwd{registUser.UserName}";
29                 if (!_cache.TryGetValue<MoUserInfo>(key, out var checkUser))
30                 {
31                     return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "請求已過期,重新操作!" });
32                 }
33 
34                 var user = _context.ToUserInfo.Where(b => b.Id == checkUser.Id && b.Email == checkUser.Email).SingleOrDefault();
35                 if (user == null)
36                 {
37                     _cache.Remove(key);
38                     return RedirectToAction(nameof(HomeController.Error), "home", new { msg = "重置的密碼失敗,請稍後重試!" });
39                 }
40 
41                 user.UserPwd = PublicClass._Md5(registUser.UserPwd.Trim());
42                 var result = await _context.SaveChangesAsync();
43                 if (result > 0)
44                 {
45                     _cache.Remove(key);
46                     this.MsgBox("重置密碼成功!");
47                 }
48                 else { this.MsgBox("重置密碼失敗!"); }
49             }
50             return View(registUser);
51         }

下面是確認密碼介面的View設計

 1 @model LovePicture.Model.MoClass.MoRegisterUser
 2 
 3 @{
 4     ViewData["Title"] = "重置密碼";
 5 }
 6 
 7 <h3><span class="glyphicon glyphicon-flag" aria-hidden="true"></span> 重置密碼</h3>
 8 <form name="form_submit" asp-action="ConfirmPassword">
 9     <div class="form-horizontal">
10         <h4>  愛留圖:歡迎您成為我們的一份子,讓我們一起留存珍惜的圖片吧。</h4>
11         <hr />
12         <input type="hidden" name="UserName" value="@Model.UserName"/>
13         <div asp-validation-summary="ModelOnly" class="text-danger"></div>
14         <div class="form-group">
15             <label asp-for="UserPwd" class="col-md-2 control-label">密碼</label>
16             <div class="col-md-10">
17                 <input asp-for="UserPwd" required="required" class="form-control" />
18                 <span asp-validation-for="UserPwd" class="text-danger"></span>
19             </div>
20         </div>
21         <div class="form-group">
22             <label asp-for="ComfirmPwd" class="col-md-2 control-label">確認密碼</label>
23             <div class="col-md-10">
24                 <input asp-for="ComfirmPwd" class="form-control" />
25                 <span asp-validation-for="ComfirmPwd" class="text-danger"></span>
26             </div>
27         </div>
28 
29         <div class="form-group">
30             <div class="col-md-offset-2 col-md-10">
31                 <input type="button" value="提 交" name="btnSubmit" class="btn btn-default" />
32                 <span id="msgbox" style="color:red">@ViewData["msgbox"]</span>
33             </div>
34         </div>
35     </div>
36 </form>
37 <br />
38 <div>
39      <a href="/member/login">有賬號去登入</a> | <a href="/member/register">沒賬號這裡註冊</a>
40 </div>
41 <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
42 <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

好的,此此盤文章很長,如果您有耐心讀完相信您能有好的收穫,一天一進步,別人的經驗只要您學習到了同樣也是自己的了,就算您沒有做過類似的功能或者系統,當您讀完後您也有大概的思路了呢;如果幫助,請不吝點個“推薦”,謝謝。

相關文章