修改脚本

This commit is contained in:
孙小云 2026-02-03 15:53:59 +08:00
parent 0db6eecceb
commit da82d8ee17
3 changed files with 441 additions and 0 deletions

View File

@ -11,8 +11,11 @@ import time
import yaml import yaml
import subprocess import subprocess
import socket import socket
import threading
import json
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from flask import Flask, request, jsonify, render_template_string
# 添加当前目录到 Python 路径 # 添加当前目录到 Python 路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
@ -659,6 +662,62 @@ class GitMonitor:
at_all=True at_all=True
) )
def deploy_by_name(self, repo_name):
"""通过项目名称触发部署
参数
repo_name: 项目名称
返回
(success, message): 成功标志和消息
"""
repos = self.config.get('repositories', [])
# 查找匹配的仓库配置
repo_config = None
for repo in repos:
if repo['name'] == repo_name:
repo_config = repo
break
if not repo_config:
return False, f"未找到项目: {repo_name}"
Logger.info(f"HTTP触发部署: {repo_name}")
# 获取最新的commit hash
repo_url = repo_config['url']
current_commit = self.get_remote_commit(repo_url, self.global_branch)
if current_commit:
self.last_commits[repo_name] = current_commit
# 执行部署
success = self.deploy(repo_config)
if success:
return True, f"项目 {repo_name} 部署成功"
else:
return False, f"项目 {repo_name} 部署失败"
def get_all_projects(self):
"""获取所有可部署的项目列表
返回
项目列表每个项目包含 name, type, docker_service
"""
repos = self.config.get('repositories', [])
projects = []
for repo in repos:
projects.append({
'name': repo['name'],
'type': repo['type'],
'docker_service': repo['docker_service']
})
return projects
def run(self): def run(self):
"""持续监听运行""" """持续监听运行"""
poll_interval = self.config['monitor']['poll_interval'] poll_interval = self.config['monitor']['poll_interval']
@ -675,6 +734,365 @@ class GitMonitor:
Logger.error(f"监听异常: {e}") Logger.error(f"监听异常: {e}")
class DeploymentServer:
"""HTTP部署服务器"""
def __init__(self, monitor, port=9999):
"""初始化HTTP服务器
参数
monitor: GitMonitor实例
port: HTTP服务器端口默认9999
"""
self.monitor = monitor
self.port = port
self.app = Flask(__name__)
self._setup_routes()
def _setup_routes(self):
"""设置路由"""
@self.app.route('/')
def index():
"""根路径 - 显示操作说明"""
projects = self.monitor.get_all_projects()
html = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DevOps 部署系统</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 50px auto;
padding: 20px;
background-color: #f5f5f5;
}
h1 {
color: #333;
text-align: center;
}
.info {
background-color: #e3f2fd;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
background-color: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #1976d2;
color: white;
}
tr:hover {
background-color: #f5f5f5;
}
.deploy-link {
color: #1976d2;
text-decoration: none;
font-weight: bold;
}
.deploy-link:hover {
text-decoration: underline;
}
.type-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
}
.type-java {
background-color: #ff9800;
color: white;
}
.type-nodejs {
background-color: #4caf50;
color: white;
}
.type-python {
background-color: #2196f3;
color: white;
}
</style>
</head>
<body>
<h1>🚀 RuoYi Cloud DevOps 部署系统</h1>
<div class="info">
<h3>📖 使用说明</h3>
<p><strong>触发部署</strong>点击下方表格中的"部署链接"或直接访问 <code>http://IP:9999/项目名称</code></p>
<p><strong>示例</strong><code>http://IP:9999/tuoheng-device</code> 触发 tuoheng-device 项目部署</p>
<p><strong>查看日志</strong><a href="/logs" style="color: #1976d2; font-weight: bold;">点击这里查看部署日志</a></p>
</div>
<h2>📦 可部署项目列表</h2>
<table>
<thead>
<tr>
<th>项目名称</th>
<th>类型</th>
<th>Docker服务</th>
<th>部署链接</th>
</tr>
</thead>
<tbody>
'''
for project in projects:
type_class = f"type-{project['type']}"
html += f'''
<tr>
<td>{project['name']}</td>
<td><span class="{type_class} type-badge">{project['type'].upper()}</span></td>
<td>{project['docker_service']}</td>
<td><a href="/{project['name']}" class="deploy-link">/{project['name']}</a></td>
</tr>
'''
html += '''
</tbody>
</table>
</body>
</html>
'''
return html
@self.app.route('/logs')
def view_logs():
"""查看日志页面"""
html = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DevOps 日志查看</title>
<style>
body {
font-family: 'Courier New', monospace;
margin: 0;
padding: 20px;
background-color: #1e1e1e;
color: #d4d4d4;
}
.header {
background-color: #2d2d30;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
h1 {
margin: 0;
color: #4ec9b0;
font-size: 24px;
}
.controls {
display: flex;
gap: 10px;
}
button {
background-color: #0e639c;
color: white;
border: none;
padding: 8px 16px;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #1177bb;
}
.log-container {
background-color: #1e1e1e;
border: 1px solid #3e3e42;
border-radius: 5px;
padding: 15px;
height: calc(100vh - 180px);
overflow-y: auto;
font-size: 13px;
line-height: 1.6;
}
.log-line {
margin: 2px 0;
white-space: pre-wrap;
word-wrap: break-word;
}
.log-error {
color: #f48771;
}
.log-warning {
color: #dcdcaa;
}
.log-info {
color: #4ec9b0;
}
.log-success {
color: #4ec9b0;
}
.loading {
text-align: center;
padding: 20px;
color: #858585;
}
.back-link {
color: #4ec9b0;
text-decoration: none;
font-size: 14px;
}
.back-link:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="header">
<div>
<h1>📋 DevOps 部署日志</h1>
<a href="/" class="back-link"> 返回首页</a>
</div>
<div class="controls">
<button onclick="refreshLogs()">🔄 刷新</button>
<button onclick="clearDisplay()">🗑 清空显示</button>
<button onclick="toggleAutoRefresh()"> <span id="autoRefreshText">开启自动刷新</span></button>
</div>
</div>
<div class="log-container" id="logContainer">
<div class="loading">正在加载日志...</div>
</div>
<script>
let autoRefreshInterval = null;
let isAutoRefresh = false;
function formatLogLine(line) {
if (line.includes('ERROR') || line.includes('失败') || line.includes('异常')) {
return '<div class="log-line log-error">' + escapeHtml(line) + '</div>';
} else if (line.includes('WARNING') || line.includes('警告')) {
return '<div class="log-line log-warning">' + escapeHtml(line) + '</div>';
} else if (line.includes('INFO') || line.includes('')) {
return '<div class="log-line log-info">' + escapeHtml(line) + '</div>';
} else if (line.includes('成功') || line.includes('完成')) {
return '<div class="log-line log-success">' + escapeHtml(line) + '</div>';
} else {
return '<div class="log-line">' + escapeHtml(line) + '</div>';
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function refreshLogs() {
fetch('/api/logs')
.then(response => response.json())
.then(data => {
const container = document.getElementById('logContainer');
if (data.logs && data.logs.length > 0) {
container.innerHTML = data.logs.map(formatLogLine).join('');
container.scrollTop = container.scrollHeight;
} else {
container.innerHTML = '<div class="loading">暂无日志</div>';
}
})
.catch(error => {
console.error('获取日志失败:', error);
document.getElementById('logContainer').innerHTML =
'<div class="log-line log-error">获取日志失败: ' + error.message + '</div>';
});
}
function clearDisplay() {
document.getElementById('logContainer').innerHTML = '<div class="loading">显示已清空,点击刷新重新加载</div>';
}
function toggleAutoRefresh() {
isAutoRefresh = !isAutoRefresh;
const text = document.getElementById('autoRefreshText');
if (isAutoRefresh) {
text.textContent = '关闭自动刷新';
autoRefreshInterval = setInterval(refreshLogs, 3000);
refreshLogs();
} else {
text.textContent = '开启自动刷新';
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
}
}
// 页面加载时获取日志
refreshLogs();
</script>
</body>
</html>
'''
return html
@self.app.route('/api/logs')
def get_logs():
"""获取日志内容API"""
try:
log_file = self.monitor.config.get('logging', {}).get('file', '.devops/logs/devops.log')
log_path = Path(log_file)
if not log_path.exists():
return jsonify({'logs': [], 'message': '日志文件不存在'})
# 读取最后1000行日志
with open(log_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 只返回最后1000行
recent_lines = lines[-1000:] if len(lines) > 1000 else lines
return jsonify({'logs': [line.rstrip() for line in recent_lines]})
except Exception as e:
return jsonify({'logs': [], 'error': str(e)})
@self.app.route('/<project_name>')
def deploy_project(project_name):
"""触发项目部署"""
# 避免与 /logs 路由冲突
if project_name == 'logs' or project_name == 'api':
return jsonify({'code': 404, 'message': '路径不存在'})
Logger.info(f"收到HTTP部署请求: {project_name}")
# 在后台线程中执行部署
def deploy_task():
success, message = self.monitor.deploy_by_name(project_name)
Logger.info(f"部署结果: {message}")
thread = threading.Thread(target=deploy_task)
thread.daemon = True
thread.start()
return jsonify({
'code': 200,
'message': f'已触发 {project_name} 部署,请查看日志了解部署进度',
'project': project_name
})
def run(self):
"""启动HTTP服务器"""
Logger.info(f"HTTP部署服务器启动在端口 {self.port}")
self.app.run(host='0.0.0.0', port=self.port, debug=False, use_reloader=False)
def main(): def main():
"""主函数""" """主函数"""
import argparse import argparse
@ -682,6 +1100,8 @@ def main():
parser = argparse.ArgumentParser(description='Git 仓库监听器') parser = argparse.ArgumentParser(description='Git 仓库监听器')
parser.add_argument('--config', default='.devops/config.yaml', help='配置文件路径') parser.add_argument('--config', default='.devops/config.yaml', help='配置文件路径')
parser.add_argument('--once', action='store_true', help='只执行一次检查') parser.add_argument('--once', action='store_true', help='只执行一次检查')
parser.add_argument('--http-port', type=int, default=9999, help='HTTP服务器端口默认9999')
parser.add_argument('--no-http', action='store_true', help='禁用HTTP服务器')
args = parser.parse_args() args = parser.parse_args()
@ -690,6 +1110,15 @@ def main():
if args.once: if args.once:
monitor.run_once() monitor.run_once()
else: else:
# 启动HTTP服务器在单独的线程中
if not args.no_http:
server = DeploymentServer(monitor, port=args.http_port)
http_thread = threading.Thread(target=server.run)
http_thread.daemon = True
http_thread.start()
Logger.info(f"✓ HTTP部署服务器已启动: http://0.0.0.0:{args.http_port}")
# 启动Git监听
monitor.run() monitor.run()

View File

@ -42,6 +42,10 @@ if ! python3 -c "import yaml" 2>/dev/null; then
echo "安装 PyYAML..." echo "安装 PyYAML..."
pip3 install --user PyYAML pip3 install --user PyYAML
fi fi
if ! python3 -c "import flask" 2>/dev/null; then
echo "安装 Flask..."
pip3 install --user flask
fi
echo "✓ Python 依赖检查完成" echo "✓ Python 依赖检查完成"
# 5. 删除已存在的 PM2 进程 # 5. 删除已存在的 PM2 进程

View File

@ -44,12 +44,20 @@ if [ -z "$VIRTUAL_ENV" ]; then
echo "警告: 未找到 PyYAML尝试安装..." echo "警告: 未找到 PyYAML尝试安装..."
pip3 install --user PyYAML || echo "请手动安装: pip3 install --user PyYAML" pip3 install --user PyYAML || echo "请手动安装: pip3 install --user PyYAML"
fi fi
if ! python3 -c "import flask" 2>/dev/null; then
echo "警告: 未找到 Flask尝试安装..."
pip3 install --user flask || echo "请手动安装: pip3 install --user flask"
fi
else else
echo "检测到虚拟环境: $VIRTUAL_ENV" echo "检测到虚拟环境: $VIRTUAL_ENV"
if ! python3 -c "import yaml" 2>/dev/null; then if ! python3 -c "import yaml" 2>/dev/null; then
echo "安装 PyYAML..." echo "安装 PyYAML..."
pip install PyYAML pip install PyYAML
fi fi
if ! python3 -c "import flask" 2>/dev/null; then
echo "安装 Flask..."
pip install flask
fi
fi fi
# 进入项目根目录 # 进入项目根目录