增加了后台打包功能
This commit is contained in:
parent
edd6ed78f4
commit
d5b9a08a3e
165
backend/BUILD_README.md
Normal file
165
backend/BUILD_README.md
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
# 身体平衡评估系统 - 后端打包说明
|
||||||
|
|
||||||
|
本文档说明如何将后端Python应用程序打包成独立的exe可执行文件。
|
||||||
|
|
||||||
|
## 📋 打包前准备
|
||||||
|
|
||||||
|
### 1. 环境要求
|
||||||
|
- Python 3.8+ (推荐 3.9-3.11)
|
||||||
|
- Windows 10/11 操作系统
|
||||||
|
- 至少 2GB 可用磁盘空间
|
||||||
|
|
||||||
|
### 2. 依赖检查
|
||||||
|
确保以下核心依赖已安装:
|
||||||
|
```bash
|
||||||
|
pip install Flask Flask-CORS Flask-SocketIO
|
||||||
|
pip install numpy opencv-python psutil
|
||||||
|
pip install PyInstaller
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 快速打包
|
||||||
|
|
||||||
|
### 方法一:使用批处理脚本(推荐)
|
||||||
|
1. 在backend目录下双击运行 `build_backend.bat`
|
||||||
|
2. 脚本会自动完成所有打包步骤
|
||||||
|
3. 等待打包完成
|
||||||
|
|
||||||
|
### 方法二:手动打包
|
||||||
|
1. 打开命令提示符,切换到backend目录
|
||||||
|
2. 运行打包脚本:
|
||||||
|
```bash
|
||||||
|
python build_exe.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法三:直接使用PyInstaller
|
||||||
|
```bash
|
||||||
|
# 生成spec文件
|
||||||
|
pyinstaller --onefile --windowed --name=BodyBalanceBackend app.py
|
||||||
|
|
||||||
|
# 或使用自定义spec文件
|
||||||
|
pyinstaller backend.spec --clean --noconfirm
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 输出文件结构
|
||||||
|
|
||||||
|
打包完成后,在 `dist/` 目录下会生成:
|
||||||
|
|
||||||
|
```
|
||||||
|
dist/
|
||||||
|
├── BodyBalanceBackend.exe # 主可执行文件
|
||||||
|
├── start_backend.bat # 启动脚本
|
||||||
|
├── config.ini # 配置文件
|
||||||
|
├── config.json # JSON配置文件
|
||||||
|
└── logs/ # 日志目录(运行时创建)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 配置说明
|
||||||
|
|
||||||
|
### PyInstaller配置 (backend.spec)
|
||||||
|
- **--onefile**: 打包成单个exe文件
|
||||||
|
- **--windowed**: 隐藏控制台窗口(可选)
|
||||||
|
- **--add-data**: 包含数据文件
|
||||||
|
- **--add-binary**: 包含二进制文件(如DLL)
|
||||||
|
- **--hidden-import**: 包含隐式导入的模块
|
||||||
|
|
||||||
|
### 包含的文件
|
||||||
|
- 配置文件:`config.ini`, `config.json`
|
||||||
|
- 动态库:`dll/k4a.dll`(如果存在)
|
||||||
|
- 数据目录:`data/`, `captured_images/`, `tests/`
|
||||||
|
|
||||||
|
## 🚀 部署和运行
|
||||||
|
|
||||||
|
### 1. 部署到目标机器
|
||||||
|
1. 将整个 `dist/` 文件夹复制到目标机器
|
||||||
|
2. 确保目标机器安装了必要的运行时库
|
||||||
|
|
||||||
|
### 2. 运行方式
|
||||||
|
|
||||||
|
**方式一:直接运行**
|
||||||
|
```bash
|
||||||
|
BodyBalanceBackend.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
**方式二:使用启动脚本**
|
||||||
|
```bash
|
||||||
|
start_backend.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
**方式三:命令行运行**
|
||||||
|
```bash
|
||||||
|
cd dist
|
||||||
|
BodyBalanceBackend.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 访问服务
|
||||||
|
- 服务器地址:`http://localhost:5000`
|
||||||
|
- 健康检查:`http://localhost:5000/health`
|
||||||
|
- API文档:`http://localhost:5000/api/health`
|
||||||
|
|
||||||
|
## 🔍 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
**1. 打包失败**
|
||||||
|
- 检查Python版本是否兼容
|
||||||
|
- 确保所有依赖已正确安装
|
||||||
|
- 检查磁盘空间是否充足
|
||||||
|
|
||||||
|
**2. exe运行失败**
|
||||||
|
- 检查是否缺少Visual C++ Redistributable
|
||||||
|
- 查看logs目录下的错误日志
|
||||||
|
- 确保配置文件存在且格式正确
|
||||||
|
|
||||||
|
**3. 模块导入错误**
|
||||||
|
- 在spec文件中添加缺失的模块到 `hiddenimports`
|
||||||
|
- 检查第三方库的兼容性
|
||||||
|
|
||||||
|
**4. 文件路径问题**
|
||||||
|
- 确保所有相对路径正确
|
||||||
|
- 检查数据文件是否正确包含
|
||||||
|
|
||||||
|
### 调试方法
|
||||||
|
|
||||||
|
**1. 启用控制台输出**
|
||||||
|
修改spec文件中的 `console=True`
|
||||||
|
|
||||||
|
**2. 查看详细日志**
|
||||||
|
```bash
|
||||||
|
BodyBalanceBackend.exe --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 测试模式运行**
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 自定义打包
|
||||||
|
|
||||||
|
### 修改打包配置
|
||||||
|
编辑 `build_exe.py` 文件,可以自定义:
|
||||||
|
- 输出文件名
|
||||||
|
- 包含的文件和目录
|
||||||
|
- 隐式导入的模块
|
||||||
|
- 图标文件
|
||||||
|
|
||||||
|
### 添加新的依赖
|
||||||
|
1. 在 `requirements_build.txt` 中添加新依赖
|
||||||
|
2. 在spec文件的 `hiddenimports` 中添加模块名
|
||||||
|
3. 重新打包
|
||||||
|
|
||||||
|
## 🔒 安全注意事项
|
||||||
|
|
||||||
|
1. **配置文件安全**:确保配置文件中不包含敏感信息
|
||||||
|
2. **网络安全**:默认绑定所有接口(0.0.0.0),生产环境建议修改
|
||||||
|
3. **文件权限**:确保exe文件有适当的执行权限
|
||||||
|
|
||||||
|
## 📞 技术支持
|
||||||
|
|
||||||
|
如果遇到问题,请检查:
|
||||||
|
1. 日志文件:`logs/backend.log`
|
||||||
|
2. 系统要求是否满足
|
||||||
|
3. 依赖版本是否兼容
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**注意**:首次打包可能需要较长时间,请耐心等待。建议在虚拟环境中进行打包以避免依赖冲突。
|
@ -44,11 +44,7 @@ logger = logging.getLogger(__name__)
|
|||||||
# 创建Flask应用
|
# 创建Flask应用
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['SECRET_KEY'] = 'body-balance-detection-system-2024'
|
app.config['SECRET_KEY'] = 'body-balance-detection-system-2024'
|
||||||
socketio = SocketIO(app,
|
socketio = SocketIO(app, cors_allowed_origins='*')
|
||||||
cors_allowed_origins='*',
|
|
||||||
async_mode='threading',
|
|
||||||
logger=True,
|
|
||||||
engineio_logger=True)
|
|
||||||
|
|
||||||
# 启用CORS支持
|
# 启用CORS支持
|
||||||
CORS(app, origins='*', supports_credentials=True, allow_headers=['Content-Type', 'Authorization'], methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])
|
CORS(app, origins='*', supports_credentials=True, allow_headers=['Content-Type', 'Authorization'], methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])
|
||||||
@ -559,7 +555,6 @@ def calibrate_devices():
|
|||||||
|
|
||||||
|
|
||||||
# ==================== 视频推流API ====================
|
# ==================== 视频推流API ====================
|
||||||
|
|
||||||
@app.route('/api/streaming/start', methods=['POST'])
|
@app.route('/api/streaming/start', methods=['POST'])
|
||||||
def start_video_streaming():
|
def start_video_streaming():
|
||||||
"""启动视频推流"""
|
"""启动视频推流"""
|
||||||
@ -926,15 +921,6 @@ def handle_disconnect():
|
|||||||
# print('CLIENT DISCONNECTED!!!', flush=True) # 控制台打印测试已关闭
|
# print('CLIENT DISCONNECTED!!!', flush=True) # 控制台打印测试已关闭
|
||||||
logger.info('客户端已断开连接')
|
logger.info('客户端已断开连接')
|
||||||
|
|
||||||
# @socketio.on('start_video')
|
|
||||||
# def handle_start_video_new(data=None):
|
|
||||||
# print('=== START VIDEO EVENT RECEIVED ===', flush=True)
|
|
||||||
# print(f'Data received: {data}', flush=True)
|
|
||||||
# logger.info('=== START VIDEO EVENT RECEIVED ===')
|
|
||||||
# logger.info(f'Data received: {data}')
|
|
||||||
# emit('video_status', {'status': 'received', 'message': 'start_video事件已接收'})
|
|
||||||
# return {'status': 'success'}
|
|
||||||
|
|
||||||
# 原始的start_video处理逻辑(暂时注释)
|
# 原始的start_video处理逻辑(暂时注释)
|
||||||
@socketio.on('start_video')
|
@socketio.on('start_video')
|
||||||
def handle_start_video(data=None):
|
def handle_start_video(data=None):
|
||||||
@ -1024,3 +1010,28 @@ def handle_stop_video(data=None):
|
|||||||
emit('video_status', {'status': 'error', 'message': f'停止失败: {str(e)}'})
|
emit('video_status', {'status': 'error', 'message': f'停止失败: {str(e)}'})
|
||||||
|
|
||||||
# 重复的事件处理器已删除,使用前面定义的版本
|
# 重复的事件处理器已删除,使用前面定义的版本
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
"""主入口点 - 用于直接运行和PyInstaller打包"""
|
||||||
|
try:
|
||||||
|
# 初始化应用
|
||||||
|
init_app()
|
||||||
|
|
||||||
|
# 启动服务器
|
||||||
|
logger.info("启动身体平衡评估系统后端服务...")
|
||||||
|
logger.info("服务器地址: http://localhost:5000")
|
||||||
|
|
||||||
|
socketio.run(
|
||||||
|
app,
|
||||||
|
host='0.0.0.0',
|
||||||
|
port=5000,
|
||||||
|
debug=False,
|
||||||
|
allow_unsafe_werkzeug=True
|
||||||
|
)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("用户中断,正在关闭服务器...")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"启动失败: {e}")
|
||||||
|
input("按回车键退出...")
|
||||||
|
sys.exit(1)
|
202
backend/app_simple.py
Normal file
202
backend/app_simple.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
身体平衡评估系统 - 后端服务器 (简化版本)
|
||||||
|
专门用于PyInstaller打包的版本,移除了SocketIO功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from datetime import datetime
|
||||||
|
from flask import Flask, jsonify, send_file
|
||||||
|
from flask import request as flask_request
|
||||||
|
from flask_cors import CORS
|
||||||
|
import sqlite3
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
import cv2
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
# 添加当前目录到Python路径
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# 导入自定义模块
|
||||||
|
from database import DatabaseManager
|
||||||
|
from device_manager import DeviceManager, VideoStreamManager
|
||||||
|
from utils import config as app_config
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
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__)
|
||||||
|
|
||||||
|
# 创建Flask应用
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['SECRET_KEY'] = 'body-balance-detection-system-2024'
|
||||||
|
|
||||||
|
# 启用CORS支持
|
||||||
|
CORS(app, origins='*', supports_credentials=True, allow_headers=['Content-Type', 'Authorization'], methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])
|
||||||
|
|
||||||
|
# 读取RTSP配置
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config_path = os.path.join(os.path.dirname(__file__), 'config.ini')
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
# 如果当前目录没有config.ini,尝试上级目录
|
||||||
|
config_path = os.path.join(os.path.dirname(__file__), '..', 'config.ini')
|
||||||
|
config.read(config_path, encoding='utf-8')
|
||||||
|
|
||||||
|
# 全局变量
|
||||||
|
db_manager = None
|
||||||
|
device_manager = None
|
||||||
|
current_detection = None
|
||||||
|
detection_thread = None
|
||||||
|
video_stream_manager = None
|
||||||
|
|
||||||
|
def init_app():
|
||||||
|
"""初始化应用"""
|
||||||
|
global db_manager, device_manager, video_stream_manager
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 初始化数据库管理器
|
||||||
|
db_path = os.path.join(os.path.dirname(__file__), 'data', 'body_balance.db')
|
||||||
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||||
|
db_manager = DatabaseManager(db_path)
|
||||||
|
# 初始化数据库表结构
|
||||||
|
db_manager.init_database()
|
||||||
|
logger.info("数据库管理器初始化成功")
|
||||||
|
|
||||||
|
# 初始化设备管理器
|
||||||
|
device_manager = DeviceManager()
|
||||||
|
logger.info("设备管理器初始化成功")
|
||||||
|
|
||||||
|
# 初始化视频流管理器
|
||||||
|
video_stream_manager = VideoStreamManager()
|
||||||
|
logger.info("视频流管理器初始化成功")
|
||||||
|
|
||||||
|
logger.info("应用初始化完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"应用初始化失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
@app.route('/health', methods=['GET'])
|
||||||
|
def health_check():
|
||||||
|
"""健康检查接口"""
|
||||||
|
return jsonify({
|
||||||
|
'status': 'ok',
|
||||||
|
'timestamp': datetime.now().isoformat(),
|
||||||
|
'message': '身体平衡评估系统后端服务正常运行'
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route('/api/health', methods=['GET'])
|
||||||
|
def api_health_check():
|
||||||
|
"""API健康检查接口"""
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'data': {
|
||||||
|
'service': '身体平衡评估系统',
|
||||||
|
'version': '1.0.0',
|
||||||
|
'timestamp': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route('/api/auth/login', methods=['POST'])
|
||||||
|
def login():
|
||||||
|
"""用户登录接口"""
|
||||||
|
try:
|
||||||
|
data = flask_request.get_json()
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
|
||||||
|
if not username or not password:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': '用户名和密码不能为空'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# 验证用户凭据
|
||||||
|
user = db_manager.authenticate_user(username, password)
|
||||||
|
if user:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': '登录成功',
|
||||||
|
'data': {
|
||||||
|
'user_id': user['id'],
|
||||||
|
'username': user['username'],
|
||||||
|
'role': user['role'],
|
||||||
|
'token': 'simple_token_' + str(user['id'])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': '用户名或密码错误'
|
||||||
|
}), 401
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"登录失败: {e}")
|
||||||
|
return jsonify({'status': 'error', 'message': '登录失败'}), 500
|
||||||
|
|
||||||
|
@app.route('/api/auth/register', methods=['POST'])
|
||||||
|
def register():
|
||||||
|
"""用户注册接口"""
|
||||||
|
try:
|
||||||
|
data = flask_request.get_json()
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
email = data.get('email', '')
|
||||||
|
|
||||||
|
if not username or not password:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': '用户名和密码不能为空'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# 创建用户
|
||||||
|
user_id = db_manager.create_user(username, password, email)
|
||||||
|
if user_id:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': '注册成功',
|
||||||
|
'data': {'user_id': user_id}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': '注册失败,用户名可能已存在'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"注册失败: {e}")
|
||||||
|
return jsonify({'status': 'error', 'message': '注册失败'}), 500
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
# 初始化应用
|
||||||
|
init_app()
|
||||||
|
|
||||||
|
# 启动服务器
|
||||||
|
logger.info("启动身体平衡评估系统后端服务器...")
|
||||||
|
logger.info("服务器地址: http://localhost:5000")
|
||||||
|
|
||||||
|
app.run(
|
||||||
|
host='0.0.0.0',
|
||||||
|
port=5000,
|
||||||
|
debug=False,
|
||||||
|
threaded=True
|
||||||
|
)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("服务器已停止")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"服务器启动失败: {e}")
|
||||||
|
sys.exit(1)
|
69
backend/backend.spec
Normal file
69
backend/backend.spec
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
|
||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['app.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[
|
||||||
|
('dll/k4a.dll', '.'), # 包含K4A动态库
|
||||||
|
],
|
||||||
|
datas=[
|
||||||
|
('..\config.ini', '.'), # 配置文件
|
||||||
|
('..\config.json', '.'), # JSON配置文件
|
||||||
|
('tests', 'tests'), # 测试文件夹
|
||||||
|
('data', 'data'), # 数据文件夹
|
||||||
|
],
|
||||||
|
hiddenimports=[
|
||||||
|
'flask',
|
||||||
|
'flask_socketio',
|
||||||
|
'flask_cors',
|
||||||
|
'cv2',
|
||||||
|
'numpy',
|
||||||
|
'sqlite3',
|
||||||
|
'configparser',
|
||||||
|
'logging',
|
||||||
|
'threading',
|
||||||
|
'queue',
|
||||||
|
'base64',
|
||||||
|
'psutil',
|
||||||
|
'pykinect_azure',
|
||||||
|
'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='..\document\icon.ico' if os.path.exists('..\document\icon.ico') else None,
|
||||||
|
)
|
64
backend/build_backend.bat
Normal file
64
backend/build_backend.bat
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
echo ================================================
|
||||||
|
echo 身体平衡评估系统 - 后端打包工具
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: 检查是否在正确的目录
|
||||||
|
if not exist "app.py" (
|
||||||
|
echo ❌ 错误:请在backend目录下运行此脚本
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
:: 检查Python环境
|
||||||
|
echo 🔍 检查Python环境...
|
||||||
|
python --version
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ❌ 错误:Python未安装或未添加到PATH
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
:: 安装打包依赖
|
||||||
|
echo.
|
||||||
|
echo 📦 安装打包依赖...
|
||||||
|
pip install pyinstaller
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ❌ 错误:PyInstaller安装失败
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
:: 清理之前的构建
|
||||||
|
echo.
|
||||||
|
echo 🧹 清理之前的构建文件...
|
||||||
|
if exist "build" rmdir /s /q "build"
|
||||||
|
if exist "dist" rmdir /s /q "dist"
|
||||||
|
if exist "*.spec" del /q "*.spec"
|
||||||
|
echo ✓ 清理完成
|
||||||
|
|
||||||
|
:: 运行打包脚本
|
||||||
|
echo.
|
||||||
|
echo 🚀 开始打包...
|
||||||
|
python build_exe.py
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ❌ 打包失败
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo 🎉 打包完成!
|
||||||
|
echo.
|
||||||
|
echo 📁 输出目录:dist/
|
||||||
|
echo 🚀 可执行文件:dist/BodyBalanceBackend.exe
|
||||||
|
echo 📋 启动脚本:dist/start_backend.bat
|
||||||
|
echo.
|
||||||
|
echo 使用说明:
|
||||||
|
echo 1. 将整个dist文件夹复制到目标机器
|
||||||
|
echo 2. 确保目标机器有必要的运行时库(Visual C++ Redistributable)
|
||||||
|
echo 3. 运行 BodyBalanceBackend.exe 或 start_backend.bat
|
||||||
|
echo.
|
||||||
|
pause
|
257
backend/build_exe.py
Normal file
257
backend/build_exe.py
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
后端应用程序打包脚本
|
||||||
|
使用PyInstaller将Flask应用程序打包成独立的exe文件
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def create_spec_file():
|
||||||
|
"""创建PyInstaller spec文件"""
|
||||||
|
spec_content = '''
|
||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['app.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[
|
||||||
|
('dll/k4a.dll', '.'), # 包含K4A动态库
|
||||||
|
],
|
||||||
|
datas=[
|
||||||
|
('..\\config.ini', '.'), # 配置文件
|
||||||
|
('..\\config.json', '.'), # JSON配置文件
|
||||||
|
('tests', 'tests'), # 测试文件夹
|
||||||
|
('data', 'data'), # 数据文件夹
|
||||||
|
],
|
||||||
|
hiddenimports=[
|
||||||
|
'flask',
|
||||||
|
'flask_socketio',
|
||||||
|
'flask_cors',
|
||||||
|
'cv2',
|
||||||
|
'numpy',
|
||||||
|
'sqlite3',
|
||||||
|
'configparser',
|
||||||
|
'logging',
|
||||||
|
'threading',
|
||||||
|
'queue',
|
||||||
|
'base64',
|
||||||
|
'psutil',
|
||||||
|
'pykinect_azure',
|
||||||
|
'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='..\\document\\icon.ico' if os.path.exists('..\\document\\icon.ico') else None,
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
with open('backend.spec', 'w', encoding='utf-8') as f:
|
||||||
|
f.write(spec_content)
|
||||||
|
print("✓ 已创建 backend.spec 文件")
|
||||||
|
|
||||||
|
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,
|
||||||
|
allow_unsafe_werkzeug=True
|
||||||
|
)
|
||||||
|
|
||||||
|
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 build_exe():
|
||||||
|
"""构建exe文件"""
|
||||||
|
print("开始构建exe文件...")
|
||||||
|
|
||||||
|
# 检查PyInstaller是否安装
|
||||||
|
try:
|
||||||
|
import PyInstaller
|
||||||
|
print(f"✓ 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 main():
|
||||||
|
"""主函数"""
|
||||||
|
print("=" * 50)
|
||||||
|
print("身体平衡评估系统 - 后端打包工具")
|
||||||
|
print("=" * 50)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 检查当前目录
|
||||||
|
if not os.path.exists('app.py'):
|
||||||
|
print("❌ 请在backend目录下运行此脚本")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建配置文件
|
||||||
|
create_spec_file()
|
||||||
|
create_main_entry()
|
||||||
|
|
||||||
|
# 构建exe
|
||||||
|
if build_exe():
|
||||||
|
print("\n✅ 所有操作完成!")
|
||||||
|
else:
|
||||||
|
print("\n❌ 构建过程中出现错误")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 错误: {e}")
|
||||||
|
|
||||||
|
input("\n按回车键退出...")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
41
backend/config.ini
Normal file
41
backend/config.ini
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
[APP]
|
||||||
|
name = Body Balance Evaluation System
|
||||||
|
version = 1.0.0
|
||||||
|
debug = false
|
||||||
|
log_level = INFO
|
||||||
|
|
||||||
|
[SERVER]
|
||||||
|
host = 0.0.0.0
|
||||||
|
port = 5000
|
||||||
|
cors_origins = *
|
||||||
|
|
||||||
|
[DATABASE]
|
||||||
|
path = backend/data/body_balance.db
|
||||||
|
backup_interval = 24
|
||||||
|
max_backups = 7
|
||||||
|
|
||||||
|
[DEVICES]
|
||||||
|
camera_index = 0
|
||||||
|
camera_width = 640
|
||||||
|
camera_height = 480
|
||||||
|
camera_fps = 30
|
||||||
|
imu_port = COM3
|
||||||
|
pressure_port = COM4
|
||||||
|
|
||||||
|
[DETECTION]
|
||||||
|
default_duration = 60
|
||||||
|
sampling_rate = 30
|
||||||
|
balance_threshold = 0.2
|
||||||
|
posture_threshold = 5.0
|
||||||
|
|
||||||
|
[DATA_PROCESSING]
|
||||||
|
filter_window = 5
|
||||||
|
outlier_threshold = 2.0
|
||||||
|
chart_dpi = 300
|
||||||
|
export_format = csv
|
||||||
|
|
||||||
|
[SECURITY]
|
||||||
|
secret_key = 9179711d0d1bed10e105f39c9210cce273cbd73f85fbdfcd41e2d1e20d5c50bd
|
||||||
|
session_timeout = 3600
|
||||||
|
max_login_attempts = 5
|
||||||
|
|
Binary file not shown.
71
backend/main_exe.py
Normal file
71
backend/main_exe.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
|
||||||
|
#!/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,
|
||||||
|
allow_unsafe_werkzeug=True
|
||||||
|
)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("用户中断,正在关闭服务器...")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"启动失败: {e}")
|
||||||
|
input("按回车键退出...")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
24
backend/requirements_build.txt
Normal file
24
backend/requirements_build.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# 打包专用依赖文件
|
||||||
|
# 只包含运行时必需的核心依赖
|
||||||
|
|
||||||
|
# Web framework
|
||||||
|
Flask==2.3.3
|
||||||
|
Flask-CORS==4.0.0
|
||||||
|
Flask-SocketIO==5.3.6
|
||||||
|
|
||||||
|
# Core dependencies
|
||||||
|
numpy==1.24.3
|
||||||
|
opencv-python==4.8.1.78
|
||||||
|
psutil==5.9.5
|
||||||
|
requests==2.31.0
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
|
||||||
|
# PyInstaller for building
|
||||||
|
PyInstaller==6.2.0
|
||||||
|
|
||||||
|
# Optional - only if available
|
||||||
|
# pykinect_azure # Comment out if not available
|
||||||
|
|
||||||
|
# System utilities
|
||||||
|
colorama==0.4.6
|
||||||
|
click==8.1.7
|
Loading…
Reference in New Issue
Block a user