实习和工作我用过的前端包上传到测试就两种方法:FTP/SFTP和Jenkins(也就是CI/CD自动部署),目前一直是手动拖dist包,最近感觉比较麻烦,让ai帮我写了个脚本去部署。
一、FTP、SFTP
手动拖dist包到服务器上,FTP是文件传输协议、SFTP是基于SSH的更安全的文件传输协议,我用的工具是FileZilla,还可以用WinSCP。输入服务器IP和用户名密码连接之后就把dist包手动拖进去,之后刷新页面就ok了
二、CI/CD自动部署
CI = Continuous Integration(持续集成):代码 push 后自动构建、测试
CD = Continuous Deployment(持续部署):构建成功后自动发布到服务器
需要在项目里创建CI文件,告诉系统什么时候打包(push到哪个分支)、几点上传、怎么打包和上传
三、脚本化部署
我们现在用的是第一种,手动拖到FileZilla里,时间长了我觉得有点麻烦,然后让ai帮我写了个脚本,我的想法是创建一个中间文件夹,新的包打好之后先放这个新文件夹里,然后把当前项目里的dist包命名为dist_时间戳的,再把新的包拿出来
新加了这两个命令:
"deploy:sit": "node deploy/deploy.js sit",
"deploy:uat": "node deploy/deploy.js uat",
在项目根目录下创建deploy文件夹(注意不要放在src里,src里是会被打包上去的)
deploy.js:
const { exec } = require('child_process');
const path = require('path');
const fs = require('fs');
const readline = require('readline');
// ============================================================
// 使用方式:
// node deploy/deploy.js sit → 部署到 SIT 环境
// node deploy/deploy.js → 交互式选择环境
// ============================================================
// 获取环境参数
let env = process.argv[2];
// 如果没有指定环境,交互式选择
if (!env) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log('\n 请选择部署环境:');
console.log(' 1. sit (SIT 测试环境)');
console.log(' 2. uat (UAT 测试环境)');
console.log(' 3. 退出');
rl.question('\n请输入编号 (1-3): ', (answer) => {
const map = { '1': 'sit', '2': 'uat' };
if (map[answer]) {
rl.close();
deploy(map[answer]);
} else {
console.log('已退出');
rl.close();
}
});
} else {
deploy(env);
}
// ============================================================
// 部署主函数
// ============================================================
function deploy(env) {
console.log(`\n 开始部署到 ${env} 环境...\n`);
// 1. 加载配置文件
const configPath = path.join(__dirname, `config.${env}.js`);
if (!fs.existsSync(configPath)) {
console.error(`配置文件不存在: ${configPath}`);
console.log('请先创建 deploy/config.' + env + '.js 文件');
process.exit(1);
}
const config = require(configPath);
console.log(`环境: ${config.env}`);
console.log(`服务器: ${config.server.host}`);
console.log(`应用路径: ${config.remotePath}`);
console.log(`打包命令: ${config.buildCommand}`);
console.log('');
// 2. 确认部署
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question(`确认部署到 ${env} 环境?(y/N) `, (answer) => {
rl.close();
if (!/^[Yy]$/.test(answer)) {
console.log('已取消部署');
process.exit(0);
}
// 3. 检查服务器连接
checkServer(config);
});
}
// ============================================================
// 检查服务器连接
// ============================================================
function checkServer(config) {
console.log(`\n 检查服务器连接 ${config.server.host}...`);
const checkCmd = `ssh -p ${config.server.port} ${config.server.username}@${config.server.host} "echo '连接成功'"`;
exec(checkCmd, (err, stdout) => {
if (err) {
console.error('无法连接服务器');
console.error('请确认:');
console.error(' 1. 已连接内网 VPN');
console.error(` 2. 可以 ping 通 ${config.server.host}`);
console.error(` 3. SSH 端口 ${config.server.port} 是否开放`);
process.exit(1);
}
console.log(` ${stdout.trim()}`);
doBuild(config);
});
}
// ============================================================
// 打包
// ============================================================
function doBuild(config) {
console.log(`\n 正在打包 (${config.buildCommand})...`);
const build = exec(config.buildCommand, {
cwd: path.join(__dirname, '..')
});
build.stdout.on('data', (data) => {
console.log(data);
});
build.stderr.on('data', (data) => {
console.error(data);
});
build.on('close', (code) => {
if (code !== 0) {
console.error(`打包失败,退出码: ${code}`);
process.exit(1);
}
// 检查 dist 是否存在
const distPath = path.join(__dirname, '../dist');
if (!fs.existsSync(distPath)) {
console.error('dist 文件夹不存在,打包可能有问题');
process.exit(1);
}
const distSize = fs.statSync(distPath);
if (distSize.size === 0) {
console.error('dist 文件夹为空');
process.exit(1);
}
console.log('打包完成');
doUpload(config);
});
}
// ============================================================
// 上传
// ============================================================
function doUpload(config) {
const releaseDistPath = `${config.remotePath}/releases/dist`;
console.log(`\n 上传到 ${releaseDistPath}...`);
// 先确保 releases/dist 目录存在(使用 --delete 会清空旧内容)
const mkdirCmd = `ssh -p ${config.server.port} ${config.server.username}@${config.server.host} "mkdir -p ${releaseDistPath}"`;
exec(mkdirCmd, (err) => {
if (err) {
console.error('创建 releases/dist 目录失败');
process.exit(1);
}
// 上传到 releases/dist
const rsyncCmd = `rsync -avz --delete -e "ssh -p ${config.server.port}" ./dist/ ${config.server.username}@${config.server.host}:${releaseDistPath}/`;
const rsync = exec(rsyncCmd, { cwd: path.join(__dirname, '..') });
rsync.stdout.on('data', (data) => console.log(data));
rsync.stderr.on('data', (data) => console.error(data));
rsync.on('close', (code) => {
if (code !== 0) {
console.error(`上传失败,退出码: ${code}`);
process.exit(1);
}
console.log('上传完成');
doSwitchVersion(config);
});
});
}
// ============================================================
// 切换版本(重命名老版本 + 移出新版本)
// ============================================================
function doSwitchVersion(config) {
const now = new Date();
const dateStr =
String(now.getFullYear()) +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0') +
'_' +
String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0');
const appPath = config.remotePath;
const oldDistPath = `${appPath}/dist`;
const backupPath = `${appPath}/dist_${dateStr}`;
const newDistPath = `${appPath}/releases/dist`;
console.log(`\n 切换版本...`);
console.log(` 老版本备份为: dist_${dateStr}`);
console.log(` 新版本从: releases/dist -> dist`);
// 原子操作:重命名老版本 → 移出新版本
const switchCmd = `ssh -p ${config.server.port} ${config.server.username}@${config.server.host} "
if [ -d ${oldDistPath} ]; then
echo '备份老版本...' && mv ${oldDistPath} ${backupPath}
else
echo '首次部署,无需备份'
fi &&
echo '部署新版本...' && mv ${newDistPath} ${oldDistPath} &&
echo '切换完成'
"`;
exec(switchCmd, (err, stdout, stderr) => {
if (err) {
console.error('切换版本失败');
console.error(stderr);
process.exit(1);
}
console.log(stdout);
console.log(`\n 部署完成!`);
console.log(`当前版本: ${oldDistPath}`);
console.log(`备份版本: ${backupPath}`);
console.log('\n 全部完成!');
});
}
其实命令也可以不带具体环境,这里做了容错可以手动输入,我嫌麻烦直接写死了
同目录下的config.sit.js:
module.exports = {
env: 'sit',
server: {
host: 'xxxx', // 内网开发服务器
username: 'xxx',
password: 'xxxx',
port: xx,
},
remotePath: 'xxxx',
buildCommand: 'node ./addVersion.js && vue-cli-service build --mode sit',
//这里是打包命令,所以这个命令是打包+部署
};
上传成功之后可以去服务器上检查一下是否多了release文件夹和一个带时间戳的dist旧包