[AbpvNext源码分析]-20.与短信支持

[AbpvNext源码分析]-20.电⼦邮件与短信⽀持
⼀、简介
ABP vNext 使⽤ Volo.Abp.Sms 包和 Volo.Abp.Emailing 包将短信和电⼦邮件作为基础设施进⾏了抽象,开发⼈员仅需要在使⽤的时候注⼊ ISmsSender 或 IEmailSender 即可实现短信发送和邮件发送。
⼆、源码分析
2.1 启动模块
短信发送的抽象层⽐较简单,AbpSmsModule 模块内部并⽆任何操作,仅作为空模块进⾏定义。
电⼦邮件的 AbpEmailingModule 模块内,主要添加了⼀些本地化资源⽀持。另⼀个动作就是添加了⼀个 BackgroundEmailSendingJob 后台作业,这个后台作业主要是⽤于后续发送电⼦邮件使⽤。因为邮件发送这个动作实时性要求并不⾼,在实际的业务实践当中,我们基本会将其加⼊到⼀个后台队列慢慢发送,所以这⾥ ABP 为我们实现了BackgroundEmailSendingJob。
BackgroundEmailSendingJob.cs:
public class BackgroundEmailSendingJob : AsyncBackgroundJob<BackgroundEmailSendingJobArgs>, ITransientDependency
{
protected IEmailSender EmailSender { get; }
public BackgroundEmailSendingJob(IEmailSender emailSender)
{
EmailSender = emailSender;
}
public override async Task ExecuteAsync(BackgroundEmailSendingJobArgs args)
{
if (args.From.IsNullOrWhiteSpace())
{
await EmailSender.SendAsync(args.To, args.Subject, args.Body, args.IsBodyHtml);
}
else
{
await EmailSender.SendAsync(args.From, args.To, args.Subject, args.Body, args.IsBodyHtml);
}
}
}
这个后台任务的逻辑也不复杂,就使⽤ IEmailSender 发送邮件,我们在任何地⽅需要后台发送邮件的时,只需要注⼊ IBackgroundJobManager,使⽤BackgroundEmailSendingJobArgs 作为参数添加⼊队⼀个后台作业即可。
使⽤ IBackgroundJobManager 添加⼀个新的邮件发送欢迎邮件:
public class DemoClass
{
private readonly IBackgroundJobManager _backgroundJobManager;
private readonly IUserInfoRepository _userRep;
public DemoClass(IBackgroundJobManager backgroundJobManager,
IUserInfoRepository userRep)
{
_backgroundJobManager = backgroundJobManager;
_userRep = userRep;
}
public async Task SendWelcomeEmailAsync(Guid userId)
{
var userInfo = await _userRep.GetByIdAsync(userId);
await _backgroundJobManager.EnqueueAsync(new BackgroundEmailSendingJobArgs
{
To = userInfo.EmailAddress,
Subject = "Welcome",
Body = "Welcome, Hello World!",
IsBodyHtml = false;
});
}
}
注意
⽬前 BackgroundEmailSendingJobArgs 参数不⽀持发送附件,ABP 可能在以后的版本会进⾏实现。
2.2 Email 的核⼼组件
ABP 定义了⼀个 IEmailSender 接⼝,定义了多个 SendAsync() ⽅法重载,⽤于直接发送电⼦邮件。同时也提供了 QueueAsync() ⽅法,通过后台任务队列来发送邮件。
public interface IEmailSender
{
Task SendAsync(
string to,
string subject,
string body,
bool isBodyHtml = true
);
Task SendAsync(
string from,
string to,
string subject,
string body,
bool isBodyHtml = true
);
Task SendAsync(
MailMessage mail,
bool normalize = true
)
;
Task QueueAsync(
string to,
string subject,
string body,
bool isBodyHtml = true
);
Task QueueAsync(
string from,
string to,
string subject,
string body,
bool isBodyHtml = true
);
//TODO: 准备添加的 QueueAsync ⽅法。⽬前存在的问题: MailMessage 不能够被序列化,所以不能加⼊到后台任务队列当中。
}
ABP 实际拥有两种 Email Sender 实现,分别是 SmtpEmailSender 和 MailkitEmailSender,各个类型的关系如下。
UML 类图:
classDiagram class IEmailSender{ <<Interface>> +SendAsync(string,string,string,bool=true) Task +SendAsync(string,string,string,string,bool=true) Task
+SendAsync(MailMessage,bool=true) Task +QueueAsync(string,string,string,bool=true) Task +QueueAsync(string,string,string,string,bool=true) Task } class ISmtpEmailSender{
<<Interface>> ...... +BuildClientAsync() Task~SmtpClient~ } class IMailKitSmtpEmailSemder{ <<Interface>> ...... +BuildClientAsync() Task~SmtpClient~ } class EmailSenderBase{
<<Abstract>> ...... } class SmtpEmailSender{ ...... } class MailKitSmtpEmailSender{ ...... } class NullEmailSender{ ...... } ISmtpEmailSender --|> IEmailSender: 继承IMailKitSmtpEmailSemder --|> IEmailSender: 继承 EmailSenderBase ..|> IEmailSender: 实现 SmtpEmailSender ..|> ISmtpEmailSender: 实现 SmtpEmailSender --|> EmailSenderBase: 继承 NullEmailSender --|> EmailSenderBase: 继承 MailKitSmtpEmailSender ..|> IMailKitSmtpEmailSemder: 实现 MailKitSmtpEmailSender --|> EmailSenderBase:继承
可以从 UML 类图看出,每个 EmailSender 实现都与⼀个 IXXXConfiguration 对应,这个配置类存储了基于 Smtp 发件的必须配置。因为 MailKit 本⾝也是基于 Smtp 发送邮件的,所以没有重新定义新的配置类,⽽是直接复⽤的 ISmtpEmailSenderConfiguration 接⼝与实现。
在 EmailSenderBase 基类当中,基本实现了 IEmailSender 接⼝的所有⽅法的逻辑,只留下了 SendEmailAsync(MailMessage mail) 作为⼀个抽象⽅法等待⼦类实现。也就是说其
他的⽅法最终都是使⽤该⽅法来最终发送邮件。
public abstract class EmailSenderBase : IEmailSender
{
protected IEmailSenderConfiguration Configuration { get; }
protected IBackgroundJobManager BackgroundJobManager { get; }
protected EmailSenderBase(IEmailSenderConfiguration configuration, IBackgroundJobManager backgroundJobManager)
{
Configuration = configuration;
BackgroundJobManager = backgroundJobManager;
}
// ... 实现的接⼝⽅法
protected abstract Task SendEmailAsync(MailMessage mail);
// 使⽤ Configuration ⾥⾯的参数,统⼀处理邮件数据。
protected virtual async Task NormalizeMailAsync(MailMessage mail)
{
if (mail.From == null || mail.From.Address.IsNullOrEmpty())
{
mail.From = new MailAddress(
await Configuration.GetDefaultFromAddressAsync(),
await Configuration.GetDefaultFromDisplayNameAsync(),
Encoding.UTF8
);
}
if (mail.HeadersEncoding == null)
{
mail.HeadersEncoding = Encoding.UTF8;
}
if (mail.SubjectEncoding == null)
{
mail.SubjectEncoding = Encoding.UTF8;
}
if (mail.BodyEncoding == null)
{
mail.BodyEncoding = Encoding.UTF8;
}
}
}
ABP 默认可⽤的邮件发送组件是 SmtpEmailSender,它使⽤的是 .NET ⾃带的邮件发送组件,本质上就是构建了⼀个 SmtpClient 客户端,然后调⽤它的发件⽅法进⾏邮件发送。public class SmtpEmailSender : EmailSenderBase, ISmtpEmailSender, ITransientDependency
{
// ... 省略的代码。
public async Task<SmtpClient> BuildClientAsync()
{
var host = await SmtpConfiguration.GetHostAsync();
var port = await SmtpConfiguration.GetPortAsync();
var smtpClient = new SmtpClient(host, port);
// 从 SettingProvider 中获取各个配置参数,构建 Client 进⾏发送。
try
{
if (await SmtpConfiguration.GetEnableSslAsync())
{
smtpClient.EnableSsl = true;
}
if (await SmtpConfiguration.GetUseDefaultCredentialsAsync())
{
smtpClient.UseDefaultCredentials = true;
}
else
{
smtpClient.UseDefaultCredentials = false;
var userName = await SmtpConfiguration.GetUserNameAsync();
if (!userName.IsNullOrEmpty())
{
var password = await SmtpConfiguration.GetPasswordAsync();
var domain = await SmtpConfiguration.GetDomainAsync();
smtpClient.Credentials = !domain.IsNullOrEmpty()
new NetworkCredential(userName, password, domain)
: new NetworkCredential(userName, password);
}
}
return smtpClient;
}
catch
{
smtpClient.Dispose();
throw;
}
}
protected override async Task SendEmailAsync(MailMessage mail)
{
// 调⽤构建⽅法,构建 Client,⽤于发送 mail 数据。
using (var smtpClient = await BuildClientAsync())
{
await smtpClient.SendMailAsync(mail);
}
}
}
针对属性注⼊失败的情况,ABP 提供了 NullEmailSender 作为默认实现,在发送邮件的时候会使⽤ Logger 打印具体的信息。
public class NullEmailSender : EmailSenderBase
{
public ILogger<NullEmailSender> Logger { get; set; }
public NullEmailSender(IEmailSenderConfiguration configuration, IBackgroundJobManager backgroundJobManager)
: base(configuration, backgroundJobManager)
{
Logger = NullLogger<NullEmailSender>.Instance;
}
protected override Task SendEmailAsync(MailMessage mail)
{
Logger.LogWarning("USING NullEmailSender!");
Logger.LogDebug("SendEmailAsync:");
LogEmail(mail);
return Task.FromResult(0);
}
// ... 其他⽅法。
}
2.3 Email 的配置存储
从 EmailSenderBase ⾥⾯可以看到,它从 IEmailSenderConfiguration 当中获取发件⼈的邮箱地址和展⽰名称,它的 UML 类图关系如下。
classDiagram class IEmailSenderConfiguration{ <<Interface>> +GetDefaultFromAddressAsync() Task~string~ +GetDefaultFromDisplayNameAsync() Task~string~ } class ISmtpEmailSenderConfiguration{ <<Interface>> +GetHostAsync() Task~string~ +GetPortAsync() Task~int~ +GetUserNameAsync() Task~string~ +GetPasswordAsync() Task~string~ +GetDomainAsync() Task~string~ +GetEnableSslAsync() Task~bool~ +GetUseDefaultCredentialsAsync() Task~bool~ } class EmailSenderConfiguration{
#GetNotEmptySettingValueAsync(string name) Task~string~ } class SmtpEmailSenderConfiguration{ } class ISettingProvider{ <<Interface>> +GetOrNullAsync(string name)
Task~string~ } ISmtpEmailSenderConfiguration --|> IEmailSenderConfiguration: 继承 EmailSenderConfiguration ..|> IEmailSenderConfiguration: 实现 EmailSenderConfiguration ..> ISettingProvider: 依赖 SmtpEmailSenderConfiguration --|> EmailSenderConfiguration: 继承 SmtpEmailSenderConfiguration ..|> ISmtpEmailSenderConfiguration: 实现
可以看到配置⽂件时通过 ISettingProvider 获取的,这样就可以保证从不同租户甚⾄是⽤户来获取发件⼈的配置信息。这⾥值得注意的是在 EmailSenderConfiguration 中,实现了⼀个 GetNotEmptySettingValueAsync(string name) ⽅法,该⽅法主要是封装了获取逻辑,当值不存在的时候抛出 AbpException 异常。
protected async Task<string> GetNotEmptySettingValueAsync(string name)
{
var value = await SettingProvider.GetOrNullAsync(name);
if (value.IsNullOrEmpty())
{
throw new AbpException($"Setting value for '{name}' is null or empty!");
}
return value;
}
⾄于 SmtpEmailSenderConfiguration,只是提供了其他的属性获取(密码、端⼝等)⽽已,本质上还是调⽤的 GetNotEmptySettingValueAsync() ⽅法从 SettingProvider 中获取具体的配置信息。
sequenceDiagram 发送邮件 ->> Smtp 配置类: 1.GetHostAsync() Smtp 配置类 ->> Email 配置类: 2.GetNotEmptySettingValueAsync("HotsItem") Email 配置类 ->> Setting Provider: 3.GetOrNullAsync("HotsItem") Setting Provider -->> 发送邮件: 4.获得主机数据。
关于配置名称的常量,都在 EmailSettingNames ⾥⾯进⾏定义,并使⽤ EmailSettingProvider 将其注册到 ABP 的配置模块当中:
EmailSettingNames.cs
namespace Volo.Abp.Emailing
{
public static class EmailSettingNames
{
public const string DefaultFromAddress = "Abp.Mailing.DefaultFromAddress";
public const string DefaultFromDisplayName = "Abp.Mailing.DefaultFromDisplayName";
public static class Smtp
{
public const string Host = "Abp.Mailing.Smtp.Host";
public const string Port = "Abp.Mailing.Smtp.Port";
// ... 其他常量定义。
}
}
}
EmailSettingProvider.cs
internal class EmailSettingProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(
new SettingDefinition(
EmailSettingNames.Smtp.Host,
"127.0.0.1",
L("DisplayName:Abp.Mailing.Smtp.Host"),
L("Description:Abp.Mailing.Smtp.Host")),
new SettingDefinition(EmailSettingNames.Smtp.Port,
"25",
L("DisplayName:Abp.Mailing.Smtp.Port"),
L("Description:Abp.Mailing.Smtp.Port")),
// ... 其他配置参数。
);
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<EmailingResource>(name);
}
}
2.4 邮件模板
⽂字模板是 ABP 后续提供的⼀个新的模块,它可以让开发⼈员预先定义⽂本模板,然后使⽤时根据对象数据替换模板中的内容,并且 ABP 提供的⽂本模板还⽀持本地化。关于⽂本模板的功能,我们后续单独会写⼀篇⽂章进⾏说明,在这⾥只是⼤概 Mail 是如何使⽤的。
在项⽬当中,ABP 仅定义了两个 *.tpl 的模板⽂件,分别是控制布局的 Layout.tpl,还有渲染具体消息的 Message.tpl。同权限、Setting ⼀样,模板也会使⽤⼀个StandardEmailTemplates 类型定义模板的编码常量,并且实现⼀个 XXXDefinitionProvider 类型将其注⼊到 ABP 框架当中。
StandardEmailTemplates.cs
public static class StandardEmailTemplates
{
public const string Layout = "Abp.StandardEmailTemplates.Layout";
public const string Message = "Abp.StandardEmailTemplates.Message";
}
StandardEmailTemplateDefinitionProvider.cs
public class StandardEmailTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition(
StandardEmailTemplates.Layout,
displayName: LocalizableString.Create<EmailingResource>("TextTemplate:StandardEmailTemplates.Layout"),
isLayout: true
).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Layout.tpl", true)
);
context.Add(
new TemplateDefinition(
StandardEmailTemplates.Message,
displayName: LocalizableString.Create<EmailingResource>("TextTemplate:StandardEmailTemplates.Message"),
layout: StandardEmailTemplates.Layout
).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Message.tpl", true)
);
}
}
2.5 MailKit 集成

本文发布于:2024-09-22 03:30:05,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/3/94003.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:邮件   发送   配置   获取   实现   模板
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议