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);
    });
相关推荐
我一定会有钱4 小时前
Linux爆音问题解决方法(隔一会会有奇怪噪音)
linux·运维·服务器
Dobby_056 小时前
【Ansible】变量与敏感数据管理:Vault加密与Facts采集详解
linux·运维·云原生·ansible
记忆不曾留7 小时前
unbuntu 20.04 docker 部署wordpress
运维·docker·容器·wordpress·独立站建站
l_tian_tian_7 小时前
SpringClound——网关、服务保护和分布式事务
linux·服务器·前端
zcz16071278217 小时前
服务器与客户端
运维·服务器
门思科技8 小时前
LoRaWAN 的网络拓扑全解析:架构、原理与应用实践
服务器·网络·人工智能·科技·物联网·架构
我的收藏手册8 小时前
Linux 网络命令大全
linux·服务器·网络
xx.ii8 小时前
28.Linux :通过源代码编译安装lamp
linux·运维·服务器
焊锡与代码齐飞8 小时前
嵌入式第三十五课!!Linux下的网络编程
linux·运维·服务器·开发语言·网络·学习·算法
2501_927773079 小时前
Linux操作系统编程——网络
linux·运维·网络