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()
|