前言
人生苦短,我用python~
作为一名专职前端开发的我,为了帮忙解决目前工作中的一些繁琐的工作(次要是解决 excel 数据),解放程序员双手,前阵子就刚刚入了 python 的坑,毕竟也算是门工具语言,都曾经退出少儿编程了,哈哈哈!
背景
实际是测验学习成绩的唯一标准!

在我学习过程中,始终推敲着如何将学习的实践与我所把握的常识联合起来,来解决或者解决理论问题,于是就有了 前端自动化打包部署 的念头。

尽快近几年,市面上对于自动化部署的工具层出不穷,比方当下比拟风行的Jenkins,尽管如此,我还是想本人试一试~

环境配置
初学乍道,切不可眼高手低,先给本人定个小指标,先实现一个最简略版本。
工欲善其事,必先利其器,开发环境的配置是开发的第一步。

对于 python 的装置配置我就不赘述了。

为了不便测试,我本地利用 VM 虚拟机装置了 centos 零碎,装置并配置 nginx 充当了服务器。

难点剖析

要想实现打包,外围须要思考上面2个问题:

  • 在 python 脚本中如何去执行前端的打包命令npm run build(这里以vue我的项目作为测试)
  • 在 python 脚本中如何连贯服务器将打包好的问题上传到服务器的指定目录中去

实践求证
通过查阅材料得悉,python中的 os 模块提供了十分丰盛的办法用来解决文件和目录,其中 os模块中的system()函数能够不便地运行其余程序或者脚本,其语法如下:
os.system(command)
command 要执行的命令,相当于在Windows的cmd窗口中输出的命令。如果要向程序或者脚本传递参数,能够应用空格分隔程序及多个参数,该办法返回后果如果为 0,则表示命令执行胜利,其它值则示意谬误。

这样就解决了第一个问题。

对于服务器连贯这一块,能够应用python的一个第三方模块 paramiko,它实现了SSHv2协定,容许咱们间接应用SSH协定对近程服务器执行操作,
这样下面两个难点就解决了,咱们就能够动工了。
小试牛刀

首先定义一个类 SSHConnect 后续的办法咱们都会在这个类外面欠缺
class SSHConnect:

    # 定义一个公有变量,用来保留ssh连贯通道,初始化为None    __transport = None

初始构造函数

咱们须要在构造函数中定义咱们须要的参数,初始化咱们的连贯

# 初始化构造函数(主机,用户名,明码,端口,默认22)def __init__(self, hostname, username, password, port=22):    self.hostname = hostname    self.port = port    self.username = username    self.password = password    # 创立 ssh 连贯通道    self.connect()

建设 ssh 连贯通道

咱们在构造函数中最初调用了一个 connect 办法建设 ssh 连贯通道,当初咱们来具体的实现它

# 建设ssh连贯通道,并绑定在 __transport 上def connect(self):    try:        # 设置SSH连贯的近程主机地址和端口        self.__transport = paramiko.Transport((self.hostname, self.port))        # 通过用户名和明码连贯SSH服务端        self.__transport.connect(username=self.username, password=self.password)    except Exception as e:        # 连贯出错        print(e)

执行打包
当初咱们须要创立一个打包办法,执行 npm run build 命令,利用咱们 os.system 办法,入参是 work_path 是打包我的项目所在的目录

前端打包(入参work_path为我的项目目录)

def build(self, work_path):

    # 开始打包    print('########### run build ############')    # 打包命令    cmd = 'npm run build'    # 切换到须要我的项目目录    os.chdir(work_path)    # 以后我的项目目录下执行打包命令    if os.system(cmd) == 0:        # 打包实现        print('########### build complete ############')

只有一点要留神,就是要通过 os.chdir(work_path) 办法切换到我的项目的所在目录,打包以后的我的项目。
文件上传
打包完结后,咱们须要将打包好的 dist 文件夹下的文件上传到服务器,因而,咱们须要创立一个文件上传办法,咱们通过 paramiko.SFTPClient 办法创立 sftp 来实现

该办法入参须要两个参数,一个是本地我的项目打包后的dist门路 local_path,另一个是要上传到服务器的目标目录 target_path

# 文件上传def upload(self, local_path, target_path):    # 判断门路问题    if not os.path.exists(local_path):        return print('local path is not exist')    print('文件上传中...')    # 实例化一个 sftp 对象,指定连贯的通道    sftp = paramiko.SFTPClient.from_transport(self.__transport)    # 打包后的文件门路    local_path = os.path.join(local_path, 'dist')    # 本地门路转换,将windows下的 \ 转成 /    local_path = '/'.join(local_path.split('\\'))    # 递归上传文件    self.upload_file(sftp, local_path, target_path)    print('文件上传实现...')    # 敞开连贯    self.close()

为了不便操作,须要将 windows 中的门路分隔符\转成 linux 下的分隔符/
此外,该办法中调用了另外两个办法,别离是 upload_file 和 close,close 办法的定义很简略,间接调用 __transport.close() 办法即可

# 敞开连贯def close(self):    self.__transport.close()

思考到咱们的 static 不是文件,而是一个文件夹,因而须要递归遍历,并将其拷贝到服务器上,所以咱们定义了upload_file 办法,专门负责这个事件。
执行linux命令
下面咱们也提到了,须要递归遍历static并上传到服务器,那么上传到服务器的目录构造必定须要跟原来的 static 保持一致,因而对服务器的操作必定是不可少的,须要执行linux命令,咱们须要一个 exec 办法来实现这个性能,入参就是 linux 命令

执行linux命令

def exec(self, command):

        # 创立 ssh 客户端    ssh = paramiko.SSHClient()    # 指定连贯的通道    ssh._transport = self.__transport        # 调用 exec_command 办法执行命令    stdin, stdout, stderr = ssh.exec_command(command)        # 获取命令后果,返回是二进制,须要编码一下    res = stdout.read().decode('utf-8')    # 获取错误信息    error = stderr.read().decode('utf-8')        # 如果没出错    if error.strip():        # 返回错误信息        return error    else:        # 返回后果        return res    

当初你能够连贯服务器测试一下,这个办法的正确性

    ssh = SSHConnect(hostname='x.x.x.x', username='root', password='xxx')    print(ssh.exec(r'df -h'))

咱们连贯到服务器并尝试调用 linux 中的 df -h 命令查看咱们零碎文件系统的磁盘应用状况,不出意外的话,会看到控制台返回的信息
ps:命令 df -h 后面的 r 是为了让python解释器不本义

递归上传文件
筹备工作做好当前,咱们就能够来是实现咱们的递归上传的办法 upload_file 了,次要是通过后面创立的 sftp 对象的 put 办法,将本地文件上传到对应的服务器中

# 递归上传文件def upload_file(self, sftp, local_path, target_path):    # 判断以后门路是否是文件夹    if not os.path.isdir(local_path):        # 如果是文件,获取文件名        file_name = os.path.basename(local_path)        # 查看服务器文件夹是否存在        self.check_remote_dir(sftp, target_path)        # 服务器创立文件        target_file_path = os.path.join(target_path, file_name).replace('\\', '/')        # 上传到服务器        sftp.put(local_path, target_file_path)    else:        # 查看以后文件夹下的子文件        file_list = os.listdir(local_path)        # 遍历子文件        for p in file_list:            # 拼接以后文件门路            current_local_path = os.path.join(local_path, p).replace('\\', '/')            # 拼接服务器文件门路            current_target_path = os.path.join(target_path, p).replace('\\', '/')            # 如果曾经是文件,服务器就不须要创立文件夹了            if os.path.isfile(current_local_path):                # 提取以后文件所在的文件夹                current_target_path = os.path.split(current_target_path)[0]            # 递归判断            self.upload_file(sftp, current_local_path, current_target_path)

上述办法中增加了一个 check_remote_dir 办法,用来检测服务器端是否曾经存在了文件夹,如果服务端没有咱们就创立一个,定义如下:

# 创立服务器文件夹def check_remote_dir(self, sftp, target_path):    try:        # 判断文件夹是否存在        sftp.stat(target_path)    except IOError:        # 创立文件夹        self.exec(r'mkdir -p %s ' % target_path)

十分奇妙的利用了 sftp.stat 办法查看文件信息来辨别的。
合并流程,主动公布
当初根本的办法咱们都曾经实现了,接下来咱们须要将它们合并到 auto_deploy 办法中,真正实现主动公布。

# 自动化打包部署def auto_deploy(self, local_path, target_path):    # 打包构建    self.build(local_path)    # 文件上传    self.upload(local_path, target_path)

ok~ 咱们来调用 auto_deploy 办法测试一下:

if __name__ == '__main__':    # 我的项目目录    project_path = r'D:\learn\vue-demo'    # 服务器目录    remote_path = r'/www/project'        # 实例化    ssh = SSHConnect(hostname='x.x.x.x', username='root', password='xxx')    # 主动打包部署    ssh.auto_deploy(project_path, remote_path)

如果一切顺利,就能够看到控制台输入胜利!!

再看看服务器文件是否已上传胜利。

并尝试拜访我的主页!

十分完满!
Congratulations! 你曾经 get 了这项技能,点个赞吧!
服务器清空
到这里的话,咱们的性能已根本实现了,只是还有一个小小的问题遗留,如果咱们一直的迭代优化,那么如果咱们不革除服务器的目录的话,会沉积越来越多的旧的文件,占用服务器的空间,因而咱们须要在打包上传前清空一下。

无妨定义一个clear_remote_dir办法来实现这个性能

# 清空文件夹def clear_remote_dir(self, target_path):    if target_path[-1] == '/':        cmd = f'rm -rf {target_path}*'    else:        cmd = f'rm -rf {target_path}/*'    self.exec(cmd)

之后把它增加到 auto_delpoy 办法中 self.upload 之前就好了~
结语
勉强算是实现了一个小工具吧,这个过程对我来说也算是对 python 的一次小小的实际吧,也算是颇有播种了。
对于上述的代码,齐全还能够通过 sys.argv 通过命令行参数解析的形式来实现真正的 cmd 调用,笔者在这里就不赘述了,感兴趣的小伙伴能够本人去实际一下。
能够看到python 在语法上的简洁和优雅,这一点也是让我感觉还是挺舒服的,对我集体来说,可能前面更多是作为一门工具语言来应用,最大水平的去解决理论问题。
人生苦短,我用 python;