上一篇文章主要这款工具的环境准备、Qt Designer的基本使用,以及工具界面的生成(备注:今天抽空又把界面优化了一下,增加了日志显示),今天主要介绍后台具体实现逻辑部分。
主界面.pngparamiko的基本使用
之前的一篇文章已经介绍过了paramiko的相关基础操作:
主要包括:
1.连接服务器
def trans_connect(host,username,password):
try:
trans = paramiko.Transport((host,22))
trans.connect(username=username,password=password)
except Exception,e:
print e
return trans
def ssh_connect(host,username,password):
try:
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(host,22,username,password)
except Exception ,e:
print e
return ssh_client
2.一些基本命令的实现
cmd = "cd /home ;ls ;pwd"
arg = 'get_pty=True'
ssh_client = ssh_connect(host, username, password)
stdin, stdout, stderr = ssh_exec_cmd(ssh_client, cmd, arg)
for line in stdout:
print line.strip('\n')
time.sleep(2)
ssh_close(ssh_client)
3.上传下载文件至服务器
def trans_web(trans,remotepath,localpath):
sftp = paramiko.SFTPClient.from_transport(trans)
sftp.put(localpath,remotepath)
trans.close()
下载文件,只需切换localpath和remotepath的位置即可
内部逻辑实现
上一篇说到了界面与逻辑分离的规则,所有的界面实现都集中在FirstMainWin.py 这个文件中实现,而所有后台逻辑实现都集中在Change.py中实现,保证页面发生改变时不需要大概后台逻辑,同样改动后台逻辑时也不会对前台页面造成影响。
首先缕一下在xshell中手动部署替换文件的顺序:
1.连接服务器
2.cd 到文件路径,备份该文件
3.上传文件
4.重启服务器(非必要操作)
同样的顺序,在用这个工具的时候,也是按照连接服务--备份--上传文件--重启的顺序;
即:在点开始部署这个pushbutton时要激活上述所有步骤:
首先定义这些基础的步骤
连接服务
先介绍一下QComboBox(下拉列表框)的一些常用方法和常用信号:
(1)addItems
在QComboBox的添加一个下拉选项。
(2)count
返回列表项总数。
(3)currentIndex
当前显示的列表项序号。
(4)currentText
返回当前显示的文本。
常用信号:
(1)Activated
当用户选中一个下拉框时发射该信号
(2)currentIndexChanged
当下拉选项的索引发生变化时发射该信号
(3)highlighted
当选中一个已经选中的下拉选项时,发射该信号
class Main(QMainWindow,Ui_Form):
def __init__(self,parent=None):
super(Main,self).__init__(parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.setWindowTitle('Waiqin365-DATT-V1.0.0')
self.password = 'xxxxxxxx'
self.username='xxxx'
self.ui.environment.activated.connect(self.printtomcat)
self.ui.filename.editingFinished.connect(self.filename)
self.ui.filenumber.activated.connect(self.filenumber)
self.ui.filetype.activated.connect(self.filetype)
self.ui.restart.activated.connect(self.needrestart)
self.ui.start.clicked.connect(self.start)
def printtomcat(self):
'''
根据选择的环境返回Index值,供下面connect使用
1 231
2 233
'''
print(self.ui.environment.currentText())
tomcat = self.ui.environment.currentIndex()
self.ui.log.setPlainText('部署的环境为:'+self.ui.environment.currentText())
return tomcat
def connect(self,Index):
'''
根据传入的Index连接服务器
:param Index: 1 231 2 233
:return: ssh
'''
if Index == 1:
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('172.31.3.231',22,self.username, self.password)
except Exception:
print (Exception)
return ssh
elif Index == 2:
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('172.31.3.233',22,self.username, self.password)
except Exception:
print (Exception)
return ssh
替换环境这个下拉框的object name(在设计界面的时候可以自定义)为environment
self.ui.environment.activated.connect(self.printtomcat)
当选中一个下拉选项时,激活printtomcat这段代码,并返回选项的Index,供下面连接服务器connect部分使用
同样的道理:
文件类型的两个下拉选项,是否重启的两个下拉选项,都是选中一个选项时,激活相应的操作
返回文件数,是单个文件还是压缩文件
self.ui.filenumber.activated.connect(self.filenumber)
def filenumber(self):
'''
根据选择的文件数量:单个文件还是web.zip,返回Index供下面onefile和zip使用
1 单个文件
2 web.zip
'''
print(self.ui.filenumber.currentIndex())
filenumber = self.ui.filenumber.currentIndex()
self.ui.log.setPlainText('文件类型为:'+self.ui.filenumber.currentText())
return filenumber
返回文件类型是平台文件还是应用文件
self.ui.filetype.activated.connect(self.filetype)
def filetype(self):
'''
根据选择的文件类型:平台文件还是应用文件,自动加上前缀 /home/...web/WEB-INFO/class 还是 /home/...web/
1 应用文件
2 平台文件
'''
print(self.ui.filetype.currentIndex())
filetype = self.ui.filetype.currentIndex()
self.ui.log.setPlainText('文件为:'+self.ui.filetype.currentText())
return filetype
是否需要重启
self.ui.restart.activated.connect(self.needrestart)
def needrestart(self):
'''
根据所选,返回Index,供下面使用是否需要重启
1 需要
2 不需要
'''
need = self.ui.restart.currentIndex()
print(need)
return need
备份替换文件并上传文件
如果文件是单个文件,根据传入的Index判断是平台文件还是应用文件,在返回的文件路径前自动补全地址
/home/iorder_appsvr/iorder_appsvr/web/WEB-INF/classes 或者 /home/iorder_appsvr/iorder_appsvr/web
返回文件名
def filename(self):
'''
返回填写的filename
'''
print(self.ui.filename.text())
filename = self.ui.filename.text()
self.ui.log.setPlainText('文件名为:'+self.ui.filename.text())
return filename
def localfile(self):
'''
返回local,即文件所在路径
:return:
'''
print(self.ui.filename.text())
filename = self.ui.filename.text()
localfile = 'D:/file/'+ filename
print(localfile)
return localfile
def path(self):
'''
返回填写的filepath 文件路径
'''
print(self.ui.filepath.toPlainText())
filepath = self.ui.filepath.toPlainText()
return filepath
filename和filepath分别是QLineEdit和QTextEdit
QLineEdit常用方法:
(1)clear
清除文本内容
(2)setText
设置文本内容
(3)Text
返回文本框内容
(4)selectAll
全选
QTextEdit常用方法:
(1)setPlainText
设置多行文本框内容
(2)toPlainText
返回多行文本框内容
其中trans主要用来上传文件,根据Index来连接对应的服务器
onefile这个方法,主要用来替换单个文件,根据传入的Index,type连接对应的服务器,并根据类型拼接文件地址,然后执行相应的命令,备份文件,上传文件等操作
zip这个方法,主要用来替换web.zip,只需要一个参数Index,连接对应的服务器即可以,也是类似的操作:上传文件,解压,复制。
def trans(self,Index,localpath,remotepath):
'''
根据传入的Index连接服务器并上传文件
:param Index:
:return:
'''
if Index == 1:
try:
trans = paramiko.Transport(('172.31.3.231',22))
trans.connect(username=self.username,password=self.password)
except Exception as e:
print (e)
sftp = paramiko.SFTPClient.from_transport(trans)
sftp.put(localpath,remotepath)
time.sleep(2)
trans.close()
elif Index == 2 :
try:
trans = paramiko.Transport(('172.31.3.233',22))
trans.connect(username=self.username,password=self.password)
except Exception as e:
print (e)
sftp = paramiko.SFTPClient.from_transport(trans)
sftp.put(localpath,remotepath)
time.sleep(2)
trans.close()
def onefile(self,Index,type):
'''
一个文件,根据传入的type判断平台文件还是应用文件,拼接完整的路径
:param type: 1 应用文件 2 平台文件
:param type: Index 1 连接231 2 连接233
:return:
'''
if type == 1:
remotepath = '/home/iorder_appsvr/iorder_appsvr/web/WEB-INF/classes' + str(self.path())
self.ui.log.setPlainText('文件路径为:'+ remotepath)
filename = self.filename()
self.localfile()
bak = filename + time.strftime("%y%m%d") + 'bak'
cmd = 'cd {0};mv {1} {2}'.format(remotepath,self.filename(),bak)
#备份并上传替换文件
print(cmd)
self.ui.log.setPlainText('备份替换文件')
# 根据Index 连接对应的环境
ssh = self.connect(Index)
stdin, stdout, stderr = ssh.exec_command(cmd,get_pty=True)
for line in stdout:
print (line.strip('\n'))
ssh.close()
remotefile = '/home/iorder_appsvr/iorder_appsvr/web/WEB-INF/classes' + str(self.path())+str(self.filename())
self.trans(Index,self.localfile(),remotefile)
self.ui.log.setPlainText('文件上传成功')
return cmd,filename,remotepath
elif type == 2:
remotepath = '/home/iorder_appsvr/iorder_appsvr/web' + str(self.path())
self.ui.log.setPlainText('文件路径为:'+ remotepath)
filename = self.filename()
bak = filename + time.strftime("%y%m%d") + 'bak'
cmd = 'cd {0} ;mv {1} {2}'.format(remotepath,self.filename(),bak)
print(cmd)
self.ui.log.setPlainText('备份替换文件')
ssh = self.connect(Index)
stdin, stdout, stderr = ssh.exec_command(cmd,get_pty=True)
for line in stdout:
print (line.strip('\n'))
ssh.close()
remotefile = '/home/iorder_appsvr/iorder_appsvr/web' + str(self.path())+str(self.filename())
self.trans(Index,self.localfile(),remotefile)
self.ui.log.setPlainText('文件上传成功')
return cmd,filename,remotepath
def zip(self,Index):
'''
web.zip包直接复制到opt后解压后复制
Index 用来判断231 还是 233
:return:
'''
cmd = 'cd /opt;rm -rf web;mkdir web;ls'
# cd 到opt下创建新的web目录
ssh = self.connect(Index)
stdin, stdout, stderr = ssh.exec_command(cmd,get_pty=True)
for line in stdout:
print (line.strip('\n'))
ssh.close()
#上传文件至opt/web 并解压
remotepath = '/opt/web/{}'.format(self.filename())
localpath = r'D:/file/{}'.format(self.filename())
print(remotepath,localpath)
self.trans(Index,localpath,remotepath)
self.ui.log.setPlainText('压缩包文件上传成功')
cmd1 = 'cd /opt/web;ls;unzip {};rm -rf {}'.format(self.filename(),self.filename())
ssh = self.connect(Index)
stdin, stdout, stderr = ssh.exec_command(cmd1,get_pty=True)
for line in stdout:
print (line.strip('\n'))
time.sleep(2)
#复制解压后的文件
cmd2 = '\cp -Rf /opt/web/* /home/iorder_appsvr/iorder_appsvr/'
stdin, stdout, stderr = ssh.exec_command(cmd2,get_pty=True)
for line in stdout:
print (line.strip('\n'))
ssh.close()
self.ui.log.setPlainText('复制zip替换文件成功')
开始部署
上面一切准备工作就绪后,点击开始部署就Ok了
开始部署.png部署部分的逻辑如下:
根据上面printtomcat这个方法返回的Index,判断是对哪台服务器进行部署
根据filenumber这个方法,判断是单个文件还是web.zip,并对应的使用onefile这个方法还是zip这个方法
根据needrestart这个方法,判断是否需要重启
def start(self):
'''
开始部署 1 部署231 2 部署233
:return:
'''
if self.printtomcat() == 1:
print('部署环境为:'+ self.ui.environment.currentText())
# 部署单个文件
if self.filenumber() == 1:
self.onefile(1,self.filetype())
#部署web.zip
elif self.filenumber() == 2:
#连接231 部署web.zip
self.zip(1)
# print('222')
#判断是否需要重启
if self.needrestart() == 1:
ssh = self.connect(1)
stdin, stdout, stderr = ssh.exec_command('service tomcat_iorder_appsvr restart',get_pty=False)
for line in stdout:
print (line.strip('\n'))
time.sleep(5)
ssh.close()
elif self.needrestart() == 2:
time.sleep(2)
elif self.printtomcat() == 2:
print('部署环境为:'+ self.ui.environment.currentText())
# 部署单个文件
if self.filenumber() == 1:
self.onefile(2,self.filetype())
# 部署web.zip
elif self.filenumber()== 2:
#连接231 部署web.zip
# print('222 222')
self.zip(2)
# 判断是否需要重启
if self.needrestart() == 1:
ssh = self.connect(2)
stdin, stdout, stderr = ssh.exec_command('service tomcat_iorder_appsvr restart',get_pty=False)
for line in stdout:
print (line.strip('\n'))
time.sleep(2)
ssh.close()
self.ui.log.setPlainText('部署完成,服务器重启中,请稍候')
elif self.needrestart() == 2:
time.sleep(2)
self.ui.log.setPlainText('部署完成')
上面就是后台逻辑实现部分。
既然这是一个窗口工具,那么如何在其他电脑上运行呢,只需要一步;
打包成exe文件
打包生成EXE文件只需要两个步骤
1.安装PyInstaller
pip install PyInstaller
安装成功后,可以在python/Scripts下找到
PyInstaller.png2.打包
pyinstaller [opts] xxxxx.py
可选参数有
- -F,-onefile 打包成一个EXE文件
- -D,-onedir,创建一个目录,包含EXE文件,但会依赖很多文件
- -c,-console,-nowindowed,使用控制台,五窗口
- -w,-windowed,noconsole,使用窗口,无控制台
打开命令行,进入到需要打包的.py文件目录,运行下面的命令:
pyinstaller -F -w xxxxx.py
完成后会在同目录的dist文件夹中生成一个同名的.exe文件,双击这个exe文件,运行效果和直接使用编辑器运行脚本效果一样
目录.png exe.png在其他电脑上使用时,直接将dist目录拷贝过去即可,我这边加了platforms是因为在其他电脑运行报错,paltforms文件夹来自于
C:\Python34\Lib\site-packages\PyQt5\plugins\platforms
网友评论