前端自动化部署node-ssh2-archiver

环境准备

本文采用vue-cli搭建项目2

npm i ssh2 archiver

整体思路

项目打包=>将打包文件压缩=>建立ssh连接=>通过sftp协议上传=>exec在远程服务器上执行命令 解压 删除压缩包 => 删除本地压缩包 关闭连接。

archiver fs 压缩文件

const fs =require("fs");
const archiver = require("archiver");
const output = fs.createWriteStream(__dirname + "/example.zip");
//archiver(format, options): 创建一个新的 archiver 实例。
//format: 压缩文件的格式,如 'zip'、'tar' 等。
//options: 可选配置对象,用于设置压缩选项,例如压缩级别等
const archive = archiver("zip",{zlib:{level:9}});
// 捕获错误事件
output.on('close', () => {
    console.log(`Archive created. Total bytes: ${archive.pointer()}`);
  });
  
  archive.on('error', (err) => {
    throw err;
  });
//archive.pipe(outputStream): 将 archiver 的输出流连接到指定的可写流(例如文件写入流),以便将压缩文件写入磁盘或发送到其他目标。
archive.pipe(output);
//archive.file(filename, options): 将指定的文件添加到压缩文件中。

//filename: 要添加的文件路径。
//options: 可选配置对象,用于设置文件在压缩文件中的路径、权限等
archive.file("test.js",{name:"test.js"}); //单个文件
archive.file("test1.js",{name:"test1.js"}); //单个文件
//archive.directory(directory, dest, data): 将指定目录中的所有文件添加到压缩文件中。
//directory: 要添加的目录路径。
//dest: 目录在压缩文件中的路径。
//data: 可选配置对象,用于设置目录添加选项,例如递归、过滤等。
// 添加整个目录到压缩包
archive.directory('my-directory/', 'my-directory');
archive.finalize(): 完成压缩操作,生成压缩文件。
// 最后关闭压缩流,这将触发 `close` 事件
archive.finalize();

ssh2用法

建立连接、监听

let Client = require("ssh2").Client;
let conn = new Client();
conn.on("ready",()=>{ //连接完成监听
	//主要逻辑在建立连接后操作
}).on("error",()=>{ //连接错误监听

}).on("end",()=>{ //连接已结束监听

}).on("close",()=>{ //关闭连接监听

}).connect({ //连接配置/建立连接动作
host:"XXX.XXX.X.XXX",
port:"22",
username:"username",
password:"password",
})

sftp ssh文件传输

sftp.fastPut(localPath, remoteFilePath[, options], callback): 快速将本地文件上传到远程服务器。

localPath: 本地文件路径。
remoteFilePath: 远程文件路径。
options: 可选参数,用于设置上传选项,例如并发数等。
callback: 上传完成后的回调函数。

//省略了ssh建立连接的步骤 在on("ready)这种操作
conn.sftp((err,sftp)=>{
	if(err){ //错误
		console.log(err)
	}else{
		sftp.fastPut(localPath,remoteFilePath,(err,result)=>{
			if(err){
				console.log(err);
				return
			}
			console.log(result)
		})	
	}
})

exec ssh 在远程服务器上执行命令

conn.exec(command, [options], callback):在远程服务器上执行命令。

command :是要执行的命令
options :是一个可选的对象,用于指定执行命令的参数
callback :是一个回调函数,用于处理命令执行结果。

//省略了ssh建立连接的步骤 在on("ready)这种操作
conn.exec("ll",(err,stream)=>{
	stream.on("close",(code,signal)=>{
		console.log("当流被关闭时触发。回调函数的参数为流的状态码和信号")
	}).on("data",data=>{
		console.log("当收到数据时触发。回调函数的参数为收到的数据")
		console.log(data.toString())
	}).stderr.on("data",data=>{
		//捕获远程命令执行过程中的标准错误输出
		console.log('STDERR: ' + data);
	})
})

整套部署流程代码


let Config = {
	buildDist:"XXX", //你需要的打包后的文件夹名称 默认dist
	buildCommand:"npm run build", //打包命令
}

const fs =require("fs");
const archiver = require("archiver");
const {Client} = require("ssh2");
//ssh连接
Class SSH {
	constructor({host,port,username,password}){
		this.server={
			host,
			port,
			username,
			password
		};
		this.conn = new Client()
	}
	//连接服务器
	connectServer(){
		return new Promise((resolve,reject)=>{
			this.conn.on("ready",()=>{
				resolve({success:true})
			}).on("error",(error)=>{
				resolve({success:true,error})
			}).on("end",()=>{
				console.log("SSH连接已结束")
			}).on("close",()=>{
				console.log("SSH连接已关闭")
			}).connect(this.server)
		})
	}
	//上传文件
	uploadFile({localPath,remoteFilePath}){
		return new Promise((resolve,reject)=>{
			this.conn.sftp((error,sftp)=>{
				if(err)reject({success:false,error});
				sftp.fastPut(localPath,remoteFilePath,(error,result)=>{
					if(err)reject({success:false,error})
					resolve({success:true,result})
				})
			})
		})
	}
	//执行ssh命令
	execSSH(command){
		return new Promise((resolve,reject)=>{
			this.conn.exec(command,(error,stream)=>{
				if (err || !stream)reject({success: false,error});
				stream.on("data",data=>{
					console.log(data.toString().trim())
				}).on("close",(code,signal)=>{
					resolve({success:true})
				}).stderr.on("data",data=>{
					resolve({success:false,error.data.toString().trim()})
				})
			})
		})
	}
	//结束连接
	connEnd(){
		this.conn.end();
	}
}
const {exec} = require("child_process");
//用子进程调用打包命令 node.js
//本地压缩删除构建操作
class File {
	construction(fileName){
		this.fileName=fileName;
	}
	//执行cli中的打包命令
	buildProject(){
		console.log("---开始编译打包文件---");
		return new Promise((resolve,reject)=>{
			exec(Config.buildCommand,{maxBuffer: 200000 * 1024},async (error,stdout,stderr)=>{
				if (error) {
            		console.error(error);
            		reject({
              			error,
              			success: false,
            		});
          		} else if (stdout) {
            		resolve({
              			stdout,
              			success: true,
            		});
          		} else {
            		console.error(stderr);
            		reject({
              			error: stderr,
              			success: false,
            		});
          		}
			})
		})
	}
	//压缩文件
	zipFile(filePath){
		return new Promise((reslove,reject)=>{
			letoutput = fs.createWriteStream(__dirname + "/example.zip");
			let archive = archiver("zip", {
        		zlib: { level: 9 }, // 设置压缩级别
      		});
      		output.on("close", function () {
        		console.log(`---压缩文件总共 ${archive.pointer()} 字节---`);
        		resolve({
          			success: true,
        		});
      		});
      		// 存档警告
      		archive.on("warning", function (err) {
        		if (err.code === "ENOENT") {
          			console.error("----stat故障和其他非阻塞错误----");
        		} else {
          			console.error("----压缩失败----");
        		}
        			reject(err);
      		});
      		archive.pipe(output);
      		archive.directory(filePath, false);
      		archive.finalize().then(() => {});
		})
	}
	//删除文件方法
	deleteLocalFile(){
		return new Promise((resolve, reject) => {
      		fs.unlink(this.fileName, function (error) {
        		if (error) {
          			reject({
            			success: false,
            			error,
          			});
        		} else {
          			resolve({
            			success: true,
          			});
        		}
      		});
    	});
	}
	stopProgress(){
	this.deleteLocalFile()
      .catch((e) => {
        console.error("----删除本地文件失败,请手动删除----");
        console.error(e);
      })
      .then(() => {
        console.log("----已删除本地压缩包文件----");
      });
	}
}
//操作函数
async function handleUpload (sshConfig,fileName) {
	let sshCon = new SSH(sshConfig);
	let sshRes = await sshCon.connectServer();
  	if(!sshRes || !sshRes.success){
  		console.error("服务器连接失败");
  		return false
  	}
  	console.log("---连接服务器成功,开始上传文件---");
  	let uploadRes = await sshCon.uploadFile({
  		localPath:path.resolve(__dirname,fileName);
  		remoteFilePath:`${sshConfig.catalog}/${fileName})`;
	})
	if (!uploadRes || !uploadRes.success) {
    	console.error("----上传文件失败,请重新上传----");
    	return false;
  	}
  	let timeStr = getTimeStr();
  	let file_bak = `${Config.buildDist}-${timeStr}-bak.zip`;
  	let zipCopyFile = await sshCon.execSsh(
    `if [ -d ${sshConfig.catalog}/${Config.buildDist} ]; then cd ${sshConfig.catalog}; zip -r ${file_bak} ${Config.buildDist}; else echo "not exists"; fi`
  	);
  	console.log("----开始解压文件----");
  	let zipRes = await sshCon
    .execSsh(
      `unzip -o ${sshConfig.catalog + "/" + fileName} -d ${sshConfig.catalog}/${
        Config.buildDist
      }`
    )
  	if (!zipRes || !zipRes.success) {
    	console.error("----解压文件失败,请手动解压zip文件----");
    	console.error(`----错误原因:${zipRes.error}----`);
  	}
	f (Config.deleteFile) {
    console.log("----解压文件成功,开始删除上传的压缩包----");
    // 注意:rm -rf为危险操作,请勿对此段代码做其他非必须更改
    let deleteZipRes = await sshCon
      .execSsh(`rm -rf ${sshConfig.catalog + "/" + fileName}`)
    if (!deleteZipRes || !deleteZipRes.success) {
      console.error("----删除文件失败,请手动删除zip文件----");
      console.error(`----错误原因:${deleteZipRes.error}----`);
    }
    // 结束ssh连接
  	sshCon.connEnd();
  
}
function getTimeStr() {
  let date = new Date();
  let year = date.getFullYear();
  let month = date.getMonth() + 1;
  let day = date.getDate();
  return `${year}_${month}_${day}`;
}
// 执行前端部署
(async () => {
  // 压缩包的名字
  let timeStr = getTimeStr();
  const fileName =
    `${Config.buildDist}-` +
    timeStr +
    "-" +
    Math.random().toString(16).slice(2) +
    ".zip";

  let file = new File(fileName);

  // 打包文件
  let buildRes = await file.buildProject().catch((e) => {
    console.error(e);
  });
  if (!buildRes || !buildRes.success) {
    console.error("----编译打包文件出错----");
    return false;
  }
  console.log(buildRes.stdout);
  console.log("----编译打包文件完成----");

  // 压缩文件

  let res = await file.zipFile(`${Config.buildDist}/`).catch(() => {});
  if (!res || !res.success) return false;
  console.log("----开始进行SSH连接----");
   await sshUpload(Config, fileName);
  console.log("----部署成功,正在为您删除本地压缩包----");
  file.stopProgress();
})();
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值