基于Python實現(xiàn)前端自動化打包部署

前言

人生苦短嫌套,我用python~

作為一名專職前端開發(fā)的我逆屡,為了幫助解決目前工作中的一些繁瑣的工作(主要是處理 excel 數(shù)據(jù)),解放程序員雙手踱讨,前陣子就剛剛?cè)肓?python 的坑魏蔗,畢竟也算是門工具語言,都已經(jīng)加入少兒編程了痹筛,哈哈哈莺治!

背景

實踐是檢驗學習成果的唯一標準叠纷!

在我學習過程中尽爆,一直琢磨著如何將學習的理論與我所掌握的知識結(jié)合起來,來解決或者處理實際問題桥胞,于是就有了 前端自動化打包部署 的念頭滋早。

盡快近幾年榄审,市面上關(guān)于自動化部署的工具層出不窮,比如當下比較流行的Jenkins馆衔,盡管如此瘟判,我還是想自己試一試~

環(huán)境配置

初學乍道怨绣,切不可眼高手低,先給自己定個小目標拷获,先實現(xiàn)一個最簡單版本篮撑。

工欲善其事,必先利其器匆瓜,開發(fā)環(huán)境的配置是開發(fā)的第一步赢笨。

關(guān)于 python 的安裝配置我就不贅述了。

為了方便測試驮吱,我本地利用 VM 虛擬機安裝了 centos 系統(tǒng)茧妒,安裝并配置 nginx 充當了服務(wù)器。

難點分析

要想實現(xiàn)打包左冬,核心需要考慮下面2個問題:

  • python 腳本中如何去執(zhí)行前端的打包命令npm run build(這里以vue項目作為測試)
  • python 腳本中如何連接服務(wù)器將打包好的問題上傳到服務(wù)器的指定目錄中去

理論求證

通過查閱資料得知桐筏,python中的 os 模塊提供了非常豐富的方法用來處理文件和目錄,其中 os模塊中的system()函數(shù)可以方便地運行其他程序或者腳本拇砰,其語法如下:

os.system(command)

command 要執(zhí)行的命令梅忌,相當于在Windowscmd窗口中輸入的命令。如果要向程序或者腳本傳遞參數(shù)除破,可以使用空格分隔程序及多個參數(shù)牧氮,該方法返回結(jié)果如果為 0,則表示命令執(zhí)行成功,其它值則表示錯誤瑰枫。

這樣就解決了第一個問題踱葛。

關(guān)于服務(wù)器連接這一塊,可以使用python的一個第三方模塊 paramiko光坝,它實現(xiàn)了SSHv2協(xié)議尸诽,允許我們直接使用SSH協(xié)議對遠程服務(wù)器執(zhí)行操作,關(guān)于 paramiko 的更多知識和用法盯另,請戳這里

這樣上面兩個難點就解決了逊谋,我們就可以開工了。

小試牛刀

首先定義一個類 SSHConnect 后續(xù)的方法我們都會在這個類里面完善

class SSHConnect:
    # 定義一個私有變量土铺,用來保存ssh連接通道,初始化為None
    __transport = None
復(fù)制代碼

初始構(gòu)造函數(shù)

我們需要在構(gòu)造函數(shù)中定義我們需要的參數(shù)板鬓,初始化我們的連接

# 初始化構(gòu)造函數(shù)(主機悲敷,用戶名,密碼俭令,端口后德,默認22)
def __init__(self, hostname, username, password, port=22):
    self.hostname = hostname
    self.port = port
    self.username = username
    self.password = password
    # 創(chuàng)建 ssh 連接通道
    self.connect()
復(fù)制代碼

建立 ssh 連接通道

我們在構(gòu)造函數(shù)中最后調(diào)用了一個 connect 方法建立 ssh 連接通道,現(xiàn)在我們來具體的實現(xiàn)它

# 建立ssh連接通道抄腔,并綁定在 __transport 上
def connect(self):
    try:
        # 設(shè)置SSH連接的遠程主機地址和端口
        self.__transport = paramiko.Transport((self.hostname, self.port))
        # 通過用戶名和密碼連接SSH服務(wù)端
        self.__transport.connect(username=self.username, password=self.password)
    except Exception as e:
        # 連接出錯
        print(e)
復(fù)制代碼

執(zhí)行打包

現(xiàn)在我們需要創(chuàng)建一個打包方法瓢湃,執(zhí)行 npm run build 命令理张,利用我們 os.system 方法,入?yún)⑹?work_path 是打包項目所在的目錄

# 前端打包(入?yún)ork_path為項目目錄)
def build(self, work_path):
    # 開始打包
    print('########### run build ############')
    # 打包命令
    cmd = 'npm run build'
    # 切換到需要項目目錄
    os.chdir(work_path)
    # 當前項目目錄下執(zhí)行打包命令
    if os.system(cmd) == 0:
        # 打包完成
        print('########### build complete ############')
復(fù)制代碼

只有一點要注意绵患,就是要通過 os.chdir(work_path) 方法切換到項目的所在目錄雾叭,打包當前的項目。

文件上傳

打包結(jié)束后落蝙,我們需要將打包好的 dist 文件夾下的文件上傳到服務(wù)器织狐,因此,我們需要創(chuàng)建一個文件上傳方法筏勒,我們通過 paramiko.SFTPClient 方法創(chuàng)建 sftp 來完成

該方法入?yún)⑿枰獌蓚€參數(shù)移迫,一個是本地項目打包后的dist路徑 local_path,另一個是要上傳到服務(wù)器的目標目錄 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')
    # 本地路徑轉(zhuǎn)換管行,將windows下的 \ 轉(zhuǎn)成 /
    local_path = '/'.join(local_path.split('\\'))
    # 遞歸上傳文件
    self.upload_file(sftp, local_path, target_path)

    print('文件上傳完成...')
    # 關(guān)閉連接
    self.close()
復(fù)制代碼

為了方便操作厨埋,需要將 windows 中的路徑分隔符\轉(zhuǎn)成 linux 下的分隔符/

此外,該方法中調(diào)用了另外兩個方法捐顷,分別是 upload_fileclose荡陷,close 方法的定義很簡單,直接調(diào)用 __transport.close() 方法即可

# 關(guān)閉連接
def close(self):
    self.__transport.close()
復(fù)制代碼

考慮到我們的 static 不是文件套菜,而是一個文件夾亲善,因此需要遞歸遍歷,并將其拷貝到服務(wù)器上逗柴,所以我們定義了upload_file 方法蛹头,專門負責這個事情。

執(zhí)行linux命令

上面我們也提到了戏溺,需要遞歸遍歷static并上傳到服務(wù)器渣蜗,那么上傳到服務(wù)器的目錄結(jié)構(gòu)肯定需要跟原來的 static 保持一致,因此對服務(wù)器的操作肯定是不可少的旷祸,需要執(zhí)行linux命令耕拷,我們需要一個 exec 方法來實現(xiàn)這個功能,入?yún)⒕褪?linux 命令

# 執(zhí)行l(wèi)inux命令
def exec(self, command):

    # 創(chuàng)建 ssh 客戶端
    ssh = paramiko.SSHClient()
    # 指定連接的通道
    ssh._transport = self.__transport

    # 調(diào)用 exec_command 方法執(zhí)行命令
    stdin, stdout, stderr = ssh.exec_command(command)

    # 獲取命令結(jié)果托享,返回是二進制骚烧,需要編碼一下
    res = stdout.read().decode('utf-8')
    # 獲取錯誤信息
    error = stderr.read().decode('utf-8')

    # 如果沒出錯
    if error.strip():
        # 返回錯誤信息
        return error
    else:
        # 返回結(jié)果
        return res

復(fù)制代碼

現(xiàn)在你可以連接服務(wù)器測試一下,這個方法的正確性

    ssh = SSHConnect(hostname='x.x.x.x', username='root', password='xxx')
    print(ssh.exec(r'df -h'))
復(fù)制代碼

我們連接到服務(wù)器并嘗試調(diào)用 linux 中的 df -h 命令查看我們系統(tǒng)文件系統(tǒng)的磁盤使用情況闰围,不出意外的話赃绊,會看到控制臺返回的信息

ps:命令 df -h 前面的 r 是為了讓python解釋器不轉(zhuǎn)義

遞歸上傳文件

準備工作做好以后,我們就可以來是實現(xiàn)我們的遞歸上傳的方法 upload_file 了羡榴,主要是通過前面創(chuàng)建的 sftp 對象的 put 方法碧查,將本地文件上傳到對應(yīng)的服務(wù)器中

# 遞歸上傳文件
def upload_file(self, sftp, local_path, target_path):
    # 判斷當前路徑是否是文件夾
    if not os.path.isdir(local_path):
        # 如果是文件,獲取文件名
        file_name = os.path.basename(local_path)
        # 檢查服務(wù)器文件夾是否存在
        self.check_remote_dir(sftp, target_path)
        # 服務(wù)器創(chuàng)建文件
        target_file_path = os.path.join(target_path, file_name).replace('\\', '/')
        # 上傳到服務(wù)器
        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('\\', '/')
            # 拼接服務(wù)器文件路徑
            current_target_path = os.path.join(target_path, p).replace('\\', '/')
            # 如果已經(jīng)是文件,服務(wù)器就不需要創(chuàng)建文件夾了
            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)
復(fù)制代碼

上述方法中添加了一個 check_remote_dir 方法忠售,用來檢測服務(wù)器端是否已經(jīng)存在了文件夾传惠,如果服務(wù)端沒有我們就創(chuàng)建一個,定義如下:

# 創(chuàng)建服務(wù)器文件夾
def check_remote_dir(self, sftp, target_path):
    try:
        # 判斷文件夾是否存在
        sftp.stat(target_path)
    except IOError:
        # 創(chuàng)建文件夾
        self.exec(r'mkdir -p %s ' % target_path)
復(fù)制代碼

非常巧妙的利用了 sftp.stat 方法查看文件信息來區(qū)分的稻扬。

合并流程卦方,自動發(fā)布

現(xiàn)在基本的方法我們都已經(jīng)實現(xiàn)了,接下來我們需要將它們合并到 auto_deploy 方法中腐螟,真正實現(xiàn)自動發(fā)布愿汰。

# 自動化打包部署
def auto_deploy(self, local_path, target_path):
    # 打包構(gòu)建
    self.build(local_path)
    # 文件上傳
    self.upload(local_path, target_path)
復(fù)制代碼

ok~ 我們來調(diào)用 auto_deploy 方法測試一下:

if __name__ == '__main__':
    # 項目目錄
    project_path = r'D:\learn\vue-demo'
    # 服務(wù)器目錄
    remote_path = r'/www/project'

    # 實例化
    ssh = SSHConnect(hostname='x.x.x.x', username='root', password='xxx')
    # 自動打包部署
    ssh.auto_deploy(project_path, remote_path)
復(fù)制代碼

如果一切順利,就可以看到控制臺輸出成功@种健衬廷!

再看看服務(wù)器文件是否已上傳成功。



并嘗試訪問我的主頁汽绢!


非常完美吗跋!

Congratulations! 你已經(jīng) get 了這項技能,點個贊吧宁昭!

服務(wù)器清空

到這里的話跌宛,我們的功能已基本完成了,只是還有一個小小的問題遺留积仗,如果我們不斷的迭代優(yōu)化疆拘,那么如果我們不清除服務(wù)器的目錄的話,會堆積越來越多的舊的文件寂曹,占用服務(wù)器的空間哎迄,因此我們需要在打包上傳前清空一下。


不妨定義一個clear_remote_dir方法來實現(xiàn)這個功能

# 清空文件夾
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)
復(fù)制代碼

之后把它添加到 auto_delpoy 方法中 self.upload 之前就好了~

結(jié)語

勉強算是完成了一個小工具吧隆圆,這個過程對我來說也算是對 python 的一次小小的實踐吧漱挚,也算是頗有收獲了。

對于上述的代碼渺氧,完全還可以通過 sys.argv 通過命令行參數(shù)解析的方式來實現(xiàn)真正的 cmd 調(diào)用旨涝,筆者在這里就不贅述了,感興趣的小伙伴可以自己去實踐一下侣背。

可以看到python 在語法上的簡潔和優(yōu)雅白华,這一點也是讓我感覺還是挺舒服的,對我個人來說贩耐,可能后面更多是作為一門工具語言來使用衬鱼,最大程度的去解決實際問題。

人生苦短憔杨,我用 python

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒜胖,一起剝皮案震驚了整個濱河市消别,隨后出現(xiàn)的幾起案子抛蚤,更是在濱河造成了極大的恐慌,老刑警劉巖寻狂,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岁经,死亡現(xiàn)場離奇詭異,居然都是意外死亡蛇券,警方通過查閱死者的電腦和手機缀壤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纠亚,“玉大人塘慕,你說我怎么就攤上這事〉侔” “怎么了图呢?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長骗随。 經(jīng)常有香客問我蛤织,道長,這世上最難降的妖魔是什么鸿染? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任指蚜,我火速辦了婚禮,結(jié)果婚禮上涨椒,老公的妹妹穿的比我還像新娘摊鸡。我一直安慰自己,他們只是感情好丢烘,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布柱宦。 她就那樣靜靜地躺著,像睡著了一般播瞳。 火紅的嫁衣襯著肌膚如雪掸刊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天赢乓,我揣著相機與錄音忧侧,去河邊找鬼。 笑死牌芋,一個胖子當著我的面吹牛蚓炬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播躺屁,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肯夏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起驯击,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤烁兰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后徊都,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沪斟,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年暇矫,在試婚紗的時候發(fā)現(xiàn)自己被綠了主之。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡李根,死狀恐怖槽奕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情朱巨,我是刑警寧澤史翘,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站冀续,受9級特大地震影響琼讽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洪唐,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一钻蹬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凭需,春花似錦问欠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枯怖,卻和暖如春注整,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背度硝。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工肿轨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蕊程。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓椒袍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親藻茂。 傳聞我的和親對象是個殘疾皇子驹暑,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容