一、使用npm ssh2-sftp-client 插件
1、安装
npm install ssh2-sftp-client
2、使用
let Client = require('ssh2-sftp-client')
let sftp = new Client()
二、配置链接服务器信息 配置链接服务器信息之前,需要把自己的公钥放到服务器,如没有密钥对,则需要生成密钥对 不同服务器的链接配置
let serverConfig = {
dev: {
不同服务器的前端包放在服务器的相对路径
path: '/data/code/',
linkConfig: {
host: 'xxx.xxx.xxx.xx',
username: 'xxx',
privateKey: fs.readFileSync('C:/xxx/.ssh/id_rsa'),
port: xxx
}
},
test: {
path: '/data/code/',
linkConfig: {
host: 'xxx.xxx.xxx.xx',
username: 'xxx',
privateKey: fs.readFileSync('C:/xxx/.ssh/id_rsa'),
port: xxx
}
},
pre: {
path: '/data/code/',
linkConfig: {
host: 'xxx.xxx.xxx.xx',
username: 'xxx',
privateKey: fs.readFileSync('C:/xxx/.ssh/id_rsa'),
port: xxx
}
},
prod: {
path: '/data/code/',
linkConfig: {
host: 'xxx.xxx.xxx.xx',
username: 'xxx',
privateKey: fs.readFileSync('C:/xxx/.ssh/id_rsa'),
port: xxx
}
},
}
let config = {
// 前端包放在服务器的相对路径,如'/data/code'
path: '/data/code/',
// 服务器配置信息
serverConfig: {
host: 'xxx.xxx.xxx.xx',
username: 'xxx',
privateKey: fs.readFileSync('C:/xxx/.ssh/id_rsa'),
port: xxx
},
// 发送到服务器的名称,例如有3个开发服,dev1, dev2, dev3
serverName: ''
}
// 获取当前命令所在文件夹名称
const dirName = path.basename(__dirname)
三、根据命令初始化配置信息 从命令中获取部署环境,运行的命令 需要带参数 --serverName=xxx
const getEnv = () => {
const commandList = process.argv
const commandEnv = commandList.filter((item) => {
return item.indexOf('--serverName') > -1
})
if (commandEnv.length) {
return commandEnv[0].substring(commandEnv[0].indexOf('=') + 1)
} else {
console.log('命令需带上 打包后的服务器位置, --serverName=xxx')
return ''
}
}
不同的项目打包后可能放在不同的文件夹
const packageStorageDirectory = () => {
let path = '/html'
switch (dirName) {
case 'admin':
path += '/manage'
break
case 'pc':
path += '/www'
break
case 'm':
path += '/www/m'
break
}
return path
}
根据命令重新配置全局变量
const getConfigFromCommand = () => {
// 命令打包上传服务器名称
let serverName = getEnv()
if (!serverName) return
// 服务器地址后的文件路径
let serverFilePath = packageStorageDirectory()
config.serverName = serverName
config.serverConfig = serverConfig[serverName].linkConfig
config.path = serverConfig[serverName].path + serverFilePath
}
四、链接服务器
await sftp.connect(config.serverConfig)
五、进入对应的服务器,并删除远程文件,创建远程文件夹
判断文件夹存不存在
// 判断文件夹存不存在
const exists = async (originPath) => {
return await sftp.exists(originPath)
}
// 判断父级目录是否是文件夹
const parentIsDir = async (originPath) => {
const parentDir = originPath.substring(0, originPath.lastIndexOf('/'))
let info = await sftp.stat(parentDir)
return info.isDirectory
}
// 判断父级目录是否存在
const parentExists = async (originPath) => {
const parentDir = originPath.substring(0, originPath.lastIndexOf('/'))
return await exists(parentDir)
}
特殊处理文件夹
// pc文件夹下面,不能删除的文件夹
const pcCantRemoveList = ['m', 'xxx']
// 删除除参数外的文件
const removeFilesAndFolders = async (excludeList = pcCantRemoveList) => {
const files = await sftp.list(config.path)
const fileNames = files.map(file => file.name)
for (const fileName of fileNames) {
if (!excludeList.includes(fileName)) {
let filePath = config.path + '/' + fileName
let fileInfo = await sftp.stat(filePath)
if (fileInfo && fileInfo.isDirectory) {
const removeReulst = await sftp.rmdir(filePath, true)
} else {
const removeFileReulst = await sftp.delete(filePath)
}
}
}
}
链接服务器成功后
/**
*
* @param {*} originPath 远程文件目录,
* @param {*} targetPath 本地文件目录
*/
const serverOperate = async (originPath, targetPath) => {
try {
// 检查父级目录是否存在
const parentFlag = await parentExists(originPath)
if (!parentFlag) {
console.log('父级目录不存在')
return
}
// 检查父级目录是否存在
const parentIsDirFlag = await parentIsDir(originPath)
if (!parentIsDirFlag) {
console.log('父级不是一个文件夹')
return
}
// 如果文件存在
if (dirName == 'pc') {
// 如果pc端,那么不能全部删除,因为pc端里面有移动端文件夹 m
await removeFilesAndFolders()
} else {
const flag = await exists(originPath)
if (flag) {
// 删除当前目录下所有文件和子文件夹
await sftp.rmdir(originPath, true)
}
// 创建文件夹
await sftp.mkdir(originPath)
}
// 更新代码
await sftp.uploadDir(targetPath, originPath);
console.log('文件夹替换成功!');
} catch (err) {
console.error(err.message);
}
}
六、完整流程
// 连接服务器
const linkedServer = async () => {
try {
// 初始配置
getConfigFromCommand()
// 链接服务器
await sftp.connect(config.serverConfig)
// 获取路径目录
// await listRemoteDirectory(config.path)
// 本地目录
const targetPath = path.join(__dirname, './dist')
await goServer(config.path, targetPath)
} catch (err) {
console.error('部署失败')
} finally {
sftp.end()
}
}
linkedServer()
七、package.json配置命令
"scripts": {
"build:devServer1": "vue-cli-service build --mode development & node deploy.js --serverName=dev1",
"build:devServer2": "vue-cli-service build --mode development & node deploy.js --serverName=dev2",
"build:testServer": "vue-cli-service build --mode test & node deploy.js --serverName=test",
}
八、deploy.js文件全部内容
import path from 'path'
import fs from 'fs'
import Client from 'ssh2-sftp-client'
import { fileURLToPath } from 'url'
// 获取当前命令所在文件夹名称
const __filename = fileURLToPath(import.meta.url)
let dirPath = path.dirname(__filename)
const dirName = path.basename(dirPath)
let serverConfig = {
dev: {
// 不同服务器的前端包放在服务器的相对路径
path: '/data/code/',
linkConfig: {
host: 'xxx.xxx.xxx.xx',
username: 'xxx',
privateKey: fs.readFileSync('C:/xxx/.ssh/id_rsa'),
port: xxx
}
},
test: {
path: '/data/code/',
linkConfig: {
host: 'xxx.xxx.xxx.xx',
username: 'xxx',
privateKey: fs.readFileSync('C:/xxx/.ssh/id_rsa'),
port: xxx
}
},
prod: {
path: '/data/code/',
linkConfig: {
host: 'xxx.xxx.xxx.xx',
username: 'xxx',
privateKey: fs.readFileSync('C:/xxx/.ssh/id_rsa'),
port: xxx
}
},
}
let config = {
// 前端包放在服务器的相对路径,如'/data/code'
path: '/data/code/',
// 服务器配置信息
serverConfig: {
host: 'xxx.xxx.xxx.xx',
username: 'xxx',
privateKey: fs.readFileSync('C:/xxx/.ssh/id_rsa'),
port: xxx
},
// 发送到服务器的名称,例如有3个开发服,dev1, dev2, dev3
serverName: ''
}
// 获取命令走哪一个环境
const getEnv = () => {
const commandList = process.argv
const commandEnv = commandList.filter((item) => {
return item.indexOf('--serverName') > -1
})
if (commandEnv.length) {
return commandEnv[0].substring(commandEnv[0].indexOf('=') + 1)
} else {
console.log('命令需带上 打包后的服务器位置, --serverName=xxx')
return ''
}
}
// 不同的项目打包后可能放在不同的文件夹
const packageStorageDirectory = () => {
let path = '/html'
switch (dirName) {
case 'admin':
path += '/manage'
break
case 'pc':
path += '/www'
break
case 'm':
path += '/www/m'
break
}
return path
}
// 根据命令重新配置全局变量
const getConfigFromCommand = () => {
// 命令打包上传服务器名称
let serverName = getEnv()
if (!serverName) return
// 服务器地址后的文件路径
let serverFilePath = packageStorageDirectory()
config.serverName = serverName
config.serverConfig = serverConfig[serverName].linkConfig
config.path = serverConfig[serverName].path + serverFilePath
}
// 判断文件夹存不存在
const exists = async (originPath) => {
return await sftp.exists(originPath)
}
// 判断父级目录是否是文件夹
const parentIsDir = async (originPath) => {
const parentDir = originPath.substring(0, originPath.lastIndexOf('/'))
let info = await sftp.stat(parentDir)
return info.isDirectory
}
// 判断父级目录是否存在
const parentExists = async (originPath) => {
const parentDir = originPath.substring(0, originPath.lastIndexOf('/'))
return await exists(parentDir)
}
// pc文件夹下面,不能删除的文件夹
const pcCantRemoveList = ['m', 'xxx']
// 删除除参数外的文件
const removeFilesAndFolders = async (excludeList = pcCantRemoveList) => {
const files = await sftp.list(config.path)
const fileNames = files.map(file => file.name)
for (const fileName of fileNames) {
if (!excludeList.includes(fileName)) {
let filePath = config.path + '/' + fileName
let fileInfo = await sftp.stat(filePath)
if (fileInfo && fileInfo.isDirectory) {
const removeReulst = await sftp.rmdir(filePath, true)
} else {
const removeFileReulst = await sftp.delete(filePath)
}
}
}
}
/**
*
* @param {*} originPath 远程文件目录,
* @param {*} targetPath 本地文件目录
*/
const serverOperattion = async (originPath, targetPath) => {
try {
// 检查父级目录是否存在
const parentFlag = await parentExists(originPath)
if (!parentFlag) {
console.log('父级目录不存在')
return
}
// 检查父级目录是否存在
const parentIsDirFlag = await parentIsDir(originPath)
if (!parentIsDirFlag) {
console.log('父级不是一个文件夹')
return
}
// 如果文件存在
if (dirName == 'pc') {
// 如果pc端,那么不能全部删除,因为pc端里面有移动端文件夹 m
await removeFilesAndFolders()
} else {
const flag = await exists(originPath)
if (flag) {
// 删除当前目录下所有文件和子文件夹
await sftp.rmdir(originPath, true)
}
// 创建文件夹
await sftp.mkdir(originPath)
}
// 更新代码
await sftp.uploadDir(targetPath, originPath);
console.log('文件夹替换成功!');
} catch (err) {
console.error(err.message);
}
}
// 连接服务器
const linkedServer = async () => {
try {
// 初始配置
getConfigFromCommand()
// 链接服务器
await sftp.connect(config.serverConfig)
// 获取路径目录
// await listRemoteDirectory(config.path)
// 本地目录
const targetPath = path.join(__dirname, './dist')
await serverOperattion(config.path, targetPath)
} catch (err) {
console.error('部署失败')
} finally {
sftp.end()
}
}
linkedServer()