BodyBalanceEvaluation/backend/build_app.py

355 lines
11 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
main.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():
"""创建main.py的PyInstaller spec文件"""
spec_content = '''
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['main.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/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # SMiTSenseUsb库
('dll/smitsense/Wrapper.dll', 'dll/smitsense'), # SMiTSense传感器库包装类
],
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_directories():
"""创建必要的目录结构"""
print("创建目录结构...")
directories = [
os.path.join('dist', 'BodyBalanceBackend', 'dll'),
os.path.join('dist', 'BodyBalanceBackend', 'data'),
os.path.join('dist', 'BodyBalanceBackend', 'logs')
]
for directory in directories:
if not os.path.exists(directory):
os.makedirs(directory)
print(f"✓ 创建目录: {os.path.relpath(directory, 'dist')}/")
else:
print(f" 目录已存在: {os.path.relpath(directory, 'dist')}/")
def copy_dll_files():
"""复制DLL文件到dll目录"""
print("复制DLL文件...")
dll_dir = os.path.join('dist', 'BodyBalanceBackend', '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目录
data_dir = os.path.join('dist', 'BodyBalanceBackend', 'data')
# 数据库文件列表 - 这些文件会在程序首次运行时自动创建
db_files = ['body_balance.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 = os.path.join('dist', 'BodyBalanceBackend')
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("身体平衡评估系统 - main.py 完整版打包工具")
print("=" * 60)
print()
# 检查当前目录
if not os.path.exists('main.py'):
print("✗ 错误: 找不到 main.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()
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()