feat: 初始化考培练系统项目

- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
111
2026-01-24 19:33:28 +08:00
commit 998211c483
1197 changed files with 228429 additions and 0 deletions

164
deploy/scripts/webhook_handler.py Executable file
View File

@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""
GitHub Webhook处理器
监听GitHub推送事件自动触发部署
"""
import os
import subprocess
import json
import hmac
import hashlib
import logging
from flask import Flask, request, jsonify
import threading
import time
app = Flask(__name__)
# 配置
WEBHOOK_SECRET = "kaopeilian-webhook-secret-2025" # GitHub中配置的密钥
PROJECT_DIR = "/root/aiedu"
UPDATE_SCRIPT = "/root/aiedu/scripts/auto_update.sh"
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/kaopeilian_webhook.log'),
logging.StreamHandler()
]
)
def verify_signature(payload_body, signature_header):
"""验证GitHub Webhook签名"""
if not signature_header:
return False
hash_object = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
payload_body,
hashlib.sha256
)
expected_signature = "sha256=" + hash_object.hexdigest()
return hmac.compare_digest(expected_signature, signature_header)
def run_update_async():
"""异步执行更新脚本"""
try:
# 等待5秒再执行避免GitHub推送过程中的竞争条件
time.sleep(5)
result = subprocess.run(
[UPDATE_SCRIPT],
cwd=PROJECT_DIR,
capture_output=True,
text=True,
timeout=600 # 10分钟超时
)
if result.returncode == 0:
logging.info(f"Update completed successfully: {result.stdout}")
else:
logging.error(f"Update failed: {result.stderr}")
except subprocess.TimeoutExpired:
logging.error("Update script timed out")
except Exception as e:
logging.error(f"Error running update script: {e}")
@app.route('/webhook', methods=['POST'])
def github_webhook():
"""处理GitHub Webhook请求"""
# 验证签名
signature = request.headers.get('X-Hub-Signature-256')
if not verify_signature(request.data, signature):
logging.warning("Invalid webhook signature")
return jsonify({"error": "Invalid signature"}), 403
# 解析请求
try:
payload = request.get_json()
except Exception as e:
logging.error(f"Failed to parse JSON: {e}")
return jsonify({"error": "Invalid JSON"}), 400
# 检查事件类型
event_type = request.headers.get('X-GitHub-Event')
if event_type != 'push':
logging.info(f"Ignoring event type: {event_type}")
return jsonify({"message": "Event ignored"}), 200
# 检查分支
ref = payload.get('ref', '')
if ref != 'refs/heads/production':
logging.info(f"Ignoring push to branch: {ref}")
return jsonify({"message": "Branch ignored"}), 200
# 获取提交信息
commit_info = {
'id': payload.get('after', 'unknown'),
'message': payload.get('head_commit', {}).get('message', 'No message'),
'author': payload.get('head_commit', {}).get('author', {}).get('name', 'Unknown'),
'timestamp': payload.get('head_commit', {}).get('timestamp', 'Unknown')
}
logging.info(f"Received push event: {commit_info}")
# 异步触发更新
update_thread = threading.Thread(target=run_update_async)
update_thread.daemon = True
update_thread.start()
return jsonify({
"message": "Update triggered successfully",
"commit": commit_info
}), 200
@app.route('/health', methods=['GET'])
def health_check():
"""健康检查端点"""
return jsonify({
"status": "healthy",
"service": "kaopeilian-webhook",
"timestamp": time.time()
}), 200
@app.route('/status', methods=['GET'])
def status():
"""状态检查端点"""
try:
# 检查项目目录
project_exists = os.path.exists(PROJECT_DIR)
# 检查更新脚本
script_exists = os.path.exists(UPDATE_SCRIPT)
script_executable = os.access(UPDATE_SCRIPT, os.X_OK) if script_exists else False
# 检查Docker服务
docker_result = subprocess.run(['docker', 'compose', 'ps'],
cwd=PROJECT_DIR, capture_output=True)
docker_running = docker_result.returncode == 0
return jsonify({
"status": "ok",
"checks": {
"project_directory": project_exists,
"update_script_exists": script_exists,
"update_script_executable": script_executable,
"docker_compose_running": docker_running
}
}), 200
except Exception as e:
return jsonify({
"status": "error",
"error": str(e)
}), 500
if __name__ == '__main__':
logging.info("Starting GitHub Webhook handler...")
app.run(host='0.0.0.0', port=9000, debug=False)