feat: 添加自动发布脚本 (webhook + deploy.sh + systemd)

This commit is contained in:
zhangyuxin 2026-06-04 19:49:33 +08:00
parent ea53873fa4
commit b9dc180f86
4 changed files with 364 additions and 0 deletions

123
deploy/README.md Normal file
View File

@ -0,0 +1,123 @@
# RuoYi 自动发布配置指南
## 原理
```
你 push 代码到 Gitee
Gitee 发送 Webhook 到服务器 :9000/webhook
webhook.py 验证签名 + 过滤 master 分支
触发 deploy.sh: git pull → mvn build → 停旧 → 启新
```
## 第一步:上传文件到服务器
在本地 `deploy/` 目录执行:
```bash
scp deploy.sh root@47.94.224.255:/opt/ruoyi/
scp webhook.py root@47.94.224.255:/opt/ruoyi/
scp ruoyi-webhook.service root@47.94.224.255:/etc/systemd/system/
```
## 第二步SSH 登录服务器配置
```bash
ssh root@47.94.224.255
```
### 2.1 设置脚本权限
```bash
chmod +x /opt/ruoyi/deploy.sh
chmod +x /opt/ruoyi/webhook.py
```
### 2.2 启动 Webhook 服务
```bash
systemctl daemon-reload
systemctl enable ruoyi-webhook
systemctl start ruoyi-webhook
systemctl status ruoyi-webhook
```
### 2.3 验证 Webhook 运行
```bash
curl http://localhost:9000/health
# 应返回: OK
```
### 2.4 开放防火墙端口(如有)
```bash
# 检查防火墙
firewall-cmd --state 2>/dev/null || echo "防火墙未运行"
# 如果需要开放
firewall-cmd --add-port=9000/tcp --permanent
firewall-cmd --reload
```
**阿里云安全组也需要放行 9000 端口**(入方向)。
## 第三步:配置 Gitee Webhook
1. 打开 https://gitee.com/zhangYuXinGit/system
2. 进入 **管理 → WebHooks**
3. 点击 "添加 WebHook"
4. 填写:
- **URL**: `http://47.94.224.255:9000/webhook`
- **密码**: `ruoyi-deploy-2024`
- **事件**: 勾选 "Push"(只选 Push 就够了)
5. 点击 "添加",然后点击 "测试" 验证连接
## 第四步:测试自动发布
在本地修改代码并推送:
```bash
cd E:\java\project\system
# 修改一些代码...
git add .
git commit -m "test: 测试自动发布"
git push
```
然后在服务器查看部署日志:
```bash
ssh root@47.94.224.255 "tail -f /opt/ruoyi/deploy.log"
```
## 常用运维命令
```bash
# 查看 webhook 日志
tail -f /opt/ruoyi/webhook.log
# 查看部署日志
tail -f /opt/ruoyi/deploy.log
# 查看应用日志
tail -f /opt/ruoyi/app.log
# 重启 webhook 服务
systemctl restart ruoyi-webhook
# 手动触发部署(不走 webhook
ssh root@47.94.224.255 "/opt/ruoyi/deploy.sh"
# 停止自动发布
systemctl stop ruoyi-webhook
systemctl disable ruoyi-webhook
```
## 安全建议
- 生产环境建议将 Webhook URL 改为 HTTPS
- 可修改 `webhook.py` 中的 `WEBHOOK_SECRET` 更换密钥
- 可在阿里云安全组中限制 9000 端口仅允许 Gitee 的 IP 访问

81
deploy/deploy.sh Normal file
View File

@ -0,0 +1,81 @@
#!/bin/bash
#===========================================================================
# RuoYi 自动部署脚本
# 触发方式: Gitee Webhook → webhook.py → deploy.sh
#===========================================================================
set -e
APP_DIR=/opt/ruoyi
JAR_FILE=$APP_DIR/ruoyi-admin/target/ruoyi-admin.jar
LOG_FILE=$APP_DIR/app.log
DEPLOY_LOG=$APP_DIR/deploy.log
PROFILE=druid
PORT=8080
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [DEPLOY] $1" | tee -a "$DEPLOY_LOG"
}
log "========== 开始部署 =========="
# 1. 拉取最新代码
cd "$APP_DIR"
log "拉取最新代码..."
git pull origin master 2>&1 | tee -a "$DEPLOY_LOG"
# 2. Maven 构建
log "开始 Maven 构建..."
mvn clean package -DskipTests -pl ruoyi-admin -am -q 2>&1 | tee -a "$DEPLOY_LOG"
if [ ! -f "$JAR_FILE" ]; then
log "构建失败: JAR 文件不存在"
exit 1
fi
log "构建成功"
# 3. 停止旧进程
OLD_PID=$(ps aux | grep ruoyi-admin.jar | grep -v grep | awk '{print $2}')
if [ -n "$OLD_PID" ]; then
log "停止旧进程 PID: $OLD_PID"
kill "$OLD_PID"
sleep 3
if ps -p "$OLD_PID" > /dev/null 2>&1; then
kill -9 "$OLD_PID"
log "强制停止 PID: $OLD_PID"
fi
fi
# 4. 启动新进程
log "启动新进程..."
nohup java -Xms128m -Xmx384m -jar "$JAR_FILE" \
--spring.profiles.active="$PROFILE" \
--server.port="$PORT" \
> "$LOG_FILE" 2>&1 &
NEW_PID=$!
log "新进程 PID: $NEW_PID"
# 5. 等待启动
sleep 5
if ps -p "$NEW_PID" > /dev/null 2>&1; then
log "应用启动中,等待就绪..."
for i in 1 2 3 4 5 6; do
if grep -q "Started RuoYiApplication" "$LOG_FILE" 2>/dev/null; then
log "部署成功! 应用已就绪"
break
fi
sleep 5
done
else
log "启动失败! 查看日志: tail -50 $LOG_FILE"
exit 1
fi
# 6. 健康检查
sleep 3
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PORT 2>/dev/null || echo "000")
log "健康检查 HTTP $HTTP_CODE"
log "========== 部署结束 =========="

View File

@ -0,0 +1,16 @@
[Unit]
Description=RuoYi Webhook Deploy Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/ruoyi
ExecStart=/usr/bin/python3 /opt/ruoyi/webhook.py
Restart=always
RestartSec=10
StandardOutput=append:/opt/ruoyi/webhook.log
StandardError=append:/opt/ruoyi/webhook.log
[Install]
WantedBy=multi-user.target

144
deploy/webhook.py Normal file
View File

@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
RuoYi Gitee Webhook 接收服务
接收 Gitee 推送事件触发自动部署
启动: python3 webhook.py
"""
import hashlib
import hmac
import json
import subprocess
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
from datetime import datetime
# ============ 配置 ============
LISTEN_HOST = "0.0.0.0"
LISTEN_PORT = 9000
# Gitee Webhook 密码(请在 Gitee 仓库设置中配置相同的密钥)
WEBHOOK_SECRET = "ruoyi-deploy-2024"
# 部署脚本路径
DEPLOY_SCRIPT = "/opt/ruoyi/deploy.sh"
# 部署锁文件,防止并发部署
LOCK_FILE = "/tmp/ruoyi-deploy.lock"
# ============ 日志 ============
def log(msg):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"[{timestamp}] [WEBHOOK] {msg}"
print(line, flush=True)
class WebhookHandler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != "/webhook":
self.send_response(404)
self.end_headers()
return
# 读取请求体
content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length)
# 验证签名
signature = self.headers.get("X-Gitee-Token", "")
if signature != WEBHOOK_SECRET:
log(f"签名验证失败: {signature[:20]}...")
self.send_response(403)
self.end_headers()
self.wfile.write(b"Forbidden")
return
# 解析事件
try:
data = json.loads(body)
except json.JSONDecodeError:
log("JSON 解析失败")
self.send_response(400)
self.end_headers()
return
event = self.headers.get("X-Gitee-Event", "unknown")
ref = data.get("ref", "")
pusher = data.get("pusher", {}).get("name", "unknown")
log(f"收到事件: {event} | 分支: {ref} | 推送者: {pusher}")
# 只处理 master 分支的 push 事件
if event != "push_hooks" or ref != "refs/heads/master":
log(f"忽略事件: {event} / {ref}")
self.send_response(200)
self.end_headers()
self.wfile.write(b"Ignored")
return
# 检查锁
if self._is_locked():
log("部署正在进行中,忽略重复触发")
self.send_response(200)
self.end_headers()
self.wfile.write(b"Deploy already in progress")
return
# 异步执行部署
self._lock()
subprocess.Popen(
["/bin/bash", DEPLOY_SCRIPT],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
self.send_response(200)
self.end_headers()
self.wfile.write(b"Deploy started")
def do_GET(self):
"""健康检查"""
if self.path == "/health":
self.send_response(200)
self.end_headers()
self.wfile.write(b"OK")
else:
self.send_response(404)
self.end_headers()
def _is_locked(self):
try:
with open(LOCK_FILE, "r") as f:
lock_time = float(f.read().strip())
# 锁超过 30 分钟自动过期
if time.time() - lock_time > 1800:
return False
return True
except FileNotFoundError:
return False
def _lock(self):
with open(LOCK_FILE, "w") as f:
f.write(str(time.time()))
def log_message(self, format, *args):
"""重写默认日志,改为简洁格式"""
log(f"{self.client_address[0]} - {format % args}")
def main():
log(f"Webhook 服务启动在 http://{LISTEN_HOST}:{LISTEN_PORT}/webhook")
log(f"密钥: {WEBHOOK_SECRET[:4]}****")
server = HTTPServer((LISTEN_HOST, LISTEN_PORT), WebhookHandler)
try:
server.serve_forever()
except KeyboardInterrupt:
log("服务已停止")
server.server_close()
if __name__ == "__main__":
main()