diff --git a/.devops/monitor.py b/.devops/monitor.py
index 0727ee2..28403aa 100644
--- a/.devops/monitor.py
+++ b/.devops/monitor.py
@@ -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 = '''
+
+
+
+
+ DevOps 部署系统
+
+
+
+ 🚀 RuoYi Cloud DevOps 部署系统
+
+
📖 使用说明
+
触发部署:点击下方表格中的"部署链接",或直接访问 http://IP:9999/项目名称
+
示例:http://IP:9999/tuoheng-device 触发 tuoheng-device 项目部署
+
查看日志:点击这里查看部署日志
+
+ 📦 可部署项目列表
+
+
+
+ | 项目名称 |
+ 类型 |
+ Docker服务 |
+ 部署链接 |
+
+
+
+ '''
+
+ for project in projects:
+ type_class = f"type-{project['type']}"
+ html += f'''
+
+ | {project['name']} |
+ {project['type'].upper()} |
+ {project['docker_service']} |
+ /{project['name']} |
+
+ '''
+
+ html += '''
+
+
+
+
+ '''
+ return html
+
+ @self.app.route('/logs')
+ def view_logs():
+ """查看日志页面"""
+ html = '''
+
+
+
+
+ DevOps 日志查看
+
+
+
+
+
+
+
+
+ '''
+ 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('/')
+ 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()
diff --git a/.devops/pmstart.sh b/.devops/pmstart.sh
index 315fe07..df7458d 100755
--- a/.devops/pmstart.sh
+++ b/.devops/pmstart.sh
@@ -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 进程
diff --git a/.devops/start.sh b/.devops/start.sh
index 34668e3..6aa2a15 100755
--- a/.devops/start.sh
+++ b/.devops/start.sh
@@ -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
# 进入项目根目录