在团队中,开发流程相关的调整一定要相应的自动化工具配合。如果没有足够低的使用成本,这种调整将会是无意义的,因为根本就不会有人去使用。上一篇,我们提到 如何利用 CDN 进一步的前后端分离 , 这一篇主要讲,如何将这个流程结合到 CI 中。后端的配置,之前的 博客 中已经提及很多。后端 CI 主要是做代码检查,推送代码到 SVN 仓库,CI 部分本篇不会涉及。
1. 版本约定
前端版本由 package.json 文件决定。package.json 文件结构如下:
1
2
3
4
5
6
7
8
| {
"name": "my-project-webpack",
"version": "1.1.3",
"description": "",
"main": "index.js",
"dependencies": {
}
}
|
前端打包输出的文件在当前目录的 static 目录下:
1
2
3
4
5
6
| # 本地版本
./static/dev/
# 测试环境版本
./static/test/
# 正式环境版本
./static/dist/
|
前端静态链接形式约定:
1
| https://cdn.domain.com/1.1.3/static/dev/js/app.js
|
版本号直接放在访问的 URL 中,这样可以方便做多版本管理。同时不需要每次发布之后需要强制刷新 CDN ,以更新缓存。
前端的版本由前端控制,正式发布的版本应该是偶数版本。发布之后,需要将版本号最后一位加一。同时,每次发布版本,需要在 GitLab 仓库的 wiki 中记录变更的内容。
2. 前端改造
由于对页面进行了 JS 拆分优化,页面默认引用的是本地的 JS。通过修改 publicPath 属性,可以将引用的路径指向 CDN。
1
2
3
4
5
6
7
8
9
10
11
| var package = require("./package.json");
var version = package.version;
baseConfig.devtool = '(none)'
baseConfig.output = {
path: path.resolve(__dirname, './static/test/'),
publicPath: 'https://cdn.domain.com/'+version+'/static/test/',
filename: 'js/[name].js',
chunkFilename: '[name].js'
};
|
3. 后端改造
后端使用的是 Django。后端改造的目的有两个:
- 适配不同环境,有三个环境:本地、测试、正式。
- 前端版本控制。
适配不同环境主要是通过目录结构。前端版本控制需要从数据库中读取一条配置,前端发布时,修改配置即可。
settings.py,适配不同环境
1
2
3
4
5
6
7
| APP_CDN_URL = 'https://cdn.domain.com/'
if RUN_MODE == 'DEVELOP':
BUNDLE_NAME = '/static/dev/'
elif RUN_MODE == 'TEST':
BUNDLE_NAME = '/static/test/'
elif RUN_MODE == 'PRODUCT':
BUNDLE_NAME = '/static/dist/'
|
views.py,获取前端版本配置
1
2
3
4
| def index(request,question_id):
V = Config().get_content('V') or '1.1.3'
REMOTE_BUNDLE_URL = '%s%s%s'%(settings.APP_STATIC_URL, V, settings.BUNDLE_NAME)
return render(request, 'index.html', {'BUNDLE_URL': REMOTE_BUNDLE_URL,})
|
index.html,引用静态版本文件
1
2
3
4
| ...
<script type="text/javascript" src="{{BUNDLE_URL}}js/vendors.js"></script>
<script type="text/javascript" src="{{BUNDLE_URL}}js/app.js"></script>
...
|
4. 前端 GitLab 仓库
前端仓库新增了三个文件:
- .gitlab-ci.yml,CI 配置
- get_version.sh,获取前端版本脚本
- upload.py,上传到腾讯云 COS 脚本
下面是,前端代码仓库的 .gitlab-ci.yml 文件配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| before_script:
- source /data/runner/web/bin/activate
- which node && node --version
- which npm && npm --version
- LANG="zh_CN.utf8"
- export LC_ALL=zh_CN.UTF-8
stages:
- build
build-webpack:
stage: build
cache:
untracked: true
paths:
- ./node_modules
script:
- echo "start build test"
- rm -rf ./static/*
- npm install
- if [[ $(git log -1 --pretty=%B) = *"["*"skipbuild"*"]"* && $CI_COMMIT_REF_NAME = 'master' ]]; then echo "skip build"; else npm run build; fi;
- source get_version.sh
- echo $PACKAGE_VERSION
- touch ./static/$PACKAGE_VERSION
- if [[ $(git log -1 --pretty=%B) = *"["*"deploy"*"]"* && $CI_COMMIT_REF_NAME = 'master' ]]; then python upload.py $DEPLOY_CMD ./static $PACKAGE_VERSION; else echo "not deploy"; fi;
artifacts:
name: "static"
paths:
- ./static
only:
- master
|
get_version.sh,获取 package.json 文件中的前端版本号
1
2
| PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]')
export PACKAGE_VERSION
|
DEPLOY_CMD 是在 GitLab Settings Pipelines 页面新增的环境变量,值为:
1
| AKIXXXXXXXN 93nxxxxxxxxXHZE9 ap-shanghai app-120000000
|
前端文件上传腾讯云 COS 代码脚本,本地 ./static/ 目录,上传到云上 /:version/static/ 目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| #!/usr/bin/python
# -*- coding: utf-8 -*-
############################################
# 使用 cos-python-sdk-v5 上传文件夹到腾讯云
# 安装方式
# pip install cos-python-sdk-v5
# 使用方式
# python upload.py AKIXXXXXXXN 93nxxxxxxxxXHZE9 ap-shanghai app-120000000 ./static 1.0.0
############################################
import os
import sys
from qcloud_cos import CosConfig, CosS3Client
def main(argv):
# 校验参数
if len(argv) == 7 :
secret_id = sys.argv[1] # 用户的 secretId
secret_key = sys.argv[2] # 用户的 secretKey
region = sys.argv[3] # 用户的 Region( 'ap-beijing-1')
bucket = sys.argv[4] # 用户的 Bucket
filePath = sys.argv[5] # 上传文件夹路径
version = sys.argv[6] # 上传文件 Version,可以理解为 Prefix
else:
print 'argv error'
return
# 0,获取本地目录文件列表
# 获取文件列表
file_list = []
print 'upload dir: %s' % filePath
for root, dirs, files in os.walk("./static", topdown=False):
file_list.extend([os.path.join(root, name).replace('\\', '/') for name in files])
print 'ready to upload num of files %s,list: %s' % (len(file_list), file_list)
#1,用户配置
token = None # 使用临时密钥需要传入 Token,默认为空,可不填
scheme = 'http' # 指定使用 http/https 协议来访问 COS,默认为 https,可不填
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Scheme=scheme, Timeout=300)
# 2. 获取客户端对象
client = CosS3Client(config)
print 'Get client Success'
# 3. 开始上传
result = []
for file in file_list:
response = client.put_object_from_local_file(
Bucket=bucket,
LocalFilePath=file,
Key='/%s%s' % (version, file[1:]),
)
result.append((file, response['ETag']))
print 'upload result: %s, Total: %s, Success:%s' % (result, len(file_list), len(result))
if __name__ == '__main__':
main(sys.argv)
|
5. 最终效果
腾讯云 COS
访问应用首页
1
2
| <script type="text/javascript" src="https://cdn.domain.com/1.1.3/static/test/js/vendors.js"></script>
<script type="text/javascript" src="https://cdn.domain.com/1.1.3/static/test/js/app.js"></script>
|