对公司部署用的脚本不满已经很久了,加上最近比较有时间,所以重写了公司服务端部署的脚本。之前的脚本所做的仅仅是从 git
上pull下代码,然后编译打包部署到服务器。这里有一个问题就是:我们的静态文件是放到 cdn
上的,公司之前一贯的做法是在本地修改的时候,引用本地的静态文件,在修改完毕后,手动再改回来,然后再静态文件后加上一个版本号,然后手动将改动的文件放到 cdn
上。这样经常遇到的问题是,在本地改了 js
或者 css
,刷新页面发现不生效,整个人都蒙了,又或者是修改完后忘了将引用本地的静态文件改为引用 cdn
上的文件,导致浪费不必要的带宽。其实这些都还是小问题,一个隐藏的问题是,如果部署多台机器的时候,部分后部署的机器会使用新的静态文件,引起页面错误。
所以在此基础上,修改了部署用的脚本,此脚本满足一下功能:
- 自动将静态文件压缩,加上版本号,上传到
cdn
上 - 将引用本地的静态文件自动替换为cdn上的文件
- 记录每次替换的文件和版本号
这里对以上功能做下解释,
1、静态文件加上版本号再上传到 cdn
而不是在静态文件后加上一个请求参数,这一点上面也有提到,加上请求参数虽然也能使客户端刷新静态文件缓存,但是 cdn
上的文件是被替换掉的,如果有多台机器的情况下,先部署的机器为了能正常运行,需要将 cdn
的文件替换为新版本的文件,此时,旧版本的机器尚未部署,却引用 cdn
上新版本的文件,这样将会导致页面错误。而在静态文件上加上版本号却不会导致此问题,因为 cdn
上会保留新旧两个版本的文件。
2、不必解释。
3、因为每次部署所修改的文件不一样,假设上次修改了 a.js
,在 a.html
在部署的时候将文件中引用修改为 cdn
上的加上版本号的 a_2.js
文件,但是本次部署的时候并未修改此文件,所以在本次部署的时候无从知道 a.html
引用的 a.js
具体版本号,此时为了保证 a.html
引用正确的文件,只能重新上传一个a_3.js
到 cdn
让 a.html
引用此文件,这样的问题是所有的静态文件都要加上新的版本号,而客户端需要刷新所有的静态文件,用户体验差。
下面是修改后的脚本
#!/usr/bin/env python
import upyun
import os
import re
import copy
CODE_HOME = 'code_home'
PROJECT_NAME = 'project'
DIR_NAME = 'project'
TOMCAT_HOME = 'tomcao_home'
def main():
# pull 代码,并把改动信息保存到数组中
str_arr = pull_code()
(new_file_list, hash) = get_static_file(str_arr)
ope_replace(new_file_list, hash)
deploy()
def pull_code():
os.chdir(CODE_HOME + '/' + DIR_NAME)
os.system('git clean -fd')
os.system('git checkout .')
str_arr = os.popen('git pull').read().split('\n')
return str_arr
def get_static_file(str_arr):
# 我们用的是又拍云
up = upyun.UpYun("", username="", password="")
new_file_list = []
hash = ''
# compress static file
for str in str_arr:
if 'Already' in str: # 已经是最新的代码
break
if 'Updating' in str: # 这里我把commit id 拿下来当做版本号
matchObj = re.match(r'Updating.*\.\.(\w+)', str, re.M | re.I)
hash = matchObj.group(1)
print(hash)
elif '|' in str and 'public' in str and ('js' in str or 'css' in str): # js 和 css
str = str.split("|")[0].strip()
# 可能有一些文件被删除了,所以判断下文件是否存在
if os.path.isfile(str):
compress_str = str.replace('.', '_' + hash + '.')
os.system('java -jar ~/yuicompressor.jar ' + str + ' -o ' + str) # 用yuicompressor压缩文件
os.system('cp ' + str + ' ' + compress_str)
up.put('/' + compress_str, open(compress_str, 'rb')) # 上传
print(compress_str)
# 这里因为我们的静态文件都在public目录下,这个目录已经被映射为静态文件,
# 所以我们引用的时候并不需要带上public,而且用require.js,并不需要后缀
# 所以在具体引用的时候可能是'/js/common/a',具体文件为public/js/common/a.js
replace_base_str = str.split('.')[0].strip().replace('public', '')
new_file_list.append(replace_base_str)
return (new_file_list, hash) # 返回本次修改的静态文件数组和版本号
def ope_replace(new_file_list, new_hash):
# 在 CODE_HOME 目录,用 static_version.db 记录每次修改的文件和版本号,以,号隔开
os.chdir(CODE_HOME)
try:
fo = open("static_version.db", "r+")
except IOError:
fo = open("static_version.db", "w+")
unhash_file_list = copy.deepcopy(new_file_list)
while True:
line = fo.readline().strip()
if len(line) == 0:
break
print line
_arr = line.split(',')
file_name = _arr[0]
file_hash = _arr[1]
for new_file in new_file_list:
if file_name == new_file:
if new_file in unhash_file_list:
unhash_file_list.remove(new_file)
seek_len = len(file_hash) + 1
fo.seek(-seek_len, 1)
fo.write(new_hash)
fo.flush()
fo.seek(1, 1)
file_hash = new_hash
break
# replace file_name, file_hash
replace_static(file_name, file_hash)
if len(unhash_file_list) > 0:
for new_file in unhash_file_list:
fo.write(new_file + ',' + new_hash + '\n')
fo.flush()
# replace new_file, new_hash
replace_static(new_file, new_hash)
fo.close()
# 将引用本地文件改为引用 `cdn` 上的文件
def replace_static(str, hash):
os.chdir(CODE_HOME + '/' + DIR_NAME)
cdn_url = 'http://xxx.b0.upaiyun.com' # cdn 上文件前缀
# 替换用sed替换,具体请搜索sed语法
old_str = str.replace('/', '\/')
new_str = (cdn_url + '/public' + str + '_' + hash).replace('/', '\/')
os.system('sed -i "s/' + old_str + '/' + new_str + '/g" `grep [\\"\\\']' + old_str + ' -rl ./app/views`')
def deploy():
# 编译,部署
if __name__ == '__main__':
main()
python就没用过几次,依靠文档,折腾一下午。总算能用,有这个部署的脚本,开发起来也方便多了。