vue部署于腾讯云cos



这篇博客的起源来源于同事的一次cos部署指导,为了便于下次使用,特记录于此.

原理

整体一知半解版本:
1. 首先打包好的文件夹下会包含一个 index.html 文件和 static 文件夹,这个 static 文件夹就会包含我们需要的静态资源,使用者打开网页的时候,index.html会去找同目录下的static内的资源,而这个过程是很慢的,index.html文件很小还没有什么感觉,但要是加载打包好的静态资源速度就会变慢。
2. 这个时候就轮到我们的主角cos登场了,当我们将static文件夹发送到cos内,相当于使用者就近从服务器拿取静态资源,速度无疑快很多。
3. 所以整体步骤为:

	1. 更改打包index.html内的引用位置为cos路径
	2. 将static文件夹发送至cos服务器路径下
 	3. 将index.html发送至后台服务器路径下

cos对象存储

对象存储(Cloud Object Storage,简称:COS)是腾讯云提供的面向非结构化数据,支持 HTTP/HTTPS 协议访问的分布式存储服务,它能容纳海量数据并保证用户对带宽和容量扩充无感知,可以作为大数据计算与分析的数据池。1

简而言之,cos就是类似天猫超市方式,在离我们最近的地方构建仓库,把我们需要的资源就近调拨给我们;之前的上传服务器方式,类似淘宝普通商家,从商家那里发送资源给我们,这样就会造成住的远的人要很久才能取到资源。

因为我是基于vue部署于腾讯云cos,参考Node.js SDK

cos使用配置准备

  1. 获取腾讯云账号信息APPID
    找不到的可以点击右边,登录就好了。APPID获取
    appid获取

  2. 获取腾讯云bucket
    找不到的可以点击右边,登录就好了。bucket获取
    所属地域之后会用到,这里暂且记下就好。
    bucket获取

  3. API密钥获取
    找不到的可以点击右边,登录就好了。API密钥获取
    SecretId获取

cos辅助工具

同事推荐使用的官方工具COSBrowser,下载操作系统对应的安装包就好。
COSBrowser下载演示

下载好后安装就好,但是登陆的时候需要的是之前获取的SecretIdSecretKey作为登录凭据。登录就可以看到cos根路径下文件夹了。
COSBrowser演示

npm插件

scp2

scp2

  1. 安装
    npm i scp2 -g or cnpm i scp2 --save
  2. 使用演示,以我的代码为例:
    build/build.js
    // 引用
    const client = require('scp2')  // 自动打包部署插件
    
    // 实际使用
    /** 自动部署 */
    client.scp('dist/', {
      port: ##,  // 端口号,纯数字
      host: '###.###.###.###',  // 字符串,IP
      username: '#########',  // 登陆服务器用户名,找负责后台服务的大腿或者运维大兄弟要去吧,记得要打开读写权限
      password: '**********',  // 登陆服务器密码,同上
      path: 'home/www'  // 这个是要放在服务器上的路径
    }, function (err) {
      if (err) {
        console.log(chalk.red('  Send failed with errors.\n' + err))        
      } else {
        console.log(chalk.cyan('  Send files to server success.\n'));
      }
    })
    
    这里我使用了sftp协议,所以是22端口。所以需要配置端口号主机IP用户名密码存放路径
    有其他需求的请详询scp2

cos-nodejs-sdk-v5(不推荐)

缺陷:这个插件有时会出现打包未完成,即开始向cos传文件最终导致向cos上传文件失败,在电脑性能不行的时候体现尤为明显,故不推荐使用

腾讯云cos帮助插件

  1. 安装
    npm i cos-nodejs-sdk-v5 --save or cnpm i cos-nodejs-sdk-v5 --save

  2. 使用演示,以我的代码为例:

    • 这里需要之前获取到的 SecretIdSecretKeybucketappicos存储路径
    • 特别注意排除 index.html 是纯正则表达式,不要单引号。
    • region 和之前所属地域对应,具体详询官方文档

    在 `build/webpack.prod.conf.js` 内
    // 引用 腾讯云cos
    const CosPlugin = require('cos-webpack')  // 上传腾讯云cos插件
    
    // 这里直接写在参数webpackConfig内好像无法判断,
    // 所以写在了后面,
    // 使用push添加
    webpackConfig.plugins.push(
    	new CosPlugin({  // 配置 Plugin
      		secretId: '****************',
          	secretKey: '***************',
      		bucket: '******************',  // COS 存储对象名称,格式为对象名称加应用ID (APPID),如: bucket-1250000000
      		region: 'ap-beijing',  // COS 存储地域
      		exclude: /index.html$/,  // 可选,排除特定文件,正则表达式,如: /index.html$/
      		path: 'static/pro/'  // 存储路径, 默认为 [hash]
        })
    )
    

项目代码修改

只简单介绍一下我的修(zhe)改(teng)之旅。

相关要修改文件目录:

project
├── package.json
├── build
│		├── cos.js
│		├── secure-file-copy.js
│		└── webpack.base.conf.js
└── config
		├── index.js
		└── prod.env.js

整体修改思路:

  1. 修改打包后 index.html 文件的静态引用资源路径为cos上的资源路径,这里需要修改 webpack.base.conf.js
  2. 新建 secure-file-copy.js 文件将 index.html 推送至后台服务器
  3. 新建 cos.js 文件将 static 文件夹推送至后台服务器

package.json

{
	"scripts": {
		"deploy:pre": "cross-env COS_ENV=pre npm run build && cross-env COS_ENV=pre node build/cos.js && cross-env COS_ENV=pre node build/secure-file-copy.js",
   	 	"deploy:prod": "cross-env COS_ENV=prod npm run build && cross-env COS_ENV=prod node build/cos.js && cross-env COS_ENV=prod node build/secure-file-copy.js "
	}
}
  • Windows使用 set, set ENV=pre 设定环境变量,用于后边的打包区分不同的环境
  • 注意: 之所以 ...pre&& 之间没有空格,是为了避免之后的识别出现空格的问题
  • Mac使用 export, export ENV=pre设定

为了跨平台,所以我毅然决然的使用了 cross-env 包,但是有个问题就是我没有办法定义贯穿三个脚本的共享变量,所以如你所见,我写了三次环境变量,望有知道怎么整的大神不吝赐教,拜谢🙏

build/webpack.base.conf.js

先修改静态资源引用路径为cos路径:

const config = require('../config');  // 这个文件的修改后面说

/**
 * 根据环境变量,确定静态资源引用路径
 * @param {String} process.env.COS_ENV
 * - pre 预览版
 * - prod 正式版
 * @author simorel
 */
if (process.env.hasOwnProperty('COS_ENV') && process.env.COS_ENV) {
    webpackConfig.output.publicPath =
        process.env.COS_ENV === 'pre'
            ? config.pre.assetsPublicPath
            : config.prod.assetsPublicPath
}

build/secure-file-copy.js

这里使用 scp2 插件:

'use strict'
/**
 * 将指定文件发送至服务器
 * @author simorel
 */
 
const client = require('scp2') // 文本发送插件
const chalk = require('chalk');
const config = require('../config');
const FILE_PATH = 'dist/index.html';  // 文件路径,仅发送index.html作为页面入口,其他所有静态资源都放在cos上

if (process.env.hasOwnProperty('COS_ENV') && process.env.COS_ENV) {
    const scpConfig =
        process.env.COS_ENV === 'pre'
            ? config.pre.server
            : config.prod.server;

    client.scp(FILE_PATH, {
        port: 22,
        host: scpConfig.host,
        username: scpConfig.username,
        password: scpConfig.password,
        path: scpConfig.path
    }, function (err) {
        if (err) {
            console.log(chalk.red('  Send failed with errors.\n' + err))
        } else {
            console.log(chalk.cyan('  Send files to server success.\n'));
        }
    })
}

build/cos.js

重点说说使用的插件吧:

  • glob 文件夹遍历插件,主要是方便快捷
  • ora、chalk主要是为了输出进度条使用的
  • fs 获取文件大小,以便于区分使用cos哪个接口上传,并获取文件的流传递给cos接口
  • cos-nodejs-sdk-v5 是腾讯云cos官方插件
'use strict';

const fs = require('fs'); // node的文件插件
const ora = require('ora'); // 日志打印归于一行插件
const chalk = require('chalk'); // 日志美化插件
const COS = require('cos-nodejs-sdk-v5'); // 腾讯cos插件
const glob = require('glob'); // 遍历文件夹插件
const config = require('../config'); // 配置文件
const FILES_PATH = 'dist/**/*.*'; // 打包后静态资源路径
const IGNORE_PATH = '*/index.html';  // 忽略index.html文件

/**
 * 发送文件至cos类
 * @class DeployCos
 */
class DeployCos {
    constructor() {
        this.filesPath = FILES_PATH; // 打包后的静态资源路径
        this.cos = this._createCosInstance();
        this.cosConfig = this._getCosConfig();
        this.spinner = this._createSpinner();
        this.total = 0; // 总文件数

        this.main();
    }

    /**
     * 获取文件路径,并上传至cos
     * @memberof DeployCos
     */
    main() {
        glob(
            this.filesPath,
            {
                ignore: [IGNORE_PATH]
            },
            (err, fileNames) => {
                this.total = fileNames.length;

                let promiseArr = fileNames.map((file, index) => {
                    return this._uploadFile(file, index + 1); // 因为index从零开始,所以这里加一
                });

                Promise.all(promiseArr)
                    .then(res => {
                        this.spinner.succeed(); // 结束上传进度条打印
                        console.log(
                            chalk.cyan('\n  Send files to tencent cos success.\n')
                        );
                    })
                    .catch(err => {
                        console.log(
                            chalk.red('  Send failed with errors.\n' + err)
                        );
                    });
            }
        );
    }

    /**
     * 创建cos实例
     */
    _createCosInstance() {
        return new COS({
            SecretId: config.cos.secretId,
            SecretKey: config.cos.secretKey
        });
    }

    /**
     * 创建进度条
     */
    _createSpinner() {
        return ora({
            text: this._getTip(0, 0),
            color: 'green'
        }).start();
    }

    /**
     * 根据环境变量获取cos配置
     * @memberof DeployCos
     */
    _getCosConfig() {
        return process.env.COS_ENV === 'pre' ? config.pre.cos : config.prod.cos;
    }

    /**
     * 展示进度提示条
     * @param {number} index 当前文件索引
     * @param {number} sum 总文件数
     * @memberof DeployCos
     */
    _getTip(index, sum) {
        let percentage = sum === 0 ? 0 : Math.round((index / sum) * 100);
        return `Uploading to Tencent COS: ${
            percentage === 0 ? '' : percentage + '% '
            }${index}/${sum} files uploaded`;
    }

    /**
     * 实际调用cos接口上传文件函数
     * @param {String} file 文件相对路径
     * @param {number} index 文件计数器
     * @memberof DeployCos
     */
    _uploadFile(file, index) {
        return new Promise((resolve, reject) => {
            /**
             * 因为不希望cos上的路径多一层dist,所以这里移除
             * - 不移除,形如: [cos路径]/[dist/static/xx]
             * - 移除后,形如: [cos路径]/[static/xx]
             */
            let removeDistFilePath = file.replace('dist/', '');
            /** 上传文件基础配置 */
            let baseConfig = {
                Bucket: config.cos.bucket, // 存储桶 格式:test-1250000000
                Region: config.cos.region, // 地域
                Key: `${this.cosConfig.path}${removeDistFilePath}` // [cos存储路径]/[文件相对存储路径]
            };
            // 根据文件大小判断使用何种方式上传,大于1M的使用分块上传
            if (fs.statSync(file).size > 1024 * 1024) {
                this.cos.sliceUploadFile(
                    {
                        ...baseConfig,
                        FilePath: file
                    },
                    (err, data) => {
                        this.spinner.text = this._getTip(index, this.total);
                        return err ? reject(err) : resolve(data);
                    }
                );
            } else {
                this.cos.putObject(
                    {
                        ...baseConfig,
                        Body: fs.createReadStream(file),
                        ContentLength: fs.statSync(file).size // putObject接口必须传文件大小
                    },
                    (err, data) => {
                        this.spinner.text = this._getTip(index, this.total);
                        return err ? reject(err) : resolve(data);
                    }
                );
            }
        });
    }
}

new DeployCos();  // 执行推送

config

config/index.js

	/**
     * 预览版(静态资源推送至cos)
     * @author simorel
     */
    pre: {
        /** 静态资源引用路径 */
        assetsPublicPath: '####',
        /** 服务器 */
        server: {
            path: '####',
            host: '####',
            username: '####',
            password: '####'
        },
        /** cos */
        cos: {
            path: '####'
        }
    },

    /**
     * 正式版(静态资源推送至cos)
     * @author simorel
     */
    prod: {
        /** 静态资源引用路径,cos上的路径 */
        assetsPublicPath: 'http(s)://###',
        /** 服务器 */
        server: {
            path: '###',
            host: '###',
            username: '###',
            password: '###'
        },
        /** cos */
        cos: {
            path: '###/###/###/'
        }
    },

    /**
     * 腾讯云cos基础配置
     */
    cos: {
        secretId: '#########',
        secretKey: '#########',
        bucket: '#########', // COS 存储对象名称,格式为对象名称加应用 ID,如: bucket-1250000000
        region: '#########' // COS 存储地域
    }

config/prod.env.js

'use strict';

const prodConfig = {
    NODE_ENV: '"production"'
};

/** 
 * @author simorel 
 */
if (process.env.hasOwnProperty('COS_ENV') && process.env.COS_ENV) {
    /**
     * 注意(https://www.webpackjs.com/plugins/define-plugin/):
     *  因为这个插件直接执行文本替换,给定的值必须包含字符串本身内的实际引号。
     *  通常,有两种方式来达到这个效果,使用 '"production"', 或者使用 JSON.stringify('production')。
     */
    prodConfig.COS_ENV = JSON.stringify(process.env.COS_ENV);
}

module.exports = prodConfig;

  1. cos命名参考 ↩︎

Logo

前往低代码交流专区

更多推荐