2025-08-13 14:17:50 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
"""
|
2025-08-18 18:56:58 +08:00
|
|
|
|
main.py 完整版打包脚本
|
2025-08-13 14:17:50 +08:00
|
|
|
|
使用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():
|
2025-08-18 18:56:58 +08:00
|
|
|
|
"""创建main.py的PyInstaller spec文件"""
|
2025-08-13 14:17:50 +08:00
|
|
|
|
spec_content = '''
|
|
|
|
|
# -*- mode: python ; coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
block_cipher = None
|
|
|
|
|
|
|
|
|
|
a = Analysis(
|
2025-08-18 18:56:58 +08:00
|
|
|
|
['main.py'],
|
2025-08-13 14:17:50 +08:00
|
|
|
|
pathex=[],
|
|
|
|
|
binaries=[
|
2025-08-15 11:29:50 +08:00
|
|
|
|
('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传感器库
|
2025-08-19 14:03:34 +08:00
|
|
|
|
('dll/smitsense/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # SMiTSenseUsb库
|
|
|
|
|
('dll/smitsense/Wrapper.dll', 'dll/smitsense'), # SMiTSense传感器库包装类
|
2025-08-13 14:17:50 +08:00
|
|
|
|
],
|
|
|
|
|
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,
|
|
|
|
|
[],
|
2025-08-16 12:11:08 +08:00
|
|
|
|
exclude_binaries=True,
|
2025-08-15 10:12:38 +08:00
|
|
|
|
name='BodyBalanceBackend',
|
2025-08-13 14:17:50 +08:00
|
|
|
|
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
|
|
|
|
|
)
|
2025-08-16 12:11:08 +08:00
|
|
|
|
|
|
|
|
|
coll = COLLECT(
|
|
|
|
|
exe,
|
|
|
|
|
a.binaries,
|
|
|
|
|
a.zipfiles,
|
|
|
|
|
a.datas,
|
|
|
|
|
strip=False,
|
|
|
|
|
upx=True,
|
|
|
|
|
upx_exclude=[],
|
|
|
|
|
name='BodyBalanceBackend'
|
|
|
|
|
)
|
2025-08-13 14:17:50 +08:00
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
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("创建目录结构...")
|
|
|
|
|
|
2025-08-18 18:56:58 +08:00
|
|
|
|
directories = [
|
|
|
|
|
os.path.join('dist', 'BodyBalanceBackend', 'dll'),
|
|
|
|
|
os.path.join('dist', 'BodyBalanceBackend', 'data'),
|
|
|
|
|
os.path.join('dist', 'BodyBalanceBackend', 'logs')
|
|
|
|
|
]
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
|
|
|
|
for directory in directories:
|
2025-08-18 18:56:58 +08:00
|
|
|
|
if not os.path.exists(directory):
|
|
|
|
|
os.makedirs(directory)
|
|
|
|
|
print(f"✓ 创建目录: {os.path.relpath(directory, 'dist')}/")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
else:
|
2025-08-18 18:56:58 +08:00
|
|
|
|
print(f"ℹ️ 目录已存在: {os.path.relpath(directory, 'dist')}/")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
|
|
|
|
def copy_dll_files():
|
|
|
|
|
"""复制DLL文件到dll目录"""
|
|
|
|
|
print("复制DLL文件...")
|
|
|
|
|
|
2025-08-18 18:56:58 +08:00
|
|
|
|
dll_dir = os.path.join('dist', 'BodyBalanceBackend', 'dll')
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
|
|
|
|
# 查找所有DLL文件
|
|
|
|
|
dll_files = []
|
|
|
|
|
|
2025-08-15 10:12:38 +08:00
|
|
|
|
# 复制dll目录下的所有文件夹和文件到目标目录的dll目录下
|
|
|
|
|
source_dll_dir = 'dll'
|
|
|
|
|
if not os.path.exists(source_dll_dir):
|
|
|
|
|
print("⚠️ 未找到dll目录")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
return
|
|
|
|
|
|
2025-08-15 10:12:38 +08:00
|
|
|
|
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}")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
|
|
|
|
def copy_data_files():
|
2025-08-16 12:11:08 +08:00
|
|
|
|
"""准备数据库文件到data目录"""
|
|
|
|
|
print("准备数据库文件...")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
2025-08-18 18:56:58 +08:00
|
|
|
|
# 数据库文件应该复制到可执行文件同级的data目录
|
|
|
|
|
data_dir = os.path.join('dist', 'BodyBalanceBackend', 'data')
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
2025-08-16 12:11:08 +08:00
|
|
|
|
# 数据库文件列表 - 这些文件会在程序首次运行时自动创建
|
2025-08-18 18:56:58 +08:00
|
|
|
|
db_files = ['body_balance.db']
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
2025-08-16 12:11:08 +08:00
|
|
|
|
# 检查是否存在现有数据库文件,如果存在则复制
|
|
|
|
|
copied_count = 0
|
2025-08-13 14:17:50 +08:00
|
|
|
|
for db_file in db_files:
|
2025-08-16 12:11:08 +08:00
|
|
|
|
# 检查当前目录和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} 个数据库文件")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
|
|
|
|
def copy_config_files():
|
|
|
|
|
"""复制配置文件到dist目录"""
|
|
|
|
|
print("复制配置文件...")
|
|
|
|
|
|
|
|
|
|
config_files = ['config.ini']
|
2025-08-18 18:56:58 +08:00
|
|
|
|
dist_dir = os.path.join('dist', 'BodyBalanceBackend')
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
|
|
|
|
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)
|
2025-08-18 18:56:58 +08:00
|
|
|
|
print("身体平衡评估系统 - main.py 完整版打包工具")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
print("=" * 60)
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# 检查当前目录
|
2025-08-18 18:56:58 +08:00
|
|
|
|
if not os.path.exists('main.py'):
|
|
|
|
|
print("✗ 错误: 找不到 main.py 文件")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
print("请确保在backend目录下运行此脚本")
|
|
|
|
|
input("按回车键退出...")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# 清理构建目录
|
|
|
|
|
clean_build_dirs()
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# 创建spec文件
|
|
|
|
|
print("创建PyInstaller配置...")
|
|
|
|
|
create_app_spec()
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# 构建exe
|
|
|
|
|
if build_exe():
|
|
|
|
|
print()
|
|
|
|
|
print("后处理...")
|
|
|
|
|
|
|
|
|
|
# 检查生成的exe文件
|
2025-08-16 12:11:08 +08:00
|
|
|
|
exe_path = 'dist/BodyBalanceBackend/BodyBalanceBackend.exe'
|
2025-08-13 14:17:50 +08:00
|
|
|
|
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()
|
|
|
|
|
|
2025-08-18 18:56:58 +08:00
|
|
|
|
|
|
|
|
|
|
2025-08-13 14:17:50 +08:00
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print("🎉 打包完成!")
|
|
|
|
|
print()
|
|
|
|
|
print("输出文件:")
|
|
|
|
|
print(f"- 可执行文件: {exe_path}")
|
2025-08-15 10:12:38 +08:00
|
|
|
|
print("- 启动脚本: dist/start_backend.bat")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
print("- 配置文件: dist/config.ini")
|
|
|
|
|
print()
|
|
|
|
|
print("目录结构:")
|
2025-08-16 12:11:08 +08:00
|
|
|
|
print("- BodyBalanceBackend/ - 应用程序文件夹")
|
|
|
|
|
print("- dll/ - DLL文件")
|
|
|
|
|
print("- data/ - 数据库文件")
|
|
|
|
|
print("- logs/ - 日志文件")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
print()
|
|
|
|
|
print("使用方式:")
|
2025-08-16 12:11:08 +08:00
|
|
|
|
print("1. 直接运行: dist/BodyBalanceBackend/BodyBalanceBackend.exe")
|
2025-08-15 10:12:38 +08:00
|
|
|
|
print("2. 使用脚本: dist/start_backend.bat")
|
2025-08-13 14:17:50 +08:00
|
|
|
|
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()
|