web前端 node + rsync 上传到服务器

140 阅读1分钟

一、安装rsync

在官网下载安装rsync

二、配置环境变量

window 配置环境变量,编辑完,重启客户端

image.png

三、使用 1、rsync链接的服务器是没办法使用命令创建多层目录, 所以需要一个创建本地文件夹的目录,本地文件夹目录格式与远程一样,在上传文件到远程失败的时候,先上传一次模板目录

createTemplate.js 在E盘创建一个 服务器的新目录结构,用于rsync 创建新的平级目录包

import fs from 'fs'
import { exec } from 'child_process'
const platformList = ['m', 'xxx']

// 创建发布ftp目录模板
export const createTemplate = async () => {
  // 创建manage 目录
  fs.mkdirSync(`E:/release/tempate/manage/`, { recursive: true })
  fs.mkdirSync(`E:/release/tempate/cdn/`, { recursive: true })
  // 创建 pc目录
  fs.mkdirSync(`E:/release/tempate/www/`, { recursive: true })
  // 创建 www下其他项目的,不需要的时候可以为空
  for (let i = 0; i < platformList.length; i++) {
    fs.mkdirSync(`E:/release/tempate/www/${platformList[i]}/`, { recursive: true })
  }
  return `E:/release/tempate/`
}

2、配置全局环境变量

const __filename = fileURLToPath(import.meta.url)
let dirPath = path.dirname(__filename)
const dirName = path.basename(dirPath)

// 环境配置
const useName = 'xxx
const moduleName = 'xxxx'
// 本地密码地址 ftp
const passwordPath = '/cygdrive/d/xxx/xxx.pwd'
// 远程服务器地址 
const serverPath = xxx.xxx.xxx.xxx
// 本地目录模版文件夹地址
const templatePath = 'E:/release/tempate'

3、声明一下前置处理函数

// 判断文件夹是否存在
const folderExists = (path) => {
  try {
    return fs.existsSync(path) && fs.statSync(path).isDirectory()
  } catch (err) {
    return false
  }
}

// 运行命令
const runCommand = (commandRow) => {
  return new Promise((resolve, reject) => {
    const child = exec(commandRow, (error, stdout, stderr) => {
      if (error) {
        console.error(`执行命令时出错:${error.message}`)
        reject(false)
        return
      }
      console.log('命令的标准输出', stdout)
      resolve(stdout)
    })
    // 监听子进程的标准输出
    child.stdout.on('data', (data) => {
      console.log('命令运行中', data)
    })
  })
}

// 获取对应的 rsync文件路径,把window的 路径转 rsync需要的路径格式,像D盘虚转为/cygdrive/d/
const getRsyncFolderPath = (path) => {
  let driveLetter = path.charAt(0).toLowerCase()
  const cygwinPath = path.replace(/\\/g, '/').replace(/^([A-Za-z]):/, `/cygdrive/${driveLetter}`)
  return cygwinPath
}

4、获取运行 rsync 运行命令

// 生成rsync提交命令
// --password-file='/cygdrive/d/xxx/xxx/xxx.pwd'  /cygdrive/d/xx/xx/rsync.pwd ftp密码存放地址
// e/xxx/xxx/ 本地代码地址
//  xxx@xxx.xxx.xxx.xxx::xxx/xxx/ 服务器地址路径 例如 useNme68.100.100::modeulename/test1/
const updateCommand = (releaseName = 'dev') => {
  // 项目对应的服务器 游戏商 gbshtml 游戏大厅 bshtml
  // 项目打包后的文件夹名称路径
  let dirpathStr = path.join(dirPath, './dist/')
  let commandStr = `rsync -av --progress --delete --password-file='/cygdrive/d/xxx/xxx/xxx.pwd' '${getRsyncFolderPath(dirpathStr)}' `
  switch (dirName) {
    case 'admin':
      commandStr += `${useName}@${serverPath}::moduleName}/${releaseName}/manage/`
      break
    case 'pc':
      // 删除的时候,忽略m文件夹,xxx文件夹
      commandStr = `rsync -av --progress --delete --exclude 'm/' --exclude 'xxx --password-file=${passwordPath}'${getRsyncFolderPath(dirpathStr)}' ${useName}@${serverPath}::${moduleName}/${releaseName}/www/`
      break
    case 'm':
      commandStr += `${useName}@${serverPath}::${moduleName}/${releaseName}/www/m/`
      break
  }
  return commandStr
}

5、运行rsync命令操作 因为ftp服务器可能不存在对应发布的文件夹,所以加了一个try catch,运行失败的时候,把模板文件夹上传到ftp服务器,再重新上传文件

// 上传代码到服务器
// -- recursive 递归创建文件夹
// serverName项目名称
const releaseToFrom = async (serverName) => {
  const releaseCommand = updateCommand(serverName)
  // 如果远程文件目录存在
  try {
    const data = await runCommand(releaseCommand)
    console.log('提交服务器', data)
  } catch {
    // 如果远程文件目录不存在
    // 生成远程服务器的部署文件目录格式的本地目录
    let sourcePath = `E:/release/tempate/`
    // 如果本地目录不存在,那么生成模板目录
    if (!folderExists(sourcePath)) {
      await createTemplate(platform)
    }
    // 把模板目录部署到远程服务器
    let commandStr = `rsync -av --progress --delete --recursive --password-file=${passwordPath} '${getRsyncFolderPath(sourcePath)}' ${useName}@${serverPath}::${moduleName}/${serverName}`
    let result= await runCommand(commandStr)
    console.log('创建远程模板文件夹', result)
    // 创建完成继续提交代码
    releaseToFrom(serverName)
    return
  }
}

6、updateToRelease.js代码内容

import path from 'path'
// 用于运行命令
import { exec } from 'child_process'
// 生成本地模板目录文件夹
import { createTemplate } from './createTemplateRelease.js'
import { fileURLToPath } from 'url'
import fs from 'fs'

const __filename = fileURLToPath(import.meta.url)
let dirPath = path.dirname(__filename)
const dirName = path.basename(dirPath)

// 环境配置
const useName = 'xxx
const moduleName = 'xxxx'
// 本地密码地址 ftp
const passwordPath = '/cygdrive/d/xxx/xxx.pwd'
// 远程服务器地址 
const serverPath = xxx.xxx.xxx.xxx
// 本地目录模版文件夹地址
const templatePath = 'E:/release/tempate'

// 判断文件夹是否存在
const folderExists = (path) => {
  try {
    return fs.existsSync(path) && fs.statSync(path).isDirectory()
  } catch (err) {
    return false
  }
}

// 运行命令
const runCommand = (commandRow) => {
  return new Promise((resolve, reject) => {
    const child = exec(commandRow, (error, stdout, stderr) => {
      if (error) {
        console.error(`执行命令时出错:${error.message}`)
        reject(false)
        return
      }
      console.log('命令的标准输出', stdout)
      resolve(stdout)
    })
    // 监听子进程的标准输出
    child.stdout.on('data', (data) => {
      console.log('命令运行中', data)
    })
  })
}

// 获取对应的 ftp本地文件路径,把window的 路径转 rsync需要的路径格式,像D盘虚转为/cygdrive/d/
const getRsyncFolderPath = (path) => {
  let driveLetter = path.charAt(0).toLowerCase()
  const cygwinPath = path.replace(/\\/g, '/').replace(/^([A-Za-z]):/, `/cygdrive/${driveLetter}`)
  return cygwinPath
}

// 生成rsync提交命令
// --password-file='/cygdrive/d/xxx/xxx/xxx.pwd'  /cygdrive/d/xx/xx/rsync.pwd ftp密码存放地址
// e/xxx/xxx/ 本地代码地址
//  xxx@xxx.xxx.xxx.xxx::xxx/xxx/ 服务器地址路径 例如 useNme68.100.100::modeulename/test1/
const updateCommand = (releaseName = 'dev') => {
  // 项目对应的服务器 游戏商 gbshtml 游戏大厅 bshtml
  // 项目打包后的文件夹名称路径
  let dirpathStr = path.join(dirPath, './dist/')
  let commandStr = `rsync -av --progress --delete --password-file='/cygdrive/d/xxx/xxx/xxx.pwd' '${getRsyncFolderPath(dirpathStr)}' `
  switch (dirName) {
    case 'admin':
      commandStr += `${useName}@${serverPath}::moduleName}/${releaseName}/manage/`
      break
    case 'pc':
      // 删除的时候,忽略m文件夹,xxx文件夹
      commandStr = `rsync -av --progress --delete --exclude 'm/' --exclude 'xxx --password-file=${passwordPath}'${getRsyncFolderPath(dirpathStr)}' ${useName}@${serverPath}::${moduleName}/${releaseName}/www/`
      break
    case 'm':
      commandStr += `${useName}@${serverPath}::${moduleName}/${releaseName}/www/m/`
      break
  }
  return commandStr
}

// 上传代码到服务器
// -- recursive 递归创建文件夹
// serverName项目名称
const releaseToFrom = async (serverName) => {
  const releaseCommand = updateCommand(serverName)
  // 如果远程文件目录存在
  try {
    const data = await runCommand(releaseCommand)
    console.log('提交服务器', data)
  } catch {
    // 如果远程文件目录不存在
    // 生成远程服务器的部署文件目录格式的本地目录
    let sourcePath = `E:/release/tempate/`
    // 如果本地目录不存在,那么生成模板目录
    if (!folderExists(sourcePath)) {
      await createTemplate(platform)
    }
    // 把模板目录部署到远程服务器
    let commandStr = `rsync -av --progress --delete --recursive --password-file=${passwordPath} '${getRsyncFolderPath(sourcePath)}' ${useName}@${serverPath}::${moduleName}/${serverName}`
    let result= await runCommand(commandStr)
    console.log('创建远程模板文件夹', result)
    // 创建完成继续提交代码
    releaseToFrom(serverName)
    return
  }
}