This commit is contained in:
beabigegg
2025-10-03 08:19:40 +08:00
commit 6599716481
99 changed files with 28184 additions and 0 deletions

297
start.py Normal file
View File

@@ -0,0 +1,297 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PANJIT Document Translator - 統一啟動入口
適用於 1Panel 環境部署
此腳本會:
1. 啟動 Flask Web 服務
2. 啟動 Celery Worker翻譯任務處理
3. 啟動 Celery Beat定時任務
Author: PANJIT IT Team
Created: 2025-10-03
"""
import os
import sys
import signal
import subprocess
import time
from pathlib import Path
from multiprocessing import Process
# 添加專案根目錄到 Python 路徑
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
# ANSI 顏色碼
class Colors:
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
# 全域進程列表
processes = []
def log_info(message):
"""顯示資訊日誌"""
print(f"{Colors.BLUE}[INFO]{Colors.ENDC} {message}")
def log_success(message):
"""顯示成功日誌"""
print(f"{Colors.GREEN}[SUCCESS]{Colors.ENDC} {message}")
def log_warning(message):
"""顯示警告日誌"""
print(f"{Colors.YELLOW}[WARNING]{Colors.ENDC} {message}")
def log_error(message):
"""顯示錯誤日誌"""
print(f"{Colors.RED}[ERROR]{Colors.ENDC} {message}")
def show_banner():
"""顯示啟動橫幅"""
print(f"""
{Colors.BOLD}╔═══════════════════════════════════════════════════════════╗
║ PANJIT Document Translator V2 ║
║ 正在啟動服務... ║
╚═══════════════════════════════════════════════════════════╝{Colors.ENDC}
""")
def check_environment():
"""檢查環境配置"""
log_info("檢查環境配置...")
# 檢查 Python 版本
if sys.version_info < (3, 10):
log_error(f"Python 版本過低: {sys.version}")
log_error("需要 Python 3.10 或更高版本")
sys.exit(1)
log_success(f"Python 版本: {sys.version.split()[0]}")
# 檢查必要檔案
required_files = ['app.py', 'celery_app.py', 'requirements.txt']
for file in required_files:
if not Path(file).exists():
log_error(f"找不到必要檔案: {file}")
sys.exit(1)
log_success("必要檔案檢查完成 ✓")
# 檢查環境變數
if not Path('.env').exists():
log_warning("找不到 .env 檔案,將使用預設配置")
else:
log_success("找到 .env 配置檔案 ✓")
# 檢查必要目錄
directories = ['uploads', 'logs', 'static']
for directory in directories:
Path(directory).mkdir(parents=True, exist_ok=True)
log_success("目錄結構檢查完成 ✓")
print()
def start_flask_app():
"""啟動 Flask Web 服務"""
log_info("啟動 Flask Web 服務...")
# 從環境變數讀取配置
host = os.environ.get('HOST', '0.0.0.0')
port = int(os.environ.get('PORT', 12010))
# 使用 gunicorn 啟動(生產環境)
if os.environ.get('FLASK_ENV') == 'production':
workers = int(os.environ.get('GUNICORN_WORKERS', 4))
cmd = [
'gunicorn',
'--bind', f'{host}:{port}',
'--workers', str(workers),
'--timeout', '300',
'--access-logfile', 'logs/access.log',
'--error-logfile', 'logs/error.log',
'--log-level', 'info',
'app:app' # 直接使用 app.py 中的 app 物件
]
else:
# 開發環境使用 Flask 內建伺服器
cmd = ['python3', 'app.py']
try:
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
bufsize=1
)
processes.append(('Flask App', process))
log_success(f"Flask 服務已啟動 (PID: {process.pid}) ✓")
log_info(f"服務地址: http://{host}:{port}")
return process
except Exception as e:
log_error(f"Flask 服務啟動失敗: {e}")
sys.exit(1)
def start_celery_worker():
"""啟動 Celery Worker"""
log_info("啟動 Celery Worker...")
# 檢查 Redis 連線
redis_url = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
log_info(f"Redis URL: {redis_url}")
# Celery Worker 命令
cmd = [
'celery',
'-A', 'celery_app.celery',
'worker',
'--loglevel=info',
'--concurrency=2',
'--logfile=logs/celery_worker.log'
]
try:
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
bufsize=1
)
processes.append(('Celery Worker', process))
log_success(f"Celery Worker 已啟動 (PID: {process.pid}) ✓")
return process
except Exception as e:
log_error(f"Celery Worker 啟動失敗: {e}")
log_warning("如果沒有 Redis 服務,翻譯功能將無法使用")
return None
def start_celery_beat():
"""啟動 Celery Beat定時任務"""
log_info("啟動 Celery Beat...")
cmd = [
'celery',
'-A', 'celery_app.celery',
'beat',
'--loglevel=info',
'--logfile=logs/celery_beat.log'
]
try:
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
bufsize=1
)
processes.append(('Celery Beat', process))
log_success(f"Celery Beat 已啟動 (PID: {process.pid}) ✓")
return process
except Exception as e:
log_error(f"Celery Beat 啟動失敗: {e}")
log_warning("定時任務將無法執行")
return None
def signal_handler(signum, frame):
"""處理終止信號"""
print()
log_warning("收到終止信號,正在關閉所有服務...")
for name, process in processes:
if process and process.poll() is None:
log_info(f"停止 {name} (PID: {process.pid})...")
try:
process.terminate()
process.wait(timeout=5)
log_success(f"{name} 已停止 ✓")
except subprocess.TimeoutExpired:
log_warning(f"{name} 未響應,強制終止...")
process.kill()
log_success(f"{name} 已強制終止 ✓")
except Exception as e:
log_error(f"停止 {name} 時發生錯誤: {e}")
log_success("\n所有服務已停止")
sys.exit(0)
def monitor_processes():
"""監控進程狀態"""
log_info("開始監控服務狀態...")
print()
print("=" * 60)
print(f"{Colors.BOLD}服務狀態:{Colors.ENDC}")
for name, process in processes:
if process:
status = "運行中 ✓" if process.poll() is None else "已停止 ✗"
print(f"{name:20s} PID: {process.pid:6d} {status}")
print("=" * 60)
print()
log_success("所有服務已啟動完成!")
print()
log_info("按 Ctrl+C 停止所有服務")
print()
try:
while True:
time.sleep(5)
# 檢查進程是否異常退出
for name, process in processes:
if process and process.poll() is not None:
log_error(f"{name} 異常退出 (退出碼: {process.returncode})")
log_warning("正在停止其他服務...")
signal_handler(signal.SIGTERM, None)
sys.exit(1)
except KeyboardInterrupt:
signal_handler(signal.SIGINT, None)
def main():
"""主函數"""
# 註冊信號處理
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# 顯示橫幅
show_banner()
# 檢查環境
check_environment()
# 啟動服務
log_info("正在啟動所有服務...")
print()
# 1. 啟動 Flask Web 服務
flask_process = start_flask_app()
time.sleep(2) # 等待 Flask 啟動
# 2. 啟動 Celery Worker
worker_process = start_celery_worker()
time.sleep(2) # 等待 Worker 啟動
# 3. 啟動 Celery Beat
beat_process = start_celery_beat()
time.sleep(2) # 等待 Beat 啟動
print()
# 監控進程
monitor_processes()
if __name__ == '__main__':
try:
main()
except Exception as e:
log_error(f"啟動失敗: {e}")
import traceback
traceback.print_exc()
sys.exit(1)