人生苦短,我用python
~html
做爲一名專職前端開發的我,爲了幫助解決目前工做中的一些繁瑣的工做(主要是處理 excel
數據),解放程序員雙手,前陣子就剛剛入了 python
的坑,畢竟也算是門工具語言,都已經加入少兒編程了,哈哈哈!前端
實踐是檢驗學習成果的惟一標準!vue
在我學習過程當中,一直琢磨着如何將學習的理論與我所掌握的知識結合起來,來解決或者處理實際問題,因而就有了 前端自動化打包部署 的念頭。python
儘快近幾年,市面上關於自動化部署的工具層出不窮,好比當下比較流行的Jenkins
,儘管如此,我仍是想本身試一試~linux
初學乍道,切不可眼高手低,先給本身定個小目標,先實現一個最簡單版本。nginx
工欲善其事,必先利其器,開發環境的配置是開發的第一步。git
關於 python
的安裝配置我就不贅述了。程序員
爲了方便測試,我本地利用 VM
虛擬機安裝了 centos
系統,安裝並配置 nginx
充當了服務器。github
要想實現打包,核心須要考慮下面2個問題:npm
python
腳本中如何去執行前端的打包命令npm run build
(這裏以vue
項目做爲測試)python
腳本中如何鏈接服務器將打包好的問題上傳到服務器的指定目錄中去經過查閱資料得知,python
中的 os
模塊提供了很是豐富的方法用來處理文件和目錄,其中 os
模塊中的system()
函數能夠方便地運行其餘程序或者腳本,其語法以下:
os.system(command)
command
要執行的命令,至關於在Windows
的cmd
窗口中輸入的命令。若是要向程序或者腳本傳遞參數,可使用空格分隔程序及多個參數,該方法返回結果若是爲 0,則表示命令執行成功,其它值則表示錯誤。
這樣就解決了第一個問題。
關於服務器鏈接這一塊,可使用python
的一個第三方模塊 paramiko
,它實現了SSHv2
協議,容許咱們直接使用SSH
協議對遠程服務器執行操做,關於 paramiko
的更多知識和用法,請戳這裏
這樣上面兩個難點就解決了,咱們就能夠開工了。
首先定義一個類 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
;