#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ app.py 完整版打包脚本 使用PyInstaller将完整版Flask应用程序(包含SocketIO)打包成独立的exe文件 """ import os import sys import shutil import subprocess from pathlib import Path def clean_build_dirs(): """清理构建目录""" print("清理构建目录...") dirs_to_clean = ['build', 'dist', '__pycache__'] for dir_name in dirs_to_clean: if os.path.exists(dir_name): try: shutil.rmtree(dir_name) print(f"✓ 已删除 {dir_name}") except Exception as e: print(f"⚠️ 删除 {dir_name} 失败: {e}") def create_app_spec(): """创建app.py的PyInstaller spec文件""" spec_content = ''' # -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis( ['app.py'], pathex=[], binaries=[ ('dll/femtobolt/bin/k4a.dll', 'dll/femtobolt/bin'), # K4A动态库 ('dll/femtobolt/bin/k4arecord.dll', 'dll/femtobolt/bin'), # K4A录制库 ('dll/femtobolt/bin/depthengine_2_0.dll', 'dll/femtobolt/bin'), # 深度引擎 ('dll/femtobolt/bin/OrbbecSDK.dll', 'dll/femtobolt/bin'), # Orbbec SDK ('dll/femtobolt/bin/ob_usb.dll', 'dll/femtobolt/bin'), # Orbbec USB库 ('dll/femtobolt/bin/live555.dll', 'dll/femtobolt/bin'), # Live555库 ('dll/femtobolt/bin/OrbbecSDKConfig_v1.0.xml', 'dll/femtobolt/bin'), # Orbbec配置文件 ('dll/smitsense/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # SMiTSense传感器库 ('dll/smitsense/SMiTSenseUsbWrapper.dll', 'dll/smitsense'), # SMiTSense传感器库包装类 ], datas=[ ('config.ini', '.'), # 配置文件 ('data', 'data'), # 数据文件夹 ('dll', 'dll'), # DLL目录结构 ], 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', 'eventlet', 'socketio', 'engineio', 'engineio.async_drivers.threading', 'engineio.async_drivers.eventlet', 'engineio.async_eventlet', 'socketio.async_eventlet', 'greenlet', 'gevent', 'gevent.socket', 'gevent.select', 'dns', 'dns.resolver', 'dns.reversename', 'dns.e164', ], 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, [], exclude_binaries=True, 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 ) coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='BodyBalanceBackend' ) ''' with open('app.spec', 'w', encoding='utf-8') as f: f.write(spec_content) print("✓ 已创建 app.spec 文件") def build_exe(): """构建exe文件""" print("\n开始构建exe文件...") try: # 使用PyInstaller构建 cmd = [sys.executable, '-m', 'PyInstaller', 'app.spec', '--clean', '--noconfirm'] print(f"执行命令: {' '.join(cmd)}") # 修改编码处理,避免UTF-8错误 result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore') if result.returncode == 0: print("✓ 构建成功!") return True else: print(f"✗ 构建失败") print(f"错误输出: {result.stderr}") return False except Exception as e: print(f"✗ 构建过程出错: {e}") return False def create_startup_script(): """创建启动脚本""" startup_script = '''@echo off echo 启动身体平衡评估系统后端服务(完整版)... echo. echo 服务信息: echo - HTTP API: http://localhost:5000 echo - SocketIO: ws://localhost:5000 echo - 管理界面: http://localhost:5000 echo. echo 按Ctrl+C停止服务 echo. cd BodyBalanceBackend "BodyBalanceBackend.exe" if %errorlevel% neq 0 ( echo. echo 服务启动失败,请检查错误信息 pause ) ''' dist_dir = 'dist' if not os.path.exists(dist_dir): os.makedirs(dist_dir) with open(os.path.join(dist_dir, 'start_backend.bat'), 'w', encoding='utf-8') as f: f.write(startup_script) print("✓ 创建启动脚本: dist/start_backend.bat") def create_directories(): """创建必要的目录结构""" print("创建目录结构...") dist_dir = 'dist' directories = ['dll', 'data', 'logs'] for directory in directories: dir_path = os.path.join(dist_dir, directory) if not os.path.exists(dir_path): try: os.makedirs(dir_path) print(f"✓ 创建目录: {directory}/") except Exception as e: print(f"⚠️ 创建目录 {directory} 失败: {e}") else: print(f"✓ 目录已存在: {directory}/") def copy_dll_files(): """复制DLL文件到dll目录""" print("复制DLL文件...") dll_dir = os.path.join('dist', 'dll') # 查找所有DLL文件 dll_files = [] # 复制dll目录下的所有文件夹和文件到目标目录的dll目录下 source_dll_dir = 'dll' if not os.path.exists(source_dll_dir): print("⚠️ 未找到dll目录") return try: # 遍历dll目录下的所有内容 for item in os.listdir(source_dll_dir): source_path = os.path.join(source_dll_dir, item) target_path = os.path.join(dll_dir, item) if os.path.isdir(source_path): # 如果是目录,递归复制整个目录 shutil.copytree(source_path, target_path, dirs_exist_ok=True) print(f"✓ 已复制目录 {item}") else: # 如果是文件,直接复制 shutil.copy2(source_path, target_path) print(f"✓ 已复制文件 {item}") except Exception as e: print(f"⚠️ 复制dll目录内容失败: {e}") def copy_data_files(): """准备数据库文件到data目录""" print("准备数据库文件...") data_dir = os.path.join('dist', 'data') # 数据库文件列表 - 这些文件会在程序首次运行时自动创建 db_files = ['body_balance.db', 'database.db'] # 检查是否存在现有数据库文件,如果存在则复制 copied_count = 0 for db_file in db_files: # 检查当前目录和data目录中的数据库文件 source_paths = [db_file, os.path.join('data', db_file)] for source_path in source_paths: if os.path.exists(source_path): try: shutil.copy2(source_path, data_dir) print(f"✓ 已复制 {source_path} -> {data_dir}") copied_count += 1 break # 找到一个就跳出内层循环 except Exception as e: print(f"⚠️ 复制 {source_path} 失败: {e}") if copied_count == 0: print("ℹ️ 未找到现有数据库文件,程序首次运行时将自动创建") else: print(f"✓ 成功复制 {copied_count} 个数据库文件") def copy_config_files(): """复制配置文件到dist目录""" print("复制配置文件...") config_files = ['config.ini'] dist_dir = 'dist' if not os.path.exists(dist_dir): os.makedirs(dist_dir) for config_file in config_files: if os.path.exists(config_file): try: shutil.copy2(config_file, dist_dir) print(f"✓ 已复制 {config_file}") except Exception as e: print(f"⚠️ 复制 {config_file} 失败: {e}") else: print(f"⚠️ 配置文件不存在: {config_file}") def main(): """主函数""" print("=" * 60) print("身体平衡评估系统 - app.py 完整版打包工具") print("=" * 60) print() # 检查当前目录 if not os.path.exists('app.py'): print("✗ 错误: 找不到 app.py 文件") print("请确保在backend目录下运行此脚本") input("按回车键退出...") return try: # 清理构建目录 clean_build_dirs() print() # 创建spec文件 print("创建PyInstaller配置...") create_app_spec() print() # 构建exe if build_exe(): print() print("后处理...") # 检查生成的exe文件 exe_path = 'dist/BodyBalanceBackend/BodyBalanceBackend.exe' if os.path.exists(exe_path): print(f"✓ exe文件位置: {exe_path}") # 创建目录结构 create_directories() print() # 复制DLL文件 copy_dll_files() print() # 复制数据库文件 copy_data_files() print() # 复制配置文件 copy_config_files() print() # 创建启动脚本 create_startup_script() print() print("🎉 打包完成!") print() print("输出文件:") print(f"- 可执行文件: {exe_path}") print("- 启动脚本: dist/start_backend.bat") print("- 配置文件: dist/config.ini") print() print("目录结构:") print("- BodyBalanceBackend/ - 应用程序文件夹") print("- dll/ - DLL文件") print("- data/ - 数据库文件") print("- logs/ - 日志文件") print() print("使用方式:") print("1. 直接运行: dist/BodyBalanceBackend/BodyBalanceBackend.exe") print("2. 使用脚本: dist/start_backend.bat") print() print("服务地址: http://localhost:5000") print("SocketIO: ws://localhost:5000") else: print("✗ 错误: 未找到生成的exe文件") else: print("\n✗ 打包失败") except Exception as e: print(f"\n✗ 打包过程出错: {e}") print() input("按回车键退出...") if __name__ == '__main__': main()