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);
    });
相关推荐
t***82112 分钟前
华为数据中心CE系列交换机级联M-LAG配置示例
服务器·华为·php
J***Q2928 分钟前
DevOps金融服务安全要求
运维·安全·devops
Dovis(誓平步青云)23 分钟前
《内核视角下的 Linux 锁与普通生产消费模型:同步原语设计与性能优化思路》
linux·运维·性能优化
D***t13128 分钟前
DevOps技能提升路径
运维·devops
xu_yule34 分钟前
Linux_13(多线程)页表详解+轻量级进程+pthread_create
linux·运维·服务器
草莓熊Lotso3 小时前
Git 分支管理:从基础操作到协作流程(本地篇)
大数据·服务器·开发语言·c++·人工智能·git·sql
江湖有缘3 小时前
Linux系统之htop命令基本使用
linux·运维·服务器
B***y8853 小时前
配置nginx访问本地静态资源、本地图片、视频。
运维·nginx
w***Q3506 小时前
Git工作流自动化
运维·git·自动化
qq_401700416 小时前
嵌入式用Unix时间的优势及其C语言转换
服务器·c语言·unix