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