自动发邮件做成可视化可以连接数据库取数据可设置定时发送等

因工作需要做一个自动发送邮件的功能,要求是周末定时发送。

原因是这样,公司的行政办公人员周末和节假日不上班,但总有一些人加班,而公司是采用报餐的形式去报中午的餐数量,

平时都是行政人员发数据给中央厨房,而周末节日行政人员不上班,厨房又没有配置电脑这些,而且厨房不一定在公司附近,

可能距离比较远。所以有了这样的需求,总结就是:

1:周末,节日自动定时发送邮件给中央厨房。邮件内容含当天的用餐早报人数

2:厨房没有办法连接到公司的网络,也没有配备电脑。

那我们开始设计思路:

做一个自动发送邮件,邮件正文含当天的报餐人数给厨房就行了。那么我们要解决的有以下两点

一:从公司的报餐数据库中取出当日的报餐数量,汇总后以正文的形式发一封邮件给厨房

二:定时发送给指定邮箱的人。

考虑到以上可能数据库会变,邮件接收人会更换等。应该做成可配置灵活的,而不是写死。

基于保密,数据库连接密码,邮件授权码这些就做成加密的形式保存。

先看一下UI布局的整体效果。

我用的是PySimpleGUI这个库。

先导入要用到的库,如果没有的自己去:pip install XXXXXX

一:程序中用到的库如下:

import time as t

import PySimpleGUI as sg

import smtplib

import os

import pyodbc

import _thread

from configparser import ConfigParser

from datetime import datetime,time

import GetDinData

from email.mime.text import MIMEText

from email.mime.multipart import MIMEMultipart

import base64

from Crypto.Cipher import AES

二:

界面UI布局及主题

sg.theme('GreenMono') # 设置当前主题

布局设置

layout = [

#收件信息控件布置

[sg.Text('收件人: ',font=('微软雅黑', 12)), sg.InputText('', key='_To',size=(70, 1), font=('微软雅黑', 12))],

[sg.Text('抄送:\t',font=('微软雅黑', 12)), sg.InputText('', key='_To_copy',size=(70, 1), font=('微软雅黑', 12))],

[sg.Text('主题:\t',font=('微软雅黑', 12)), sg.InputText('理研-报/用餐数据汇总及明细表', key='_Subject',size=(70, 1), font=('微软雅黑', 12))],

[sg.Text('附件:\t',font=('微软雅黑', 12)),

sg.FileBrowse('添加附件', file_types=(('Text Files', '. '),), size=(10, 1), font=('微软雅黑', 11),key='_OpenFile'),

sg.Button('删除附件', font=('微软雅黑', 12),key='_Delete')

],

#正文

[sg.Frame(layout=[

[sg.Multiline(default_text='您好:'+'\n'+' 这是您申请发送的(报/用餐数据汇总)情况,请您查看! 注:此邮件为自动发送邮件!' + '\n',

size=(63, 10),font=('微软雅黑', 14), text_color='Blue', key='_Content',auto_size_text=True)],

],

title='邮件正文', title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='邮件正文')

],

服务器及发件人控件布置

[sg.Frame(layout=[

[sg.Text('服务器: ', font=('微软雅黑', 12), text_color='red'),

sg.InputText('smtp.qq.com', key='_Server', size=(25, 1), font=('微软雅黑', 12)),

sg.Text('端口号: ', font=('微软雅黑', 12), text_color='red'),

sg.InputText('465', key='_Port', size=(5, 1), font=('微软雅黑', 12)),

sg.Text('授权码: ',font=('微软雅黑', 12), text_color='red'),

sg.InputText('',key='_AutoCode', size=(18, 1), font=('微软雅黑', 12)), ],

[sg.Text('发件人: ', font=('微软雅黑', 12), text_color='red'),

sg.InputText('', key='_From', size=(69, 1), font=('微软雅黑', 12))],

        [sg.Text('定时1 :   ', font=('微软雅黑', 12),text_color='blue'),sg.InputText('10:00', key='_AutoTime1',size=(9, 1)),
         sg.Text('定时2 :   ', font=('微软雅黑', 12),text_color='blue'),sg.InputText('15:00', key='_AutoTime2',size=(9, 1)),
         sg.Button('邮件服务器连接测试', font=('微软雅黑', 12),button_color='blue', key='_ConnectEmail'),
         sg.Button('显示配置信息', font=('微软雅黑', 12),key='_ShowAutoCode'),
         sg.Button('保存配置', font=('微软雅黑', 12),key='_SaveConfigEmail')],
         ],
         title='(1): 邮件服务器及发件人配置', title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='邮件服务器及发件人设置')
     ],
    # 服务器数据库控件布置
    [sg.Frame(layout=[
        [sg.Text('服务器地址:', font=('微软雅黑', 12), text_color='red'),
         sg.InputText('', key='_ServerName', size=(30, 1), font=('微软雅黑', 12), tooltip='服务名或IP地址'),
         sg.Text('数据库名称:', font=('微软雅黑', 12), text_color='red'),
         sg.InputText('', key='_Database', size=(25, 1), font=('微软雅黑', 12), tooltip='数据库名称')],
        [sg.Text('登录用户名:', font=('微软雅黑', 12), text_color='red'),
         sg.InputText('sa', key='_LogId', size=(30, 1), font=('微软雅黑', 12), tooltip='正常是sa'),
         sg.Text('用户名密码:', font=('微软雅黑', 12), text_color='red'),
         sg.InputText('',key='_LogPassword', size=(25, 1), font=('微软雅黑', 12), tooltip='sa对应的密码')],

        [sg.Text('操作按钮 >>>>>>>>>>>>>>>', text_color='blue', font=('微软雅黑', 12)),
         sg.Button('数据库连接测试', font=('微软雅黑', 12), button_color='blue', key='_ConnectDB'),
         sg.Button('显示配置信息', font=('微软雅黑', 12), key='_ShowConfig'),
         sg.Button('保存配置', font=('微软雅黑', 12), key='_SaveConfigDB'),
         sg.Button('测试获取数据', font=('微软雅黑', 12), key='_Get'),
         ],
    ],
        title='(2): 服务器数据库配置', title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='服务器数据库设置')
    ],
     [sg.Text('说明: (1) 多个收件人地址用分号";"隔开 (2) 定时周末及节假日才会发送',text_color='red',font=('微软雅黑', 12)),
      sg.Button('发送邮件', font=('微软雅黑', 12),key='_Send'),
      sg.Button('开启自动发送', font=('微软雅黑', 12),key='_AutoSend')],
     [sg.Text('运行状态:  还未开启自动发送邮件',text_color='yellow', font=('微软雅黑', 12),key='_State')],
     # [sg.StatusBar('运行状态:  还未开启自动发送邮件',text_color='yellow', font=('微软雅黑', 12),key='_State')]
     ]

创建窗口

window = sg.Window('定时自动发送邮件小程序,Author:Running Ver:1.0 ; 程序运行时间: ' + t.strftime('%Y-%m-%d %H:%M:%S'), layout,font=('微软雅黑', 12), default_element_size=(50, 1))

三:配置文件及相关

配置文件

app_path = 'setting.ini'

log_path = 'log.txt'

key = 'lsqily82lsqily82' #自己密钥一定要16字节不然会报错

控制是否点了"显示授权码"默认为True

AutoFlag = False

四:AES加密及解密

def AES_Encrypt(key, data):

密钥(key), 密斯偏移量(iv) CBC模式加密

vi = '0102030405060708'

pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)

data = pad(data)

字符串补位

cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))

encryptedbytes = cipher.encrypt(data.encode('utf8'))

加密后得到的是bytes类型的数据

encodestrs = base64.b64encode(encryptedbytes)

使用Base64进行编码,返回byte字符串

enctext = encodestrs.decode('utf8')

对byte字符串按utf-8进行解码

return enctext

def AES_Decrypt(key, data):

vi = '0102030405060708'

data = data.encode('utf8')

encodebytes = base64.decodebytes(data)

将加密数据转换位bytes类型数据

cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))

text_decrypted = cipher.decrypt(encodebytes)

unpad = lambda s: s[0:-s[-1]]

text_decrypted = unpad(text_decrypted)

去补位

text_decrypted = text_decrypted.decode('utf8')

return text_decrypted

五:邮件服务器配置相关函数-

----------------邮件服务器配置相关函数--------------------

定义全局变量

Server=''

def ini():

"初始化创建自动发邮件配置文件"

创建一个ConfigParser对象

config = ConfigParser()

if not os.path.exists(app_path):

sg.popup('没有'+app_path+'配置文件,请先在当前界面配置邮件服务器并保存配置文件', title='提示', )

return 'N'

else:

return 'Y'

def Data_Effect():

"数据有效性验证"

info='OK'

#获取UI界面上的各项值

ToMail = values['_To'] # 收件人昵称和地址

Subject = values['_Subject'] # 邮件主题

Smtp = values['_Server']

Port = values['_Port']

AutoCode = values['_AutoCode']

SendAddress = values['_From']

AutoTime1 = values['_AutoTime1']

AutoTime2 = values['_AutoTime2']

Server = values['_ServerName']

DataBase = values['_Database']

LogId = values['_LogId']

LogPassword = values['_LogPassword']

#判断是否为空

if len(ToMail)0:
info="收件人邮件地址不能为空"
sg.popup('收件人邮件地址不能为空', title='提示', )
elif len(Subject)0:

sg.popup('邮件主题不能为空', title='提示', )

elif len(Smtp)0:
sg.popup('邮件服务器不能为空', title='提示', )
elif len(Port)0:

sg.popup('端口号不能为空', title='提示', )

elif len(AutoCode)0:
sg.popup('授权码不能为空', title='提示', )
elif len(AutoTime1)0:

sg.popup('定时1不能为空', title='提示', )

elif len(AutoTime2)0:
sg.popup('定时2不能为空', title='提示', )
elif len(Server)0:

sg.popup('服务器地址不能为空', title='提示', )

elif len(DataBase)0:
sg.popup('数据库名称不能为空', title='提示', )
elif len(LogId)0:

sg.popup('登录用户名不能为空', title='提示', )

elif len(LogPassword)0:
sg.popup('用户名密码不能为空', title='提示', )
window['_State'].update('当前状态: '+ info)
return info
def show_ini():
#显示配置文件前,先判断是否有生成配置文件
if ini()'N':

return

创建一个ConfigParser对象

config = ConfigParser()

try:

从 INI文件中读取配置信息

config.read(app_path, encoding="utf-8")

获取指定节点下的键值对

Smtp = config['SMTP']['Smtp']

Port = config['SMTP']['Port']

AutoCode = config['SMTP']['AutoCode']

显示出解密的明文 ,授权码jyfgyfmienircage

SendAddress = config['SMTP']['SendAddress']

AutoTime1 = config['SMTP']['AutoTime1']

AutoTime2 = config['SMTP']['AutoTime2']

#显示在UI界面

window['_Server'].update(Smtp)

window['_Port'].update(Port)

window['_AutoCode'].update(AutoCode)

window['_From'].update(SendAddress)

window['_AutoTime1'].update(AutoTime1)

window['_AutoTime2'].update(AutoTime2)

window['_State'].update('当前状态: 邮件服务器配置文件显示成功!')

except:

window['_State'].update('当前状态: 邮件服务器配置文件显示失败,请确认是否有配置文件!')

sg.popup('配置文件显示失败,请确认是否有配置文件', title='提示', )

def Save_ServerEmail():

"添加邮件服务器的节和配置的项的值"

创建一个ConfigParser对象

config = ConfigParser()

config.add_section('SMTP')

config.set('SMTP', 'Smtp', values['_Server']) # 邮件服务器地址smtp及

config.set('SMTP', 'Port', values['_Port']) # 端口号port

config.set('SMTP', 'SendAddress', values['_From']) # 发件人邮箱地址

config.set('SMTP', 'AutoTime1', values['_AutoTime1']) #定时1

config.set('SMTP', 'AutoTime2', values['_AutoTime2']) #定时1

#保存前先判断是不是新建的配置文件,如果是新建的加密,否则取出配置解密后判断是不是和当前显示的一样

if not os.path.exists(app_path):

config.set('SMTP', 'AutoCode', AES_Encrypt(key, values['_AutoCode'])) # 发件人授权码加密后再保存

添加数据库的节和配置项的值供保存使用

config.add_section('database')

config.set('database', 'ServerName', values['_ServerName'])

config.set('database', 'Database', values['_Database'])

config.set('database', 'LogId', values['_LogId'])

config.set('database', 'LogPassword', AES_Encrypt(key, values['_LogPassword']))

config.set('database', 'LogPassword', values['_LogPassword'])

else:

config.read(app_path, encoding="utf-8") #读取ini配置文件

AES_AutoCode = config['SMTP']['AutoCode']

if AES_AutoCode != values['_AutoCode']:

config.set('SMTP', 'AutoCode', AES_Encrypt(key, values['_AutoCode'])) # 发件人授权码加密后再保存

else:

config.set('SMTP', 'AutoCode', values['_AutoCode']) # 发件人授权码

   AES_LogPassword = config['database']['LogPassword']
   if AES_LogPassword != values['_LogPassword']:
      config.set('database', 'LogPassword', AES_Encrypt(key, values['_LogPassword']))  # 用户名密码加密后再保存
   else:
      config.set('database', 'LogPassword', values['_LogPassword'])  # 用户名密码

# 写入配置文件
with open(app_path, 'w', encoding="utf-8") as configfile:
    config.write(configfile)
print('邮件服务器配置文件保存成功')
window['_State'].update('当前状态: 邮件服务器配置文件保存成功!')
sg.popup('邮件服务器配置保存成功', title='提示', )

def Connect_email_server():

"连接邮件服务器前先显示配置信息(不知道为什么要等此函数连接成功才会显示,先显示在界面再取值取不到?)"

#显示在UI界面

show_ini()

global Server

if Server!='':

window['_State'].update('当前状态: 邮件服务器已经连接中,无需重复测试!')

Server.quit()

return Server

创建一个ConfigParser对象

config = ConfigParser()

从 INI文件中读取配置信息

config.read(app_path, encoding="utf-8")

#授权码要解密一下再去连接,不直接明文显示在界面上

AutoCode = AES_Decrypt(key,config['SMTP']['AutoCode'])

#print(AutoCode)

try:

连接服务器

Server = smtplib.SMTP_SSL(config['SMTP']['Smtp'], config['SMTP']['Port'])

登录邮箱

loginResult = Server.login(config['SMTP']['SendAddress'], AutoCode)

print(loginResult)

if loginResult[0]==235:

print('恭喜,邮件服务器连接成功')

window['_State'].update('当前状态: 邮件服务器连接成功')

return(Server)

else:

print('邮件服务器连接失败,请确认配置是否正确后重试')

window['_State'].update('当前状态: 邮件服务器连接失败,请确认配置是否正确后重试')

return

except:

window['_State'].update('当前状态: 邮件服务器连接失败,请确认配置是否正确后重试')

sg.popup('邮件服务器连接失败,请确认配置是否正确后重试', title='提示', )

Server.quit()

return

def Send_file_email(content_data):

"此函数为,执行发送邮件的函数,可带附件"

#先进行数据有效性验证

info = Data_Effect()

if info != 'OK':

return

try:

收发相关信息

SendAddress = values['_From'] # 发件人昵称和地址

ToMail = values['_To'] # 收件人昵称和地址

ToCopy = values['_To_copy'] # 抄送人昵称和地址

Subject = values['_Subject'] # 邮件主题

print(ToMail)

定义一个可以添加正文和附件的邮件消息对象

msg = MIMEMultipart()

收件人相关信息

msg['From'] = SendAddress

msg['To'] = ToMail

msg['Cc'] = ToCopy

msg['Subject'] = Subject

邮件正文,从脚本GetDinData.getdata()中获取

content = content_data

先通过MIMEText将正文规范化,构造成邮件的一部分,再添加到邮件消息对象中

msg.attach(MIMEText(content, 'plain', 'utf-8'))

附件(添加多个附件同理),暂时关闭,有需要再打开

以二进制形式将文件的数据读出,再使用MIMEText进行规范化

attachment = MIMEText(open(f'image/logo.gif', 'rb').read(), 'base64', 'utf-8')

# 告知浏览器或邮件服务器这是字节流,浏览器处理字节流的默认方式为下载

attachment['Content-Type'] = 'application/octet-stream'

# 此部分主要是告知浏览器或邮件服务器这是一个附件,名字叫做xxxxx,这个文件名不要用中文,不同邮箱对中文的对待形式不同

attachment['Content-Disposition'] = 'attachment;filename="logo.gif"'

msg.attach(attachment)

调用函数后返回的服务器对象

Server = Connect_email_server()

向服务器提交邮件发送 ,ToMail 收件人

Server.sendmail(SendAddress, [ToMail], msg.as_string())

print('邮件发送成功')

return 'Success'

except:

return 'Faile'

sg.popup('邮件发送成功', title='提示', )

#发送成功后关闭Server

Server.close()

def Auto_Send(window,AutoFlag):

"数据有效性验证"

info = Data_Effect()

if info !='OK':

return

if AutoFlag:

定时1

Auto_H1 = int(values['_AutoTime1'][:2])

Auto_M1 = int(values['_AutoTime1'][-2:])

target_time1 = time(Auto_H1, Auto_M1,10)

定时2

Auto_H2 = int(values['_AutoTime2'][:2])

Auto_M2 = int(values['_AutoTime2'][-2:])

target_time2 = time(Auto_H2, Auto_M2, 10)

    # 无限循环,每次检查当前时间是否达到设定时间
    while True:
        now = t.localtime()
        now = t.strftime("%H:%M:%S",now)
        print('等待执行中'+str(now))
        if now == str(target_time1) or now == str(target_time2):
            # # 如果当前时间达到或超过设定时间,执行以下操作
            print("1:预设时间到,开始执行操作")
            # 你的代码放这里,连接数据库取值
            data = GetDinData.GetData()
            # print(data)
            window['_Content'].Update(data)
            #判断是不是周末或节日
            if GetDinData.Getworktype() == "周末" or GetDinData.Getworktype() == "节日":
               #自动执行发送邮件结果,返回Success表示成功
               if Send_file_email(data) == 'Success':
                  log_txt ='系统时间:' + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ' 执行自动发邮件成功!'
               else:
                  log_txt ='系统时间:' + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ' 执行自动发邮件失败!'
               #执行函数写入日志文件:log.txt
               Write_Log(log_txt)
         # break
        #等待1秒
        t.sleep(1)
        window['_State'].update('当前状态: 自动发送邮件运行中....')
else:
    window['_State'].update('当前状态: 系统己就绪等待执行指令')
    # print('关闭自动发送邮件')

def Write_Log(log_text):

写入日志,检查文件是否存在不存在先创建log.txt

if not os.path.exists(log_path):

文件不存在,则创建文件

with open(log_path, 'w') as file:

将字符串写入文件

file.write(log_text)

file.close()

else:

with open(log_path, 'w') as file:

file.write(log_text)

file.close()

六:# -----------服务器数据库配置相关函数----------------------

def Show_Config():

创建一个ConfigParser对象

config = ConfigParser()

if not os.path.exists(app_path):

sg.popup('没有'+app_path+'配置文件,请先在当前界面配置邮件服务器并保存配置文件', title='提示', )

return 'N'

else:

从 INI 文件中读取配置信息

config.read(app_path, encoding="utf-8")

获取指定节点下的键值对

Server = config['database']['ServerName']

DataBase = config['database']['Database']

LogId = config['database']['LogId']

LogPassword = config['database']['LogPassword']

#把配置信息显示在界面上

window['_ServerName'].update(Server)

window['_Database'].update(DataBase)

window['_LogId'].update(LogId)

window['_LogPassword'].update(LogPassword)

window['_State'].update('当前状态: 服务器数据库配置文件显示成功!')

return 'Y'

def Save_ServerDB():

Save_ServerEmail()

创建一个ConfigParser对象

config = ConfigParser()

if ini()'N':

return

else:

# 设置键值对

config.add_section('database')

config.set('database', 'ServerName', values['_ServerName'])

config.set('database', 'Database', values['_Database'])

config.set('database', 'LogId', values['_LogId'])

config.set('database', 'LogPassword', values['_LogPassword'])

# 写入配置文件

with open(app_path, 'w',encoding="utf-8") as configfile:

config.write(configfile)

window['_State'].update('当前状态: 服务器数据库配置文件保存成功!')

sg.popup('服务器数据库配置文件保存成功!', title='提示', )

def connect_db():
"连接前先显示出配置信息在界面方便核对"
if Show_Config()'N':

return

创建一个ConfigParser对象

config = ConfigParser()

从 INI 文件中读取配置信息

config.read(app_path,encoding="utf-8")

获取指定节点下的键值对

Server = config['database']['ServerName']

DataBase = config['database']['Database']

LogId = config['database']['LogId']

LogPassword = config['database']['LogPassword']

LogPassword = AES_Decrypt(key,LogPassword) #显示出解密后的密码

print(LogPassword)

try:

conn = pyodbc.connect('DRIVER={SQL Server};SERVER=' + Server + ';DATABASE=' + DataBase + ';UID=' + LogId + ';PWD=' + LogPassword )

print('当前状态: 服务器数据库连接成功!')

window['_State'].update('当前状态: 服务器数据库连接成功!')

return conn

except Exception as e:

print('当前状态: 数据库连接失败,请检查数据库配置文件!'+str(e))

sg.popup('数据库连接失败,请检查数据库配置文件!', title='提示', )

return 'conn_faild'

七:

事件循环并获取输入值

while True:

event, values = window.read()

打开时自动点击显示服务器邮件配置信息

if event in (None, '_Close'): # 如果用户关闭窗口或点击Close

break

elif event'_SaveConfigEmail':
Save_ServerEmail() #保存邮件服务器配置
elif event == '_ConnectEmail':
Connect_email_server() #连接邮件服务器
elif event'_ShowAutoCode':

show_ini() #显示配置文件

elif event'_Send':
Send_file_email('你好,这是一封测试邮件不用理会!')
elif event'_AutoSend': #开启、关闭自动发送

if not AutoFlag:

window.find_element('_AutoSend').update('关闭自动发送')

window.find_element('_State').update('运行状态: 开启自动发送邮件中......')

AutoFlag = True

#利用多线程,防止程序卡死无响应状态

_thread.start_new_thread(Auto_Send, (window,AutoFlag))

Auto_Send(window,AutoFlag)

else:

window.find_element('_AutoSend').update('开启自动发送')

window.find_element('_State').update('运行状态: 还未开启自动发送邮件')

thread_id = _thread.get_ident()

t.sleep(2)

AutoFlag = False

Auto_Send(window,AutoFlag)

#-------------服务器配置的事件放在下面---------------

elif event'_ShowConfig':
Show_Config()
elif event'_SaveConfigDB':

Save_ServerDB() #保存服务器数据库配置

elif event == '_ConnectDB':

connect_db() # 连接服务器数据库

elif event == '_Get':

data = GetDinData.GetData()

print(data)

window['_Content'].Update(data)

关闭邮件服务器连接和窗口

window.close()

这样配置好了,点开始自动发送邮件如下图:测试时间到自动发送了:

邮箱中看到的邮件:

我用的是QQ邮箱,授权码要自己去申请一下。