VWED_server/packaging/integrated_launcher.py
2025-09-09 10:41:27 +08:00

352 lines
12 KiB
Python
Raw 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 python
# -*- coding: utf-8 -*-
"""
VWED任务系统集成启动器
先显示配置界面,配置完成后直接启动服务
支持后台运行模式
"""
import sys
import os
import threading
import time
import logging
import argparse
from pathlib import Path
# 添加项目根目录到Python路径
if getattr(sys, 'frozen', False):
# 打包后的环境
project_root = Path(sys.executable).parent
else:
# 开发环境
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
# 导入配置相关模块
try:
from config_tool.startup_loader import load_startup_config, ensure_config_exists
except ImportError:
print("警告: 无法导入配置加载器")
class IntegratedLauncher:
"""集成启动器"""
def __init__(self, headless=False):
self.app = None
self.service_started = False
self.config_completed = False
self.headless = headless
# 在无窗口模式下重定向标准流
if self.headless:
self._setup_headless_mode()
self.setup_logging()
def _setup_headless_mode(self):
"""设置无窗口模式的标准流重定向"""
try:
# 创建空的文件对象来代替标准流
import io
null_file = io.StringIO()
# 重定向标准流到空文件避免None对象引起的问题
if sys.stdout is None:
sys.stdout = null_file
if sys.stderr is None:
sys.stderr = null_file
if sys.stdin is None:
sys.stdin = io.StringIO("")
except Exception as e:
# 如果重定向失败,记录错误但不中断程序
pass
def setup_logging(self):
"""设置日志记录"""
log_file = Path("vwed_service.log")
# 配置日志格式
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 文件日志处理器
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
# 获取根日志器
self.logger = logging.getLogger('IntegratedLauncher')
self.logger.setLevel(logging.INFO)
self.logger.addHandler(file_handler)
# 如果不是无头模式,也添加控制台日志
if not self.headless:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
self.logger.addHandler(console_handler)
def log_and_print(self, message, level=logging.INFO):
"""记录日志并在非无头模式下打印"""
self.logger.log(level, message)
if not self.headless:
print(message)
def show_startup_message(self):
"""显示启动信息"""
self.log_and_print("=" * 60)
self.log_and_print(" VWED任务系统启动器")
self.log_and_print("=" * 60)
self.log_and_print("正在初始化系统...")
# 确保配置文件存在
try:
ensure_config_exists()
load_startup_config()
except Exception as e:
self.log_and_print(f"配置初始化失败: {e}", logging.ERROR)
def start_configuration_gui(self):
"""启动配置GUI"""
# 检查是否存在配置文件且配置是否完整
config_file = Path("config.ini")
if config_file.exists() and self.check_config_complete():
self.log_and_print("检测到完整的配置文件,跳过配置界面直接启动服务...")
self.log_and_print("如需重新配置请删除config.ini文件后重新启动")
# 模拟配置完成,直接启动服务
self.on_config_completed({})
return
# 无头模式下跳过GUI
if self.headless:
self.log_and_print("无头模式运行,使用默认配置启动服务...")
self.start_service()
return
try:
from config_tool.integrated_config_gui import IntegratedConfigGUI
self.log_and_print("启动配置界面...")
self.config_gui = IntegratedConfigGUI(self.on_config_completed)
self.config_gui.run()
except ImportError as e:
self.log_and_print(f"无法导入配置界面: {e}", logging.ERROR)
# 如果GUI不可用使用默认配置直接启动服务
self.log_and_print("使用默认配置启动服务...")
self.start_service()
except Exception as e:
self.log_and_print(f"配置界面启动失败: {e}", logging.ERROR)
self.start_service()
def check_config_complete(self):
"""检查配置是否完整"""
try:
import configparser
config = configparser.ConfigParser()
config.read("config.ini", encoding='utf-8')
# 检查必要的配置项
required_sections = {
'database': ['username', 'password', 'host'],
'api': ['tf_api_base_url']
}
for section, keys in required_sections.items():
if section not in config:
return False
for key in keys:
if key not in config[section] or not config[section][key].strip():
return False
return True
except Exception as e:
self.log_and_print(f"检查配置文件失败: {e}", logging.ERROR)
return False
def on_config_completed(self, config_data):
"""配置完成回调"""
self.log_and_print("配置完成,准备启动服务...")
self.config_completed = True
# 应用配置到环境变量
if 'database' in config_data:
os.environ.update({
'DB_USER': config_data['database'].get('username', 'root'),
'DB_PASSWORD': config_data['database'].get('password', 'root'),
'DB_HOST': config_data['database'].get('host', 'localhost')
})
if 'api' in config_data:
os.environ['TF_API_BASE_URL'] = config_data['api'].get('tf_api_base_url', 'http://111.231.146.230:4080/jeecg-boot')
self.log_and_print("环境变量已设置,启动服务...")
# 直接启动服务,不使用守护线程
self.start_service()
def start_service(self):
"""启动VWED服务"""
if self.service_started:
self.log_and_print("服务已经在运行中...")
return
try:
self.log_and_print("\n" + "=" * 60)
self.log_and_print(" 启动VWED任务系统服务")
self.log_and_print("=" * 60)
# 写入PID文件用于后台管理
pid_file = Path("vwed_service.pid")
with open(pid_file, 'w') as f:
f.write(str(os.getpid()))
try:
# 导入主程序
from app import app, settings
from utils.logger import get_logger
logger = get_logger("launcher")
# 标记服务已启动
self.service_started = True
port = 8000
self.log_and_print(f"服务启动成功!")
self.log_and_print(f"请在浏览器中访问: http://localhost:{port}")
self.log_and_print(f"API文档地址: http://localhost:{port}/docs")
if not self.headless:
self.log_and_print("\n按 Ctrl+C 停止服务")
# 启动服务器
import uvicorn
# 在无窗口模式下使用简化配置
if self.headless:
# 无窗口模式禁用reload、单worker、简化日志
uvicorn.run(
app, # 直接传递app对象而不是字符串
host="0.0.0.0",
port=port,
reload=False,
workers=1,
access_log=False, # 禁用访问日志避免格式化问题
log_config=None # 禁用默认日志配置
)
else:
# 前台模式:使用正常配置
uvicorn.run(
"app:app",
host="0.0.0.0",
port=port,
reload=settings.SERVER_RELOAD,
workers=settings.SERVER_WORKERS
)
except Exception as uvicorn_error:
self.log_and_print(f"Uvicorn启动失败: {uvicorn_error}", logging.ERROR)
# 尝试使用最基本的方式启动
self.log_and_print("尝试使用基本模式启动服务...", logging.WARNING)
try:
from app import app
import uvicorn
uvicorn.run(
app,
host="0.0.0.0",
port=8000,
log_level="error" # 只显示错误日志
)
except Exception as fallback_error:
self.log_and_print(f"基本模式启动也失败: {fallback_error}", logging.ERROR)
raise fallback_error
except ImportError as e:
self.log_and_print(f"无法导入主程序模块: {e}", logging.ERROR)
self.log_and_print("请检查项目依赖是否正确安装", logging.ERROR)
except Exception as e:
self.log_and_print(f"服务启动失败: {e}", logging.ERROR)
import traceback
self.log_and_print(traceback.format_exc(), logging.ERROR)
finally:
self.service_started = False
# 清理PID文件
pid_file = Path("vwed_service.pid")
if pid_file.exists():
pid_file.unlink()
def cleanup(self):
"""清理资源"""
try:
self.log_and_print("清理资源中...")
if hasattr(self, 'config_gui'):
if hasattr(self.config_gui, 'root') and self.config_gui.root:
self.config_gui.root.destroy()
# 清理PID文件
pid_file = Path("vwed_service.pid")
if pid_file.exists():
pid_file.unlink()
except:
pass
def main():
"""主函数"""
# 解析命令行参数
parser = argparse.ArgumentParser(description='VWED任务系统集成启动器')
parser.add_argument('--headless', action='store_true',
help='无头模式运行后台运行不显示GUI')
parser.add_argument('--daemon', action='store_true',
help='守护进程模式运行')
args = parser.parse_args()
launcher = None
try:
launcher = IntegratedLauncher(headless=args.headless)
# 显示启动信息
launcher.show_startup_message()
# 启动配置GUI
launcher.start_configuration_gui()
except KeyboardInterrupt:
if launcher:
launcher.log_and_print("\n程序被用户中断")
else:
print("\n程序被用户中断")
except Exception as e:
if launcher:
launcher.log_and_print(f"程序异常退出: {e}", logging.ERROR)
import traceback
launcher.log_and_print(traceback.format_exc(), logging.ERROR)
else:
print(f"程序异常退出: {e}")
import traceback
traceback.print_exc()
if not args.headless:
input("按回车键退出...")
finally:
# 清理资源
if launcher:
launcher.cleanup()
def cleanup_and_exit():
"""清理资源并退出"""
print("正在清理资源...")
# 强制退出所有线程
os._exit(0)
if __name__ == "__main__":
try:
main()
except:
cleanup_and_exit()