目录

    在团队中,开发流程相关的调整一定要相应的自动化工具配合。如果没有足够低的使用成本,这种调整将会是无意义的,因为根本就不会有人去使用。上一篇,我们提到 如何利用 CDN 进一步的前后端分离 , 这一篇主要讲,如何将这个流程结合到 CI 中。后端的配置,之前的 博客 中已经提及很多。后端 CI 主要是做代码检查,推送代码到 SVN 仓库,CI 部分本篇不会涉及。

    1. 版本约定

    前端版本由 package.json 文件决定。package.json 文件结构如下:

    {
      "name": "my-project-webpack",
      "version": "1.1.3",
      "description": "",
      "main": "index.js",
      "dependencies": {
      }
    }
    

    前端打包输出的文件在当前目录的 static 目录下:

    # 本地版本
    ./static/dev/
    # 测试环境版本
    ./static/test/
    # 正式环境版本
    ./static/dist/
    

    前端静态链接形式约定:

    https://cdn.domain.com/1.1.3/static/dev/js/app.js
    

    版本号直接放在访问的 URL 中,这样可以方便做多版本管理。同时不需要每次发布之后需要强制刷新 CDN ,以更新缓存。

    前端的版本由前端控制,正式发布的版本应该是偶数版本。发布之后,需要将版本号最后一位加一。同时,每次发布版本,需要在 GitLab 仓库的 wiki 中记录变更的内容。

    2. 前端改造

    由于对页面进行了 JS 拆分优化,页面默认引用的是本地的 JS。通过修改 publicPath 属性,可以将引用的路径指向 CDN。

    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,适配不同环境

    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,获取前端版本配置

    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,引用静态版本文件

    ...
    <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 文件配置

    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 文件中的前端版本号

    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 页面新增的环境变量,值为:

    AKIXXXXXXXN   93nxxxxxxxxXHZE9  ap-shanghai app-120000000
    

    前端文件上传腾讯云 COS 代码脚本,本地 ./static/ 目录,上传到云上 /:version/static/ 目录。

    #!/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

    访问应用首页

    <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>