使用vb.net编写一个邮件定时发送程序(三)

时间:2020-12-22 14:01:19

    一不小心,国庆长假就结束了,上班第一天抓紧时间把其余的部分放上来吧。

    首先进行数据库的结构设计,在我的设想中,最少应该有三个数据表,一个是发件箱(mail_outbox),用来存放准备发送的邮件,一个是已发送(mail_sent),发送正确的邮件都被写入这个表中,还有一个是发送错误(mail_error),发送时出现问题的邮件就存放在这里,就这个小程序而言,它最主要的功能是将mail_outbox中的邮件发送出去,具体数据是怎么被写入这个数据表的,我们就先不讨论了。

    大体的思路确定以后,就可以开始进行设计了,数据表的字段应该包括发件人姓名、收件人地址、收件人姓名、主题、内容、是否HTML邮件、拟发送时间、邮件优先级等内容,本来还想再加入一个回复邮件地址的项目,后来发现不知道为什么,Jmail4.4中都还有的ReplyTo属性,在Jmail.Net 1.1中竟然没有了,也就不再加这个字段了。建表语句如下:

CREATE TABLE [dbo].[mail_outbox](
[mail_id] [int] IDENTITY(1,1) NOT NULL,
[mail_sendtime] [datetime] NULL,
[mail_sendname] [nvarchar](30) NULL,
[mail_toemail] [varchar](50) NULL,
[mail_toname] [nvarchar](30) NULL,
[mail_subject] [nvarchar](100) NULL,
[mail_body] [nvarchar](max) NULL,
[mail_html] [bit] NULL,
[mail_Priority] [char](1) NULL,
[mail_flag] [char](1) NULL,
[mail_senduser] [varchar](14) NULL,
CONSTRAINT [PK_mail_outbox] PRIMARY KEY CLUSTERED
(
[mail_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

    在对该表进行读取时,最多的动作就是检查是否已经到了预定的发送时间,因此在这个表上对mail_sendtime增加了一条索引,加快处理速度。

create index idx_mailoutbox on mail_outbox
(mail_sendtime)

    Mail_sent表、Mail_error表与Mail_outbox表的结构基本相同,只是增加了一个Mail_senttime字段记录实际的发送时间及Mail_error字段记录报错信息,同时去掉了Mail_id的主键及自动增长,并没有加上任何索引。

 

使用vb.net编写一个邮件定时发送程序(三)使用vb.net编写一个邮件定时发送程序(三)代码
   
   
   
1 create table mail_sent
2 (
3 mail_id int ,
4 mail_sendtime datetime ,
5 mail_sendname nvarchar ( 30 ),
6 mail_toemail varchar ( 50 ),
7 mail_toname nvarchar ( 30 ),
8 mail_subject nvarchar ( 100 ),
9 mail_body nvarchar ( max ),
10 mail_html bit ,
11 mail_Priority char ( 1 ),
12 mail_flag char ( 1 ),
13 mail_senduser varchar ( 14 ),
14 mail_senttime datetime
15 );
16   create table mail_error
17 (
18 mail_id int ,
19 mail_sendtime datetime ,
20 mail_sendname nvarchar ( 30 ),
21 mail_toemail varchar ( 50 ),
22 mail_toname nvarchar ( 30 ),
23 mail_subject nvarchar ( 100 ),
24 mail_body nvarchar ( max ),
25 mail_html bit ,
26 mail_Priority char ( 1 ),
27 mail_flag char ( 1 ),
28 mail_senduser varchar ( 14 ),
29 mail_senttime datetime ,
30 mail_error_msg varchar ( 200 )
31 )
32

    数据表建好后,开始增加程序功能,因为数据库使用的是微软自己的SQL SERVER 2005,因此就使用了SqlClient.SqlConnection来进行连接,新建一个名为sql_conn.vb的类,将主要功能都进入,然后在主程序中进行调用。

Public Class sql_conn
Private Shared Conn As SqlClient.SqlConnection
Public Shared connStr As String
Public Shared Function Open() As Boolean '连接数据库
Try
Conn = New SqlClient.SqlConnection(connStr)
Conn.Open()
Return True
Catch
'MsgBox("数据库无法连接1", MsgBoxStyle.OkOnly, "错误")
Return False
End Try
End Function


Public Shared Function Open(ByVal p_ServerName As String, ByVal p_UID As String, ByVal p_PassWord As String, ByVal p_DataBase As String) As Boolean '连接数据库,四个参数是服务器名,用户名,密码,数据库名
Try
connStr = "server=" + p_ServerName + ";User ID =" + p_UID + ";password=" + p_PassWord + ";database =" + p_DataBase
Conn = New SqlClient.SqlConnection(connStr)
Conn.Open()
Return True
Catch
'MsgBox("数据库无法连接2", MsgBoxStyle.OkOnly, "错误")
Return False
End Try
End Function

#Region "得到数据集"
Public Shared Function GetDS(ByVal strSql As String) As DataSet '通过Sql语句得到数据集
If Conn Is Nothing Then
Open()
End If
Dim DS As New DataSet
Dim sqlComm As New SqlClient.SqlCommand(strSql, Conn)
Dim sqlDA As New SqlClient.SqlDataAdapter(sqlComm)
Try
sqlDA.Fill(DS)
sqlComm.Cancel()
Return DS
Catch
MsgBox(Err.Description, MsgBoxStyle.OkOnly, "错误")
Return Nothing
End Try
End Function
#End Region

#Region "执行Sql语句"
Public Shared Function Exec_Sql(ByVal strSql As String) As Integer '执行Sql语句,不返回数据集
If Conn Is Nothing Then
Open()
End If
Dim sqlComm As New SqlClient.SqlCommand(strSql, Conn)
Dim SqlCount As Integer = 0
Try
SqlCount = sqlComm.ExecuteNonQuery() '执行Sql语句
sqlComm.Cancel()
Return SqlCount
Catch
'MsgBox(Err.Description, "错误")
Return -1
End Try
End Function
#End Region
End Class


    在一般常见的代码中,对于exec_sql这类不需要返回数据集的sql执行语句一般都选择了返回Boolean值,但我选择了返回一个Integer值,最主要的功能是用来返回update数据库时受影响的记录条数,方便下一步的判断和继续操作。

    好,现在数据库的结构和连接、读写数据库的代码都有了,要实现循环读取待发邮件的功能,我们还需要再加上一个Timer控件,将控件拖到Form中,在用户点击“开始”按钮执行程序时对Timer进行处理,我设定的Interval值为1000,也就是第一秒运行一次,这样的话就可以把扫描的间隔时间按秒数进行设定,十分方便,而且也比较精确。

    下面是“开始”按钮及Timer_Tick的代码:

    Private Sub Btn_Start_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Btn_Start.Click
If Running = False Then
If Txt_ScanSec.Text < 30 Or Txt_ScanSec.Text > 86400 Then
MessageBox.Show("扫描的间隔时间不应小于30秒或大于86400秒,请重新设置!", "注意", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Txt_ScanSec.Text = "180"
Exit Sub
End If
If Not Open(Txt_DbServer.Text, Txt_DbUser.Text, DbPassword, Txt_DataBase.Text) Then
MessageBox.Show("数据库连接有误,请检查后再运行程序程序!", "注意", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Exit Sub
End If
Running = True
RunStrat = Now()
Btn_Start.Text = "停止(&S)"
Btn_Exit.Enabled = False
Btn_SaveIni.Enabled = False
'Tbl_Main.Enabled = False
Txt_ScanSec.ReadOnly = True
Chk_LogFile.Enabled = False
Tbl_Main.TabPages(1).Enabled = False
Tbl_Main.TabPages(2).Enabled = False
'ReadRow("select * from yn_login where user_role = '2'")
Time_SendMail.Interval = 1000
Time_SendMail.Enabled = True
Time_SendMail.Start()
Txt_Message.Text = RunStrat + "程序开始运行……" + vbCrLf

Else
Time_SendMail.Stop()
Time_SendMail.Enabled = False
Running = False
Btn_Start.Text = "开始(&S)"
Btn_Exit.Enabled = True
Btn_SaveIni.Enabled = True
Txt_ScanSec.ReadOnly = False
Chk_LogFile.Enabled = True
Tbl_Main.TabPages(1).Enabled = True
Tbl_Main.TabPages(2).Enabled = True
End If
End Sub

Private Sub Time_SendMail_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Time_SendMail.Tick
If ScanSec >= Txt_ScanSec.Text Then
Time_SendMail.Stop()
Txt_Message.Text = "正在进行邮件发送……" + vbCrLf + "启动时间:" + Now()
SentOutBox()
Application.DoEvents()
ScanCount = ScanCount + 1
Txt_Message.Text = "本次扫描启动于" + RunStrat + vbCrLf + "共扫描了" + ScanCount.ToString + "次,发送成功邮件" + SendSuc.ToString + "封,失败" + SendErr.ToString + "封!" + vbCrLf + "暂停" + Txt_ScanSec.Text.ToString + "秒,等待下一次扫描……" + vbCrLf
ScanSec = 0
Time_SendMail.Start()
Else
ScanSec = ScanSec + 1
Txt_Message.Text = Txt_Message.Text + "."
End If
End Sub

    这样的话,整个程序的主要步骤就已经完成了,剩下来的就是一些小细节方面的改动,将在下一节的时候再和大家一一交流。