Vue项目使用ssh2-sftp-client实现打包自动上传到服务器(完整教程)

告别手动拖拽上传!本教程将手把手教你如何通过ssh2-sftp-client实现Vue项目打包后自动上传到服务器,提升部署效率300%。🚀

一、需求场景与解决方案

在Vue项目开发中,每次执行npm run build后都需要手动将dist目录上传到服务器,既耗时又容易出错。通过ssh2-sftp-client库,我们可以实现:

  1. 打包完成后自动上传文件到服务器
  2. 支持覆盖更新和增量上传
  3. 保留文件权限和目录结构
  4. 部署过程可视化(进度条显示)

二、环境准备

确保你的开发环境已安装:

  • 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);
    });
相关推荐
-SGlow-1 小时前
MySQL相关概念和易错知识点(3)(表内容的CURD、内置函数)
linux·运维·服务器·数据库·mysql
不搞学术柒柒2 小时前
vscode、cursor无密码ssh远程连接服务器(配置密钥)
服务器·ssh·github
UQWRJ2 小时前
菜鸟教程Linux ViVimYumApt笔记
linux·运维·笔记
cpsvps3 小时前
文件系统完整性校验工具在美服安全审计中的关键作用与实施步骤
服务器·网络·架构
sz66cm3 小时前
Linux基础 -- 内核快速向用户态共享内核变量方案之ctl_table
linux·运维·服务器
努力一点9484 小时前
ubuntu22.04系统入门 linux入门(二) 简单命令 多实践以及相关文件管理命令
linux·运维·服务器·人工智能·gpu算力
MediaTea4 小时前
Python 库手册:getopt Unix 风格参数解析模块
服务器·开发语言·python·unix
wb1894 小时前
企业WEB应用服务器TOMCAT
运维·前端·笔记·tomcat·云计算