Files
beabigegg 6599716481 1panel
2025-10-03 08:19:40 +08:00

298 lines
8.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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)