Vue项目使用ssh2-sftp-client实现打包自动上传到服务器(完整教程)
告别手动拖拽上传!本教程将手把手教你如何通过ssh2-sftp-client实现Vue项目打包后自动上传到服务器,提升部署效率300%。🚀
一、需求场景与解决方案
在Vue项目开发中,每次执行npm run build
后都需要手动将dist目录上传到服务器,既耗时又容易出错。通过ssh2-sftp-client
库,我们可以实现:
- 打包完成后自动上传文件到服务器
- 支持覆盖更新和增量上传
- 保留文件权限和目录结构
- 部署过程可视化(进度条显示)
二、环境准备
确保你的开发环境已安装:
- Node.js 14+
- Vue CLI创建的项目
- 服务器SSH连接信息(IP、用户名、密码/密钥
三、安装依赖
安装核心库和进度显示工具:
npm install ssh2-sftp-client progress --save-dev
npm install chalk --save-dev# 或
yarn add ssh2-sftp-client progress -D
四、安装依赖
配置package.json
"scripts": {"dev": "vite --mode development","look": "vite --mode production","build": "vite build --mode production","preview": "vite --mode production","deploy": "node deploy.js","build:deploy": "npm run build && npm run deploy"},
五、核心代码
在根目录上新建deploy.js 文件
import Client from 'ssh2-sftp-client';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs/promises';
import chalk from 'chalk';const server = {host: '',port: 22,username: '',password: '',remoteRoot: '/www/wwwroot'
};// 使用chalk定义颜色主题
const colors = {header: chalk.cyan.bold,success: chalk.green.bold,warning: chalk.yellow.bold,error: chalk.red.bold,file: chalk.blue,progress: chalk.magenta
};const __dirname = path.dirname(fileURLToPath(import.meta.url));
const localPath = path.resolve(__dirname, 'dist');const sftp = new Client();console.log(colors.header('🚀 开始部署操作'));
console.log(colors.header('===================='));
console.log(colors.header(`📡 连接 ${server.username}@${server.host}:${server.port}`));
console.log(colors.header(`📁 本地目录: ${localPath}`));
console.log(colors.header(`🌐 远程目录: ${server.remoteRoot}`));
console.log(colors.header('====================\n'));// 递归计算文件总数
async function getTotalFiles(dir) {const entries = await fs.readdir(dir, { withFileTypes: true });let count = 0;for (const entry of entries) {const fullPath = path.join(dir, entry.name);if (entry.isDirectory()) {count += await getTotalFiles(fullPath);} else if (entry.isFile() && !entry.name.includes('.DS_Store')) {count++;}}return count;
}sftp.connect({host: server.host,port: server.port,username: server.username,password: server.password,tryKeyboard: true
}).then(async () => {console.log(colors.success('🔑 认证成功,开始扫描本地文件...'));const totalFiles = await getTotalFiles(localPath);if (totalFiles === 0) {console.log(colors.warning('⚠️ 警告: 本地目录为空,没有文件需要上传'));await sftp.end();return;}console.log(colors.success(`📊 发现 ${totalFiles} 个文件需要上传\n`));console.log(colors.header('🚚 开始上传文件:'));console.log(colors.header('------------------------------------'));let uploadedCount = 0;return sftp.uploadDir(localPath, server.remoteRoot, {ticker: (localFile) => {uploadedCount++;const relativePath = path.relative(localPath, localFile);const progress = Math.round((uploadedCount / totalFiles) * 100);console.log(colors.progress(`[${uploadedCount.toString().padStart(3, ' ')}/${totalFiles}]`) +colors.file(` ${relativePath}`) +colors.progress(` (${progress}%)`));},filter: f => !f.includes('.DS_Store')});}).then(() => {console.log('\n' + colors.success('✅ 所有文件上传完成!'));console.log(colors.success('🏁 部署成功'));sftp.end();}).catch(err => {console.error('\n' + colors.error('❌ 严重错误: ' + err.message));console.error(colors.error('🔍 失败原因分析:'));if (err.message.includes('connect')) {console.error(colors.error('- 无法连接到服务器,请检查网络'));console.error(colors.error('- 防火墙设置可能阻止了连接'));console.error(colors.error('- 服务器可能未运行SSH服务'));} else if (err.message.includes('Authentication')) {console.error(colors.error('- 用户名或密码错误'));console.error(colors.error('- 服务器可能禁用了密码登录'));console.error(colors.error('- 尝试使用SSH密钥认证'));} else if (err.message.includes('No such file')) {console.error(colors.error('- 本地文件不存在或路径错误'));console.error(colors.error('- 检查本地dist目录是否存在'));}console.error('\n' + colors.error('🛠️ 诊断命令:'));console.error(colors.error(`telnet ${server.host} ${server.port}`));console.error(colors.error(`ssh ${server.username}@${server.host}`));if (sftp) sftp.end();process.exit(1);});