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
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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
|
2025-07-30 19:09:15 +08:00
|
|
|
|
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-07-31 17:19:17 +08:00
|
|
|
|
from utils import config as app_config
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
|
|
|
|
# 配置日志
|
|
|
|
|
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'
|
2025-08-03 21:50:50 +08:00
|
|
|
|
socketio = SocketIO(app,
|
|
|
|
|
cors_allowed_origins='*',
|
|
|
|
|
async_mode='threading',
|
2025-08-05 13:51:03 +08:00
|
|
|
|
logger=False,
|
|
|
|
|
engineio_logger=False)
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
logging.getLogger('socketio').setLevel(logging.WARNING)
|
|
|
|
|
logging.getLogger('engineio').setLevel(logging.WARNING)
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
|
|
|
|
# 启用CORS支持
|
2025-07-30 19:09:15 +08:00
|
|
|
|
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
|
|
|
|
|
2025-08-03 21:50:50 +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-02 16:52:17 +08:00
|
|
|
|
config.read(os.path.join(os.path.dirname(__file__), '..', 'config.ini'), encoding='utf-8')
|
|
|
|
|
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-07-30 13:47:47 +08:00
|
|
|
|
|
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
|
|
|
|
def init_app():
|
|
|
|
|
"""初始化应用"""
|
2025-08-03 21:50:50 +08:00
|
|
|
|
global db_manager, device_manager, video_stream_manager
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# 创建必要的目录
|
|
|
|
|
os.makedirs('logs', exist_ok=True)
|
2025-08-02 16:52:17 +08:00
|
|
|
|
os.makedirs('data', exist_ok=True)
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-07-31 17:53:09 +08:00
|
|
|
|
# 从配置文件读取数据库路径
|
2025-08-02 16:52:17 +08:00
|
|
|
|
db_path_config = app_config.get('DATABASE', 'path', 'backend/data/body_balance.db')
|
|
|
|
|
# 如果是相对路径,基于当前脚本目录解析
|
|
|
|
|
if not os.path.isabs(db_path_config):
|
|
|
|
|
# 获取当前脚本所在目录(backend目录)
|
|
|
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
# 如果配置路径以 'backend/' 开头,去掉这个前缀
|
|
|
|
|
if db_path_config.startswith('backend/'):
|
|
|
|
|
db_path_config = db_path_config[8:] # 去掉 'backend/' 前缀
|
|
|
|
|
db_path = os.path.join(current_dir, db_path_config)
|
|
|
|
|
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-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-03 21:50:50 +08:00
|
|
|
|
device_manager = DeviceManager(db_manager)
|
|
|
|
|
device_manager.set_socketio(socketio) # 设置WebSocket连接
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-07-31 17:53:09 +08:00
|
|
|
|
# 初始化视频流管理器
|
|
|
|
|
video_stream_manager = VideoStreamManager(socketio)
|
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
logger.info('应用初始化完成')
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f'应用初始化失败: {e}')
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
# ==================== 基础API ====================
|
|
|
|
|
|
|
|
|
|
@app.route('/health', methods=['GET'])
|
|
|
|
|
def health_check():
|
|
|
|
|
"""健康检查"""
|
|
|
|
|
return jsonify({
|
|
|
|
|
'status': 'ok',
|
|
|
|
|
'timestamp': datetime.now().isoformat(),
|
|
|
|
|
'version': '1.0.0'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
@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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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等)
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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:
|
|
|
|
|
# 这里应该验证管理员权限
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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)
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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
|
|
|
|
# 这里应该验证管理员权限
|
2025-08-03 21:50:50 +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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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
|
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
# ==================== 设备管理API ====================
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
@app.route('/api/devices/status', methods=['GET'])
|
|
|
|
|
def get_device_status():
|
|
|
|
|
"""获取设备状态"""
|
2025-07-28 11:59:56 +08:00
|
|
|
|
try:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
if not device_manager:
|
|
|
|
|
return jsonify({'camera': False, 'imu': False, 'pressure': False})
|
2025-07-31 17:23:05 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 视频推流API ====================
|
|
|
|
|
@app.route('/api/streaming/start', methods=['POST'])
|
|
|
|
|
def start_video_streaming():
|
|
|
|
|
"""启动视频推流"""
|
|
|
|
|
try:
|
|
|
|
|
if not device_manager:
|
|
|
|
|
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
# 设置WebSocket连接
|
|
|
|
|
device_manager.set_socketio(socketio)
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
result = device_manager.start_streaming()
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.info(f'视频推流启动结果: {result}')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
2025-08-03 21:50:50 +08:00
|
|
|
|
'message': '视频推流已启动',
|
|
|
|
|
'streaming_status': result
|
2025-07-28 11:59:56 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.error(f'启动视频推流失败: {e}')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
@app.route('/api/streaming/stop', methods=['POST'])
|
|
|
|
|
def stop_video_streaming():
|
|
|
|
|
"""停止视频推流"""
|
|
|
|
|
try:
|
|
|
|
|
if not device_manager:
|
|
|
|
|
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
|
|
|
|
|
|
|
|
|
|
result = device_manager.stop_streaming()
|
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
logger.info('视频推流已停止')
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': '视频推流已停止'
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '停止推流失败'
|
|
|
|
|
}), 500
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f'停止视频推流失败: {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
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
data = flask_request.get_json()
|
2025-08-06 08:48:38 +08:00
|
|
|
|
patient_id = data.get('patient_id')
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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:
|
|
|
|
|
recording_response = device_manager.start_recording(session_id, patient_id)
|
|
|
|
|
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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.error(f'开始检测失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
2025-07-31 17:23:05 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +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-06 08:48:38 +08:00
|
|
|
|
if not db_manager or not device_manager:
|
|
|
|
|
logger.error('数据库管理器或设备管理器未初始化')
|
|
|
|
|
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
|
2025-08-03 21:50:50 +08:00
|
|
|
|
|
|
|
|
|
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({
|
2025-08-03 21:50:50 +08:00
|
|
|
|
'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()
|
|
|
|
|
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格式
|
|
|
|
|
|
|
|
|
|
# 验证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)
|
|
|
|
|
# except Exception as e:
|
|
|
|
|
# return jsonify({
|
|
|
|
|
# 'success': False,
|
|
|
|
|
# 'message': f'视频数据解码失败: {str(e)}'
|
|
|
|
|
# }), 400
|
|
|
|
|
# 停止同步录制,传递视频数据
|
|
|
|
|
try:
|
|
|
|
|
logger.debug(f'调用device_manager.stop_recording,session_id: {session_id}, video_data长度: {len(video_data) if video_data else 0}')
|
|
|
|
|
if video_data is None:
|
|
|
|
|
logger.warning(f'视频数据为空,session_id: {session_id}')
|
|
|
|
|
else:
|
|
|
|
|
logger.debug(f'视频数据长度: {len(video_data)} 字符,约 {len(video_data)*3/4/1024:.2f} KB, session_id: {session_id}')
|
|
|
|
|
restrt=device_manager.stop_recording(session_id, video_data_base64=video_data)
|
|
|
|
|
logger.error(restrt)
|
|
|
|
|
except Exception as rec_e:
|
|
|
|
|
logger.error(f'停止同步录制失败: {rec_e}', exc_info=True)
|
|
|
|
|
raise
|
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
# 更新会话状态为已完成
|
|
|
|
|
success = db_manager.update_session_status(session_id, 'completed')
|
2025-07-31 17:23:05 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +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('停止检测失败,更新会话状态失败')
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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)
|
2025-08-03 21:50:50 +08:00
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
if not db_manager:
|
|
|
|
|
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
if not session_id:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '缺少会话ID'
|
|
|
|
|
}), 400
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
# 获取会话数据
|
|
|
|
|
session_data = db_manager.get_session_data(session_id)
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +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-03 21:50:50 +08:00
|
|
|
|
@app.route('/api/detection/<session_id>/collect', methods=['POST'])
|
|
|
|
|
def collect_detection_data(session_id):
|
|
|
|
|
"""采集检测数据"""
|
2025-07-28 11:59:56 +08:00
|
|
|
|
try:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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')
|
|
|
|
|
screen_image_base64 = data.get('screen_image')
|
|
|
|
|
|
|
|
|
|
# 如果没有提供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-03 21:50:50 +08:00
|
|
|
|
# 调用设备管理器采集数据
|
|
|
|
|
collected_data = device_manager.collect_data(
|
|
|
|
|
session_id=session_id,
|
|
|
|
|
patient_id=patient_id,
|
|
|
|
|
screen_image_base64=screen_image_base64
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 将采集的数据保存到数据库
|
|
|
|
|
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,
|
2025-08-03 21:50:50 +08:00
|
|
|
|
'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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.error(f'采集检测数据失败: {e}')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
# ==================== 同步录制API ====================
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
@app.route('/api/recording/sync/start', methods=['POST'])
|
|
|
|
|
def start_sync_recording():
|
|
|
|
|
"""启动同步录制"""
|
2025-07-28 11:59:56 +08:00
|
|
|
|
try:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
if not device_manager:
|
|
|
|
|
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
data = flask_request.get_json()
|
|
|
|
|
session_id = data.get('session_id')
|
|
|
|
|
patient_id = data.get('patient_id')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
if not session_id or not patient_id:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '缺少必要参数: session_id 和 patient_id'
|
|
|
|
|
}), 400
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
result = device_manager.start_recording(session_id, patient_id)
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
if result['success']:
|
|
|
|
|
logger.info(f'同步录制已启动 - 会话ID: {session_id}, 患者ID: {patient_id}')
|
|
|
|
|
return jsonify(result)
|
|
|
|
|
else:
|
|
|
|
|
return jsonify(result), 500
|
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
except Exception as e:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.error(f'启动同步录制失败: {e}')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
@app.route('/api/recording/sync/stop', methods=['POST'])
|
|
|
|
|
def stop_sync_recording():
|
|
|
|
|
"""停止同步录制"""
|
2025-07-28 11:59:56 +08:00
|
|
|
|
try:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
if not device_manager:
|
|
|
|
|
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
data = flask_request.get_json()
|
|
|
|
|
session_id = data.get('session_id')
|
2025-08-06 08:48:38 +08:00
|
|
|
|
video_data = data.get('videoData') # 新增接收前端传递的视频数据
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
if not session_id:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '缺少必要参数: session_id'
|
|
|
|
|
}), 400
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-06 08:48:38 +08:00
|
|
|
|
result = device_manager.stop_recording(session_id, video_data_base64=video_data)
|
2025-08-03 21:50:50 +08:00
|
|
|
|
|
|
|
|
|
if result['success']:
|
|
|
|
|
logger.info(f'同步录制已停止 - 会话ID: {session_id}')
|
|
|
|
|
return jsonify(result)
|
|
|
|
|
else:
|
|
|
|
|
return jsonify(result), 500
|
2025-08-06 08:48:38 +08:00
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
except Exception as e:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.error(f'停止同步录制失败: {e}')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
@app.route('/api/history/sessions', methods=['GET'])
|
|
|
|
|
def get_detection_sessions():
|
|
|
|
|
"""获取检测会话历史"""
|
|
|
|
|
try:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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-30 19:09:15 +08:00
|
|
|
|
|
2025-07-28 11:59:56 +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
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2025-07-30 19:09:15 +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-07-28 11:59:56 +08:00
|
|
|
|
try:
|
|
|
|
|
# 初始化应用
|
|
|
|
|
init_app()
|
|
|
|
|
|
2025-07-30 19:09:15 +08:00
|
|
|
|
# 确定主机和端口
|
|
|
|
|
host = args.host if args.host else config.get('SERVER', 'host', fallback='127.0.0.1')
|
|
|
|
|
port = args.port if args.port else config.getint('SERVER', 'port', fallback=5000)
|
|
|
|
|
debug = args.debug if args.debug else config.getboolean('APP', 'debug', fallback=False)
|
|
|
|
|
|
2025-07-29 16:44:17 +08:00
|
|
|
|
# 启动Flask+SocketIO服务
|
2025-07-30 19:09:15 +08:00
|
|
|
|
logger.info(f'启动后端服务... Host: {host}, Port: {port}, Debug: {debug}')
|
2025-07-29 16:44:17 +08:00
|
|
|
|
socketio.run(app,
|
2025-07-30 19:09:15 +08:00
|
|
|
|
host=host,
|
|
|
|
|
port=port,
|
|
|
|
|
debug=debug,
|
|
|
|
|
use_reloader=False, # 禁用热重载以避免进程问题
|
|
|
|
|
log_output=True, # 启用详细日志
|
2025-07-29 16:44:17 +08:00
|
|
|
|
allow_unsafe_werkzeug=True
|
2025-07-28 11:59:56 +08:00
|
|
|
|
)
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
logger.info('服务被用户中断')
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f'服务启动失败: {e}')
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
finally:
|
2025-07-29 16:44:17 +08:00
|
|
|
|
logger.info('后端服务已停止')
|
|
|
|
|
|
2025-07-31 17:23:05 +08:00
|
|
|
|
# ==================== WebSocket 事件处理 ====================
|
2025-07-29 16:44:17 +08:00
|
|
|
|
|
2025-08-03 21:50:50 +08:00
|
|
|
|
# 简单的测试事件处理器
|
|
|
|
|
@socketio.on('connect')
|
|
|
|
|
def handle_connect():
|
2025-08-04 10:18:50 +08:00
|
|
|
|
# print('CLIENT CONNECTED!!!', flush=True) # 控制台打印测试已关闭
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.info('客户端已连接')
|
|
|
|
|
|
|
|
|
|
@socketio.on('disconnect')
|
|
|
|
|
def handle_disconnect():
|
2025-08-04 10:18:50 +08:00
|
|
|
|
# print('CLIENT DISCONNECTED!!!', flush=True) # 控制台打印测试已关闭
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.info('客户端已断开连接')
|
|
|
|
|
|
|
|
|
|
# 原始的start_video处理逻辑(暂时注释)
|
2025-08-06 10:37:21 +08:00
|
|
|
|
@socketio.on('start_video_stream')
|
2025-08-02 16:52:17 +08:00
|
|
|
|
def handle_start_video(data=None):
|
2025-07-29 18:28:40 +08:00
|
|
|
|
try:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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}')
|
|
|
|
|
|
|
|
|
|
# 启动视频流管理器(普通摄像头)
|
2025-07-31 17:23:05 +08:00
|
|
|
|
if video_stream_manager:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.info('正在启动视频流管理器...')
|
|
|
|
|
video_result = video_stream_manager.start_video_stream()
|
|
|
|
|
logger.info(f'视频流管理器启动结果: {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深度相机启动失败'}
|
2025-07-31 17:23:05 +08:00
|
|
|
|
else:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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}')
|
|
|
|
|
emit('video_status', results)
|
2025-07-29 18:28:40 +08:00
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.error(f'启动视频流失败: {e}', exc_info=True)
|
2025-08-02 16:52:17 +08:00
|
|
|
|
emit('video_status', {'status': 'error', 'message': f'启动失败: {str(e)}'})
|
2025-07-29 16:44:17 +08:00
|
|
|
|
|
2025-08-06 10:37:21 +08:00
|
|
|
|
@socketio.on('stop_video_stream')
|
2025-08-02 16:52:17 +08:00
|
|
|
|
def handle_stop_video(data=None):
|
2025-08-03 21:50:50 +08:00
|
|
|
|
logger.info(f'收到stop_video事件,数据: {data}')
|
2025-07-29 18:28:40 +08:00
|
|
|
|
|
|
|
|
|
try:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
results = {'status': 'success', 'cameras': {}}
|
|
|
|
|
|
|
|
|
|
# 停止视频流管理器(普通摄像头)
|
2025-07-31 17:23:05 +08:00
|
|
|
|
if video_stream_manager:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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:
|
2025-08-03 21:50:50 +08:00
|
|
|
|
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'] = '所有相机停止成功'
|
|
|
|
|
|
|
|
|
|
emit('video_status', results)
|
2025-07-29 18:28:40 +08:00
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-08-02 16:52:17 +08:00
|
|
|
|
logger.error(f'停止视频流失败: {e}')
|
|
|
|
|
emit('video_status', {'status': 'error', 'message': f'停止失败: {str(e)}'})
|
2025-07-29 18:28:40 +08:00
|
|
|
|
|
2025-08-06 09:52:45 +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:
|
|
|
|
|
emit('imu_status', {'status': 'success', 'message': 'IMU头部姿态数据推流已启动'})
|
|
|
|
|
logger.info('IMU头部姿态数据推流已启动')
|
|
|
|
|
else:
|
|
|
|
|
emit('imu_status', {'status': 'error', 'message': 'IMU头部姿态数据推流启动失败'})
|
|
|
|
|
logger.error('IMU头部姿态数据推流启动失败')
|
|
|
|
|
else:
|
|
|
|
|
emit('imu_status', {'status': 'error', 'message': '设备管理器未初始化'})
|
|
|
|
|
logger.error('设备管理器未初始化')
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f'启动IMU数据推流失败: {e}')
|
|
|
|
|
emit('imu_status', {'status': 'error', 'message': f'启动失败: {str(e)}'})
|
|
|
|
|
|
|
|
|
|
@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:
|
|
|
|
|
emit('imu_status', {'status': 'success', 'message': 'IMU头部姿态数据推流已停止'})
|
|
|
|
|
logger.info('IMU头部姿态数据推流已停止')
|
|
|
|
|
else:
|
|
|
|
|
emit('imu_status', {'status': 'error', 'message': 'IMU头部姿态数据推流停止失败'})
|
|
|
|
|
logger.error('IMU头部姿态数据推流停止失败')
|
|
|
|
|
else:
|
|
|
|
|
emit('imu_status', {'status': 'error', 'message': '设备管理器未初始化'})
|
|
|
|
|
logger.error('设备管理器未初始化')
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f'停止IMU数据推流失败: {e}')
|
|
|
|
|
emit('imu_status', {'status': 'error', 'message': f'停止失败: {str(e)}'})
|
|
|
|
|
|
|
|
|
|
@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-06 09:04:13 +08:00
|
|
|
|
# 重复的事件处理器已删除,使用前面定义的版本
|
|
|
|
|
|
|
|
|
|
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)
|