修改脚本

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 subprocess
import socket
import threading
import json
from datetime import datetime
from pathlib import Path
from flask import Flask, request, jsonify, render_template_string
# 添加当前目录到 Python 路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
@ -659,6 +662,62 @@ class GitMonitor:
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):
"""持续监听运行"""
poll_interval = self.config['monitor']['poll_interval']
@ -675,6 +734,365 @@ class GitMonitor:
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():
"""主函数"""
import argparse
@ -682,6 +1100,8 @@ def main():
parser = argparse.ArgumentParser(description='Git 仓库监听器')
parser.add_argument('--config', default='.devops/config.yaml', 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()
@ -690,6 +1110,15 @@ def main():
if args.once:
monitor.run_once()
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()

View File

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

View File

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