• 结合ABP源码实现邮件发送功能


    1. 前言

    最近pm临时提出了多种邮件验证操作的需求,因为一时间也没有找到好的邮件收发组件,也抱着研究ABP的心态,就花了几小时时间探究了一下ABP中关于Email的处理和操作。其实邮件操作大多大同小异,这次只是希望介绍一下ABP中实现功能的代码结构而已,以下是具体过程

    演示的ABP代码版本为0.9.0.0,不过后面版本对于这部分的修改较少,所以完全不影响之后版本的移植使用

     

    2. 实现过程

    ABP的Mail操作放在了Abp.Net.Mail和Abp.Net.Mail.Smtp中,第一步先让我们直接看看这个文件夹下类及接口的代码图(未经允许不可使用)

     

    1. 代码图(重)

    根据代码图可以发现ABP对于Mail处理主要由三部分组成

    • 第一部分是通过继承SettingProvider的EmailSettingProvider来对Mail相关参数进行设置(其中EmailSettingNames定义相关字符串)
    • 第二部分是以IEmailSenderConfiguration接口为基派生出的对SettingProvider设置的邮件参数进行读取和传输的相关操作类
    • 第三部分是以IEmailSender接口为基派生出的Mail发送操作相关类

    至于Smtp开头的文件,则是以Smtp形式进行邮件发送的一种实现文件而已,后文也将直接使用该种方式进行处理

     

    2.具体实现

    在具体的实现上,我发现ABP本身的Mail相关类已经十分完整,只是在邮件参数的配置上需要采取自定义的实现,所以我直接抽取了ABP的源码来进行演示

     

    2.1 定义AppSettingNames及AppSettingProvider

    AppSettingNames中定义相关的唯一字符串,大家可以认为是Key,而AppSettingProvider中则是将Key对应的邮件参数赋值,供之后的Configuration读取

    邮件功能推荐放在Core模块中,完成相关的provider后在CoreModule加入Configuration.Settings.Providers.Add<AppSettingProvider>();即可生效

    public static class AppSettings
    {
        /// <summary>
        /// SMTP related email settings.
        /// </summary>
        public static class Smtp
        {
            /// <summary>
            /// Abp.Net.Mail.DefaultFromAddress
            /// </summary>
            public const string DefaultAddress = "Trucking.Net.Mail.DefaultFromAddress";
    
            /// <summary>
            /// Abp.Net.Mail.DefaultFromDisplayName
            /// </summary>
            public const string DefaultDisplayName = "Trucking.Net.Mail.DefaultFromDisplayName";
    
            /// <summary>
            /// Abp.Net.Mail.Smtp.Host
            /// </summary>
            public const string Host = "Trucking.Net.Mail.Smtp.Host";
    
            /// <summary>
            /// Abp.Net.Mail.Smtp.Port
            /// </summary>
            public const string Port = "Trucking.Net.Mail.Smtp.Port";
    
            /// <summary>
            /// Abp.Net.Mail.Smtp.UserName
            /// </summary>
            public const string UserName = "Trucking.Net.Mail.Smtp.UserName";
    
            /// <summary>
            /// Abp.Net.Mail.Smtp.Password
            /// </summary>
            public const string Password = "Trucking.Net.Mail.Smtp.Password";
    
            /// <summary>
            /// Abp.Net.Mail.Smtp.Domain
            /// </summary>
            public const string Domain = "Trucking.Net.Mail.Smtp.Domain";
    
            /// <summary>
            /// Abp.Net.Mail.Smtp.EnableSsl
            /// </summary>
            public const string EnableSsl = "Trucking.Net.Mail.Smtp.EnableSsl";
    
            /// <summary>
            /// Abp.Net.Mail.Smtp.UseDefaultCredentials
            /// </summary>
            public const string UseDefaultCredentials = "Trucking.Net.Mail.Smtp.UseDefaultCredentials";
        }
    }
    
    public class AppSettingProvider : SettingProvider
    {
        public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context)
        {
            return new[]
            {
                new SettingDefinition(AppSettings.Smtp.Host, "smtp.gmail.com", L("SmtpHost"),
                    scopes: SettingScopes.Application | SettingScopes.Tenant),
                new SettingDefinition(AppSettings.Smtp.Port, "587", L("SmtpPort"),
                    scopes: SettingScopes.Application | SettingScopes.Tenant),
                new SettingDefinition(AppSettings.Smtp.UserName, "myemail@gmail.com", L("Username"),
                    scopes: SettingScopes.Application | SettingScopes.Tenant),
                new SettingDefinition(AppSettings.Smtp.Password, "mypassword", L("Password"),
                    scopes: SettingScopes.Application | SettingScopes.Tenant),
                new SettingDefinition(AppSettings.Smtp.Domain, "", L("DomainName"),
                    scopes: SettingScopes.Application | SettingScopes.Tenant),
                new SettingDefinition(AppSettings.Smtp.EnableSsl, "true", L("UseSSL"),
                    scopes: SettingScopes.Application | SettingScopes.Tenant),
                new SettingDefinition(AppSettings.Smtp.UseDefaultCredentials, "false", L("UseDefaultCredentials"),
                    scopes: SettingScopes.Application | SettingScopes.Tenant),
                new SettingDefinition(AppSettings.Smtp.DefaultAddress, "myemail@gmail.com",
                    L("DefaultEmailAddress"), scopes: SettingScopes.Application | SettingScopes.Tenant),
                new SettingDefinition(AppSettings.Smtp.DefaultDisplayName, "CompanyName",
                    L("DefaultDisplayName"), scopes: SettingScopes.Application | SettingScopes.Tenant)
            };
        }
    
        private static LocalizableString L(string name)
        {
            return new LocalizableString(name, AbpConsts.LocalizationSourceName);
        }
    }
     

    2.2 EmailSenderConfiguration配置

    这个类的作用如上面提到的那样,主要是读取自定义的AppSettingProvider中设置的邮件参数值

    IUserEmailSenderConfiguration接口略

    public class UserEmailSenderConfiguration : TruckingServiceBase, IUserEmailSenderConfiguration, ITransientDependency
    {
        /// <summary>
        /// Gets a setting value by checking. Throws <see cref="AbpException"/> if it's null or empty.
        /// </summary>
        /// <param name="name">Name of the setting</param>
        /// <returns>Value of the setting</returns>
        protected string GetNotEmptySettingValue(string name)
        {
            var value = SettingManager.GetSettingValue(name);
            if (value.IsNullOrEmpty())
            {
                throw new AbpException(String.Format("Setting value for '{0}' is null or empty!", name));
            }
    
            return value;
        }
    
        /// <summary>
        /// SMTP Host name/IP.
        /// </summary>
        public string Host
        {
            get { return GetNotEmptySettingValue(AppSettings.Smtp.Host); }
        }
    
        /// <summary>
        /// SMTP Port.
        /// </summary>
        public int Port
        {
            get { return SettingManager.GetSettingValue<int>(AppSettings.Smtp.Port); }
        }
    
        /// <summary>
        /// User name to login to SMTP server.
        /// </summary>
        public string UserName
        {
            get { return GetNotEmptySettingValue(AppSettings.Smtp.UserName); }
        }
    
        /// <summary>
        /// Password to login to SMTP server.
        /// </summary>
        public string Password
        {
            get { return GetNotEmptySettingValue(AppSettings.Smtp.Password); }
        }
    
        /// <summary>
        /// Domain name to login to SMTP server.
        /// </summary>
        public string Domain
        {
            get { return SettingManager.GetSettingValue(AppSettings.Smtp.Domain); }
        }
    
        /// <summary>
        /// Is SSL enabled?
        /// </summary>
        public bool EnableSsl
        {
            get { return SettingManager.GetSettingValue<bool>(AppSettings.Smtp.EnableSsl); }
        }
    
        /// <summary>
        /// Use default credentials?
        /// </summary>
        public bool UseDefaultCredentials
        {
            get { return SettingManager.GetSettingValue<bool>(AppSettings.Smtp.UseDefaultCredentials); }
        }
    
        public string DefaultAddress
        {
            get { return GetNotEmptySettingValue(AppSettings.Smtp.DefaultAddress); }
        }
    
        public string DefaultDisplayName
        {
            get { return SettingManager.GetSettingValue(AppSettings.Smtp.DefaultDisplayName); }
        }
    }
     

    2.3 SmtpEmailSender实现(Smtp实现邮件发送)

    UserSmtpEmailSender类才是真正的对Mail操作类,它通过注入IUserEmailSenderConfiguration接口,读取相关的Mail参数,如Host,UserName,Password等,然后再调用.NET的Mail发送邮件。

    IUserSmtpEmailSender接口略

    public class UserSmtpEmailSender : IUserSmtpEmailSender, ITransientDependency
    {
        private readonly IUserEmailSenderConfiguration _configuration;
    
        public UserSmtpEmailSender(IUserEmailSenderConfiguration configuration)
        {
            _configuration = configuration;
        }
        
        public async Task SendAsync(string to, string subject, string body, bool isBodyHtml = true)
        {
            await SendAsync(_configuration.DefaultAddress, to, subject, body, isBodyHtml);
        }
    
        public void Send(string to, string subject, string body, bool isBodyHtml = true)
        {
            Send(_configuration.DefaultAddress, to, subject, body, isBodyHtml);
        }
    
        public async Task SendAsync(string from, string to, string subject, string body, bool isBodyHtml = true)
        {
            await SendAsync(new MailMessage(from, to, subject, body) {IsBodyHtml = isBodyHtml});
        }
    
        public void Send(string from, string to, string subject, string body, bool isBodyHtml = true)
        {
            Send(new MailMessage(from, to, subject, body) {IsBodyHtml = isBodyHtml});
        }
    
        public async Task SendAsync(MailMessage mail, bool normalize = true)
        {
            if (normalize)
                NormalizeMail(mail);
    
            await SendEmailAsync(mail);
        }
    
        public void Send(MailMessage mail, bool normalize = true)
        {
            if (normalize)
                NormalizeMail(mail);
    
            SendEmail(mail);
        }
    
        public SmtpClient BuildClient()
        {
            var host = _configuration.Host;
            var port = _configuration.Port;
    
            var smtpClient = new SmtpClient(host, port);
            try
            {
                if (_configuration.EnableSsl)
                    smtpClient.EnableSsl = true;
    
                if (_configuration.UseDefaultCredentials)
                {
                    smtpClient.UseDefaultCredentials = true;
                }
                else
                {
                    smtpClient.UseDefaultCredentials = false;
    
                    var userName = _configuration.UserName;
                    if (!userName.IsNullOrEmpty())
                    {
                        var password = _configuration.Password;
                        var domain = _configuration.Domain;
                        smtpClient.Credentials = !domain.IsNullOrEmpty()
                            ? new NetworkCredential(userName, password, domain)
                            : new NetworkCredential(userName, password);
                }
            }
            
            return smtpClient;
        }
        catch
        {
                smtpClient.Dispose();
                throw;
            }
        }
    
        /// <summary>
        ///     Normalizes given email.
        ///     Fills <see cref="MailMessage.From" /> if it's not filled before.
        ///     Sets encodings to UTF8 if they are not set before.
        /// </summary>
        /// <param name="mail">Mail to be normalized</param>
        protected virtual void NormalizeMail(MailMessage mail)
        {
            if ((mail.From == null) || mail.From.Address.IsNullOrEmpty())
                mail.From = new MailAddress(
                    _configuration.DefaultAddress,
                    _configuration.DefaultDisplayName,
                    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;
        }
    
        protected async Task SendEmailAsync(MailMessage mail)
        {
            using (var smtpClient = BuildClient())
            {
                await smtpClient.SendMailAsync(mail);
            }
        }
    
        protected void SendEmail(MailMessage mail)
        {
            using (var smtpClient = BuildClient())
            {
                smtpClient.Send(mail);
            }
        }
    }

    之后我们只需要再调用该EmailSender的SendAsync,填入对应的参数,亲测有效。如果之后要更换邮件组件,则只需要实现对应的UserLibraryEmailSerder即可。

    至此,我们便将ABP中单独的一个邮件功能抽离了出来并做了相关解释,其实只要花点功夫,自己手动剥离代码图也可以理解了。至于一个简单的邮件功能为什么在ABP中要实现得如此复杂,每个程序员有每个程序员的答案,还是继续学习吧

  • 相关阅读:
    LeetCode——二叉搜索树中的中序后继 II
    一加 2020.10.13 笔试
    携程 2020.10.13 笔试
    中科曙光 2020.10.12 面试
    58 2020.10.11 笔试
    华为 2020.10.11 面试
    LeetCode——二叉搜索树中的顺序后继
    健网未来 2020.10.10 面试
    波特率
    PGA基础知识极简教程(4)从FIFO设计讲起之异步FIFO篇
  • 原文地址:https://www.cnblogs.com/Wddpct/p/5919507.html
Copyright © 2020-2023  润新知