BodyBalanceEvaluation/backend/app.py

1221 lines
45 KiB
Python
Raw Normal View History

2025-07-28 11:59:56 +08:00
#!/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
2025-07-28 11:59:56 +08:00
from flask_cors import CORS
import sqlite3
import logging
from pathlib import Path
2025-07-29 16:44:17 +08:00
from flask_socketio import SocketIO, emit
import cv2
import configparser
import os
2025-07-28 11:59:56 +08:00
# 添加当前目录到Python路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
# 导入自定义模块
from database import DatabaseManager
2025-07-31 17:23:05 +08:00
from device_manager import DeviceManager, VideoStreamManager
2025-08-20 10:30:51 +08:00
from devices.screen_recorder import RecordingManager
from devices.camera_manager import CameraManager
2025-07-31 17:19:17 +08:00
from utils import config as app_config
2025-07-28 11:59:56 +08:00
2025-08-13 14:17:50 +08:00
# 确定日志文件路径
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')
2025-07-28 11:59:56 +08:00
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
2025-08-13 14:17:50 +08:00
logging.FileHandler(log_file, encoding='utf-8'),
2025-07-28 11:59:56 +08:00
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 创建Flask应用
app = Flask(__name__)
app.config['SECRET_KEY'] = 'body-balance-detection-system-2024'
2025-08-13 14:17:50 +08:00
# 初始化SocketIO
try:
socketio = SocketIO(
app,
cors_allowed_origins='*',
async_mode='threading',
logger=False,
engineio_logger=False,
ping_timeout=60,
2025-08-13 16:57:28 +08:00
ping_interval=25,
manage_session=False,
always_connect=False,
2025-08-18 18:56:58 +08:00
transports=['polling'], # 只使用polling避免打包环境websocket问题
allow_upgrades=False, # 禁用升级到websocket
2025-08-13 16:57:28 +08:00
cookie=None # 禁用cookie
2025-08-13 14:17:50 +08:00
)
2025-08-13 16:57:28 +08:00
2025-08-13 14:17:50 +08:00
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)
2025-07-28 11:59:56 +08:00
# 启用CORS支持
CORS(app, origins='*', supports_credentials=True, allow_headers=['Content-Type', 'Authorization'], methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])
2025-07-28 11:59:56 +08:00
# 注册Blueprint如需要可在此处添加
2025-07-31 17:23:05 +08:00
2025-07-29 16:44:17 +08:00
# 读取RTSP配置
config = configparser.ConfigParser()
2025-08-13 14:17:50 +08:00
# 确定配置文件路径
if getattr(sys, 'frozen', False):
# 如果是打包后的exe配置文件在exe同目录下
config_path = os.path.join(os.path.dirname(sys.executable), 'config.ini')
else:
# 如果是开发环境,配置文件在上级目录
2025-08-13 16:57:28 +08:00
config_path = os.path.join(os.path.dirname(__file__), 'config.ini')
2025-08-13 14:17:50 +08:00
config.read(config_path, encoding='utf-8')
2025-08-02 16:52:17 +08:00
device_index = config.get('CAMERA', 'device_index', fallback=None)
2025-07-29 16:44:17 +08:00
2025-07-28 11:59:56 +08:00
# 全局变量
db_manager = None
device_manager = None
current_detection = None
detection_thread = None
2025-07-31 17:23:05 +08:00
video_stream_manager = None
2025-08-20 10:30:51 +08:00
recording_manager = None
camera_manager = None
2025-07-30 13:47:47 +08:00
2025-07-28 11:59:56 +08:00
def init_app():
"""初始化应用"""
global db_manager, device_manager, video_stream_manager
2025-07-28 11:59:56 +08:00
try:
2025-08-13 14:17:50 +08:00
# 确定基础目录
if getattr(sys, 'frozen', False):
# 如果是打包后的exe使用exe同目录
base_dir = os.path.dirname(sys.executable)
else:
# 如果是开发环境,使用当前脚本目录
base_dir = os.path.dirname(os.path.abspath(__file__))
2025-07-28 11:59:56 +08:00
# 创建必要的目录
2025-08-13 14:17:50 +08:00
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)
2025-07-28 11:59:56 +08:00
2025-07-31 17:53:09 +08:00
# 从配置文件读取数据库路径
2025-08-13 14:17:50 +08:00
db_path_config = app_config.get('DATABASE', 'path', 'data/body_balance.db')
# 如果是相对路径,基于基础目录解析
2025-08-02 16:52:17 +08:00
if not os.path.isabs(db_path_config):
# 如果配置路径以 'backend/' 开头,去掉这个前缀
if db_path_config.startswith('backend/'):
db_path_config = db_path_config[8:] # 去掉 'backend/' 前缀
2025-08-13 14:17:50 +08:00
db_path = os.path.join(base_dir, db_path_config)
2025-08-02 16:52:17 +08:00
else:
db_path = db_path_config
2025-07-31 17:53:09 +08:00
# 确保数据库目录存在
db_dir = os.path.dirname(db_path)
os.makedirs(db_dir, exist_ok=True)
2025-08-13 14:17:50 +08:00
# 输出数据库路径信息
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")
2025-07-28 11:59:56 +08:00
# 初始化数据库
2025-07-31 17:53:09 +08:00
db_manager = DatabaseManager(db_path)
2025-07-28 11:59:56 +08:00
db_manager.init_database()
2025-08-13 16:57:28 +08:00
# 初始化设备管理器(不自动初始化设备)
2025-08-20 16:04:38 +08:00
device_manager = DeviceManager(db_manager, recording_manager)
2025-08-20 10:30:51 +08:00
# 初始化相机管理器
global camera_manager, recording_manager
camera_manager = CameraManager()
# 初始化录制管理器
recording_manager = RecordingManager(camera_manager=camera_manager, db_manager=db_manager)
2025-08-13 14:17:50 +08:00
if socketio is not None:
2025-08-13 16:57:28 +08:00
logger.info('SocketIO已启用')
2025-08-13 14:17:50 +08:00
device_manager.set_socketio(socketio) # 设置WebSocket连接
# 初始化视频流管理器
t_vsm = time.time()
logger.info(f'[TIMING] 准备创建VideoStreamManager - {datetime.now().strftime("%H:%M:%S.%f")[:-3]}')
2025-08-13 14:17:50 +08:00
video_stream_manager = VideoStreamManager(socketio, device_manager)
logger.info(f'[TIMING] VideoStreamManager创建完成耗时: {(time.time()-t_vsm)*1000:.2f}ms')
2025-08-13 14:17:50 +08:00
else:
logger.info('SocketIO未启用跳过WebSocket相关初始化')
video_stream_manager = None
2025-08-13 16:57:28 +08:00
# 可选:在后台线程中初始化设备,避免阻塞应用启动
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
)
2025-07-28 11:59:56 +08:00
logger.info('应用初始化完成')
except Exception as e:
logger.error(f'应用初始化失败: {e}')
2025-08-06 17:13:05 +08:00
logger.warning('部分功能可能不可用,但服务将继续运行')
# 不再抛出异常,让应用继续运行
2025-07-28 11:59:56 +08:00
# ==================== 基础API ====================
@app.route('/health', methods=['GET'])
def health_check():
2025-08-13 16:57:28 +08:00
"""健康检查接口"""
2025-07-28 11:59:56 +08:00
return jsonify({
2025-08-13 16:57:28 +08:00
'status': 'healthy',
2025-07-28 11:59:56 +08:00
'timestamp': datetime.now().isoformat(),
'version': '1.0.0'
})
2025-08-13 16:57:28 +08:00
@app.route('/test-socketio')
def test_socketio():
"""SocketIO连接测试页面"""
return send_file('test_socketio_connection.html')
2025-07-28 11:59:56 +08:00
@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()
2025-07-28 11:59:56 +08:00
username = data.get('username')
password = data.get('password')
remember = data.get('remember', False)
2025-07-31 17:19:17 +08:00
if not username or not password:
return jsonify({
'success': False,
'message': '用户名或密码不能为空'
}), 400
2025-07-28 11:59:56 +08:00
2025-07-31 17:19:17 +08:00
# 使用数据库验证用户
user = db_manager.authenticate_user(username, password)
if user:
# 检查用户是否已激活
if not user['is_active']:
return jsonify({
'success': False,
'message': '账户未激活,请联系管理员审核'
}), 403
# 构建用户数据
2025-07-28 11:59:56 +08:00
user_data = {
2025-07-31 17:19:17 +08:00
'id': user['id'],
'username': user['username'],
'name': user['name'],
'role': 'admin' if user['user_type'] == 'admin' else 'user',
'user_type': user['user_type'],
2025-07-28 11:59:56 +08:00
'avatar': ''
}
2025-07-31 17:19:17 +08:00
# 生成token实际项目中应使用JWT等安全token
2025-07-28 11:59:56 +08:00
token = f"token_{username}_{int(time.time())}"
2025-07-31 17:19:17 +08:00
logger.info(f'用户 {username} 登录成功')
2025-07-28 11:59:56 +08:00
return jsonify({
'success': True,
'data': {
'token': token,
'user': user_data
},
'message': '登录成功'
})
else:
2025-07-31 17:19:17 +08:00
logger.warning(f'用户 {username} 登录失败:用户名或密码错误')
2025-07-28 11:59:56 +08:00
return jsonify({
'success': False,
2025-07-31 17:19:17 +08:00
'message': '用户名或密码错误'
}), 401
2025-07-28 11:59:56 +08:00
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()
2025-07-28 11:59:56 +08:00
username = data.get('username')
password = data.get('password')
2025-07-31 17:19:17 +08:00
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
2025-07-28 11:59:56 +08:00
2025-07-31 17:19:17 +08:00
# 构建用户数据字典
user_data = {
'username': username,
'password': password,
'name': name,
'phone': phone
}
2025-07-28 11:59:56 +08:00
2025-07-31 17:19:17 +08:00
# 使用数据库注册用户
result = db_manager.register_user(user_data)
if result['success']:
logger.info(f'用户 {username} 注册成功,等待管理员审核')
2025-07-28 11:59:56 +08:00
return jsonify({
'success': True,
2025-07-31 17:19:17 +08:00
'message': '注册成功,请等待管理员审核后登录'
2025-07-28 11:59:56 +08:00
})
else:
return jsonify({
'success': False,
2025-07-31 17:19:17 +08:00
'message': result['message']
2025-07-28 11:59:56 +08:00
}), 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')
2025-07-28 11:59:56 +08:00
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():
2025-07-31 17:19:17 +08:00
"""忘记密码 - 根据用户名和手机号找回密码"""
2025-07-28 11:59:56 +08:00
try:
data = flask_request.get_json()
2025-07-31 17:19:17 +08:00
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
2025-07-28 11:59:56 +08:00
2025-07-31 17:19:17 +08:00
# 验证手机号格式
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:
2025-08-02 16:52:17 +08:00
# 用户存在且手机号匹配,返回数据库中存储的实际密码
actual_password = user['password']
logger.info(f'用户 {username} 密码查询成功')
2025-07-31 17:19:17 +08:00
2025-07-28 11:59:56 +08:00
return jsonify({
'success': True,
2025-08-02 16:52:17 +08:00
'password': actual_password, # 返回数据库中存储的实际密码
2025-07-31 17:19:17 +08:00
'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
2025-07-31 17:19:17 +08:00
users = db_manager.get_users(page, size, status)
total = db_manager.get_users_count(status)
2025-07-31 17:19:17 +08:00
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):
"""审核用户(管理员功能)"""
2025-07-28 11:59:56 +08:00
try:
2025-07-31 17:19:17 +08:00
# 这里应该验证管理员权限
data = flask_request.get_json()
2025-07-31 17:19:17 +08:00
approve = data.get('approve', True)
2025-07-28 11:59:56 +08:00
2025-07-31 17:19:17 +08:00
result = db_manager.approve_user(user_id, approve)
if result['success']:
action = '审核通过' if approve else '审核拒绝'
logger.info(f'用户 {user_id} {action}')
2025-07-28 11:59:56 +08:00
return jsonify({
'success': True,
2025-07-31 17:19:17 +08:00
'message': f'用户{action}成功'
2025-07-28 11:59:56 +08:00
})
else:
return jsonify({
'success': False,
2025-07-31 17:19:17 +08:00
'message': result['message']
2025-07-28 11:59:56 +08:00
}), 400
except Exception as e:
2025-07-31 17:19:17 +08:00
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
2025-07-28 11:59:56 +08:00
@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', '')
2025-07-28 11:59:56 +08:00
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()
2025-07-28 11:59:56 +08:00
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()
2025-07-28 11:59:56 +08:00
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
2025-07-31 17:23:05 +08:00
@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
2025-07-28 11:59:56 +08:00
@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 ====================
2025-07-28 11:59:56 +08:00
@app.route('/api/devices/status', methods=['GET'])
def get_device_status():
"""获取设备状态"""
2025-07-28 11:59:56 +08:00
try:
if not device_manager:
return jsonify({'camera': False, 'imu': False, 'pressure': False})
2025-07-31 17:23:05 +08:00
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():
"""开始检测"""
2025-07-28 11:59:56 +08:00
try:
2025-08-06 08:48:38 +08:00
if not db_manager or not device_manager:
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
2025-07-31 17:23:05 +08:00
data = flask_request.get_json()
2025-08-06 08:48:38 +08:00
patient_id = data.get('patient_id')
creator_id = data.get('creator_id')
2025-08-06 08:48:38 +08:00
if not patient_id or not creator_id:
return jsonify({'success': False, 'error': '缺少患者ID或创建人ID'}), 400
2025-07-28 11:59:56 +08:00
2025-08-06 08:48:38 +08:00
# 调用create_detection_session方法settings传空字典
session_id = db_manager.create_detection_session(patient_id, settings={}, creator_id=creator_id)
2025-07-31 17:23:05 +08:00
2025-08-06 08:48:38 +08:00
# 开始同步录制
recording_response = None
try:
2025-08-20 10:30:51 +08:00
recording_response = recording_manager.start_recording(session_id, patient_id)
2025-08-06 08:48:38 +08:00
except Exception as rec_e:
logger.error(f'开始同步录制失败: {rec_e}')
2025-07-28 11:59:56 +08:00
2025-08-06 08:48:38 +08:00
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})
2025-07-28 11:59:56 +08:00
except Exception as e:
logger.error(f'开始检测失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
2025-07-31 17:23:05 +08:00
@app.route('/api/detection/<session_id>/stop', methods=['POST'])
def stop_detection(session_id):
"""停止检测"""
2025-07-31 17:23:05 +08:00
try:
2025-08-20 10:30:51 +08:00
if not db_manager or not recording_manager:
logger.error('数据库管理器或录制管理器未初始化')
return jsonify({'success': False, 'error': '数据库管理器或录制管理器未初始化'}), 500
if not session_id:
2025-08-06 08:48:38 +08:00
logger.error('缺少会话ID')
2025-07-31 17:23:05 +08:00
return jsonify({
'success': False,
'error': '缺少会话ID'
2025-07-31 17:23:05 +08:00
}), 400
2025-08-06 08:48:38 +08:00
data = flask_request.get_json()
2025-08-07 14:38:08 +08:00
# logger.debug(f'接收到停止检测请求session_id: {session_id}, 请求数据: {data}')
2025-08-06 08:48:38 +08:00
# video_data = data.get('videoData') if data else None
video_data = data['videoData']
mime_type = data.get('mimeType', 'video/webm;codecs=vp9') # 默认webm格式
2025-08-06 14:51:42 +08:00
import base64
2025-08-06 08:48:38 +08:00
# 验证base64视频数据格式
if not video_data.startswith('data:video/'):
return jsonify({
'success': False,
'message': '无效的视频数据格式'
}), 400
2025-08-06 14:51:42 +08:00
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
2025-08-20 10:30:51 +08:00
# 停止同步录制
2025-08-06 08:48:38 +08:00
try:
2025-08-20 10:30:51 +08:00
# 使用新的录制管理器停止录制
stop_result = recording_manager.stop_recording()
logger.info(f'录制停止结果: {stop_result}')
# 处理前端传来的视频数据(如果需要保存)
if video_bytes:
# 可以在这里添加保存前端视频数据的逻辑
logger.info(f'接收到前端视频数据,大小: {len(video_bytes)} 字节')
2025-08-06 08:48:38 +08:00
except Exception as rec_e:
logger.error(f'停止同步录制失败: {rec_e}', exc_info=True)
raise
# 更新会话状态为已完成
success = db_manager.update_session_status(session_id, 'completed')
2025-07-31 17:23:05 +08:00
if success:
logger.info(f'检测会话已停止 - 会话ID: {session_id}')
return jsonify({
'success': True,
'message': '检测已停止'
})
else:
2025-08-06 08:48:38 +08:00
logger.error('停止检测失败,更新会话状态失败')
return jsonify({
'success': False,
'error': '停止检测失败'
}), 500
2025-07-31 17:23:05 +08:00
except Exception as e:
2025-08-06 08:48:38 +08:00
logger.error(f'停止检测失败: {e}', exc_info=True)
return jsonify({'success': False, 'error': str(e)}), 500
2025-07-28 11:59:56 +08:00
@app.route('/api/detection/<session_id>/status', methods=['GET'])
def get_detection_status(session_id):
2025-07-28 11:59:56 +08:00
"""获取检测状态"""
try:
if not db_manager:
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
2025-07-28 11:59:56 +08:00
if not session_id:
return jsonify({
'success': False,
'error': '缺少会话ID'
}), 400
2025-07-28 11:59:56 +08:00
# 获取会话数据
session_data = db_manager.get_session_data(session_id)
2025-07-28 11:59:56 +08:00
if session_data:
return jsonify({
'success': True,
'data': session_data
})
else:
return jsonify({
'success': False,
'error': '会话不存在'
}), 404
2025-07-28 11:59:56 +08:00
except Exception as e:
logger.error(f'获取检测状态失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
2025-08-06 17:13:05 +08:00
@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):
"""采集检测数据"""
2025-07-28 11:59:56 +08:00
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
2025-07-28 11:59:56 +08:00
2025-08-20 16:04:38 +08:00
# 调用录制管理器采集数据
collected_data = recording_manager.collect_detection_data(
session_id=session_id,
2025-08-20 16:04:38 +08:00
patient_id=patient_id
)
# 将采集的数据保存到数据库
if collected_data:
db_manager.save_detection_data(session_id, collected_data)
logger.info(f'检测数据采集并保存成功: {session_id}')
2025-07-28 11:59:56 +08:00
return jsonify({
'success': True,
'data': {
'session_id': session_id,
'timestamp': collected_data.get('timestamp'),
'data_collected': bool(collected_data)
},
'message': '数据采集成功'
2025-07-28 11:59:56 +08:00
})
except Exception as e:
logger.error(f'采集检测数据失败: {e}')
2025-07-28 11:59:56 +08:00
return jsonify({'success': False, 'error': str(e)}), 500
2025-07-28 11:59:56 +08:00
@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')
2025-07-28 11:59:56 +08:00
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
2025-07-31 17:23:05 +08:00
# ==================== WebSocket 事件处理 ====================
2025-07-29 16:44:17 +08:00
2025-08-13 14:17:50 +08:00
# 只有当socketio不为None时才注册事件处理器
if socketio is not None:
# 简单的测试事件处理器
@socketio.on('connect')
def handle_connect():
# print('CLIENT CONNECTED!!!', flush=True) # 控制台打印测试已关闭
logger.info('客户端已连接')
2025-08-13 16:57:28 +08:00
return True
2025-08-13 14:17:50 +08:00
@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]}')
2025-08-13 14:17:50 +08:00
video_result = video_stream_manager.start_video_stream()
logger.info(f'[TIMING] start_video_stream返回耗时: {(time.time()-t_vs)*1000:.2f}ms: {video_result}')
2025-08-13 14:17:50 +08:00
results['cameras']['normal'] = video_result
else:
2025-08-13 14:17:50 +08:00
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}')
2025-08-13 16:57:28 +08:00
try:
emit('video_status', results)
except Exception as emit_error:
logger.error(f'发送video_status事件失败: {emit_error}')
2025-08-13 14:17:50 +08:00
except Exception as e:
logger.error(f'启动视频流失败: {e}', exc_info=True)
2025-08-13 16:57:28 +08:00
try:
emit('video_status', {'status': 'error', 'message': f'启动失败: {str(e)}'})
except Exception as emit_error:
logger.error(f'发送错误状态失败: {emit_error}')
2025-07-29 16:44:17 +08:00
@socketio.on('stop_video_stream')
2025-08-02 16:52:17 +08:00
def handle_stop_video(data=None):
logger.info(f'收到stop_video事件数据: {data}')
2025-07-29 18:28:40 +08:00
try:
results = {'status': 'success', 'cameras': {}}
# 停止视频流管理器(普通摄像头)
2025-07-31 17:23:05 +08:00
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深度相机推流已停止'}
2025-07-31 17:23:05 +08:00
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'] = '所有相机停止成功'
2025-08-13 16:57:28 +08:00
try:
emit('video_status', results)
except Exception as emit_error:
logger.error(f'发送video_status事件失败: {emit_error}')
2025-07-29 18:28:40 +08:00
except Exception as e:
2025-08-02 16:52:17 +08:00
logger.error(f'停止视频流失败: {e}')
2025-08-13 16:57:28 +08:00
try:
emit('video_status', {'status': 'error', 'message': f'停止失败: {str(e)}'})
except Exception as emit_error:
logger.error(f'发送错误状态失败: {emit_error}')
2025-07-29 18:28:40 +08:00
@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:
2025-08-13 16:57:28 +08:00
try:
emit('imu_status', {'status': 'success', 'message': 'IMU头部姿态数据推流已启动'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.info('IMU头部姿态数据推流已启动')
else:
2025-08-13 16:57:28 +08:00
try:
emit('imu_status', {'status': 'error', 'message': 'IMU头部姿态数据推流启动失败'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.error('IMU头部姿态数据推流启动失败')
else:
2025-08-13 16:57:28 +08:00
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}')
2025-08-13 16:57:28 +08:00
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:
2025-08-13 16:57:28 +08:00
try:
emit('imu_status', {'status': 'success', 'message': 'IMU头部姿态数据推流已停止'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.info('IMU头部姿态数据推流已停止')
else:
2025-08-13 16:57:28 +08:00
try:
emit('imu_status', {'status': 'error', 'message': 'IMU头部姿态数据推流停止失败'})
except Exception as emit_error:
logger.error(f'发送IMU状态失败: {emit_error}')
logger.error('IMU头部姿态数据推流停止失败')
else:
2025-08-13 16:57:28 +08:00
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}')
2025-08-13 16:57:28 +08:00
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)}'})
2025-08-13 16:57:28 +08:00
# ==================== 错误处理 ====================
@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
2025-08-06 09:04:13 +08:00
if __name__ == '__main__':
2025-08-13 16:57:28 +08:00
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()
2025-08-06 09:04:13 +08:00
try:
# 初始化应用
2025-08-13 16:57:28 +08:00
init_app()
2025-08-06 09:04:13 +08:00
except KeyboardInterrupt:
2025-08-13 16:57:28 +08:00
logger.info('服务被用户中断')
2025-08-06 09:04:13 +08:00
except Exception as e:
2025-08-13 16:57:28 +08:00
logger.error(f'服务启动失败: {e}')
sys.exit(1)
finally:
logger.info('后端服务已停止')