目录
- 传统的打包部署 太繁琐了,特别是在测试阶段,有时候还是几个项目同时搞,不停的打包上传,一天要操作n多次,有时候人都要晕了。大公司的团队都有运维,只要上传代码,一键打包部署,小企业小团队就没那么幸福了,只能自己搞。
- 文章主要描述了一下当前的几种能实现自动化打包部署(或者说有些算半自动化吧)的方案和实现。基本上从环境安装到具体实现都有了,可能写质量稍微差了点但基本上应该能看懂,需要你具备一定的服务器知识和了解基本的命令行操作。因为文章是分了几次写的,可能有些地方看起来会感觉有点奇怪。毕竟不是干运维的,有说错的地方或者有更优雅的方法欢迎指导。整篇文章由个人纯手写,写作不易,如果对你有所帮助点个赞支持一下吧!谢谢。就不废话了,上正文。
自动化部署的几种方式
一.Jenkins+Docker
- 应该是最优的方案,这种方式虽然好用,但是安装配置麻烦,得花时间去研究摸索,毕竟不是专业干运维的。搞起来还是比较吃力。
二.git仓库推送事件+api接口
- 市面的主流代码管理仓库基本都支持,如 gitee,github,gitlab。在每次推送代码时,软件平台都提供了一个hooks事件,可以配置一个自己提前准备好的api接口,当仓库收到推送消息时,会像我们提前配置好的接口发货一个post请求,参数包含了分支信息,推送信息和提前配置好访问令牌信息,用于校验身份,当前接口收到请求后 在服务器上执行提前准备好的脚本(或者将脚本写在项目里面)。主要流程:拉去代码>安装依赖>打包>将打包好的文件拷贝到nginx托管的目录下,就算完成了。当然前提条件是得先把nginx配置好
三.服务器脚本手动执行
- 当代码推送后,手动到服务器上去执行提前写好的脚本实现打包部署。
四.nodejs本地一键打包并上传
- 上面的三种方式都是在服务器上进行打包,并且需要将代码先上传到git.在服务器上打包需要吃一些服务器资源,当然也没啥问题,一般来说测试环境都是本地的物理机,性能不用太担心。这种方式就不用在服务器上打包,也不用提交代码,只要在本地打包后 将文件上传到nginx托管的目录下,本地打包方式还是比较多的,这里说一下我的方案,先写一个nodejs程序,主要模块有:文件压缩,文件上传,执行服务器命令,用于解压文件,然后修改npm的启动运行命令(具体看第五项实现),当打包完成后,在去执行nodejs程序进行压缩上传到指定目录和执行命令解压解决,当然前提条件是得先把nginx配置好
以上就是我能想到的几种方式了,除了Jenkins+Docker的方案不太好弄,剩下三种还是容易实现的,文章主要实现了 二/三/四种解决方法。个人觉得没有最好的方案,只有更合适的方案,一个人开发的话 个人觉得第四种方法比较合适,多人开发比较适合剩下的三种。
环境准备,除了nginx是所有方式都是必须的,第二种和第三种都要安装nodejs环境和git,一般公司的服务器上可能都安装了nginx和git, node可能没有,文章会说明怎么安装。
前期环境安装和准备
- 方案二和方案三需要在服务器安装git,node,nginx,可能服务器上已经安装了就不用了就不用装了。
git 安装
yum install git -y
nodejs安装
- (如果你需要在服务器上打包的话建议安装nvm或者其它类似nvm的产品,因为服务器上打包需要安装依赖,有时候node版本不对会导致有些依赖安装补上,特别是一些比较老的项目)。
# 下载node包
wget https://nodejs.org/dist/v14.21.2/node-v18.16.0-linux-x64.tar.xz
# 如果下载不了可以手动到官网下载服务器版后在把文件手动上传到服务器
#解压
tar -xvf node-v18.16.0-linux-x64.tar.xz
#重命名
mv node-v18.16.0-linux-x64 nodejs
# 通常我会把nodejs放在 /usr/local/
# 可以直接下载或上传文件到这个目录进行解压
# 准备好之后创建软连接
# 需要注意的是你的目录不能弄错了不然不得行
ln -s /usr/local/node/bin/node /usr/bin/node
ln -s /usr/local/node/bin/npm /usr/bin/npm
# 删除一个软连接
rm /usr/bin/node
# 然后应该就可以了,可以执行node -v查看版本
# 更换npm镜像为淘宝地址
npm config set registry https://registry.npm.taobao.org
# 然后执行一下node -v 和npm -v 看看是否生效
安装pm2工具
- 用普通的nodejs启动方式一旦窗口关闭后项目就停止了,pm2 可用于在后台启动node项目,并且当node项目报错挂机了并会自动重启
npm i pm2 -g
# 安装完成后在需要被启动的项目中的package.json配置启动命令
"scripts": {
"start": "pm2 start ./app.js --name webhook --watch",
"stop": "pm2 stop webhook" # 用于停止服务
},
# 有时候可能会出现pm2 不是命令错误,这是pm2没有被安装在指定的目录下,需要手动处理一下
# 先通过find / -name pm2 找到pm2的路径
接着执行ln -s /root/home/installation-packages/node.js/node.js/bin/pm2 /usr/local/bin命令为pm2程序添加软链接。其中/root/home/installation-packages/node.js/node.js/bin/pm2就是上一步查找到pm2程序的所在路径,而/usr/local/bin是根据$PATH环境变量得到的路径,在这些目录下的程序可以在系统的任意位置直接调用执行,所以将软链接添加到此目录下。
# 一般pm2安装之后目录都是在node/bin/pm2 ,
# 最后在执行pm2 -v看看是否可以用
- pm2常用的命令
- pm2 start 启动项目
- pm2 list 查看进程
- pm2 show 0 或者 pm2 info 0 查看进程详细信息,0为PM2进程id
- pm2 monit 进入监视页面,监视每个node进程的CPU和内存的使用情况
- pm2 stop all 停止PM2列表中所有的进程
- pm2 stop 0 停止PM2列表中进程为0的进程
- pm2 reload all 重载PM2列表中所有的进程
- pm2 reload 0 重载PM2列表中进程为0的进程
- pm2 restart all 重启PM2列表中所有的进程
- pm2 restart 0 重启PM2列表中进程为0的进程
- pm2 delete 0 删除PM2列表中进程为0的进程
- pm2 delete all 删除PM2列表中所有的进程
- pm2 logs 显示所有进程的日志
- pm2 logs 0 显示进程id为 0 的日志
- pm2 flush 清空所有日志文件
- pm2 reloadLogs 重载所有日志
- npm install pm2@lastest -g 安装最新的PM2版本
- pm2 updatePM2 升级pm2
- pm2 --help 查看更多的命令
nginx源码安装
- 服务器上几乎都安装了nginx ,一般不需要在安装,只要知道项目托管在那个目录下即可。
# 安装nginx源码编译工具gcc
yum install gcc patch libffi-devel python-devel zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readlinedevel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel openssl openssl-devel -y
# 开始下载nginx源代码
cd /usr/local
# 下载
wget -c https://nginx.org/download/nginx-1.12.0.tar.gz
# 解压
tar -zxvf nginx-1.12.0.tar.gz
# 进入nginx目录中
cd ./nginx-1.12.0
# 执行当前目录下的 .configure脚本进行配置编译安装信息
# --prefix 是一个参数,指定要将nginx安装到哪里 后面的是支持一些其他功能
./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_stub_status_module
# 开始编译安装
make && make install
# 编译完成后进入上面配置的安装目录
cd /usr/local/nginx
# nginx启动命令就放在sbin目录中 进入这个目录
cd ./sbin
# 执行当前目录下的nginx文件进行启动nginx
./nginx
# 到此 nginx就安装并启动成功了
- 站点配置 (写一个作为参考案例)
#进入到nginx的配置目录
cd /usr/local/nginx/conf
# 打开配置文件
vi nginx.conf
# 按 i 进入编辑模式
# 写入配置 具体配置放下面,
# 完成后按 ESC 在输入 :wq 保存退出
# 如果你不熟悉可以让后端给你弄或者用ftp在本地用text文件打开进行编辑
- 具体配置
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 4403;
server_name localhost;
location / {
root /usr/local/nginx/html/hxfH5/;
index index.html index.htm;
try_files $uri $uri/ /index.html = 404;
}
location /prod-api/ {
proxy_pass http://127.0.0.1:8899/;
proxy_http_version 1.1;
proxy_send_timeout 300;
proxy_read_timeout 300;
proxy_connect_timeout 300;
client_max_body_size 500M;
}
}
}
具体实现
- 只实现了二,三,四种方案
第二种方案的实现
- 通过git推送事件(webHook)去调用自定义的api,通过去执行脚本打包。
- git ssh密钥生成及配置,用于拉去代码时不用输入账号密码。
ssh-keygen -t RSA -b 4096 # 一直回车 或者根据自己情况设置参数
# ssh-keygen 生成公钥的意思
# -t rsa 使用rsa算法
# -b 4096 公钥的大小 4096个字节
# 查看公钥 公钥创建之后会显示公钥的所在目录
cat /root/.ssh/id_rsa.pub
# 将公钥配置到git上,一个账号可以配置公钥,一个项目也可以配置公钥。
# 这里已gitee的单个项目配置为例(也可以全局配置):打开项目/管理/部署公钥管理/添加公钥。 其他平台也差不多 ,全局配置:打开设置/ssh公钥。这个配置不止能服务器上用,在windos电脑的git也一样能用
# 然后在服务器上克隆代码,先cd 进入项目文件夹
git clone ssh克隆地址 (注意:必须用ssh拉取地址)
- 推送事件调用api配置(这里已gitee为例)
打开gitee上你要配置推送事件的项目>点击 “管理” > WebHooks > 添加WebHooks ,如下图填写你的api地址和密钥
配置完成后添加。
- api服务编写(这里以node为例)
创建一个目录webhook-server 进入目录下进行npm包初始化 npm init -y , 创建一个app.js
- app.js
const http = require('http')
const { spawn } = require('child_process')
const mail = require('nodemailer');
const transport = mail.createTransport({
host: 'smtp.qq.com', //主机qq邮箱,如果是126邮箱,请换成smtp.126.com
port: 465, //STMP端口号
secure:true,
auth: {
user: "", //你的邮箱账号
pass: "" //授权码
}
});
function openMail() {
var options = {
from: '2654392501@qq.com', //YOURNAME将变为你邮件的名字
to: "2654392501@qq.com", //收件人,多个用逗号隔开
subject: '前端发布更新提醒', //主题
html:'<p>Alink聚合前端已发布更新</p>',
attachments:[]
// attachments: [{ //附件
// filename: "附加文件", //附件名字
// path: __dirname + "/user.js" //附件路径
// }]
};
transport.sendMail(options, function (err, result) {
if (err) return console.log(err);
console.log('更新邮件发送成功');
});
}
const server = http.createServer(function (req, res) {
if (req.method === 'POST' && (req.url === '/web' || req.url === '/webServer')) {
let buffers = ''
req.on('data', be => {
buffers += be
})
req.on('end', () => {
const body = buffers
const token = req.headers['x-gitee-token']
if (token == 123456) {
console.log('密码验证成功');
let child = {}
let buffers = ''
if (req.url == '/web') {
child = spawn('sh', ['./web-vue.sh'])
} else {
child = spawn('sh', ['./web-server.sh'])
}
child.stdout.on('data', buf => {
console.log('--------', buf);
buffers += buf
})
child.stdout.on('end', buf => {
console.log(buffers, '脚本执行完成');
openMail()
})
} else {
console.log('密码验证失败');
}
})
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({
ok: true
}))
console.log('项目推送了');
} else {
res.end('not font')
}
})
server.listen(3001, console.log('webhook服务启动成功'))
- 在当前项目目录下准备好要执行的脚本
web-vue.sh 用于更新打包vue项目
cd '/porejct/yw-web-vue'
echo '先放弃变更'
git reset --hard origin/master
git clean -f
echo '拉新代码'
git pull origin master
echo '安装依赖'
# npm i 测试环境可以不用每次都更新依赖,
echo '编译'
npm run build
echo '删除原文件'
rm -rf /web/yw-vue
echo '写入新文件'
mv dist /web/yw-vue
web-server.sh 用于更新服务端项目
cd '/porejct/yw-web-server'
echo '放弃变更'
git reset --hard origin/master
git clean -f
echo '拉新代码'
git pull origin master
echo '安装依赖'
npm i
echo '停止服务'
npm run stop
echo '重启服务'
npm run start
- 配置服务启动 这里使用了pm2启动 ,在package.json中进行配置, 执行npm run start启动,npm run stop 停止服务。
{
"scripts": {
"start": "pm2 stop webhook && pm2 start ./app.js --name webhook --watch",
"stop": "pm2 stop webhook"
}
}
- 服务启动后,当收到推送事件发送的请求时就执行脚本进行打包。
第三种方案的实现
- 服务器手动执行脚本打包部署。
- git ssh密钥生成及配置,用于拉去代码时不用输入账号密码。
ssh-keygen -t RSA -b 4096 # 一直回车 或者根据自己情况设置参数
# ssh-keygen 生成公钥的意思
# -t rsa 使用rsa算法
# -b 4096 公钥的大小 4096个字节
# 查看公钥 公钥创建之后会显示公钥的所在目录
cat /root/.ssh/id_rsa.pub
# 将公钥配置到git上,一个账号可以配置公钥,一个项目也可以配置公钥。
# 这里已gitee的单个项目配置为例(也可以全局配置):打开项目/管理/部署公钥管理/添加公钥。 其他平台也差不多 ,全局配置:打开设置/ssh公钥。这个配置不止能服务器上用,在windos电脑的git也一样能用
# 然后在服务器上克隆代码,先cd 进入项目文件夹
git clone ssh克隆地址 (注意:必须用ssh拉取地址)
- 脚本准备,根据自己项目的实际情况做调整
# cd 到放项目的目录 比如 我是放在 /web下
cd /web
# 创建脚本文件
# 如果你会用vi编辑器就用vi编辑
vi sh-client-xs
# 打开后按 i 进入编辑模式,将下面的降本放进去
# 处理好之后 按 : 退出编辑并保存
# 然后用 ./sh-client-xs 执行,有可能会报错 说没有权限,要给它分配权限
chmod +x sh-client-xs
# 首次要手动将代码包拉去下来
git clone ssh克隆地址
# 安装依赖
# 在执行就可以了
- 脚本代码
# 接收要打包的分支 如果是指定分支可以改造为指定
if [ -z "$1" ]; then
echo "错误: 缺少分支参数"
exit 1
fi
# 进入到代码拉取的目录
cd '/web/kht-client-xs' || { echo "无法切换到目录 /web/kht-client-xs"; exit 1; }
# 放弃修改,防止拉去代码冲突
echo '放弃更改'
git reset --hard
echo '切换分支'
git checkout $1
echo '拉取最新代码 :分支'$1
git pull
# 这里根据情况需不需要安装依赖,如果依赖没有大的变动可以不安装,首次安装或者有新依赖可以手动执行一下依赖安装。
echo '构建'
npm run test
echo '部署'
source="bank-zb-custom-xs-html"
destination="/usr/local/nginx/html/bank-zb-custom-xs-html"
# 检查目标路径是否已存在同名文件或目录
if [ -e "$destination" ]; then
# 备份目标路径
backup_destination="${destination}_clone_$(date +%Y%m%d%H%M%S)"
cp -r "$destination" "$backup_destination"
echo "已备份目标路径到: $backup_destination"
fi
# 删除原有文件
rm -rf "$destination"
# 移动并覆盖目标路径
mv "$source" "$destination"
echo '更新完成'
- 进入目录 安装依赖 (这里我已经拉去过代码了) ,首次可以手动安装依赖,就不用每次都在脚本中安装了,当然也可以在脚本中安装。
- 执行脚本
第四种方案的实现
- 本地打包上传进行部署
- 在项目根目录下创建文件夹build(可以根据实际情况设置名字),cd到build目录下,在build下创建一个package,json文件,因为是一个nodejs程序,所以这个文件是必须的,用npm init -y 创建,注意喔,必须是在build目录下。 然后在创建一个app.js文件,将下面的app.js代码放进去。程序中使用 archiver模块用于压缩打包文件,用了ssh2 模块,用于连接服务器 上传文件和执行命令行。这两个模块可以和项目依赖安装在一起,cd …/ 回到项目目录下安装依赖,npm i archiver ssh2 。
npm i archiver ssh2
- app,js文件代码
/**
* 自动化压缩并上传到服务器 ,解压到nginx目录
* 因为我的项目打包输出后的文件夹名称为 bank-zb-platform-html,nginx上的目录也为bank-zb-platform-html,所以需要根据项目情况自行修改
* */
const fs = require('fs');
const archiver = require('archiver');
const path = require('path');
// 压缩文件
function zipFiles() {
console.log('开始压缩文件');
return new Promise((resolve, reject) => {
// 同步删除当前目录下的 ./bank-zb-platform-html.zip 根据自己的文件名称修改即可
const zipPath = path.join(__dirname, 'bank-zb-platform-html.zip')
try {
fs.unlinkSync(zipPath);
} catch (err){
}
// 创建一个输出流
const output = fs.createWriteStream(zipPath);
const archive = archiver('zip', {
zlib: { level: 9 } // 设置压缩级别
});
// 监听所有归档数据写入完成事件
output.on('close', function () {
console.log('文件压缩成功 ZIP 文件大小: ' + archive.pointer() + ' b');
resolve()
});
// 监听错误事件
archive.on('error', function (err) {
reject(err)
});
// 将输出流与归档对象连接
archive.pipe(output);
// 往压缩包中注入文件和文件夹 可以注入多个
// 添加文件到压缩包中
// archive.file('path/to/your/file.txt', { name: 'file.txt' });
// 添加文件夹 将../bank-zb-platform-html/ 下文件全部写入 根据自己项目打包输出的文件夹名称修改
archive.directory(path.join(__dirname, '../bank-zb-platform-html/'), false);
// 完成归档
archive.finalize();
})
}
// 执行上传
function upload() {
return new Promise((resolve, reject) => {
const { Client } = require('ssh2');
// SSH连接配置
const conn = new Client();
const host = '192.168.1.111';
const port = 22;
const username = 'root';
const password = 'Pay2025!';
console.log('开始连接服务器');
conn.on('ready', () => {
console.log('已连接到服务器');
// 要上传的文件
const localFilePath = path.join(__dirname, 'bank-zb-platform-html.zip')
// 要上传的完整路径(包括文件名)
const remoteFilePath = '/usr/local/nginx/html/bank-zb-platform-html.zip';
conn.sftp((err, sftp) => {
if (err) throw err;
const readStream = fs.createReadStream(localFilePath);
const writeStream = sftp.createWriteStream(remoteFilePath);
console.log('准备上传压缩包');
writeStream.on('close', () => {
console.log(`文件成功上传到 ${remoteFilePath}`);
console.log('开始解压');
// 串联多个命令 根据自己项目的情况修改
const commands = 'cd /usr/local/nginx/html; rm -rf bank-zb-platform-html; mkdir bank-zb-platform-html; unzip bank-zb-platform-html.zip -d bank-zb-platform-html; rm -rf bank-zb-platform-html.zip; exit'
conn.exec(commands, (err, stream) => {
if (err) throw err;
console.log('解压命令执行中...')
stream.on('close', (code, signal) => {
console.log('完成');
// 命令执行完成后 断开与服务器的连接
conn.end()
}).on('data', (data) => {
// console.log('STDOUT: ' + data);
}).stderr.on('data', (data) => {
// console.log('STDERR: ' + data);
});
});
});
readStream.pipe(writeStream);
});
}).connect({
host,
port,
username,
password
});
conn.on('error', (err) => {
console.error(`连接错误: ${err.message}`);
reject(err)
});
conn.on('end', () => {
console.log('连接已断开');
const zipPath = path.join(__dirname, 'bank-zb-platform-html.zip')
fs.unlinkSync(zipPath);
resolve()
});
conn.on('close', (hadError) => {
console.log(`连接关闭 :: hadError :: ${hadError}`);
reject()
});
})
}
// 调用,先压缩文件,在连接服务器上传文件和处理解压
zipFiles().then(upload)
- 最后就是打包及压缩上传配置了, 在项目中的package.json下的scripts进行配置
{
"scripts": {
"dev": "vite serve --mode development",
// 我主要是在测试环境用,所以在这里需要加加上执行node程序的命令 这样当打包完成后就会自动去执行nodejs程序完成压缩及上传
"test": "vite build --mode test && node ./build/app.js",
"build": "vite build --mode production"
}
}
- 效果
uniapp h5的自动化更新
- uniapp通过Hbuild可视化创建的项目一般不支持命令行自动化打包,当然也不绝对,个人的看法是可以实现的,虽然uniapp很多依赖和编译环境都是依赖于Hbuild本身,但是只要安装对应的依赖包到工程本地也一样可以实现,就看uniapp官方有没有提供这些包,具体没研究过这里就不说了,通常我们都是通过Hbuild工具来进行打包,最后得到静态文件,当得到静态文件后,依然可以通过上面的几种方式来实现自动化更新。具体方案如下。
- 将打包得到静态文件一起推送到远程仓库,利用webhook推送事件调用api去执行更新脚本,这个脚本只负责拉取代码,将打包上传的静态文件拷贝到nginx的html目录下就可以了。
- 这个方案和上面的方案差不多,都是通过脚本去拉去代码,将静态文件拷贝到nginx目录下,这种方案是将脚本放在服务器上手动去执行,上面的方案是通过推送事件执行。
- 在本地通过node程序连接服务器,将静态文件上传到nginx的html目录下。