场景:由于工作原因,开发打包后都要上传包到对应linux 服务器,并执行对应shell脚本,替换包内配置文件,启动服务。换包频率过于频繁,因此需要实现一种不用打开xshell、xftp的方法,直接将包放在本地文件,双击exe运行所有操作,以节省时间,想到使用python的paramiko、pyinstaller模块实现。
功能分析:
- 1、遍历本地嵌套文件夹获取文件
- 2、上传到远程linux服务器
- 3、上传文件进度条
- 4、程序运行日志按时间存放本地
- 5、打包成exe,双击就可以运行
代码实现:
autoChangePackage.py
import paramiko
import os
import time
import sys, math, select
import log
def get_all_files_in_local_dir(local_dir):
"""递归获取当前目录下所有文件目录"""
all_files = []
# 获取当前指定目录下的所有目录及文件,包含属性值
files = os.listdir(local_dir)
for x in files:
# local_dir目录中每一个文件或目录的完整路径
filename = os.path.join(local_dir, x)
# 如果是目录,则递归处理该目录
if os.path.isdir(filename):
all_files.extend(get_all_files_in_local_dir(filename))
else:
all_files.append(filename)
return all_files
def progressbar(cur, total):
percent = '{:.2%}'.format(cur / total)
sys.stdout.write('\r')
sys.stdout.write('[%-50s] %s' % ('=' * int(math.floor(cur * 50 / total)), percent))
sys.stdout.flush()
if cur == total:
sys.stdout.write('\n')
class Dossh():
def __init__(self, ip, port, uname, passwd):
self.ip = ip
self.port = port
self.uname = uname
self.passwd = passwd
self.sshclt = paramiko.SSHClient()
self.sshclt.load_system_host_keys()
self.sshclt.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.sshclt.connect(hostname=self.ip, port=self.port, username=self.uname, password=self.passwd,
allow_agent=False, look_for_keys=False)
self.t = paramiko.Transport((self.ip, self.port))
self.t.connect(username=self.uname, password=self.passwd)
self.sftp = paramiko.SFTPClient.from_transport(self.t)
def getssh(self):
return self.sshclt
def close_ssh(self):
self.sshclt.close()
self.sftp.close()
def uploadfile_path(self, local_path, remote_path):
"""
:param local_path:待上传文件夹路径
:param remote_path:远程路径
:return:
"""
# 待上传目录名
local_pathname = os.path.split(local_path)[-1]
# 上传远程后的目录名
real_remote_Path = remote_path + '/' + local_pathname
##判断是否存在,不存在则创建
try:
self.sftp.stat(remote_path)
except Exception as e:
self.sshclt.exec_command("mkdir -p %s" % remote_path)
self.sshclt.exec_command("mkdir -p %s" % real_remote_Path)
# 获取本地文件夹下所有文件路径
all_files = get_all_files_in_local_dir(local_path)
# 依次判断远程路径是否存在,不存在则创建,然后上传文件
for file_path in all_files:
# 统一win和linux 路径分隔符
file_path = file_path.replace("\\", "/")
# 用本地根文件夹名分隔本地文件路径,取得相对的文件路径
# 必须加变量1,仅切割一次,否则会将二级目录下与一级目录名相同的文件上传错误
off_path_name = file_path.split(local_pathname, 1)[-1]
# 取得本地存在的嵌套文件夹层级
abs_path = os.path.split(off_path_name)[0]
# 生产期望的远程文件夹路径
reward_remote_path = real_remote_Path + abs_path
# 判断期望的远程目录是否存在,不存在则创建
try:
self.sftp.stat(reward_remote_path)
except Exception as e:
self.sshclt.exec_command("mkdir -p %s" % reward_remote_path)
# 待上传的文件名
abs_file = os.path.split(file_path)[1]
# 上传后的远端路径,文件名不变
to_remote = reward_remote_path + '/' + abs_file
time.sleep(0.1)
# callback=progressbar,添加文件上传进度条
self.sftp.put(file_path, to_remote, callback=progressbar)
print(file_path, to_remote)
def execute_cmd(self, cmd):
current_cmd = "bash --login -c '%s'" % cmd
stdin, stdout, stderr = self.sshclt.exec_command(current_cmd, get_pty=True, bufsize=1024 * 1024 * 100)
res, err = stdout.read(), stderr.read()
result = res if res else err
return result.decode('utf-8')
def do_tail(self, cmd):
transport = self.sshclt.get_transport()
channel = transport.open_session()
channel.exec_command(cmd)
while 1:
try:
rl, _, _ = select.select([channel], [], [], 0.0)
if len(rl) > 0:
print("----------------do_tail服务开始读取日志-------------")
for line in self.linesplit(channel):
print(line)
except (KeyboardInterrupt, SystemExit):
print('do_tail服务日志读取异常...')
break
def linesplit(self, socket):
buffer_bytes = socket.recv(4048)
buffer_string = buffer_bytes.decode('utf-8', 'ignore')
done = False
while not done:
if "\n" in buffer_string:
(line, buffer_string) = buffer_string.split("\n", 1)
yield line + "\n"
else:
more = socket.recv(4048)
if not more:
done = True
else:
buffer_string = buffer_string + more.decode('utf-8', 'ignore')
if buffer_string:
yield buffer_string
if __name__ == "__main__":
# 自定义目录存放日志文件
log_path = './Logs/'
if not os.path.exists(log_path):
os.makedirs(log_path)
# 日志文件名按照程序运行时间设置
log_file_name = log_path + 'log-' + time.strftime("%Y%m%d-%H%M%S", time.localtime()) + '.log'
# 记录正常的 print 信息
sys.stdout = log.Logger(log_file_name)
# 记录 traceback 异常信息
sys.stderr = log.Logger(log_file_name)
print("==========1. 日志记录文件创建成功,见%s==========" % log_file_name)
print("==========2. 开始连接服务器==========")
# 服务器信息
ma_ip = '*.*.*.*'
ma_port = 22
ma_user = ''
ma_passwd = ''
sshclent = Dossh(ma_ip, int(ma_port), ma_user, ma_passwd)
print("==========3. 服务器连接成功==========")
print("==========4. 开始从本地上传文件到106服务器==========")
# 上传package包(jar+lib)
# 本地目录,建议使用相对路径,如:'./test'
# linux服务器目录,绝对路径,如:'/home/'
# 会本地test目录下所有文件上传到linux服务器的/home/test
# a、目录存在则仅覆盖文件
# b、目录不存在则先创建目录,再上传目录下的文件
sshclent.uploadfile_path("本地目录", 'linux服务器目录')
# 查看linux服务器上test目录下的文件
# cmd_ll = 'ls -l /home/test'
print("==========5. 文件上传成功==========")
print("==========6. 开始执行shell脚本==========")
cmd_shell = 'sh /home/test/test.sh'
sshclent.execute_cmd(cmd_shell)
time.sleep(0.1)
# 由于换包脚本会产生执行日志,每次需要通过查看日志运行状态,来判断配置文件是否替换,服务是否启动成功
# 但又因为execute_cmd()方法执行tail命令,控制台没有回显,因此使用do_tail()方法查看log
print("==========7. 开始打印脚本执行日志==========")
cmd_tailf = 'tail -F /home/tets/test.log'
sshclent.do_tail(cmd_tailf)
sshclent.close_ssh()
log.py
import sys
# 控制台输出记录到文件
class Logger(object):
def __init__(self, file_name="Default.log", stream=sys.stdout):
self.terminal = stream
self.log = open(file_name, "a", encoding='utf-8')
def write(self, message):
self.terminal.write(message)
self.log.write(message)
def flush(self):
pass
打包成exe文件
1、安装pyinstaller
模块
2、pycharm编辑器控制台输入pyinstaller -F autoChangePackage.py
3、打开资源管理器,进入对应工程的dist目录下,会看见生成了autoChangePackage.exe
4、双击运行如果提示缺失配置文件,复制python安装目录下所有dll文件到exe目录,就可以成功,但一般很少遇见
注:涉及到的问题解决方法百度都有
参考文献
1、python之paramiko文件夹远程上传
2、Python 将控制台输出日志文件