系統需求與架構概覽
- 核心功能:多執行緒異步發送、發送狀態標記、資料逐條提取、氣泡提示、最小化到系統托盤、即時進度條。
- 技術棧:C# .NET Framework,
System.Net.Mail, System.Threading, OleDb (Access 資料庫), WinForms。
- 架構分層:UI 層 (Form) → 控制層 (發送協調器) → 資料存取層 (郵件隊列/收件人清單) → 通訊層 (SMTP Client 封裝)。
關鍵命名空間
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Threading;
using System.Text;
using System.Windows.Forms;
using System.Net.Mail;
using System.Net;
using System.Text.RegularExpressions;
核心類別設計
1. 郵件實體類
public class MailEntity
{
public int Id { get; set; }
public string ToAddress { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public bool IsHtml { get; set; }
public List Attachments { get; set; }
public MailStatus Status { get; set; } // Pending, Sending, Sent, Failed
public string ErrorMessage { get; set; }
public DateTime? SentTime { get; set; }
}
2. SMTP 設定類
public class SmtpConfig
{
public string Host { get; set; }
public int Port { get; set; } = 25;
public string UserName { get; set; }
public string Password { get; set; }
public bool EnableSsl { get; set; }
public int TimeoutMs { get; set; } = 30000;
}
3. 發送引擎 (核心邏輯)
public class MailSender : IDisposable
{
private readonly SmtpConfig _config;
private readonly SmtpClient _client;
private readonly Queue _queue;
private readonly object _lock = new object();
private volatile bool _running;
private Thread _workerThread;
public event Action OnMailStatusChanged;
public event Action OnProgressChanged; // current, total
public MailSender(SmtpConfig config, IEnumerable mails)
{
_config = config;
_client = new SmtpClient(config.Host, config.Port)
{
Credentials = new NetworkCredential(config.UserName, config.Password),
EnableSsl = config.EnableSsl,
Timeout = config.TimeoutMs
};
_queue = new Queue(mails);
}
public void Start()
{
if (_running) return;
_running = true;
_workerThread = new Thread(SendLoop) { IsBackground = true };
_workerThread.Start();
}
public void Stop()
{
_running = false;
_workerThread?.Join();
}
private void SendLoop()
{
int total = _queue.Count;
int sent = 0;
while (_running && _queue.Count > 0)
{
MailEntity mail;
lock (_lock) { if (_queue.Count == 0) break; mail = _queue.Dequeue(); }
mail.Status = MailStatus.Sending;
OnMailStatusChanged?.Invoke(mail);
try
{
using (var msg = new MailMessage())
{
msg.From = new MailAddress(_config.UserName);
msg.To.Add(mail.ToAddress);
msg.Subject = mail.Subject;
msg.Body = mail.Body;
msg.IsBodyHtml = mail.IsHtml;
foreach (var att in mail.Attachments ?? Enumerable.Empty())
msg.Attachments.Add(new Attachment(att));
_client.Send(msg); // 同步發送,阻塞執行緒
}
mail.Status = MailStatus.Sent;
mail.SentTime = DateTime.Now;
}
catch (Exception ex)
{
mail.Status = MailStatus.Failed;
mail.ErrorMessage = ex.Message;
}
OnMailStatusChanged?.Invoke(mail);
OnProgressChanged?.Invoke(++sent, total);
}
}
public void Dispose() => _client?.Dispose();
}
4. UI 協調器 (Form 代碼片段)
public partial class Form1 : Form
{
private MailSender _sender;
private BindingList _mailList;
private NotifyIcon _trayIcon;
public Form1()
{
InitializeComponent();
InitTrayIcon();
LoadMailsFromDb();
dataGridView1.DataSource = _mailList;
}
private void LoadMailsFromDb()
{
_mailList = new BindingList();
using (var conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=mails.mdb"))
using (var cmd = new OleDbCommand("SELECT * FROM Mails WHERE Status=0", conn))
{
conn.Open();
using (var reader = cmd.ExecuteReader())
while (reader.Read())
_mailList.Add(new MailEntity {
Id = reader.GetInt32(0),
ToAddress = reader.GetString(1),
Subject = reader.GetString(2),
Body = reader.GetString(3),
IsHtml = reader.GetBoolean(4),
Status = MailStatus.Pending
});
}
}
private void btnStart_Click(object sender, EventArgs e)
{
var config = new SmtpConfig { Host = txtHost.Text, Port = int.Parse(txtPort.Text),
UserName = txtUser.Text, Password = txtPwd.Text, EnableSsl = chkSsl.Checked };
_sender = new MailSender(config, _mailList);
_sender.OnMailStatusChanged += mail => this.Invoke((Action)(() => dataGridView1.Refresh()));
_sender.OnProgressChanged += (cur, tot) => this.Invoke((Action)(() => {
progressBar1.Value = cur * 100 / tot;
lblProgress.Text = $"{cur}/{tot}";
}));
_sender.Start();
btnStart.Enabled = false;
btnStop.Enabled = true;
}
private void btnStop_Click(object sender, EventArgs e) => _sender?.Stop();
private void Form1_Resize(object sender, EventArgs e)
{
if (WindowState == FormWindowState.Minimized) { Hide(); _trayIcon.Visible = true; }
}
private void InitTrayIcon()
{
_trayIcon = new NotifyIcon { Icon = Icon, Text = "郵件群發中...", Visible = false };
_trayIcon.DoubleClick += (s, e) => { Show(); WindowState = FormWindowState.Normal; _trayIcon.Visible = false; };
}
}
關鍵技術點總結
| 需求 | 實現方案 | 關鍵類/方法 |
| 異步發送不凍結 UI | 專用背景執行緒 + Control.Invoke 回報進度 | Thread, IsBackground=true, Invoke |
| 逐條狀態追蹤 | 實體類含 Status 枚舉,事件驅動 UI 更新 | MailEntity, BindingList, OnMailStatusChanged |
| 資料持久化 | Access + OleDb,狀態欄位標記避免重發 | OleDbConnection, OleDbCommand |
| 托盤常駐 | NotifyIcon,縮小時隱藏主視窗 | NotifyIcon, Form.Resize |
| 即時進度條 | 事件傳遞 (current, total),UI 線程計算百分比 | ProgressBar, OnProgressChanged |
| 氣泡提示 | NotifyIcon.ShowBalloonTip 於狀態變更/完成時觸發 | NotifyIcon |
| 郵件格式驗證 | Regex 驗證 Email 格式,發送前預檢 | System.Text.RegularExpressions |
擴展建議
- 併發控制:引入
SemaphoreSlim 限制同時 SMTP 連線數,避免被封 IP。
- 重試機制:失敗郵件加入重試隊列,指數退避策略。
- 模板引擎:整合 Razor/RazorLight 實現郵件模板化。
- 日誌審計:Serilog/NLog 記錄發送詳情,支援查詢與統計。
- 資料庫升級:遷移至 SQLite/SQL Server,支援更大數據量與並發。