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
    );
}

其接口实现部分类图如下。 204953959_df537d38-1a79-4e5f-92e7-9192ffd95f24 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实现取配置逻辑,该部分取配置逻辑类图如下 204955546_0ecf20c4-a799-49dd-b9c3-8aacc9395b71 在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)
    {


    }
}

这一块类图如下 204956915_9471e385-f4fe-453b-8015-13c26a35f61d 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。

204958358_22a65d55-0687-4dac-a90e-e12a26fee646

扩展

  • 如果想要变更名字,不使用ABP提供的这套配置参数设置名,也可以自己写一个CustomSmtpEmailSenderConfiguration,服务注册中替换掉默认的,同时增加一个CustomEmailSettingDefinitionProvider将配置注册到SettingProvider中,其具体值可以在appsettings.json或数据库都行。

  • 如果想支持延迟发送,可以改写QueueAsync方法,在其中加入延迟时间,优先级等。

2024-11-08,望技术有成后能回来看见自己的脚步。