VWED_server/packaging/integrated_launcher.py

352 lines
12 KiB
Python
Raw Normal View History

2025-09-09 10:41:27 +08:00
#!/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()