之前介绍的友情链接功能,只实现了友情链接的展示和管理接口。
还缺失友情链接申请、审核管理、通知,现在把这块功能补全。
Model 什么的之前那篇文章都有,本文直接补全逻辑代码~
详见: 基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能
友情链接申请页面
实现一个简单的通知功能,申请通过之后,给申请友链的邮箱发通知。
使用 MimeKit 这个库可以很方便的实现发邮件功能
为了更方便使用,我封装了一个 EmailUtils
放在 StarBlog.Share.Utils
里面
public class EmailAccountConfig { public string Host { get; set; } public int Port { get; set; } public string FromUsername { get; set; } public string FromPassword { get; set; } public string FromAddress { get; set; } } public static class EmailUtils { public static async Task<MessageSentEventArgs> SendEmailAsync( EmailAccountConfig config, string subject, string htmlBody, string toName, string toAddress, string fromName = "StarBlog" ) { return await SendEmailAsync( config, new MimeMessage { Subject = subject, From = {new MailboxAddress(fromName, config.FromAddress)}, To = {new MailboxAddress(toName, toAddress)}, Body = new BodyBuilder { HtmlBody = htmlBody }.ToMessageBody() } ); } public static async Task<MessageSentEventArgs> SendEmailAsync(EmailAccountConfig config, MimeMessage message, HttpProxyClient? proxyClient = null) { MessageSentEventArgs result = null; using var client = new SmtpClient { ServerCertificateValidationCallback = (s, c, h, e) => true, }; if (proxyClient != null) { client.ProxyClient = proxyClient; } client.AuthenticationMechanisms.Remove("XOAUTH2"); client.MessageSent += (sender, args) => { result = args; }; await client.ConnectAsync(config.Host, config.Port, SecureSocketOptions.Auto); await client.AuthenticateAsync(config.FromUsername, config.FromPassword); await client.SendAsync(message); await client.DisconnectAsync(true); return result; } }
使用比较简单,传入邮箱配置和邮件主题、内容、收件地址就行。
具体的可以接着看下面的代码。
管理友情链接申请记录的逻辑,同样也是有增删改查,这部分代码跟上面的一样,省略了
构造方法通过依赖注入,从配置系统里读取了邮箱配置,读者可以自行将邮箱配置添加到 appsettings.json
中,这里给出一个outlook邮箱的配置
"EmailAccountConfig": { "Host": "smtp-mail.outlook.com", "Port": 587, "FromUsername": "邮箱地址@outlook.com", "FromPassword": "邮箱密码", "FromAddress": "邮箱地址@outlook.com" }
下面开始是 service 的代码
这里只贴设置是否验证 和 发邮件通知 的代码
public class LinkExchangeService { private readonly IBaseRepository<LinkExchange> _repo; private readonly LinkService _linkService; private readonly EmailAccountConfig _emailAccountConfig; public LinkExchangeService(IBaseRepository<LinkExchange> repo, LinkService linkService, IOptions<EmailAccountConfig> options) { _repo = repo; _linkService = linkService; _emailAccountConfig = options.Value; } public async Task<LinkExchange?> SetVerifyStatus(int id, bool status, string? reason = null) { var item = await GetById(id); if (item == null) return null; item.Verified = status; item.Reason = reason; await _repo.UpdateAsync(item); var link = await _linkService.GetByName(item.Name); if (status) { await SendEmailOnAccept(item); if (link == null) { await _linkService.AddOrUpdate(new Link { Name = item.Name, Description = item.Description, Url = item.Url, Visible = true }); } else { await _linkService.SetVisibility(link.Id, true); } } else { await SendEmailOnReject(item); if (link != null) await _linkService.DeleteById(link.Id); } return await GetById(id); } // 本文仅贴上申请通过的代码,其他的也是类似的写法 public async Task SendEmailOnAccept(LinkExchange item) { const string starblogLink = "<a href=\"https://deali.cn\">StarBlog</a>"; var sb = new StringBuilder(); sb.AppendLine($"<p>您好,友链申请已通过!感谢支持,欢迎互访哦~</p>"); sb.AppendLine($"<br>"); sb.AppendLine($"<p>以下是您申请的友链信息:</p>"); sb.AppendLine($"<p>网站名称:{item.Name}</p>"); sb.AppendLine($"<p>介绍:{item.Description}</p>"); sb.AppendLine($"<p>网址:{item.Url}</p>"); sb.AppendLine($"<p>站长:{item.WebMaster}</p>"); sb.AppendLine($"<p>补充信息:{item.Reason}</p>"); sb.AppendLine($"<br>"); sb.AppendLine($"<br>"); sb.AppendLine($"<br>"); sb.AppendLine($"<p>本消息由 {starblogLink} 自动发送,无需回复。</p>"); await EmailUtils.SendEmailAsync( _emailAccountConfig, "[StarBlog]友链申请结果反馈", sb.ToString(), item.WebMaster, item.Email ); } }
在设置是否验证的方法中,实现了:
其他地方就跟上面的友情链接一样了。
写完之后别忘了注册服务
builder.Services.AddScoped<LinkExchangeService>(); builder.Services.AddScoped<LinkService>();
展示功能做完了,还得接着做友链申请的功能,以方便路过的站长申请互换友链~
添加 StarBlog.Web/ViewModels/LinkExchange/LinkExchangeAddViewModel.cs
文件
我们使用 AspNetCore MVC 框架提供的表单验证功能
public class LinkExchangeAddViewModel { /// <summary> /// 网站名称 /// </summary> [Display(Name = "网站名称")] [Required(ErrorMessage = "必须填写网站名称")] public string Name { get; set; } /// <summary> /// 介绍 /// </summary> [Display(Name = "介绍")] public string? Description { get; set; } /// <summary> /// 网址 /// </summary> [Display(Name = "网址")] [Required(ErrorMessage = "必须填写网址")] [DataType(DataType.Url)] public string Url { get; set; } /// <summary> /// 站长 /// </summary> [Display(Name = "站长名称")] [Required(ErrorMessage = "必须填写站长名称")] public string WebMaster { get; set; } /// <summary> /// 联系邮箱 /// </summary> [Display(Name = "联系邮箱")] [Required(ErrorMessage = "必须填写联系邮箱")] [DataType(DataType.EmailAddress)] public string Email { get; set; } }
接着写一下页面 StarBlog.Web/Views/LinkExchange/Add.cshtml
@model StarBlog.Web.ViewModels.LinkExchange.LinkExchangeAddViewModel @{ ViewData["Title"] = "申请友链"; } <div class="container px-4 py-3"> <h2 class="d-flex w-100 justify-content-between pb-2 mb-3 border-bottom"> <div>申请友链</div> <div>Link Exchange</div> </h2> <div class="card px-1 py-3"> <form enctype="multipart/form-data" class="card-body row" asp-controller="LinkExchange" asp-action="Add" method="post"> <div class="col-xl-6"> <div class="mb-4"> <h4 class="card-title">友链信息</h4> <h6 class="card-subtitle mb-3 text-muted">请输入您的网站信息,方便后续联系</h6> </div> <div class="mb-3"> <label asp-for="Name" class="form-label"></label> <input asp-for="Name" class="form-control"> <span asp-validation-for="Name" class="form-text text-danger"></span> </div> <div class="mb-3"> <label asp-for="Description" class="form-label"></label> <input asp-for="Description" class="form-control"> <span asp-validation-for="Description" class="form-text text-danger"></span> </div> <div class="mb-3"> <label asp-for="Url" class="form-label"></label> <input asp-for="Url" class="form-control"> <span asp-validation-for="Url" class="form-text text-danger"></span> </div> <div class="mb-3"> <label asp-for="WebMaster" class="form-label"></label> <input asp-for="WebMaster" class="form-control"> <span asp-validation-for="WebMaster" class="form-text text-danger"></span> </div> <div class="mb-3"> <label asp-for="Email" class="form-label"></label> <input asp-for="Email" class="form-control"> <span asp-validation-for="Email" class="form-text text-danger"></span> </div> </div> <div class="col-xl-6"> <div class="ms-3 mb-4"> <h4 class="card-title">注意事项</h4> <h6 class="card-subtitle mb-3 text-muted">申请友情链接需符合以下几点要求</h6> </div> <ul class="list-group list-group-flush list-group-numbered"> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">相互性</div> 请先在您的网站添加本站链接,再进行友链申请 </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">内容类别</div> 本站优先招同类原创、内容相近的博客或网站 </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">SEO</div> Baidu和Google有正常收录,有近期快照的网站优先 </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">合法性</div> 不含有违反相关国家法律内容的合法网站,不接受TB客等垃圾站的链接 </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">更新及时性</div> 不接受原创内容很少,且长期不更新的网站 </div> </li> <li class="list-group-item d-flex justify-content-between align-items-start"> <div class="ms-2 me-auto"> <div class="fw-bold">可访问性</div> 如您的网站无法访问,将会暂时撤销友情链接,如需恢复请联系站长处理 </div> </li> </ul> </div> <div class="form-group"> <button type="submit" class="btn btn-outline-primary">提交</button> <button type="reset" class="btn btn-outline-warning">重置</button> </div> </form> </div> </div>
最后是Controller,添加 StarBlog.Web/Controllers/LinkExchangeController.cs
文件
代码如下
public class LinkExchangeController : Controller { private readonly ILogger<LinkExchangeController> _logger; private readonly LinkExchangeService _service; private readonly IMapper _mapper; private readonly Messages _messages; public LinkExchangeController( ILogger<LinkExchangeController> logger, LinkExchangeService service, IMapper mapper, Messages messages) { _logger = logger; _service = service; _mapper = mapper; _messages = messages; } [HttpGet] public IActionResult Add() { return View(); } [HttpPost] public async Task<IActionResult> Add(LinkExchangeAddViewModel vm) { if (!ModelState.IsValid) return View(); if (await _service.HasUrl(vm.Url)) { _messages.Error("相同网址的友链申请已提交!"); return View(); } var item = _mapper.Map<LinkExchange>(vm); item = await _service.AddOrUpdate(item); // 发送邮件通知 await _service.SendEmailOnAdd(item); _messages.Info("友链申请已提交,正在处理中,请及时关注邮件通知~"); return RedirectToAction("Index", "Home"); } }
搞定~
欢迎各位站长大佬来交换友链~!