352 lines
12 KiB
Python
352 lines
12 KiB
Python
#!/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() |