ABP Framework-EmailSending源码解析
目录
https://abp.io/docs/6.0/Emailing
Version
6.0.3
Package
Volo.Abp.Emailing
其组件包为
Volo.Abp.MailKit
在Volo.Abp.Emailing中本身具有SmtpClient,这是微软提供的,如果可以满足使用,可以不需要在加入Volo.Abp.MailKit包。
IEmailSender
其核心接口如下,其中Queue方法目的是用来做延迟发送,走BackgroundJob,再BackgroundJob中再调用IEmailSender的SendAsync方法发送。
public interface IEmailSender
{
/// <summary>
/// Sends an email.
/// </summary>
Task SendAsync(
string to,
string? subject,
string? body,
bool isBodyHtml = true,
AdditionalEmailSendingArgs? additionalEmailSendingArgs = null
);
/// <summary>
/// Sends an email.
/// </summary>
Task SendAsync(
string from,
string to,
string? subject,
string? body,
bool isBodyHtml = true,
AdditionalEmailSendingArgs? additionalEmailSendingArgs = null
);
/// <summary>
/// Sends an email.
/// </summary>
/// <param name="mail">Mail to be sent</param>
/// <param name="normalize">
/// Should normalize email?
/// If true, it sets sender address/name if it's not set before and makes mail encoding UTF-8.
/// </param>
Task SendAsync(
MailMessage mail,
bool normalize = true
);
/// <summary>
/// Adds an email to queue to send via background jobs.
/// </summary>
Task QueueAsync(
string to,
string subject,
string body,
bool isBodyHtml = true,
AdditionalEmailSendingArgs? additionalEmailSendingArgs = null
);
/// <summary>
/// Adds an email to queue to send via background jobs.
/// </summary>
Task QueueAsync(
string from,
string to,
string subject,
string body,
bool isBodyHtml = true,
AdditionalEmailSendingArgs? additionalEmailSendingArgs = null
);
}
其接口实现部分类图如下。
Volo.Abp.Emailing库中包含了EmailSenderBase类,提供了一个核心的抽象方法SendEmailAsync
protected abstract Task SendEmailAsync(MailMessage mail);
在IEmailSender中所列举的接口对应实现中,全都最终汇聚到SendEmailAsync方法中。在SmtpEmailSender或是MailKitSmptEmailSender,都只需要override这个方法,设计上也很是优雅。
邮件配置
Abp.Emailing中会按照如下json结构取配置,当然其值来源不一定是appsettings.json,可以是数据库,可以是默认值。
{
"Settings": {
"Abp.Mailing.Smtp.Host": "",
"Abp.Mailing.Smtp.Port": "",
"Abp.Mailing.Smtp.UserName": "",
"Abp.Mailing.Smtp.Password": "",
"Abp.Mailing.Smtp.Domain": "",
"Abp.Mailing.Smtp.EnableSsl": "",
"Abp.Mailing.Smtp.UseDefaultCredentials": "",
"Abp.Mailing.DefaultFromAddress": "",
"Abp.Mailing.DefaultFromDisplayName": ""
}
}
在源码中EmailSenderConfiguration实现取配置逻辑,该部分取配置逻辑类图如下
在IEmailSenderConfiguration中只设计了获取发送方和发送人的配置。
public interface IEmailSenderConfiguration
{
Task<string> GetDefaultFromAddressAsync();
Task<string> GetDefaultFromDisplayNameAsync();
}
在ISmtpEmailSenderConfiguration中增加了,像邮件服务商地址,端口,账号密码等等配置。
public interface ISmtpEmailSenderConfiguration : IEmailSenderConfiguration
{
Task<string> GetHostAsync();
Task<int> GetPortAsync();
Task<string> GetUserNameAsync();
Task<string> GetPasswordAsync();
Task<string?> GetDomainAsync();
Task<bool> GetEnableSslAsync();
Task<bool> GetUseDefaultCredentialsAsync();
}
在SmtpEmailSenderConfiguration实现中,又将获取配置值的逻辑转交给ISettingProvider,这样一来,具体的配置值的来源可以从默认的配置注册,表中获取或是配置文件中获取。
public class SmtpEmailSenderConfiguration : EmailSenderConfiguration, ISmtpEmailSenderConfiguration, ITransientDependency
{
public SmtpEmailSenderConfiguration(ISettingProvider settingProvider)
: base(settingProvider)
{
}
}
这一块类图如下
Emailing提前在源码中注册了一个SettingDefinitionProvider,如此给定在SmtpEmailSenderConfiguration中调用SettingProvider获取配置时才不会报错。
internal class EmailSettingProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
}
}
BackgroundJob
在IEmailSender方法中提供了Queue的两个方法,在其实现中,会判断如果没有开启BackgroundJob功能,则像SendAsync方法一样,直接发送邮件,当开启后,会先加入到Background队列中。该方法支持实现这override,以方便加入其他参数,比如延迟时间,优先级等。
public virtual async Task QueueAsync(string to, string subject, string body, bool isBodyHtml = true, AdditionalEmailSendingArgs? additionalEmailSendingArgs = null)
{
ValidateEmailAddress(to);
if (!BackgroundJobManager.IsAvailable())
{
await SendAsync(to, subject, body, isBodyHtml, additionalEmailSendingArgs);
return;
}
await BackgroundJobManager.EnqueueAsync(
new BackgroundEmailSendingJobArgs
{
TenantId = CurrentTenant.Id,
To = to,
Subject = subject,
Body = body,
IsBodyHtml = isBodyHtml,
AdditionalEmailSendingArgs = additionalEmailSendingArgs
}
);
}
在其对应Job中,实际上还是调用IEmailSender,调用SenderAsync方法,因此该部分功能实际上是为了满足延迟发送。
public class BackgroundEmailSendingJob : AsyncBackgroundJob<BackgroundEmailSendingJobArgs>, ITransientDependency
{
protected IEmailSender EmailSender { get; }
public BackgroundEmailSendingJob(IEmailSender emailSender)
{
EmailSender = emailSender;
}
public async override Task ExecuteAsync(BackgroundEmailSendingJobArgs args)
{
if (args.From.IsNullOrWhiteSpace())
{
await EmailSender.SendAsync(args.To, args.Subject, args.Body, args.IsBodyHtml, args.AdditionalEmailSendingArgs);
}
else
{
await EmailSender.SendAsync(args.From!, args.To, args.Subject, args.Body, args.IsBodyHtml, args.AdditionalEmailSendingArgs);
}
}
}
MailKit
mailkit是一个优秀的邮件发送库,ABP中,在该库的基础上包装了下,形成MailKitEmailSender。
扩展
如果想要变更名字,不使用ABP提供的这套配置参数设置名,也可以自己写一个CustomSmtpEmailSenderConfiguration,服务注册中替换掉默认的,同时增加一个CustomEmailSettingDefinitionProvider将配置注册到SettingProvider中,其具体值可以在appsettings.json或数据库都行。
如果想支持延迟发送,可以改写QueueAsync方法,在其中加入延迟时间,优先级等。
2024-11-08,望技术有成后能回来看见自己的脚步。