#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 后端应用程序打包脚本 使用PyInstaller将Flask应用程序打包成独立的exe文件 """ import os import sys import shutil import subprocess from pathlib import Path def create_spec_file(): """创建PyInstaller spec文件""" spec_content = ''' # -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis( ['main_exe.py'], pathex=[], binaries=[ ('dll/femtobolt/bin/k4a.dll', '.'), # K4A动态库 ('dll/femtobolt/bin/k4arecord.dll', '.'), # K4A录制库 ('dll/femtobolt/bin/depthengine_2_0.dll', '.'), # 深度引擎 ('dll/femtobolt/bin/OrbbecSDK.dll', '.'), # Orbbec SDK ('dll/femtobolt/bin/ob_usb.dll', '.'), # Orbbec USB库 ('dll/femtobolt/bin/live555.dll', '.'), # Live555库 ('dll/smitsense/SMiTSenseUsb-F3.0.dll', '.'), # SMiTSense传感器库 ('dll/femtobolt/bin/OrbbecSDKConfig_v1.0.xml', '.'), # Orbbec配置文件 ], datas=[ ('..\\config.ini', '.'), # 配置文件 ('..\\config.json', '.'), # JSON配置文件 ('tests', 'tests'), # 测试文件夹 ('data', 'data'), # 数据文件夹 ], hiddenimports=[ 'flask', 'flask_socketio', 'flask_cors', 'cv2', 'numpy', 'pandas', 'scipy', 'matplotlib', 'seaborn', 'sklearn', 'PIL', 'reportlab', 'sqlite3', 'configparser', 'logging', 'threading', 'queue', 'base64', 'psutil', 'pykinect_azure', 'pyserial', 'requests', 'yaml', 'click', 'colorama', 'tqdm', 'database', 'device_manager', 'utils', ], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='BodyBalanceBackend', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=True, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, icon=None, # 图标文件不存在,暂时禁用 ) ''' with open('backend.spec', 'w', encoding='utf-8') as f: f.write(spec_content) print("✓ 已创建 backend.spec 文件") def create_debug_spec(): """创建诊断脚本的spec文件""" import subprocess spec_content = f'''# -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis( ['test_socketio_debug.py'], pathex=['{os.getcwd()}'], binaries=[], datas=[], hiddenimports=[ 'flask', 'flask_socketio', 'eventlet', 'threading', 'engineio.async_drivers.threading', 'engineio.async_drivers.eventlet', 'engineio.async_eventlet', 'socketio.async_eventlet', 'greenlet', ], hookspath=[], hooksconfig={{}}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='test_socketio_debug', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=True, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, ) ''' with open('debug.spec', 'w', encoding='utf-8') as f: f.write(spec_content) print("✓ 已创建 debug.spec 文件") # 直接构建诊断exe print("开始构建诊断exe文件...") result = subprocess.run([sys.executable, '-m', 'PyInstaller', 'debug.spec'], capture_output=True, text=True, encoding='utf-8') if result.returncode == 0: print("✓ 诊断exe构建成功!") print("运行诊断: dist/test_socketio_debug.exe") else: print(f"❌ 诊断exe构建失败: {result.stderr}") def create_minimal_spec(): """创建最小化测试脚本的spec文件""" print("创建最小化测试脚本的spec文件...") spec_content = '''# -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis( ['test_socketio_minimal.py'], pathex=[], binaries=[], datas=[], hiddenimports=[ 'flask', 'flask_socketio', 'eventlet', 'eventlet.hubs', 'eventlet.hubs.epolls', 'eventlet.hubs.kqueue', 'eventlet.hubs.selects', 'threading', 'engineio', 'engineio.async_drivers', 'engineio.async_drivers.threading', 'engineio.async_drivers.eventlet', 'engineio.async_drivers.gevent', 'socketio', 'socketio.namespace' ], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='test_socketio_minimal', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=True, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, ) ''' with open('minimal.spec', 'w', encoding='utf-8') as f: f.write(spec_content) print("✓ 已创建 minimal.spec 文件") # 直接构建 print("开始构建最小化测试exe...") result = subprocess.run([sys.executable, '-m', 'PyInstaller', 'minimal.spec', '--clean'], capture_output=True, text=True, encoding='utf-8') if result.returncode == 0: print("✓ 最小化测试exe构建完成: dist/test_socketio_minimal.exe") else: print(f"❌ 最小化测试exe构建失败: {result.stderr}") def create_main_entry(): """创建主入口文件""" main_content = ''' #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 身体平衡评估系统 - 后端服务入口 独立运行的exe版本 """ import os import sys import logging from pathlib import Path # 设置工作目录 if getattr(sys, 'frozen', False): # 如果是打包后的exe application_path = os.path.dirname(sys.executable) else: # 如果是开发环境 application_path = os.path.dirname(os.path.abspath(__file__)) os.chdir(application_path) sys.path.insert(0, application_path) # 创建必要的目录 os.makedirs('logs', exist_ok=True) os.makedirs('data', exist_ok=True) # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('logs/backend.log', encoding='utf-8'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) def main(): """主函数""" try: logger.info("启动身体平衡评估系统后端服务...") logger.info(f"工作目录: {os.getcwd()}") # 导入并启动Flask应用 from app import app, socketio, init_app # 初始化应用 init_app() # 启动服务器 logger.info("后端服务器启动在 http://localhost:5000") socketio.run( app, host='0.0.0.0', port=5000, debug=False ) except KeyboardInterrupt: logger.info("用户中断,正在关闭服务器...") except Exception as e: logger.error(f"启动失败: {e}") input("按回车键退出...") sys.exit(1) if __name__ == '__main__': main() ''' with open('main_exe.py', 'w', encoding='utf-8') as f: f.write(main_content) print("✓ 已创建 main_exe.py 入口文件") def check_dependencies(): """检查必需的依赖模块""" required_modules = [ 'flask', 'flask_cors', 'flask_socketio', 'numpy', 'pandas', 'cv2', 'matplotlib', 'seaborn', 'PIL', 'reportlab', 'serial', 'requests', 'sqlite3', 'configparser', 'scipy', 'eventlet' ] missing_modules = [] for module in required_modules: try: __import__(module) print(f"✓ {module}") except ImportError: missing_modules.append(module) print(f"❌ {module} - 未安装") if missing_modules: print(f"\n警告: 发现 {len(missing_modules)} 个缺失的依赖模块") print("建议运行: pip install -r requirements.txt") return False print("✓ 所有必需依赖模块检查通过") return True def build_exe(): """构建exe文件""" print("开始构建exe文件...") # 检查依赖模块 print("\n检查依赖模块...") if not check_dependencies(): print("\n❌ 依赖检查失败,请先安装缺失的模块") return False # 检查PyInstaller是否安装 try: import PyInstaller print(f"\n✓ PyInstaller版本: {PyInstaller.__version__}") except ImportError: print("❌ PyInstaller未安装,正在安装...") os.system('pip install pyinstaller') # 清理之前的构建 if os.path.exists('build'): shutil.rmtree('build') print("✓ 清理build目录") if os.path.exists('dist'): shutil.rmtree('dist') print("✓ 清理dist目录") # 使用spec文件构建 cmd = 'python -m PyInstaller backend.spec --clean --noconfirm' print(f"执行命令: {cmd}") result = os.system(cmd) if result == 0: print("\n✓ 构建成功!") print("exe文件位置: dist/BodyBalanceBackend.exe") # 复制额外的文件 dist_path = Path('dist') if dist_path.exists(): # 复制配置文件 config_files = ['../config.ini', '../config.json'] for config_file in config_files: if os.path.exists(config_file): shutil.copy2(config_file, dist_path) print(f"✓ 复制配置文件: {config_file}") # 创建启动脚本 start_script = dist_path / 'start_backend.bat' with open(start_script, 'w', encoding='utf-8') as f: f.write('@echo off\n') f.write('echo 启动身体平衡评估系统后端服务...\n') f.write('BodyBalanceBackend.exe\n') f.write('pause\n') print("✓ 创建启动脚本: start_backend.bat") print("\n🎉 打包完成!") print("运行方式:") print("1. 直接运行: dist/BodyBalanceBackend.exe") print("2. 使用脚本: dist/start_backend.bat") else: print("❌ 构建失败") return False return True def check_required_files(): """检查必需的文件是否存在""" required_files = { 'app.py': '主应用文件', 'database.py': '数据库模块', 'device_manager.py': '设备管理模块', 'utils.py': '工具模块', '../config.ini': '配置文件', '../config.json': 'JSON配置文件' } required_dlls = { 'dll/femtobolt/bin/k4a.dll': 'K4A动态库', 'dll/femtobolt/bin/k4arecord.dll': 'K4A录制库', 'dll/femtobolt/bin/depthengine_2_0.dll': '深度引擎', 'dll/femtobolt/bin/OrbbecSDK.dll': 'Orbbec SDK', 'dll/femtobolt/bin/ob_usb.dll': 'Orbbec USB库', 'dll/femtobolt/bin/live555.dll': 'Live555库', 'dll/smitsense/SMiTSenseUsb-F3.0.dll': 'SMiTSense传感器库', 'dll/femtobolt/bin/OrbbecSDKConfig_v1.0.xml': 'Orbbec配置文件' } missing_files = [] print("检查必需文件...") for file_path, description in required_files.items(): if os.path.exists(file_path): print(f"✓ {description}: {file_path}") else: missing_files.append((file_path, description)) print(f"❌ {description}: {file_path} - 文件不存在") print("\n检查DLL文件...") for dll_path, description in required_dlls.items(): if os.path.exists(dll_path): print(f"✓ {description}: {dll_path}") else: missing_files.append((dll_path, description)) print(f"❌ {description}: {dll_path} - 文件不存在") if missing_files: print(f"\n警告: 发现 {len(missing_files)} 个缺失文件") print("缺失的文件可能导致打包失败或运行时错误") return False print("\n✓ 所有必需文件检查通过") return True def main(): """主函数""" print("=" * 50) print("身体平衡评估系统 - 后端打包工具") print("=" * 50) print() # 检查当前目录 if not os.path.exists('app.py'): print("❌ 请在backend目录下运行此脚本") return # 检查必需文件 print("\n检查必需文件...") if not check_required_files(): print("\n⚠️ 文件检查发现问题,但将继续执行打包") print("如果打包失败,请检查上述缺失的文件") input("按回车键继续...") try: # 创建配置文件 create_spec_file() # create_main_entry() # 注释掉以保留现有的main_exe.py修改 # 临时:为诊断脚本创建spec文件 if len(sys.argv) > 1 and sys.argv[1] == 'debug': create_debug_spec() return elif len(sys.argv) > 1 and sys.argv[1] == 'minimal': create_minimal_spec() return # 构建exe if build_exe(): print("\n✅ 所有操作完成!") else: print("\n❌ 构建过程中出现错误") except Exception as e: print(f"❌ 错误: {e}") input("\n按回车键退出...") if __name__ == '__main__': main()