艺灵设计

全部文章
×

@vue/cli3项目开发之记一次手动配置前端一键自动部署代码到服务器上的经历

作者:艺灵设计 - 来源:http://www.yilingsj.com - 发布时间:2021-06-05 21:54:46 - 阅: - 评:0 - 积分:0

摘要:@vue/cli3脚手架的出现,大大提高了前端的开发效率。但每次打包后通过WinSCP等工具手动上传代码到服务器上也挺浪费时间的,在网上也有一些相关的自动化部署插件,比如:deploy-cli-service。一般插件都是支持密码上传,今天我们来手写一个使用密钥上传的工具......

其实在很久之前,我就想捣腾下前端一键部署代码到服务器上这个听起来很高大上的黑科技了。只不过太懒,导致一直没能执行。正好前段时间正好又有机会,于是花些时间研究了下,发现方法不唯一。今天抽空记录一笔,以备日后查阅或使用。

实现一键部署大概有4个步骤,分别是:
1、打包本地代码;
2、使用密码或密钥的方式连接服务器;
3、连接成功后上传代码到指定目录,也可先删除目录再上传,此操作较敏感,建议谨慎开启;
4、上传完毕断开连接。
废话不多说,开始吧!

一、初用deploy-cli-service练手

说实在的,在前端自动化部署这一块儿,我存在比较大的盲区。感谢@陈少 分享给我一个掘金的链接,让我了解了deploy-cli-service。这是一个大神写的前端一键自动部署插件,直接使用node安装即可,有兴趣的看官可以前往github进行访问,戳我查看。下面简单说下自己在使用中的一些感受和心理变化吧。

1.1、用临时测试服务器大胆练手

由于是在服务器上操作,新手怂啊!万一把服务器上的文件删除了,那可就麻烦了,于是我先找@戴哥 要了一台临时的测试服务器。新买的临时测试服务器上啥都没有,想咋玩咋玩儿,哈哈!然后我顺带要来了ip、端口号、用户名和密码,因为这些在接下来的配置中会用到。

随后我按照github上面的教程进行了接近半个小时的配置,当我在命令行中输入npm run deploy:dev并回车后,按照提示回车了几次后,命令行中会提示我上传成功。此时我半信半疑,立马用WinSCP登录测试服务器一看,本地打包的文件确实上传成功了。当时我内心惊呼:卧槽!牛逼!

1.2、安装宝塔,运行网站

由于测试服务器没有运行环境,所以文件无法访问。于是我又装上了宝塔,并创建网站。最后用ip访问,当我看到浏览器能成功打开页面时,我这才放心了下来。

1.3、不支持.pem密钥上传?

当我在本地已经把deploy-cli-service玩熟练后,我抱着尝试的心态去找@应总 要来了线上的服务器的一些相关信息。但线上服务器只能使用xxx.pem这个密钥文件进行登陆。于是我对照github上的配置,把privateKey字段的值为自己的密钥文件,运行命令后会提示我输入密码passphrase。这可难倒我了,于是我又先后尝试了相对路径、绝对路径,最终都没有登陆成功。再后来我便放弃了,决定自己造一个能用密钥的。

二、ssh2-sftp-client解决一切

随后我又在网上搜索,发现了ssh2-sftp-client这个仓库,有兴趣的可以戳我访问文档

跟着官方文档中提供的demo,尝试用密钥的方式连接了下服务器,结果失败了(事实证明是密钥不匹配)。于是我又用帐号密码的方式测试了下,发现连接成功。为了验证不是密钥的问题,我又用WinSCP导入密钥进行登录,发现WinSCP也无法登录。后来才知道,原来@应总 给我发的密钥和ip不匹配,后来换上了正确的密钥和ip后,脚本也能登录了。下面附下当时自己在本地测试连接服务器的过程,简写步骤见下方。

2.1、安装ssh2-sftp-client

在项目根目录中右键 - Git Base Here - 粘贴下方代码并回车等待安装即可。也可以使用cmd命令进入项目目录,然后使用下方命令安装。

npm install ssh2-sftp-client --save-dev

稍等片刻后命令行中会提示安装成功,此时再打开项目根目录下的package.json会发现多了一行代码,即:"ssh2-sftp-client": "^6.0.1"

2.2、项目根目录创建执行文件

2.2.1、密码方式连接服务器

在项目根目录下创建一个名为passUpload.js的文件,取名随意,但必须要跟调用时要保持一致!代码内容如下:

#!/usr/bin/env node
/*
 * @Description: 使用密码的方式部署前端代码到服务器
 * @Author: yilingsj(315800015@qq.com)
 * @Date: 2021-06-05 14:44:12
 * @LastEditors: yilingsj(315800015@qq.com)
 * @LastEditTime: 2021-06-05 15:14:22
 */
let Client = require('ssh2-sftp-client');
let sftp = new Client();

sftp.connect({
  host: 'ip地址', // 服务器地址,修改成自己的
  port: '22', // 端口号,修改成自己的
  username: 'root', // 服务器登录用户名,修改成自己的
  password: '你的密码' // 密码,修改成自己的
}).then(() => {
  console.log('-使用密码的方式连接服务器成功', Date.now());
}).then(data => {
  console.log(data, 'the data info');
  sftp.end(); // 结束当前客户端会话
}).catch(err => {
  console.log(err, 'catch error');
  sftp.end();
});
2.2.2、密钥方式连接服务器

在项目根目录下创建一个名为keyUpload.js的文件,代码内容如下:

#!/usr/bin/env node
/*
 * @Description: 使用密钥的方式部署前端代码到服务器
 * @Author: yilingsj(315800015@qq.com)
 * @Date: 2021-06-05 14:44:12
 * @LastEditors: yilingsj(315800015@qq.com)
 * @LastEditTime: 2021-06-05 15:14:22
 */
let Client = require('ssh2-sftp-client');
const fs = require('fs'); // 新增
let sftp = new Client();
const privateKey = './xxx.pem'; // 本地密钥文件地址

sftp.connect({
  host: 'ip地址', // 服务器地址,修改成自己的
  port: '22', // 端口号,修改成自己的
  username: 'root', // 服务器登录用户名,修改成自己的
  privateKey: fs.readFileSync(privateKey) // 读取密钥
}).then(() => {
  console.log('-使用密钥的方式连接服务器成功', Date.now());
}).then(data => {
  console.log(data, 'the data info');
  sftp.end();
}).catch(err => {
  console.log(err, 'catch error');
  sftp.end();
});

2.3、尝试连接服务器

现在,我们只需要在命令行中输入不同的执行命令并回车即可看效果了。具体代码见下方演示。

2.3.1、测试密码连接情况

node ./passUpload.js

2.3.2、测试密钥连接情况

node ./keyUpload.js

当我们分别回车后,可以在命令行中看到有“-使用密码的方式连接服务器成功” 和 “-使用密钥的方式连接服务器成功” 的字样即表示全部成功,如图:密码和密钥的方式均连接服务器成功.png 密码和密钥的方式均连接服务器成功

2.4、sftp.uploadDir 实现文件夹上传

刚刚我们已经实现了使用密钥连接服务器的功能了,接下来只要解决上传那就完事儿了。

继续看官方文档,会发现有一个uploadDir的方法可以实现目录上传。一共接收3个参数(srcDir,dstDir,filter),分别表示:本地文件路径、远程文件路径、正则表达式,过滤上传的文件和目录。这里我们可以根据实际情况来设置参数,通常只需要前面2个参数就可以了。新的代码如下:

#!/usr/bin/env node
/*
 * @Description: 使用密钥的方式部署前端代码到服务器
 * @Author: yilingsj(315800015@qq.com)
 * @Date: 2021-06-05 14:44:12
 * @LastEditors: yilingsj(315800015@qq.com)
 * @LastEditTime: 2021-06-05 16:08:26
 */
let Client = require('ssh2-sftp-client');
const fs = require('fs');
let sftp = new Client();
const privateKey = './xxx.pem'; // 本地密钥文件地址
const distPath = './dist/test/css'; // 本地要上传的文件目录
const webDir = '/data/website/demo/test'; // 服务器部署路径,目录会自动创建(禁止为空或'/')

sftp.connect({
  host: 'ip地址', // 服务器地址,修改成自己的
  port: '22', // 端口号,修改成自己的
  username: 'root', // 服务器登录用户名,修改成自己的
  privateKey: fs.readFileSync(privateKey) // 读取密钥
}).then(() => {
  console.log('-使用密钥的方式连接服务器成功', Date.now());
}).then(() => {
  console.log('-------开始上传', Date.now());
  return sftp.uploadDir(distPath, webDir);
}).catch(err => {
  console.log(err, 'catch error');
  sftp.end();
});

强烈建议看官先用WinSCP等工具先登录服务器,了解目录后再设置webDir的路径。为了保险起见,可以先写一个不存在的目录,代码执行时会自动创建。在测试阶段尽量不要写已经在运行的项目目录,避免代码丢失!

下面,我们在命令行中输入node ./keyUpload.js并回车,当命令行自动结束时表示执行完毕,同时在工具中也能看到文件确实上传了。视频演示↓↓↓:使用密钥连接服务器并成功上传文件.mp4 使用密钥连接服务器并成功上传文件(友情提示:点击上面的图片即可播放视频)

2.5、与打包命令结合,手动上传是路人

目前为止,我们已经实现了密码登录密钥登录上传指定文件到服务器,一切都很顺利。但是美中不足的是:总不能每次切换环境后,我都要手动先打包,接着再修改上传的目录及配置服务器路径,最后用命令上传吧!所以,上面的代码可以做进一步的优化。多环境配置我们可以写成配置文件,手动打包和上传到服务器上也可以合二为一。

2.5.1、shelljs执行打包命令

在之前的文章中也有提及过shelljs,像手把手带你写一个用程序自动打开微信开发者工具的小插件nodejs与后端通信并动态打包项目以适应多环境开发等,这些文章中都有她的身影,今天也不例外。相关代码如下:


/**
 * @author: yiling (315800015@qq.com)
 * @description: 执行打包命令
 * @param {*}
 * @return {*}
 * @Date: 2021-05-28 17:43:03
 */
async function compileDist() {
  console.log('开始打包项目', Date.now())
  if (shell.exec(config[NODE_ENV].script).code === 0) {
    console.log('打包成功', Date.now())
  }
}

上面的config[NODE_ENV].script是我配置的打包命令,如:build:test,表示测试环境。这个在后面的配置文件中可进行设置,好处是不同的环境可以设置不同的打包命令,调用时动态执行。这个build:test是提前在package.json中定义好的,相关代码如下,仅供参考。

/* package.json中scripts的配置,复制后请删除所有注释 */
{
  "scripts": {
    "dev": "vue-cli-service serve", /* 本地开发环境 */
    "build:pre": "vue-cli-service build --mode pre", /* 预上线环境 */
    "build:test": "vue-cli-service build --mode test", /* 测试环境 */
    "build:prod": "vue-cli-service build", /* 正式环境 */
    "upload:test": "cross-env NODE_ENV=test node ./keyUpload/keyUpload.js build", /* 打包并上传测试环境到服务器 */
    "upload:pre": "cross-env NODE_ENV=pre node ./keyUpload/keyUpload.js build", /* 打包并上传预上线环境到服务器 */
    "upload:prod": "cross-env NODE_ENV=prod node ./keyUpload/keyUpload.js build", /* 打包并上传正式环境到服务器 */
  },
}

当调用await compileDist()方法后,就像在命令行中执行npm run build:test一样会对项目进行打包。然后我们再调用连接服务器的函数,即可实现自动化部署。简单案例源码见下方代码块,完整源码见github仓库。

2.6、完整demo

为了方便管理文件,在项目根目录下创建一个名为keyUpload的文件夹,然后把刚才的两个文件passUpload.jskeyUpload.js和密钥都移动进来,然后再创建一个名为config.js的配置文件。

2.6.1、config.js配置文件

config.js文件中会包含不同环境下的目录信息、打包信息等。之所以单独做一个配置文件是因为我们的项目存在多环境开发,如果把配置也放keyUpload.js这个文件中,后期维护不是很方便。

#!/usr/bin/env node
/*
 * @Description: 一键部署代码到服务器上的配置
 * @Author: yiling (315800015@qq.com)
 * @Date: 2021-05-27 21:13:59
 * @LastEditors: yiling (315800015@qq.com)
 * @LastEditTime: 2021-05-28 17:46:55
 */
const commonBase = {
  host: '*.*.*.*', // 服务器地址
  port: 22, // 服务器端口号
  username: 'root', // 服务器登录用户名
  password: '', // 服务器登录密码
  privateKey: './xxx.pem', // 密钥地址,与密码二选一均可
  isRemoveRemoteFile: false, // 是否删除远程文件(默认false)
}
const config = {
  name: '前端一键部署脚本',
  test: {
    name: '测试环境', // 环境名称
    distPath: '../dist/test', // 本地打包生成目录
    webDir: '/root/www/test', // 服务器部署路径(不可为空或'/')
    script: 'build:test', // 打包命令
    ...commonBase,
  },
  pre: {
    name: '预上线环境',
    distPath: '../dist/pre',
    webDir: '/root/www/pre',
    script: 'build:pre',
    ...commonBase,
  },
  prod: {
    name: '正式环境',
    distPath: '../dist/prod',
    webDir: '/root/www/prod',
    script: 'build:prod',
    ...commonBase,
  },
}

module.exports = config

2.6.2、keyUpload.js 密钥上传文件

#!/usr/bin/env node
/*
 * @Description: 使用密钥的方式部署前端代码到服务器
 * @Author: yilingsj(315800015@qq.com)
 * @Date: 2021-06-05 14:44:12
 * @LastEditors: yilingsj(315800015@qq.com)
 * @LastEditTime: 2021-06-05 18:22:55
 */
const shell = require('shelljs')
const path = require('path')
const fs = require('fs')
const Client = require('ssh2-sftp-client')
const config = require('./config')

// 当前运行环境变量与运行命令
const NODE_ENV = process.env.NODE_ENV

const PRESET_PATH = path.resolve(__dirname, './')
const PACKAGE = path.resolve(PRESET_PATH, '../package.json')
if (!config[NODE_ENV].webDir) {
  shell.echo('未发现【服务器部署路径webDir】,请检查配置!')
  shell.exit()
}
const privateKey = path.resolve(PRESET_PATH, config[NODE_ENV].privateKey)
const distPath = path.resolve(PRESET_PATH, config[NODE_ENV].distPath)

let PACKAGE_JSON = null
try {
  PACKAGE_JSON = JSON.parse(fs.readFileSync(PACKAGE).toString())
} catch (error) {
  shell.echo('没有找到 package.json 文件,请检查文件或路径')
  shell.exit()
}

config[NODE_ENV].privateKey = privateKey ? fs.readFileSync(privateKey) : ''
config[NODE_ENV].script = PACKAGE_JSON.scripts[config[NODE_ENV].script]
if (!config[NODE_ENV].script) {
  shell.echo('没有找到打包命令,请检查配置')
  shell.exit()
}

/**
 * @author: yiling (315800015@qq.com)
 * @description: 执行打包命令
 * @param {*}
 * @return {*}
 * @Date: 2021-05-28 17:43:03
 */
async function compileDist() {
  console.log('开始打包项目', Date.now())
  if (shell.exec(config[NODE_ENV].script).code === 0) {
    console.log('打包成功', Date.now())
  }
}

/**
 * @author: yiling (315800015@qq.com)
 * @description: 连接ssh
 * @param {*}
 * @return {*}
 * @Date: 2021-05-28 11:10:53
 */
const sftp = new Client()
const connectSSh = async () => {
  let startTime = null
  sftp
    .connect(config[NODE_ENV])
    .then(() => {
      console.log('-连接服务器成功', Date.now())
      if (config[NODE_ENV].isRemoveRemoteFile) {
        return deleteWebDir()
      }
    })
    .then(() => {
      // 上传文件
      startTime = Date.now()
      console.log('-------开始上传', startTime)
      return sftp.uploadDir(distPath, config[NODE_ENV].webDir)
    })
    .then((data) => {
      console.log('---上传完成耗时', Date.now() - startTime, 'ms')
      sftp.end()
    })
    .catch((err) => {
      console.log(err, '连接失败', Date.now())
      sftp.end()
    })
}
/**
 * @author: yiling (315800015@qq.com)
 * @description: 删除远程目录
 * @param {*}
 * @return {*}
 * @Date: 2021-05-28 16:20:14
 */
const deleteWebDir = () => {
  return findWebDir(config[NODE_ENV].webDir).then((res) => {
    if (res) {
      return sftp.rmdir(config[NODE_ENV].webDir, true)
    }
  })
}
/**
 * @author: yiling (315800015@qq.com)
 * @description: 查看远程目录是否存在
 * @param {String} path 远程路径
 * @return {*}
 * @Date: 2021-05-28 16:47:41
 */
const findWebDir = (path) => {
  return new Promise((resolve) => {
    return sftp
      .exists(path)
      .then((res) => {
        return resolve(res)
      })
      .catch((err) => {
        console.log('远程目录出现错误', err)
      })
  })
}

async function runTask() {
  await compileDist() // 打包完成
  await connectSSh() // 提交上传
}
runTask()

2.6.3、修改package.json,添加自定义命令

/* package.json中scripts的配置,复制后请删除所有注释 */
{
  "scripts": {
    "dev": "vue-cli-service serve", /* 本地开发环境 */
    "build:pre": "vue-cli-service build --mode pre", /* 预上线环境 */
    "build:test": "vue-cli-service build --mode test", /* 测试环境 */
    "build:prod": "vue-cli-service build", /* 正式环境 */
    "upload:test": "cross-env NODE_ENV=test node ./keyUpload/keyUpload.js build", /* 打包并上传测试环境到服务器 */
    "upload:pre": "cross-env NODE_ENV=pre node ./keyUpload/keyUpload.js build", /* 打包并上传预上线环境到服务器 */
    "upload:prod": "cross-env NODE_ENV=prod node ./keyUpload/keyUpload.js build", /* 打包并上传正式环境到服务器 */
  },
}

2.6.4、安装node包

在项目根目录中打开命令行窗口,粘贴下方代码并回车即可。

npm install shelljs ssh2-sftp-client cross-env --save-dev

2.6.5、运行命令,见证奇迹

继续在命令行中输入npm run upload:test并回车,当看到窗口中出现“上传完成耗时”字样时表示已经上传成功。效果如图:在命令行窗口中输入代码实现自动打包及上传.png 在命令行窗口中输入代码实现自动打包及上传

至此,一个能自动打包并上传代码到服务器上的插件已经搞定了。看官在使用到项目中时,只需要修改config.js即可。由于删除服务器资源属于敏感操作,所以我把isRemoveRemoteFile设置成了false。建议看官在测试熟练后再开启,避免因路径配置不当造成代码丢失。

三、源码下载

为了方便看官研究代码,艺灵已经将相关文件打包到了网站上,有需要的看官可以点击右侧链接进行免费下载→→
本站下载:[源码]前端一键自动部署代码到服务器插件.zip
github下载:分支:dev-vue-cli-keyUpload-20210605

四、更多想法

4.1、直传压缩包

之前我想着在打包后把文件夹弄成压缩包,然后上传压缩包这样速度会更快些。但测试时发现无法直接上传压缩文件,但文件夹里面有压缩文件的话又是可以上传的。但考虑到在服务器上解压文件又是一个难题,我就放弃了。网上也有相应的解法,就是在服务器上再放一个脚本,然后调用那个脚本来执行后续操作。

4.2、node-ssh 更强大

我在阅读文章开头提到的deploy-cli-service这个插件的源码时也学到不少东西,他使用node-ssh来打通服务器。并且通过putFile能直传压缩包文件,通过execCommand直接执行命令。比如:解压远程文件、备份远程、删除远程文件。总的感觉来说,就是这个node-ssh非常强大!

4.3、上传进度

由于上传需要等待时间,目前网上的这些插件也同样都是干等着,没有任何进度提示。我也在网上搜索过怎么实现,感觉目前对我来说真的是太难了......

虽然上传进度我实现不了,但统计上传所需要的总时间还是可以的,也就是那个“---上传完成耗时 xxx ms”。

五、最后

下周就要高考了,虽然艺灵早已毕业多年,但当年大家口口相传“三长一短选最短,三短一长选最长 两长两短就选B,同长同短就选A 长短不一选择D,参差不齐C无敌”的情景仍历历在目。啥也不说了,艺灵提前祝各位莘莘学子们考的都会,蒙的全对。清华北大,不在话下!

转载声明:
  若亲想转载本文到其它平台,请务必保留本文出处!
本文链接:http://www.yilingsj.com/xwzj/2021-06-05/vue-cli-keyUpload.html

若亲不想直保留地址,含蓄保留也行。艺灵不想再看到有人拿我的技术文章到他的地盘或者是其它平台做教(装)程(B)而不留下我的痕迹。文章你可以随便转载,随便修改,但请尊重艺灵的劳动成果!谢谢理解。

亲,扫个码支持一下艺灵呗~
如果您觉得本文的内容对您有所帮助,您可以用支付宝打赏下艺灵哦!

Tag: @vue/cli3 vue脚手架 前端 一键部署 服务器 密钥 nodejs shelljs ssh2-sftp-client 自动上传 WinSCP

上一篇: uni-app跨端开发微信小程序之动态配置自定义tabBar那点儿坑   下一篇: @vue/cli3项目开发之前端一键自动部署代码到服务器并播放随机音乐以便提醒

评论区