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);
    });
相关推荐
阿干tkl7 分钟前
K3s + Harbor 端口冲突问题解决方案(Harbor 使用 80 端口)
运维
qq_3391911419 分钟前
uv 设置系统默认版本, linux设置uv
linux·运维·uv
小猿姐22 分钟前
当KubeBlocks遇上国产数据库之Kingbase:让信创数据库“飞得更高”
运维·数据库·云原生
似水এ᭄往昔28 分钟前
【Linux】--进程概念
linux·运维·服务器
IDIOT___IDIOT28 分钟前
Linux 使用 `cp` 命令导致挂载点被覆盖问题记录
linux·运维·服务器
李彦亮老师(本人)1 小时前
Rocky Linux 9.x 安全加固实战指南:从系统初始化到生产级防护
linux·运维·安全·rocky
RisunJan1 小时前
Linux命令-mount(用于挂载Linux系统外的文件)
linux·运维·服务器
国冶机电安装1 小时前
其他弱电系统安装:从方案设计到落地施工的完整指南
大数据·运维·网络
蓝天守卫者联盟11 小时前
玩具喷涂废气治理厂家:行业现状、技术路径与选型指南
大数据·运维·人工智能·python