BodyBalanceEvaluation/backend/app.py
2025-08-20 16:04:38 +08:00

1221 lines
45 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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 -*-
"""
平衡体态检测系统 - 后端服务
基于Flask的本地API服务
"""
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
from flask_socketio import SocketIO, emit
import cv2
import configparser
import os
# 添加当前目录到Python路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
# 导入自定义模块
from database import DatabaseManager
from device_manager import DeviceManager, VideoStreamManager
from devices.screen_recorder import RecordingManager
from devices.camera_manager import CameraManager
from utils import config as app_config
# 确定日志文件路径
if getattr(sys, 'frozen', False):
# 如果是打包后的exe日志文件在exe同目录下的logs文件夹
log_dir = os.path.join(os.path.dirname(sys.executable), 'logs')
else:
# 如果是开发环境使用当前目录的logs文件夹
log_dir = 'logs'
# 确保日志目录存在
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, 'backend.log')
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_file, encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 创建Flask应用
app = Flask(__name__)
app.config['SECRET_KEY'] = 'body-balance-detection-system-2024'
# 初始化SocketIO
try:
socketio = SocketIO(
app,
cors_allowed_origins='*',
async_mode='threading',
logger=False,
engineio_logger=False,
ping_timeout=60,
ping_interval=25,
manage_session=False,
always_connect=False,
transports=['polling'], # 只使用polling避免打包环境websocket问题
allow_upgrades=False, # 禁用升级到websocket
cookie=None # 禁用cookie
)
logger.info('SocketIO初始化成功')
except Exception as e:
logger.error(f'SocketIO初始化失败: {e}')
socketio = None
import logging
logging.getLogger('socketio').setLevel(logging.WARNING)
logging.getLogger('engineio').setLevel(logging.WARNING)
# 启用CORS支持
CORS(app, origins='*', supports_credentials=True, allow_headers=['Content-Type', 'Authorization'], methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])
# 注册Blueprint如需要可在此处添加
# 读取RTSP配置
config = configparser.ConfigParser()
# 确定配置文件路径
if getattr(sys, 'frozen', False):
# 如果是打包后的exe配置文件在exe同目录下
config_path = os.path.join(os.path.dirname(sys.executable), 'config.ini')
else:
# 如果是开发环境,配置文件在上级目录
config_path = os.path.join(os.path.dirname(__file__), 'config.ini')
config.read(config_path, encoding='utf-8')
device_index = config.get('CAMERA', 'device_index', fallback=None)
# 全局变量
db_manager = None
device_manager = None
current_detection = None
detection_thread = None
video_stream_manager = None
recording_manager = None
camera_manager = None
def init_app():
"""初始化应用"""
global db_manager, device_manager, video_stream_manager
try:
# 确定基础目录
if getattr(sys, 'frozen', False):
# 如果是打包后的exe使用exe同目录
base_dir = os.path.dirname(sys.executable)
else:
# 如果是开发环境,使用当前脚本目录
base_dir = os.path.dirname(os.path.abspath(__file__))
# 创建必要的目录
logs_dir = os.path.join(base_dir, 'logs')
data_dir = os.path.join(base_dir, 'data')
os.makedirs(logs_dir, exist_ok=True)
os.makedirs(data_dir, exist_ok=True)
# 从配置文件读取数据库路径
db_path_config = app_config.get('DATABASE', 'path', 'data/body_balance.db')
# 如果是相对路径,基于基础目录解析
if not os.path.isabs(db_path_config):
# 如果配置路径以 'backend/' 开头,去掉这个前缀
if db_path_config.startswith('backend/'):
db_path_config = db_path_config[8:] # 去掉 'backend/' 前缀
db_path = os.path.join(base_dir, db_path_config)
else:
db_path = db_path_config
# 确保数据库目录存在
db_dir = os.path.dirname(db_path)
os.makedirs(db_dir, exist_ok=True)
# 输出数据库路径信息
print(f"\n=== 系统初始化 ===")
print(f"数据库配置路径: {db_path_config}")
print(f"数据库实际路径: {db_path}")
print(f"数据库目录: {db_dir}")
print(f"当前工作目录: {os.getcwd()}")
print(f"数据库文件存在: {'' if os.path.exists(db_path) else ''}")
print(f"==================\n")
# 初始化数据库
db_manager = DatabaseManager(db_path)
db_manager.init_database()
# 初始化设备管理器(不自动初始化设备)
device_manager = DeviceManager(db_manager, recording_manager)
# 初始化相机管理器
global camera_manager, recording_manager
camera_manager = CameraManager()
# 初始化录制管理器
recording_manager = RecordingManager(camera_manager=camera_manager, db_manager=db_manager)
if socketio is not None:
logger.info('SocketIO已启用')
device_manager.set_socketio(socketio) # 设置WebSocket连接
# 初始化视频流管理器
t_vsm = time.time()
logger.info(f'[TIMING] 准备创建VideoStreamManager - {datetime.now().strftime("%H:%M:%S.%f")[:-3]}')
video_stream_manager = VideoStreamManager(socketio, device_manager)
logger.info(f'[TIMING] VideoStreamManager创建完成耗时: {(time.time()-t_vsm)*1000:.2f}ms')
else:
logger.info('SocketIO未启用跳过WebSocket相关初始化')
video_stream_manager = None
# 可选:在后台线程中初始化设备,避免阻塞应用启动
def init_devices_async():
try:
device_manager._init_devices()
logger.info('后台设备初始化完成')
except Exception as e:
logger.error(f'后台设备初始化失败: {e}')
# 启动后台设备初始化线程
device_init_thread = threading.Thread(target=init_devices_async, daemon=True)
device_init_thread.start()
socketio.run(
app,
host='0.0.0.0', # 允许所有IP访问
port=5000,
debug=True,
use_reloader=False, # 禁用热重载以避免FemtoBolt设备资源冲突
log_output=True, # 输出详细日志
allow_unsafe_werkzeug=True
)
logger.info('应用初始化完成')
except Exception as e:
logger.error(f'应用初始化失败: {e}')
logger.warning('部分功能可能不可用,但服务将继续运行')
# 不再抛出异常,让应用继续运行
# ==================== 基础API ====================
@app.route('/health', methods=['GET'])
def health_check():
"""健康检查接口"""
return jsonify({
'status': 'healthy',
'timestamp': datetime.now().isoformat(),
'version': '1.0.0'
})
@app.route('/test-socketio')
def test_socketio():
"""SocketIO连接测试页面"""
return send_file('test_socketio_connection.html')
@app.route('/api/health', methods=['GET'])
def api_health_check():
"""API健康检查"""
return jsonify({
'status': 'ok',
'timestamp': datetime.now().isoformat(),
'version': '1.0.0'
})
# ==================== 认证API ====================
@app.route('/api/auth/login', methods=['POST'])
def login():
"""用户登录"""
try:
data = flask_request.get_json()
username = data.get('username')
password = data.get('password')
remember = data.get('remember', False)
if not username or not password:
return jsonify({
'success': False,
'message': '用户名或密码不能为空'
}), 400
# 使用数据库验证用户
user = db_manager.authenticate_user(username, password)
if user:
# 检查用户是否已激活
if not user['is_active']:
return jsonify({
'success': False,
'message': '账户未激活,请联系管理员审核'
}), 403
# 构建用户数据
user_data = {
'id': user['id'],
'username': user['username'],
'name': user['name'],
'role': 'admin' if user['user_type'] == 'admin' else 'user',
'user_type': user['user_type'],
'avatar': ''
}
# 生成token实际项目中应使用JWT等安全token
token = f"token_{username}_{int(time.time())}"
logger.info(f'用户 {username} 登录成功')
return jsonify({
'success': True,
'data': {
'token': token,
'user': user_data
},
'message': '登录成功'
})
else:
logger.warning(f'用户 {username} 登录失败:用户名或密码错误')
return jsonify({
'success': False,
'message': '用户名或密码错误'
}), 401
except Exception as e:
logger.error(f'登录失败: {e}')
return jsonify({'success': False, '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')
name = data.get('name') or data.get('email', '')
phone = data.get('phone')
if not username or not password:
return jsonify({
'success': False,
'message': '用户名和密码不能为空'
}), 400
if len(password) < 6:
return jsonify({
'success': False,
'message': '密码长度不能少于6位'
}), 400
# 构建用户数据字典
user_data = {
'username': username,
'password': password,
'name': name,
'phone': phone
}
# 使用数据库注册用户
result = db_manager.register_user(user_data)
if result['success']:
logger.info(f'用户 {username} 注册成功,等待管理员审核')
return jsonify({
'success': True,
'message': '注册成功,请等待管理员审核后登录'
})
else:
return jsonify({
'success': False,
'message': result['message']
}), 400
except Exception as e:
logger.error(f'注册失败: {e}')
return jsonify({'success': False, 'message': '注册失败'}), 500
@app.route('/api/auth/logout', methods=['POST'])
def logout():
"""用户退出"""
try:
return jsonify({
'success': True,
'message': '退出成功'
})
except Exception as e:
logger.error(f'退出失败: {e}')
return jsonify({'success': False, 'message': '退出失败'}), 500
@app.route('/api/auth/verify', methods=['GET'])
def verify_token():
"""验证token"""
try:
# 简单的token验证实际项目中应验证JWT等
auth_header = flask_request.headers.get('Authorization')
if auth_header and auth_header.startswith('Bearer '):
token = auth_header.split(' ')[1]
# 这里可以添加真实的token验证逻辑
return jsonify({
'success': True,
'data': {'valid': True}
})
else:
return jsonify({
'success': True,
'data': {'valid': True} # 暂时总是返回有效
})
except Exception as e:
logger.error(f'验证token失败: {e}')
return jsonify({'success': False, 'message': '验证失败'}), 500
@app.route('/api/auth/forgot-password', methods=['POST'])
def forgot_password():
"""忘记密码 - 根据用户名和手机号找回密码"""
try:
data = flask_request.get_json()
username = data.get('username')
phone = data.get('phone')
if not username:
return jsonify({
'success': False,
'error': '请输入用户名'
}), 400
if not phone:
return jsonify({
'success': False,
'error': '请输入手机号码'
}), 400
# 验证手机号格式
import re
phone_pattern = r'^1[3-9]\d{9}$'
if not re.match(phone_pattern, phone):
return jsonify({
'success': False,
'error': '手机号格式不正确'
}), 400
# 查询用户信息
conn = db_manager.get_connection()
cursor = conn.cursor()
cursor.execute('''
SELECT username, password, phone FROM users
WHERE username = ? AND phone = ?
''', (username, phone))
user = cursor.fetchone()
if user:
# 用户存在且手机号匹配,返回数据库中存储的实际密码
actual_password = user['password']
logger.info(f'用户 {username} 密码查询成功')
return jsonify({
'success': True,
'password': actual_password, # 返回数据库中存储的实际密码
'message': '密码找回成功'
})
else:
# 检查用户是否存在
cursor.execute('SELECT username FROM users WHERE username = ?', (username,))
user_exists = cursor.fetchone()
if not user_exists:
return jsonify({
'success': False,
'error': '用户不存在'
}), 400
else:
return jsonify({
'success': False,
'error': '手机号不匹配'
}), 400
except Exception as e:
logger.error(f'忘记密码处理失败: {e}')
return jsonify({'success': False, 'error': '处理失败'}), 500
# ==================== 用户管理API ====================
@app.route('/api/users', methods=['GET'])
def get_users():
"""获取用户列表(管理员功能)"""
try:
# 这里应该验证管理员权限
page = int(flask_request.args.get('page', 1))
size = int(flask_request.args.get('size', 10))
status = flask_request.args.get('status') # active, inactive, all
users = db_manager.get_users(page, size, status)
total = db_manager.get_users_count(status)
return jsonify({
'success': True,
'data': {
'users': users,
'total': total,
'page': page,
'size': size
}
})
except Exception as e:
logger.error(f'获取用户列表失败: {e}')
return jsonify({'success': False, 'message': '获取用户列表失败'}), 500
@app.route('/api/users/<int:user_id>/approve', methods=['POST'])
def approve_user(user_id):
"""审核用户(管理员功能)"""
try:
# 这里应该验证管理员权限
data = flask_request.get_json()
approve = data.get('approve', True)
result = db_manager.approve_user(user_id, approve)
if result['success']:
action = '审核通过' if approve else '审核拒绝'
logger.info(f'用户 {user_id} {action}')
return jsonify({
'success': True,
'message': f'用户{action}成功'
})
else:
return jsonify({
'success': False,
'message': result['message']
}), 400
except Exception as e:
logger.error(f'审核用户失败: {e}')
return jsonify({'success': False, 'message': '审核用户失败'}), 500
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
"""删除用户(管理员功能)"""
try:
# 这里应该验证管理员权限
result = db_manager.delete_user(user_id)
if result['success']:
logger.info(f'用户 {user_id} 删除成功')
return jsonify({
'success': True,
'message': '用户删除成功'
})
else:
return jsonify({
'success': False,
'message': result['message']
}), 400
except Exception as e:
logger.error(f'删除用户失败: {e}')
return jsonify({'success': False, 'message': '删除用户失败'}), 500
@app.route('/api/system/info', methods=['GET'])
def get_system_info():
"""获取系统信息"""
try:
info = {
'version': '1.0.0',
'start_time': datetime.now().isoformat(),
'database_status': 'connected' if db_manager else 'disconnected',
'device_count': len(device_manager.get_connected_devices()) if device_manager else 0
}
return jsonify({'success': True, 'data': info})
except Exception as e:
logger.error(f'获取系统信息失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
# ==================== 患者管理API ====================
@app.route('/api/patients', methods=['GET'])
def get_patients():
"""获取患者列表"""
try:
page = int(flask_request.args.get('page', 1))
size = int(flask_request.args.get('size', 10))
keyword = flask_request.args.get('keyword', '')
patients = db_manager.get_patients(page, size, keyword)
total = db_manager.get_patients_count(keyword)
return jsonify({
'success': True,
'data': {
'patients': patients,
'total': total,
'page': page,
'size': size
}
})
except Exception as e:
logger.error(f'获取患者列表失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/patients', methods=['POST'])
def create_patient():
"""创建患者"""
try:
data = flask_request.get_json()
patient_id = db_manager.create_patient(data)
return jsonify({
'success': True,
'data': {'patient_id': patient_id},
'message': '患者创建成功'
})
except Exception as e:
logger.error(f'创建患者失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/patients/<patient_id>', methods=['PUT'])
def update_patient(patient_id):
"""更新患者信息"""
try:
data = flask_request.get_json()
db_manager.update_patient(patient_id, data)
return jsonify({'success': True, 'message': '患者信息更新成功'})
except Exception as e:
logger.error(f'更新患者信息失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/patients/<patient_id>', methods=['GET'])
def get_patient(patient_id):
"""获取单个患者信息"""
try:
patient = db_manager.get_patient(patient_id)
if patient:
return jsonify({'success': True, 'data': patient})
else:
return jsonify({'success': False, 'error': '患者不存在'}), 404
except Exception as e:
logger.error(f'获取患者信息失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/patients/<patient_id>', methods=['DELETE'])
def delete_patient(patient_id):
"""删除患者"""
try:
db_manager.delete_patient(patient_id)
return jsonify({'success': True, 'message': '患者删除成功'})
except Exception as e:
logger.error(f'删除患者失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
# ==================== 设备管理API ====================
@app.route('/api/devices/status', methods=['GET'])
def get_device_status():
"""获取设备状态"""
try:
if not device_manager:
return jsonify({'camera': False, 'imu': False, 'pressure': False})
status = device_manager.get_device_status()
return jsonify(status)
except Exception as e:
logger.error(f'获取设备状态失败: {e}')
return jsonify({'camera': False, 'imu': False, 'pressure': False})
@app.route('/api/devices/refresh', methods=['POST'])
def refresh_devices():
"""刷新设备连接"""
try:
if device_manager:
device_manager.refresh_devices()
return jsonify({'success': True, 'message': '设备已刷新'})
except Exception as e:
logger.error(f'刷新设备失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/devices/calibrate', methods=['POST'])
def calibrate_devices():
"""校准设备"""
try:
if device_manager:
result = device_manager.calibrate_devices()
return jsonify({'success': True, 'data': result})
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
except Exception as e:
logger.error(f'设备校准失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/devices/calibrate/imu', methods=['POST'])
def calibrate_imu():
"""校准IMU头部姿态传感器"""
try:
if not device_manager:
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
if not device_manager.device_status.get('imu', False):
return jsonify({'success': False, 'error': 'IMU设备未连接'}), 400
# 执行IMU校准
result = device_manager._calibrate_imu()
if result.get('status') == 'success':
logger.info('IMU头部姿态校准成功')
return jsonify({
'success': True,
'message': 'IMU头部姿态校准成功正立状态已设为零位基准',
'data': result
})
else:
logger.error(f'IMU校准失败: {result.get("error", "未知错误")}')
return jsonify({
'success': False,
'error': result.get('error', 'IMU校准失败')
}), 500
except Exception as e:
logger.error(f'IMU校准异常: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
# ==================== 检测API ====================
@app.route('/api/detection/start', methods=['POST'])
def start_detection():
"""开始检测"""
try:
if not db_manager or not device_manager:
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
data = flask_request.get_json()
patient_id = data.get('patient_id')
creator_id = data.get('creator_id')
if not patient_id or not creator_id:
return jsonify({'success': False, 'error': '缺少患者ID或创建人ID'}), 400
# 调用create_detection_session方法settings传空字典
session_id = db_manager.create_detection_session(patient_id, settings={}, creator_id=creator_id)
# 开始同步录制
recording_response = None
try:
recording_response = recording_manager.start_recording(session_id, patient_id)
except Exception as rec_e:
logger.error(f'开始同步录制失败: {rec_e}')
start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time, 'recording': recording_response})
except Exception as e:
logger.error(f'开始检测失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/detection/<session_id>/stop', methods=['POST'])
def stop_detection(session_id):
"""停止检测"""
try:
if not db_manager or not recording_manager:
logger.error('数据库管理器或录制管理器未初始化')
return jsonify({'success': False, 'error': '数据库管理器或录制管理器未初始化'}), 500
if not session_id:
logger.error('缺少会话ID')
return jsonify({
'success': False,
'error': '缺少会话ID'
}), 400
data = flask_request.get_json()
# logger.debug(f'接收到停止检测请求session_id: {session_id}, 请求数据: {data}')
# video_data = data.get('videoData') if data else None
video_data = data['videoData']
mime_type = data.get('mimeType', 'video/webm;codecs=vp9') # 默认webm格式
import base64
# 验证base64视频数据格式
if not video_data.startswith('data:video/'):
return jsonify({
'success': False,
'message': '无效的视频数据格式'
}), 400
try:
header, encoded = video_data.split(',', 1)
video_bytes = base64.b64decode(encoded)
# with open(r'D:/111.webm', 'wb') as f:
# f.write(video_bytes)
except Exception as e:
return jsonify({
'success': False,
'message': f'视频数据解码失败: {str(e)}'
}), 400
# 停止同步录制
try:
# 使用新的录制管理器停止录制
stop_result = recording_manager.stop_recording()
logger.info(f'录制停止结果: {stop_result}')
# 处理前端传来的视频数据(如果需要保存)
if video_bytes:
# 可以在这里添加保存前端视频数据的逻辑
logger.info(f'接收到前端视频数据,大小: {len(video_bytes)} 字节')
except Exception as rec_e:
logger.error(f'停止同步录制失败: {rec_e}', exc_info=True)
raise
# 更新会话状态为已完成
success = db_manager.update_session_status(session_id, 'completed')
if success:
logger.info(f'检测会话已停止 - 会话ID: {session_id}')
return jsonify({
'success': True,
'message': '检测已停止'
})
else:
logger.error('停止检测失败,更新会话状态失败')
return jsonify({
'success': False,
'error': '停止检测失败'
}), 500
except Exception as e:
logger.error(f'停止检测失败: {e}', exc_info=True)
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/detection/<session_id>/status', methods=['GET'])
def get_detection_status(session_id):
"""获取检测状态"""
try:
if not db_manager:
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
if not session_id:
return jsonify({
'success': False,
'error': '缺少会话ID'
}), 400
# 获取会话数据
session_data = db_manager.get_session_data(session_id)
if session_data:
return jsonify({
'success': True,
'data': session_data
})
else:
return jsonify({
'success': False,
'error': '会话不存在'
}), 404
except Exception as e:
logger.error(f'获取检测状态失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/detection/<session_id>/save-info', methods=['POST'])
def save_session_info(session_id):
"""保存会话信息(诊断、处理、建议、状态)"""
try:
if not db_manager:
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
if not session_id:
return jsonify({
'success': False,
'error': '缺少会话ID'
}), 400
# 获取请求数据
data = flask_request.get_json() or {}
diagnosis_info = data.get('diagnosis_info')
treatment_info = data.get('treatment_info')
suggestion_info = data.get('suggestion_info')
status = data.get('status')
# 验证至少提供一个要更新的字段
if not any([diagnosis_info, treatment_info, suggestion_info, status]):
return jsonify({
'success': False,
'error': '至少需要提供一个要更新的字段diagnosis_info, treatment_info, suggestion_info, status'
}), 400
# 调用数据库管理器的批量更新方法
db_manager.update_session_all_info(
session_id=session_id,
diagnosis_info=diagnosis_info,
treatment_info=treatment_info,
suggestion_info=suggestion_info,
status=status
)
# 构建更新信息反馈
updated_fields = []
if diagnosis_info is not None:
updated_fields.append('诊断信息')
if treatment_info is not None:
updated_fields.append('处理信息')
if suggestion_info is not None:
updated_fields.append('建议信息')
if status is not None:
updated_fields.append(f'状态({status})')
logger.info(f'会话信息保存成功: {session_id}, 更新字段: {", ".join(updated_fields)}')
return jsonify({
'success': True,
'message': f'会话信息保存成功,更新字段: {", ".join(updated_fields)}',
'data': {
'session_id': session_id,
'updated_fields': updated_fields
}
})
except Exception as e:
logger.error(f'保存会话信息失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/detection/<session_id>/collect', methods=['POST'])
def collect_detection_data(session_id):
"""采集检测数据"""
try:
if not db_manager:
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
if not device_manager:
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
# 获取请求数据
data = flask_request.get_json() or {}
patient_id = data.get('patient_id')
# 如果没有提供patient_id从会话信息中获取
if not patient_id:
session_data = db_manager.get_session_data(session_id)
if not session_data:
return jsonify({
'success': False,
'error': '检测会话不存在'
}), 404
patient_id = session_data.get('patient_id')
if not patient_id:
return jsonify({
'success': False,
'error': '无法获取患者ID'
}), 400
# 调用录制管理器采集数据
collected_data = recording_manager.collect_detection_data(
session_id=session_id,
patient_id=patient_id
)
# 将采集的数据保存到数据库
if collected_data:
db_manager.save_detection_data(session_id, collected_data)
logger.info(f'检测数据采集并保存成功: {session_id}')
return jsonify({
'success': True,
'data': {
'session_id': session_id,
'timestamp': collected_data.get('timestamp'),
'data_collected': bool(collected_data)
},
'message': '数据采集成功'
})
except Exception as e:
logger.error(f'采集检测数据失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/history/sessions', methods=['GET'])
def get_detection_sessions():
"""获取检测会话历史"""
try:
page = int(flask_request.args.get('page', 1))
size = int(flask_request.args.get('size', 10))
patient_id = flask_request.args.get('patient_id')
sessions = db_manager.get_detection_sessions(page, size, patient_id)
total = db_manager.get_sessions_count(patient_id)
return jsonify({
'success': True,
'data': {
'sessions': sessions,
'total': total,
'page': page,
'size': size
}
})
except Exception as e:
logger.error(f'获取检测历史失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
# ==================== WebSocket 事件处理 ====================
# 只有当socketio不为None时才注册事件处理器
if socketio is not None:
# 简单的测试事件处理器
@socketio.on('connect')
def handle_connect():
# print('CLIENT CONNECTED!!!', flush=True) # 控制台打印测试已关闭
logger.info('客户端已连接')
return True
@socketio.on('disconnect')
def handle_disconnect():
# print('CLIENT DISCONNECTED!!!', flush=True) # 控制台打印测试已关闭
logger.info('客户端已断开连接')
# 原始的start_video处理逻辑暂时注释
@socketio.on('start_video_stream')
def handle_start_video(data=None):
try:
results = {'status': 'success', 'cameras': {}}
logger.info(f'video_stream_manager状态: {video_stream_manager is not None}')
logger.info(f'device_manager状态: {device_manager is not None}')
# 启动视频流管理器(普通摄像头)
if video_stream_manager:
t_vs = time.time()
logger.info(f'[TIMING] 即将调用start_video_stream - {datetime.now().strftime("%H:%M:%S.%f")[:-3]}')
video_result = video_stream_manager.start_video_stream()
logger.info(f'[TIMING] start_video_stream返回耗时: {(time.time()-t_vs)*1000:.2f}ms: {video_result}')
results['cameras']['normal'] = video_result
else:
logger.error('视频流管理器未初始化')
results['cameras']['normal'] = {'status': 'error', 'message': '视频流管理器未初始化'}
# 启动FemtoBolt深度相机
if device_manager:
logger.info('正在启动FemtoBolt深度相机...')
femtobolt_result = device_manager.start_femtobolt_stream()
logger.info(f'FemtoBolt启动结果: {femtobolt_result}')
if femtobolt_result:
results['cameras']['femtobolt'] = {'status': 'success', 'message': 'FemtoBolt深度相机推流已启动'}
else:
results['cameras']['femtobolt'] = {'status': 'error', 'message': 'FemtoBolt深度相机启动失败'}
else:
logger.error('设备管理器未初始化')
results['cameras']['femtobolt'] = {'status': 'error', 'message': '设备管理器未初始化'}
# 检查是否有任何相机启动成功
success_count = sum(1 for cam_result in results['cameras'].values() if cam_result.get('status') == 'success')
if success_count == 0:
results['status'] = 'error'
results['message'] = '所有相机启动失败'
elif success_count < len(results['cameras']):
results['status'] = 'partial'
results['message'] = f'{success_count}/{len(results["cameras"])}个相机启动成功'
else:
results['message'] = '所有相机启动成功'
logger.info(f'发送video_status事件: {results}')
try:
emit('video_status', results)
except Exception as emit_error:
logger.error(f'发送video_status事件失败: {emit_error}')
except Exception as e:
logger.error(f'启动视频流失败: {e}', exc_info=True)
try:
emit('video_status', {'status': 'error', 'message': f'启动失败: {str(e)}'})
except Exception as emit_error:
logger.error(f'发送错误状态失败: {emit_error}')
@socketio.on('stop_video_stream')
def handle_stop_video(data=None):
logger.info(f'收到stop_video事件数据: {data}')
try:
results = {'status': 'success', 'cameras': {}}
# 停止视频流管理器(普通摄像头)
if video_stream_manager:
video_result = video_stream_manager.stop_video_stream()
results['cameras']['normal'] = video_result
else:
results['cameras']['normal'] = {'status': 'error', 'message': '视频流管理器未初始化'}
# 停止FemtoBolt深度相机
if device_manager:
device_manager.stop_femtobolt_stream()
results['cameras']['femtobolt'] = {'status': 'success', 'message': 'FemtoBolt深度相机推流已停止'}
else:
results['cameras']['femtobolt'] = {'status': 'error', 'message': '设备管理器未初始化'}
# 检查是否有任何相机停止成功
success_count = sum(1 for cam_result in results['cameras'].values() if cam_result.get('status') == 'success')
if success_count == 0:
results['status'] = 'error'
results['message'] = '所有相机停止失败'
elif success_count < len(results['cameras']):
results['status'] = 'partial'
results['message'] = f'{success_count}/{len(results["cameras"])}个相机停止成功'
else:
results['message'] = '所有相机停止成功'
try:
emit('video_status', results)
except Exception as emit_error:
logger.error(f'发送video_status事件失败: {emit_error}')
except Exception as e:
logger.error(f'停止视频流失败: {e}')
try:
emit('video_status', {'status': 'error', 'message': f'停止失败: {str(e)}'})
except Exception as emit_error:
logger.error(f'发送错误状态失败: {emit_error}')
@socketio.on('start_imu_streaming')
def handle_start_imu_streaming(data=None):
"""启动IMU头部姿态数据推流"""
try:
if device_manager:
result = device_manager.start_imu_streaming()
if result:
try:
emit('imu_status', {'status': 'success', 'message': 'IMU头部姿态数据推流已启动'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.info('IMU头部姿态数据推流已启动')
else:
try:
emit('imu_status', {'status': 'error', 'message': 'IMU头部姿态数据推流启动失败'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.error('IMU头部姿态数据推流启动失败')
else:
try:
emit('imu_status', {'status': 'error', 'message': '设备管理器未初始化'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.error('设备管理器未初始化')
except Exception as e:
logger.error(f'启动IMU数据推流失败: {e}')
try:
emit('imu_status', {'status': 'error', 'message': f'启动失败: {str(e)}'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
@socketio.on('stop_imu_streaming')
def handle_stop_imu_streaming(data=None):
"""停止IMU头部姿态数据推流"""
try:
if device_manager:
result = device_manager.stop_imu_streaming()
if result:
try:
emit('imu_status', {'status': 'success', 'message': 'IMU头部姿态数据推流已停止'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.info('IMU头部姿态数据推流已停止')
else:
try:
emit('imu_status', {'status': 'error', 'message': 'IMU头部姿态数据推流停止失败'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.error('IMU头部姿态数据推流停止失败')
else:
try:
emit('imu_status', {'status': 'error', 'message': '设备管理器未初始化'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.error('设备管理器未初始化')
except Exception as e:
logger.error(f'停止IMU数据推流失败: {e}')
try:
emit('imu_status', {'status': 'error', 'message': f'停止失败: {str(e)}'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
@socketio.on('start_pressure_streaming')
def handle_start_pressure_streaming(data=None):
"""启动压力传感器足部压力数据推流"""
try:
if device_manager:
result = device_manager.start_pressure_streaming()
if result:
emit('pressure_status', {'status': 'success', 'message': '压力传感器足部压力数据推流已启动'})
logger.info('压力传感器足部压力数据推流已启动')
else:
emit('pressure_status', {'status': 'error', 'message': '压力传感器足部压力数据推流启动失败'})
logger.error('压力传感器足部压力数据推流启动失败')
else:
emit('pressure_status', {'status': 'error', 'message': '设备管理器未初始化'})
logger.error('设备管理器未初始化')
except Exception as e:
logger.error(f'启动压力传感器数据推流失败: {e}')
emit('pressure_status', {'status': 'error', 'message': f'启动失败: {str(e)}'})
@socketio.on('stop_pressure_streaming')
def handle_stop_pressure_streaming(data=None):
"""停止压力传感器足部压力数据推流"""
try:
if device_manager:
result = device_manager.stop_pressure_streaming()
if result:
emit('pressure_status', {'status': 'success', 'message': '压力传感器足部压力数据推流已停止'})
logger.info('压力传感器足部压力数据推流已停止')
else:
emit('pressure_status', {'status': 'error', 'message': '压力传感器足部压力数据推流停止失败'})
logger.error('压力传感器足部压力数据推流停止失败')
else:
emit('pressure_status', {'status': 'error', 'message': '设备管理器未初始化'})
logger.error('设备管理器未初始化')
except Exception as e:
logger.error(f'停止压力传感器数据推流失败: {e}')
emit('pressure_status', {'status': 'error', 'message': f'停止失败: {str(e)}'})
# ==================== 错误处理 ====================
@app.errorhandler(404)
def not_found(error):
return jsonify({'success': False, 'error': 'API接口不存在'}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({'success': False, 'error': '服务器内部错误'}), 500
if __name__ == '__main__':
import argparse
# 解析命令行参数
parser = argparse.ArgumentParser(description='Body Balance Evaluation System Backend')
parser.add_argument('--host', default=None, help='Host address to bind to')
parser.add_argument('--port', type=int, default=None, help='Port number to bind to')
parser.add_argument('--debug', action='store_true', help='Enable debug mode')
args = parser.parse_args()
try:
# 初始化应用
init_app()
except KeyboardInterrupt:
logger.info('服务被用户中断')
except Exception as e:
logger.error(f'服务启动失败: {e}')
sys.exit(1)
finally:
logger.info('后端服务已停止')