欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

一个简易邮件群发软件设计与实现

程序员文章站 2022-08-28 14:19:48
1 需求概述 指定一批邮箱地址,使用指定的邮箱发送指定的内容。 2 功能需求 配置文件配置用于发送的邮箱信息 邮件发送功能 日志窗口输出显示 3 界面接口 邮件列表框 标题内容输入框 发送按钮 日志输出框 4 技术选型 .Net 4.0 C Winform 5 实现 5.1 新建项目 项目命名为 S ......

1 需求概述

指定一批邮箱地址,使用指定的邮箱发送指定的内容。

2 功能需求

  • 配置文件配置用于发送的邮箱信息
  • 邮件发送功能
  • 日志窗口输出显示

3 界面接口

  • 邮件列表框
  • 标题内容输入框
  • 发送按钮
  • 日志输出框

4 技术选型

.net 4.0 c# winform

5 实现

5.1 新建项目

  • 项目命名为 simpleemailsender
    一个简易邮件群发软件设计与实现

5.2 在项目中添加配置文件

一个简易邮件群发软件设计与实现

  • 配置发件邮箱信息
<configuration>
  <appsettings>
    <add key="email_stmp" value="smtp.****.com"/>
    <add key="send_user_email" value="****@****"/>
    <add key="send_user_pass" value="密码"/>
    <add key="send_user_disp" value="发件人昵称" />
  </appsettings>
</configuration>

5.3 制作界面

根据界面接口需求,界面布局如下:
一个简易邮件群发软件设计与实现

5.4 邮件发送辅助类

首先完成辅助类开发,最后再跟界面对接完成流程。
因为读取了配置文件,需要添加 system.configuration 程序集的引用。
一个简易邮件群发软件设计与实现

定义 mailhelper 辅助类,读取配置参数,向外提供发送邮件功能方法 sendmail。

using system;
using system.collections.generic;
using system.configuration;
using system.linq;
using system.net.mail;
using system.text;

namespace simpleemailsender
{
    public class mailhelper
    {
        public static string email_username = configurationmanager.appsettings["send_user_email"];
        public static string email_dispname = configurationmanager.appsettings["send_user_disp"];
        public static string email_password = configurationmanager.appsettings["send_user_pass"];
        public static string email_smtp = configurationmanager.appsettings["email_stmp"];
        public static validateresult sendmail(string email, string name, string content)
        {
            return sendmail("系统消息", email, name, content);
        }

        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="title">邮件标题</param>
        /// <param name="email">收件人地址</param>
        /// <param name="name">收件人名称</param>
        /// <param name="content">邮件内容</param>
        public static validateresult sendmail(string title, string email, string name, string content)
        {
            mailaddress from = new mailaddress(email_username, email_dispname); //邮件的发件人
            mailmessage mail = new mailmessage();
            //设置邮件的标题
            mail.subject = title;

            //设置邮件的发件人
            //pass:如果不想显示自己的邮箱地址,这里可以填符合mail格式的任意名称,真正发mail的用户不在这里设定,这个仅仅只做显示用
            mail.from = from;

            //设置邮件的收件人
            mail.to.add(new mailaddress(email, name));

            //设置邮件的内容
            mail.body = content;

            //设置邮件的格式
            mail.bodyencoding = system.text.encoding.utf8;
            mail.isbodyhtml = true;
            //设置邮件的发送级别
            mail.priority = mailpriority.normal;

            mail.deliverynotificationoptions = deliverynotificationoptions.onsuccess;

            smtpclient client = new smtpclient();
            //设置用于 smtp 事务的主机的名称,填ip地址也可以了
            client.host = email_smtp; 
            //设置用于 smtp 事务的端口,默认的是 25
            client.port = 25;
            client.usedefaultcredentials = false;
            //这里才是真正的邮箱登陆名和密码
            client.credentials = new system.net.networkcredential(email_username, email_password);
            client.deliverymethod = smtpdeliverymethod.network;
            //都定义完了,正式发送了,很是简单吧!


            validateresult vr = new validateresult(true, "发送成功!");
            try
            {
                client.send(mail);
                return vr;
            }
            catch (exception e)
            {
                vr.isvalid = false;
                vr.message = e.message;
                return vr;
            }
        }
    }

    public class validateresult
    {
        public bool isvalid { get; set; }
        public string message { get; set; }

        public validateresult() {  
        }

        public validateresult(bool v, string m)
        {
            isvalid = v;
            message = m;
        }
    }
}

5.5 清单解析

对于邮箱列表,使用正则表达式从文本框中匹配邮箱形成 list emaillist 给后续执行。这样,对邮箱列表文本框中输入的格式就没什么要求,从其它地方复制粘贴进来,由程序完成格式化显示即可。

        /// <summary>
        /// 提取邮件列表
        /// </summary>
        /// <param name="mails"></param>
        /// <returns></returns>
        private list<string> parseemaillist(string mails)
        {
            list<string> list = new list<string>();
            var mc = regex.matches(mails, @"\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+", regexoptions.ignorecase);

            foreach (match c in mc)
            {
                list.add(c.value);
            }
            return list;
        }

5.6 日志输出方法准备

在日志框中输出内容,为了能在线程中调用,使用了 invoke 方式执行。

/// <summary>
/// 日志输出支持线程中执行
/// </summary>
/// <param name="message"></param>
private void log(string message)
{
    invoke(new methodinvoker(delegate
    {
        txtlog.appendtext(message + "\r\n");
    }));
}

5.7 线程发送

  • 发送方案设计

基本描述:给定邮箱列表,标题与内容,以线程方式执行发送,给出执行统计与状态。
具体实现:使用线程池,但一组做为一个任务,全部完成才接收下一个任务,通过完成数量与邮箱列表长度的比较来判断是否全部完成,信息通过日志输出的方式查看,形式上通过回调将日志信息传递给调用者。

为此,这里专门定义一个发送器,在应用中,定义一个实例来发起任务。尽管只定义一个实例,但这里并不需要定义为设计模式中的单例模式,事实上,它是可以多实例运行。具体代码说话!

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading;

namespace simpleemailsender
{
    public class emailsender
    {
        #region 运行时数据
        // 邮箱列表
        private list<string> _emaillist = new list<string>();
        // 完成数量
        private volatile int _overcount = 0;
        // 邮件标题
        private string _title;
        // 邮件内容
        private string _content;
        // 完成回调(主要是为了写日志)
        private action<string> _callback;
        #endregion

        /// <summary>
        /// 是否全部完成
        /// </summary>
        /// <returns></returns>
        public bool isover()
        {
            return _overcount == _emaillist.count;
        }

        /// <summary>
        /// 发起任务(如果不符合发起条件,则返回 false)
        /// </summary>
        /// <param name="emails"></param>
        /// <param name="title"></param>
        /// <param name="content"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        public bool send(list<string> emails, string title, string content, action<string> callback)
        {
            if (!isover())
            {
                return false;
            }

            _emaillist = emails;
            _overcount = 0;
            _title = title;
            _content = content;
            _callback = callback;

            start();
            return true;
        }

        /// <summary>
        /// 启动任务
        /// 
        /// 以线程池方式运行,每个邮箱不论成败完成数加1,并回调通知。
        /// </summary>
        private void start()
        {
            foreach (string email in _emaillist)
            {
                var _email = email;
                threadpool.queueuserworkitem(t =>
                {
                    var vr = mailhelper.sendmail(_title, _email, "", _content);
                    _overcount++;
                    _callback(string.format("进度[{3}/{4}] {0} 发送 {1},返回:{2}", _email, vr.isvalid ? "成功" : "失败", vr.message, _overcount, _emaillist.count));
                });
            }
        }


    }
}

5.8 按钮事件

发送按钮执行的流程为:如果之前的任务尚未完成,则等待。否则,首先提取邮箱列表,并格式化显示,然后发起任务,观察日志即可。

        private emailsender _sender = new emailsender(); 

        // 发送按钮
        private void btnsend_click(object sender, eventargs e)
        {
            if (!_sender.isover())
            {
                log("之前的任务尚未完成,请等待完成!");
                return;
            }

            // 1. 提取邮件列表并格式化显示
            string mails = txtemaillist.text.trim();
            var list = parseemaillist(mails);
            // 2. 格式化显示一下
            txtemaillist.clear();
            foreach (var mail in list)
            {
                txtemaillist.appendtext(mail + "\r\n");
            }
            // 3. 发起任务
            var b = _sender.send(list, txttitle.text.trim(), txtcontent.text, log);
            log(b ? "发起成功" : "发起失败");
        }

6 运行结果

6.1 配置信息错误时

一个简易邮件群发软件设计与实现

6.2 配置信息正确时

一个简易邮件群发软件设计与实现

7 项目源代码

https://github.com/triplestudio/simpleemailsender