""" 股票分析系统部署脚本 提供系统部署、配置和环境设置功能 """ import os import sys import subprocess import argparse import platform from pathlib import Path class DeploymentManager: """部署管理器类""" def __init__(self): self.project_root = Path(__file__).parent self.venv_path = self.project_root / 'venv' self.requirements_file = self.project_root / 'requirements.txt' self.config_dir = self.project_root / 'config' self.logs_dir = self.project_root / 'logs' self.data_dir = self.project_root / 'data' self.is_windows = platform.system() == 'Windows' self.is_linux = platform.system() == 'Linux' self.is_macos = platform.system() == 'Darwin' def check_environment(self): """检查运行环境""" print("正在检查运行环境...") # 检查Python版本 python_version = sys.version_info if python_version < (3, 8): print(f"错误: 需要Python 3.8或更高版本,当前版本: {python_version.major}.{python_version.minor}") return False print(f"✓ Python版本: {python_version.major}.{python_version.minor}.{python_version.micro}") # 检查操作系统 print(f"✓ 操作系统: {platform.system()} {platform.release()}") # 检查必要工具 tools = ['pip', 'git'] for tool in tools: try: subprocess.run([tool, '--version'], capture_output=True, check=True) print(f"✓ {tool} 已安装") except (subprocess.CalledProcessError, FileNotFoundError): print(f"⚠ {tool} 未安装或不在PATH中") return True def create_virtual_environment(self): """创建虚拟环境""" print("正在创建虚拟环境...") if self.venv_path.exists(): print("虚拟环境已存在,跳过创建") return True try: subprocess.run([ sys.executable, '-m', 'venv', str(self.venv_path) ], check=True, cwd=self.project_root) print("✓ 虚拟环境创建成功") return True except subprocess.CalledProcessError as e: print(f"✗ 创建虚拟环境失败: {e}") return False def get_venv_python(self): """获取虚拟环境中的Python路径""" if self.is_windows: return self.venv_path / 'Scripts' / 'python.exe' else: return self.venv_path / 'bin' / 'python' def get_venv_pip(self): """获取虚拟环境中的pip路径""" if self.is_windows: return self.venv_path / 'Scripts' / 'pip.exe' else: return self.venv_path / 'bin' / 'pip' def install_dependencies(self): """安装依赖包""" print("正在安装依赖包...") if not self.requirements_file.exists(): print("✗ requirements.txt 文件不存在") return False try: pip_path = self.get_venv_pip() # 升级pip subprocess.run([str(pip_path), 'install', '--upgrade', 'pip'], check=True) # 安装依赖 subprocess.run([ str(pip_path), 'install', '-r', str(self.requirements_file) ], check=True) print("✓ 依赖包安装成功") return True except subprocess.CalledProcessError as e: print(f"✗ 安装依赖包失败: {e}") return False def create_directories(self): """创建必要的目录""" print("正在创建必要的目录...") directories = [self.logs_dir, self.data_dir] for directory in directories: try: directory.mkdir(exist_ok=True) print(f"✓ 创建目录: {directory}") except Exception as e: print(f"✗ 创建目录失败 {directory}: {e}") return False return True def setup_environment_variables(self): """设置环境变量""" print("正在设置环境变量...") # 创建.env文件 env_file = self.project_root / '.env' if not env_file.exists(): try: with open(env_file, 'w', encoding='utf-8') as f: f.write("""# 股票分析系统环境配置 DATABASE_URL=sqlite:///stock_data.db LOG_LEVEL=INFO DEBUG=false # 数据源配置 AKSHARE_BASE_URL=https://api.akshare.akfamily.xyz BAOSTOCK_TIMEOUT=30 # 定时任务配置 SCHEDULER_ENABLED=true DAILY_UPDATE_TIME=18:00 # 性能配置 MAX_CONCURRENT_TASKS=10 BATCH_SIZE=100 """) print("✓ 环境配置文件创建成功") except Exception as e: print(f"✗ 创建环境配置文件失败: {e}") return False return True def run_tests(self): """运行测试""" print("正在运行测试...") try: python_path = self.get_venv_python() result = subprocess.run([ str(python_path), '-m', 'pytest', 'tests/', '-v', '--tb=short' ], cwd=self.project_root, capture_output=True, text=True) if result.returncode == 0: print("✓ 所有测试通过") return True else: print("✗ 测试失败") print(result.stdout) print(result.stderr) return False except Exception as e: print(f"✗ 运行测试失败: {e}") return False def create_startup_scripts(self): """创建启动脚本""" print("正在创建启动脚本...") try: python_path = self.get_venv_python() run_script_path = self.project_root / 'run.py' if self.is_windows: # 创建Windows批处理文件 batch_content = f"""@echo off cd /d "{self.project_root}" "{python_path}" "{run_script_path}" %* pause """ with open(self.project_root / 'start.bat', 'w', encoding='utf-8') as f: f.write(batch_content) print("✓ Windows启动脚本创建成功") # 创建Linux/Mac启动脚本 shell_content = f"""#!/bin/bash cd "{self.project_root}" "{python_path}" "{run_script_path}" "$@" """ with open(self.project_root / 'start.sh', 'w', encoding='utf-8') as f: f.write(shell_content) # 设置执行权限 if not self.is_windows: os.chmod(self.project_root / 'start.sh', 0o755) print("✓ Linux/Mac启动脚本创建成功") except Exception as e: print(f"✗ 创建启动脚本失败: {e}") return False return True def create_service_scripts(self): """创建服务脚本(用于生产环境)""" print("正在创建服务脚本...") try: python_path = self.get_venv_python() run_script_path = self.project_root / 'run.py' if self.is_linux: # 创建systemd服务文件 service_content = f"""[Unit] Description=Stock Analysis System After=network.target [Service] Type=simple User=stock Group=stock WorkingDirectory={self.project_root} ExecStart={python_path} {run_script_path} scheduler Restart=always RestartSec=10 [Install] WantedBy=multi-user.target """ with open(self.project_root / 'stock-system.service', 'w', encoding='utf-8') as f: f.write(service_content) print("✓ systemd服务文件创建成功") # 创建supervisor配置 supervisor_content = f"""[program:stock-system] command={python_path} {run_script_path} scheduler directory={self.project_root} autostart=true autorestart=true user=stock stdout_logfile={self.logs_dir}/supervisor.log stderr_logfile={self.logs_dir}/supervisor_error.log """ with open(self.project_root / 'stock-system.conf', 'w', encoding='utf-8') as f: f.write(supervisor_content) print("✓ supervisor配置创建成功") except Exception as e: print(f"✗ 创建服务脚本失败: {e}") return False return True def backup_database(self): """备份数据库""" print("正在备份数据库...") db_file = self.project_root / 'stock_data.db' backup_dir = self.project_root / 'backups' if not db_file.exists(): print("数据库文件不存在,跳过备份") return True try: backup_dir.mkdir(exist_ok=True) from datetime import datetime backup_file = backup_dir / f'stock_data_backup_{datetime.now().strftime("%Y%m%d_%H%M%S")}.db' import shutil shutil.copy2(db_file, backup_file) print(f"✓ 数据库备份成功: {backup_file}") return True except Exception as e: print(f"✗ 数据库备份失败: {e}") return False def deploy(self, skip_tests=False, production=False): """执行完整部署流程""" print("开始部署股票分析系统...") print("=" * 50) # 检查环境 if not self.check_environment(): return False print("-" * 50) # 创建虚拟环境 if not self.create_virtual_environment(): return False print("-" * 50) # 安装依赖 if not self.install_dependencies(): return False print("-" * 50) # 创建目录 if not self.create_directories(): return False print("-" * 50) # 设置环境变量 if not self.setup_environment_variables(): return False print("-" * 50) # 运行测试 if not skip_tests: if not self.run_tests(): print("测试失败,是否继续部署? (y/N): ") response = input().strip().lower() if response != 'y': return False print("-" * 50) # 创建启动脚本 if not self.create_startup_scripts(): return False print("-" * 50) # 生产环境额外配置 if production: if not self.create_service_scripts(): return False if not self.backup_database(): return False print("=" * 50) print("✓ 部署完成!") # 显示使用说明 print("\n使用说明:") print("1. 启动系统: " + ("start.bat" if self.is_windows else "./start.sh")) print("2. 查看帮助: " + ("start.bat --help" if self.is_windows else "./start.sh --help")) print("3. 初始化数据: " + ("start.bat init" if self.is_windows else "./start.sh init")) return True def main(): """主函数""" parser = argparse.ArgumentParser(description='股票分析系统部署工具') parser.add_argument( '--skip-tests', action='store_true', help='跳过测试' ) parser.add_argument( '--production', action='store_true', help='生产环境部署' ) parser.add_argument( '--backup-only', action='store_true', help='仅备份数据库' ) args = parser.parse_args() deployer = DeploymentManager() if args.backup_only: deployer.backup_database() else: deployer.deploy(skip_tests=args.skip_tests, production=args.production) if __name__ == '__main__': main()