2025-07-28 11:59:56 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
"""
|
2025-08-17 12:48:10 +08:00
|
|
|
|
AppServer类 - 主应用服务器
|
|
|
|
|
实现Flask和SocketIO服务,替代app.py功能
|
2025-07-28 11:59:56 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
2025-08-17 12:48:10 +08:00
|
|
|
|
import json
|
2025-07-28 11:59:56 +08:00
|
|
|
|
import time
|
|
|
|
|
import threading
|
2025-08-17 12:48:10 +08:00
|
|
|
|
from datetime import datetime
|
|
|
|
|
from flask import Flask, jsonify
|
|
|
|
|
from flask import request as flask_request
|
|
|
|
|
from flask_cors import CORS
|
2025-07-28 11:59:56 +08:00
|
|
|
|
import logging
|
2025-08-17 12:48:10 +08:00
|
|
|
|
from flask_socketio import SocketIO, emit
|
|
|
|
|
import configparser
|
|
|
|
|
import argparse
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# 添加当前目录到路径
|
|
|
|
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# 导入模块
|
|
|
|
|
from database import DatabaseManager
|
|
|
|
|
from utils import config as app_config
|
2025-08-20 10:30:51 +08:00
|
|
|
|
from devices.camera_manager import CameraManager
|
|
|
|
|
from devices.imu_manager import IMUManager
|
|
|
|
|
from devices.pressure_manager import PressureManager
|
|
|
|
|
from devices.femtobolt_manager import FemtoBoltManager
|
|
|
|
|
from devices.device_coordinator import DeviceCoordinator
|
|
|
|
|
from devices.screen_recorder import RecordingManager
|
|
|
|
|
from devices.utils.config_manager import ConfigManager
|
|
|
|
|
# # 导入设备管理器
|
|
|
|
|
# try:
|
|
|
|
|
# from devices.camera_manager import CameraManager
|
|
|
|
|
# from devices.imu_manager import IMUManager
|
|
|
|
|
# from devices.pressure_manager import PressureManager
|
|
|
|
|
# from devices.femtobolt_manager import FemtoBoltManager
|
|
|
|
|
# from devices.device_coordinator import DeviceCoordinator
|
|
|
|
|
# from devices.screen_recorder import RecordingManager
|
|
|
|
|
# from devices.utils.config_manager import ConfigManager
|
|
|
|
|
# except ImportError:
|
|
|
|
|
# # 如果上面的导入失败,尝试直接导入
|
|
|
|
|
# # from camera_manager import CameraManager
|
|
|
|
|
# import imu_manager
|
|
|
|
|
# import pressure_manager
|
|
|
|
|
# import femtobolt_manager
|
|
|
|
|
# import device_coordinator
|
|
|
|
|
# from utils import config_manager
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-20 10:30:51 +08:00
|
|
|
|
# IMUManager = imu_manager.IMUManager
|
|
|
|
|
# PressureManager = pressure_manager.PressureManager
|
|
|
|
|
# FemtoBoltManager = femtobolt_manager.FemtoBoltManager
|
|
|
|
|
# DeviceCoordinator = device_coordinator.DeviceCoordinator
|
|
|
|
|
# ConfigManager = config_manager.ConfigManager
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AppServer:
|
|
|
|
|
"""主应用服务器类"""
|
|
|
|
|
|
2025-08-20 16:14:32 +08:00
|
|
|
|
def __init__(self, host='0.0.0.0', port=5000, debug=False):
|
2025-08-17 12:48:10 +08:00
|
|
|
|
"""
|
|
|
|
|
初始化应用服务器
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
host: 服务器主机
|
|
|
|
|
port: 服务器端口
|
|
|
|
|
debug: 调试模式
|
|
|
|
|
"""
|
|
|
|
|
self.host = host
|
|
|
|
|
self.port = port
|
|
|
|
|
self.debug = debug
|
|
|
|
|
|
|
|
|
|
# 初始化日志
|
|
|
|
|
self._init_logging()
|
|
|
|
|
|
|
|
|
|
# Flask应用
|
|
|
|
|
self.app = Flask(__name__)
|
|
|
|
|
self.app.config['SECRET_KEY'] = 'body-balance-detection-system-2024'
|
|
|
|
|
|
|
|
|
|
# SocketIO
|
|
|
|
|
self._init_socketio()
|
|
|
|
|
|
|
|
|
|
# CORS配置
|
|
|
|
|
CORS(self.app, origins='*', supports_credentials=True,
|
|
|
|
|
allow_headers=['Content-Type', 'Authorization'],
|
|
|
|
|
methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])
|
|
|
|
|
|
|
|
|
|
# 配置管理
|
|
|
|
|
self._init_config()
|
|
|
|
|
|
|
|
|
|
# 全局变量
|
|
|
|
|
self.db_manager = None
|
|
|
|
|
self.current_detection = None
|
|
|
|
|
self.detection_thread = None
|
|
|
|
|
|
|
|
|
|
# 数据推送状态
|
|
|
|
|
self.is_pushing_data = False
|
|
|
|
|
|
|
|
|
|
# 设备管理器
|
|
|
|
|
self.config_manager = None
|
|
|
|
|
self.device_coordinator = None
|
|
|
|
|
self.device_managers = {
|
|
|
|
|
'camera': None,
|
|
|
|
|
'femtobolt': None,
|
|
|
|
|
'imu': None,
|
|
|
|
|
'pressure': None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 注册路由和事件
|
|
|
|
|
self._register_routes()
|
|
|
|
|
self._register_socketio_events()
|
|
|
|
|
|
|
|
|
|
def _init_logging(self):
|
|
|
|
|
"""初始化日志配置"""
|
|
|
|
|
# 日志目录
|
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
|
# 打包后的可执行文件
|
|
|
|
|
log_dir = os.path.join(os.path.dirname(sys.executable), 'logs')
|
|
|
|
|
else:
|
|
|
|
|
# 开发环境
|
|
|
|
|
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()
|
|
|
|
|
]
|
|
|
|
|
)
|
2025-08-19 08:30:48 +08:00
|
|
|
|
|
|
|
|
|
# 设置werkzeug日志级别为WARNING,过滤掉INFO级别的访问日志
|
|
|
|
|
logging.getLogger('werkzeug').setLevel(logging.WARNING)
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
def _init_socketio(self):
|
|
|
|
|
"""初始化SocketIO"""
|
|
|
|
|
try:
|
|
|
|
|
self.socketio = SocketIO(
|
|
|
|
|
self.app,
|
|
|
|
|
cors_allowed_origins='*',
|
|
|
|
|
async_mode='threading',
|
2025-08-17 16:42:05 +08:00
|
|
|
|
#async_mode='eventlet',
|
2025-08-17 12:48:10 +08:00
|
|
|
|
logger=False,
|
|
|
|
|
engineio_logger=False,
|
|
|
|
|
ping_timeout=60,
|
|
|
|
|
ping_interval=25,
|
2025-08-19 08:30:48 +08:00
|
|
|
|
manage_session=False,
|
2025-08-17 12:48:10 +08:00
|
|
|
|
always_connect=False,
|
2025-08-19 08:30:48 +08:00
|
|
|
|
transports=['polling', 'websocket'], # 优先使用polling
|
|
|
|
|
allow_upgrades=True, # 允许升级到websocket
|
2025-08-17 12:48:10 +08:00
|
|
|
|
cookie=None # 禁用cookie
|
|
|
|
|
)
|
|
|
|
|
self.logger.info('SocketIO初始化成功')
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'SocketIO初始化失败: {e}')
|
|
|
|
|
self.socketio = None
|
|
|
|
|
|
|
|
|
|
# 设置SocketIO日志级别
|
|
|
|
|
logging.getLogger('socketio').setLevel(logging.WARNING)
|
|
|
|
|
logging.getLogger('engineio').setLevel(logging.WARNING)
|
|
|
|
|
|
|
|
|
|
def _init_config(self):
|
|
|
|
|
"""初始化配置"""
|
|
|
|
|
self.config = configparser.ConfigParser()
|
|
|
|
|
|
|
|
|
|
# 配置文件路径
|
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
|
# 打包后的可执行文件
|
|
|
|
|
config_path = os.path.join(os.path.dirname(sys.executable), 'config.ini')
|
|
|
|
|
else:
|
|
|
|
|
# 开发环境
|
|
|
|
|
config_path = os.path.join(os.path.dirname(__file__), 'config.ini')
|
|
|
|
|
|
|
|
|
|
self.config.read(config_path, encoding='utf-8')
|
|
|
|
|
device_index = self.config.get('CAMERA', 'device_index', fallback=None)
|
|
|
|
|
print(f"设备号: {device_index}")
|
|
|
|
|
|
|
|
|
|
def init_app(self):
|
|
|
|
|
"""初始化应用组件"""
|
|
|
|
|
try:
|
|
|
|
|
# 初始化数据库管理器
|
|
|
|
|
self.logger.info('正在初始化数据库管理器...')
|
2025-08-18 18:56:58 +08:00
|
|
|
|
# 在打包环境中使用可执行文件的目录,在开发环境中使用脚本文件的目录
|
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
|
# 打包环境
|
|
|
|
|
base_dir = os.path.dirname(sys.executable)
|
|
|
|
|
else:
|
|
|
|
|
# 开发环境
|
|
|
|
|
base_dir = os.path.dirname(__file__)
|
|
|
|
|
db_path = os.path.join(base_dir, 'data', 'body_balance.db')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
|
|
|
self.db_manager = DatabaseManager(db_path)
|
|
|
|
|
self.db_manager.init_database()
|
|
|
|
|
self.logger.info('数据库管理器初始化完成')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# 初始化配置管理器
|
|
|
|
|
self.logger.info('正在初始化配置管理器...')
|
|
|
|
|
self.config_manager = ConfigManager()
|
|
|
|
|
self.logger.info('配置管理器初始化完成')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# 初始化设备管理器
|
|
|
|
|
self.logger.info('正在初始化设备管理器...')
|
|
|
|
|
self.device_managers = {
|
|
|
|
|
'camera': CameraManager(self.socketio, self.config_manager),
|
2025-08-19 15:37:56 +08:00
|
|
|
|
'femtobolt': FemtoBoltManager(self.socketio, self.config_manager),
|
|
|
|
|
'imu': IMUManager(self.socketio, self.config_manager),
|
|
|
|
|
'pressure': PressureManager(self.socketio, self.config_manager)
|
2025-08-17 12:48:10 +08:00
|
|
|
|
}
|
2025-08-17 17:39:04 +08:00
|
|
|
|
|
|
|
|
|
# 为每个设备添加状态变化回调
|
|
|
|
|
for device_name, manager in self.device_managers.items():
|
|
|
|
|
if manager and hasattr(manager, 'add_status_change_callback'):
|
|
|
|
|
manager.add_status_change_callback(self._on_device_status_change)
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.logger.info('设备管理器初始化完成')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# 初始化设备协调器
|
|
|
|
|
self.logger.info('正在初始化设备协调器...')
|
|
|
|
|
self.device_coordinator = DeviceCoordinator(self.socketio)
|
|
|
|
|
self.logger.info('设备协调器初始化完成')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-20 10:30:51 +08:00
|
|
|
|
# 初始化录制管理器
|
|
|
|
|
self.logger.info('正在初始化录制管理器...')
|
|
|
|
|
self.recording_manager = RecordingManager(
|
|
|
|
|
camera_manager=self.device_managers['camera'],
|
|
|
|
|
db_manager=self.db_manager
|
|
|
|
|
)
|
|
|
|
|
self.logger.info('录制管理器初始化完成')
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# 启动Flask应用
|
2025-08-19 15:37:56 +08:00
|
|
|
|
host = self.host
|
|
|
|
|
port = self.port
|
|
|
|
|
debug = self.debug
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.logger.info(f'启动Flask应用 - Host: {host}, Port: {port}, Debug: {debug}')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
if self.socketio:
|
|
|
|
|
self.socketio.run(self.app, host=host, port=port, debug=debug, allow_unsafe_werkzeug=True)
|
|
|
|
|
else:
|
|
|
|
|
self.app.run(host=host, port=port, debug=debug)
|
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
except Exception as e:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.logger.error(f'应用初始化失败: {e}')
|
|
|
|
|
raise
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
def _register_routes(self):
|
|
|
|
|
"""注册Flask路由"""
|
|
|
|
|
|
|
|
|
|
# ==================== 基础API ====================
|
|
|
|
|
|
|
|
|
|
@self.app.route('/health', methods=['GET'])
|
|
|
|
|
def health_check():
|
|
|
|
|
"""健康检查"""
|
|
|
|
|
return jsonify({
|
|
|
|
|
'status': 'healthy',
|
|
|
|
|
'timestamp': datetime.now().isoformat(),
|
|
|
|
|
'version': '1.0.0'
|
|
|
|
|
})
|
|
|
|
|
|
2025-08-20 16:04:38 +08:00
|
|
|
|
# ==================== 静态文件服务 ====================
|
|
|
|
|
|
|
|
|
|
@self.app.route('/data/<path:filename>', methods=['GET'])
|
|
|
|
|
def serve_static_files(filename):
|
|
|
|
|
"""提供静态文件服务,代理backend/data/目录"""
|
|
|
|
|
try:
|
|
|
|
|
# 获取data目录的绝对路径
|
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
|
# 打包环境
|
|
|
|
|
data_dir = os.path.join(os.path.dirname(sys.executable), 'data')
|
|
|
|
|
else:
|
|
|
|
|
# 开发环境
|
|
|
|
|
data_dir = os.path.join(os.path.dirname(__file__), 'data')
|
|
|
|
|
|
|
|
|
|
# 安全检查:防止路径遍历攻击
|
|
|
|
|
safe_path = os.path.normpath(filename)
|
|
|
|
|
if '..' in safe_path or safe_path.startswith('/'):
|
|
|
|
|
return jsonify({'error': '非法路径'}), 400
|
|
|
|
|
|
|
|
|
|
file_path = os.path.join(data_dir, safe_path)
|
|
|
|
|
|
|
|
|
|
# 检查文件是否存在
|
|
|
|
|
if not os.path.exists(file_path):
|
|
|
|
|
return jsonify({'error': '文件不存在'}), 404
|
|
|
|
|
|
|
|
|
|
# 检查是否在允许的目录内
|
|
|
|
|
if not os.path.commonpath([data_dir, file_path]) == data_dir:
|
|
|
|
|
return jsonify({'error': '访问被拒绝'}), 403
|
|
|
|
|
|
|
|
|
|
# 返回文件
|
|
|
|
|
from flask import send_file
|
|
|
|
|
return send_file(file_path)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'静态文件服务错误: {e}')
|
|
|
|
|
return jsonify({'error': '服务器内部错误'}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/data/', methods=['GET'])
|
|
|
|
|
@self.app.route('/data', methods=['GET'])
|
|
|
|
|
def list_data_directory():
|
|
|
|
|
"""列出data目录下的文件和文件夹"""
|
|
|
|
|
try:
|
|
|
|
|
# 获取data目录的绝对路径
|
|
|
|
|
if getattr(sys, 'frozen', False):
|
|
|
|
|
# 打包环境
|
|
|
|
|
data_dir = os.path.join(os.path.dirname(sys.executable), 'data')
|
|
|
|
|
else:
|
|
|
|
|
# 开发环境
|
|
|
|
|
data_dir = os.path.join(os.path.dirname(__file__), 'data')
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(data_dir):
|
|
|
|
|
return jsonify({'error': 'data目录不存在'}), 404
|
|
|
|
|
|
|
|
|
|
# 获取目录内容
|
|
|
|
|
items = []
|
|
|
|
|
for item in os.listdir(data_dir):
|
|
|
|
|
item_path = os.path.join(data_dir, item)
|
|
|
|
|
is_dir = os.path.isdir(item_path)
|
|
|
|
|
size = os.path.getsize(item_path) if not is_dir else None
|
|
|
|
|
modified = datetime.fromtimestamp(os.path.getmtime(item_path)).isoformat()
|
|
|
|
|
|
|
|
|
|
items.append({
|
|
|
|
|
'name': item,
|
|
|
|
|
'type': 'directory' if is_dir else 'file',
|
|
|
|
|
'size': size,
|
|
|
|
|
'modified': modified,
|
|
|
|
|
'url': f'/data/{item}' if not is_dir else None
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'path': '/data/',
|
|
|
|
|
'items': sorted(items, key=lambda x: (x['type'] == 'file', x['name']))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'目录列表错误: {e}')
|
|
|
|
|
return jsonify({'error': '服务器内部错误'}), 500
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
@self.app.route('/test-socketio')
|
|
|
|
|
def test_socketio():
|
|
|
|
|
"""测试SocketIO连接"""
|
|
|
|
|
return '<h1>SocketIO Test Page</h1><script src="/socket.io/socket.io.js"></script>'
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
@self.app.route('/api/health', methods=['GET'])
|
|
|
|
|
def api_health_check():
|
|
|
|
|
"""API健康检查"""
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': '后端服务运行正常',
|
|
|
|
|
'timestamp': datetime.now().isoformat(),
|
|
|
|
|
'database': self.db_manager is not None,
|
|
|
|
|
'config_manager': self.config_manager is not None,
|
|
|
|
|
'device_coordinator': self.device_coordinator is not None,
|
|
|
|
|
'device_managers': {name: manager is not None for name, manager in self.device_managers.items()}
|
|
|
|
|
})
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# ==================== 认证API ====================
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
@self.app.route('/api/auth/login', methods=['POST'])
|
|
|
|
|
def login():
|
|
|
|
|
"""用户登录"""
|
2025-07-28 11:59:56 +08:00
|
|
|
|
try:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# 检查Content-Type
|
|
|
|
|
if not flask_request.is_json:
|
|
|
|
|
return jsonify({'success': False, 'message': '请求Content-Type必须为application/json'}), 415
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
data = flask_request.get_json(force=True)
|
|
|
|
|
if not data:
|
|
|
|
|
return jsonify({'success': False, 'message': '请求数据为空'}), 400
|
|
|
|
|
|
|
|
|
|
username = data.get('username')
|
|
|
|
|
password = data.get('password')
|
|
|
|
|
|
|
|
|
|
if not username or not password:
|
|
|
|
|
return jsonify({'success': False, 'message': '用户名和密码不能为空'}), 400
|
|
|
|
|
|
|
|
|
|
# 验证用户
|
|
|
|
|
user = self.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_{user['username']}_{int(time.time())}"
|
|
|
|
|
|
|
|
|
|
self.logger.info(f'用户 {username} 登录成功')
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'data': {
|
|
|
|
|
'token': token,
|
|
|
|
|
'user': user_data
|
|
|
|
|
},
|
|
|
|
|
'message': '登录成功'
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
self.logger.warning(f'用户 {username} 登录失败:用户名或密码错误')
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': '用户名或密码错误'
|
|
|
|
|
}), 401
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'登录失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'message': '登录失败'}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
@self.app.route('/api/auth/register', methods=['POST'])
|
|
|
|
|
def register():
|
|
|
|
|
"""用户注册"""
|
|
|
|
|
try:
|
|
|
|
|
# 检查Content-Type
|
|
|
|
|
if not flask_request.is_json:
|
|
|
|
|
return jsonify({'success': False, 'message': '请求Content-Type必须为application/json'}), 415
|
|
|
|
|
|
|
|
|
|
data = flask_request.get_json(force=True)
|
|
|
|
|
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 = self.db_manager.register_user(user_data)
|
|
|
|
|
|
|
|
|
|
if result['success']:
|
|
|
|
|
self.logger.info(f'用户 {username} 注册成功,等待管理员审核')
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': '注册成功,请等待管理员审核后登录'
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': result['message']
|
|
|
|
|
}), 400
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'注册失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'message': '注册失败'}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
@self.app.route('/api/auth/logout', methods=['POST'])
|
|
|
|
|
def logout():
|
|
|
|
|
"""用户登出"""
|
|
|
|
|
try:
|
|
|
|
|
# 这里可以添加token失效逻辑
|
|
|
|
|
return jsonify({'success': True, 'message': '登出成功'})
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'登出失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/auth/verify', methods=['GET'])
|
|
|
|
|
def verify_token():
|
|
|
|
|
"""验证token"""
|
|
|
|
|
try:
|
|
|
|
|
# 从请求头获取token
|
|
|
|
|
auth_header = flask_request.headers.get('Authorization')
|
|
|
|
|
if not auth_header or not auth_header.startswith('Bearer '):
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': '未提供有效的认证信息',
|
|
|
|
|
'data': {'valid': False}
|
|
|
|
|
}), 401
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
token = auth_header.split(' ')[1]
|
|
|
|
|
# 这里应该验证JWT token,简化处理
|
|
|
|
|
if token.startswith('token_'):
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': 'Token有效',
|
|
|
|
|
'data': {'valid': True}
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': 'Token无效',
|
|
|
|
|
'data': {'valid': False}
|
|
|
|
|
}), 401
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'Token验证失败: {e}')
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': '验证失败',
|
|
|
|
|
'data': {'valid': False}
|
|
|
|
|
}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/auth/forgot-password', methods=['POST'])
|
|
|
|
|
def forgot_password():
|
2025-08-19 18:18:41 +08:00
|
|
|
|
"""忘记密码 - 根据用户名和手机号找回密码"""
|
2025-08-17 12:48:10 +08:00
|
|
|
|
try:
|
|
|
|
|
data = flask_request.get_json()
|
|
|
|
|
username = data.get('username')
|
2025-08-19 18:18:41 +08:00
|
|
|
|
phone = data.get('phone')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
if not username:
|
2025-08-19 18:18:41 +08:00
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '请输入用户名'
|
|
|
|
|
}), 400
|
|
|
|
|
|
|
|
|
|
if not phone:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '请输入手机号码'
|
|
|
|
|
}), 400
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-19 18:18:41 +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 = self.db_manager.get_connection()
|
|
|
|
|
cursor = conn.cursor()
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-19 18:18:41 +08:00
|
|
|
|
cursor.execute('''
|
|
|
|
|
SELECT username, password, phone FROM users
|
|
|
|
|
WHERE username = ? AND phone = ?
|
|
|
|
|
''', (username, phone))
|
|
|
|
|
|
|
|
|
|
user = cursor.fetchone()
|
|
|
|
|
|
|
|
|
|
if user:
|
|
|
|
|
# 用户存在且手机号匹配,返回数据库中存储的实际密码
|
|
|
|
|
actual_password = user['password']
|
|
|
|
|
|
|
|
|
|
self.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
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'忘记密码处理失败: {e}')
|
2025-08-19 18:18:41 +08:00
|
|
|
|
return jsonify({'success': False, 'error': '处理失败'}), 500
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
# ==================== 用户管理API ====================
|
|
|
|
|
|
|
|
|
|
@self.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))
|
|
|
|
|
|
|
|
|
|
users = self.db_manager.get_users(page, size)
|
|
|
|
|
total = self.db_manager.get_users_count()
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'data': {
|
|
|
|
|
'users': users,
|
|
|
|
|
'total': total,
|
|
|
|
|
'page': page,
|
|
|
|
|
'size': size
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'获取用户列表失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/users/<int:user_id>/approve', methods=['POST'])
|
|
|
|
|
def approve_user(user_id):
|
|
|
|
|
"""审核用户"""
|
|
|
|
|
try:
|
|
|
|
|
data = flask_request.get_json()
|
|
|
|
|
status = data.get('status') # 'approved' 或 'rejected'
|
|
|
|
|
|
|
|
|
|
if status not in ['approved', 'rejected']:
|
|
|
|
|
return jsonify({'success': False, 'error': '无效的审核状态'}), 400
|
|
|
|
|
|
|
|
|
|
result = self.db_manager.update_user_status(user_id, status)
|
|
|
|
|
if result:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': f'用户已{"通过" if status == "approved" else "拒绝"}审核'
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': '用户不存在'}), 404
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'审核用户失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/users/<int:user_id>', methods=['DELETE'])
|
|
|
|
|
def delete_user(user_id):
|
|
|
|
|
"""删除用户"""
|
|
|
|
|
try:
|
|
|
|
|
result = self.db_manager.delete_user(user_id)
|
|
|
|
|
if result:
|
|
|
|
|
return jsonify({'success': True, 'message': '用户已删除'})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': '用户不存在'}), 404
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'删除用户失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
# ==================== 患者管理API ====================
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/patients', methods=['GET', 'POST'])
|
|
|
|
|
def handle_patients():
|
|
|
|
|
"""患者管理"""
|
|
|
|
|
if flask_request.method == 'GET':
|
|
|
|
|
# 获取患者列表
|
|
|
|
|
try:
|
|
|
|
|
page = int(flask_request.args.get('page', 1))
|
|
|
|
|
size = int(flask_request.args.get('size', 10))
|
|
|
|
|
search = flask_request.args.get('search', '')
|
|
|
|
|
|
|
|
|
|
patients = self.db_manager.get_patients(page, size, search)
|
|
|
|
|
total = self.db_manager.get_patients_count(search)
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'data': {
|
|
|
|
|
'patients': patients,
|
|
|
|
|
'total': total,
|
|
|
|
|
'page': page,
|
|
|
|
|
'size': size
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'获取患者列表失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
elif flask_request.method == 'POST':
|
|
|
|
|
# 创建患者
|
|
|
|
|
try:
|
|
|
|
|
# 检查Content-Type
|
|
|
|
|
if not flask_request.is_json:
|
|
|
|
|
return jsonify({'success': False, 'message': '请求Content-Type必须为application/json'}), 415
|
|
|
|
|
|
|
|
|
|
data = flask_request.get_json(force=True)
|
|
|
|
|
|
|
|
|
|
required_fields = ['name', 'gender', 'age']
|
|
|
|
|
for field in required_fields:
|
|
|
|
|
if not data.get(field):
|
|
|
|
|
return jsonify({'success': False, 'error': f'{field}不能为空'}), 400
|
|
|
|
|
|
2025-08-19 15:37:56 +08:00
|
|
|
|
patient_data = {
|
|
|
|
|
'name': data['name'],
|
|
|
|
|
'gender': data['gender'],
|
|
|
|
|
'age': data['age'],
|
|
|
|
|
'birth_date': data.get('birth_date'),
|
|
|
|
|
'nationality': data.get('nationality'),
|
|
|
|
|
'residence': data.get('residence'),
|
|
|
|
|
'height': data.get('height'),
|
|
|
|
|
'weight': data.get('weight'),
|
|
|
|
|
'shoe_size': data.get('shoe_size'),
|
|
|
|
|
'phone': data.get('phone'),
|
|
|
|
|
'email': data.get('email'),
|
|
|
|
|
'occupation': data.get('occupation'),
|
|
|
|
|
'workplace': data.get('workplace'),
|
|
|
|
|
'medical_history': data.get('medical_history', ''),
|
|
|
|
|
'notes': data.get('notes', '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
patient_id = self.db_manager.create_patient(patient_data)
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
if patient_id:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': '患者创建成功',
|
|
|
|
|
'data': {'patient_id': patient_id}
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': '患者创建失败'}), 500
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'创建患者失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/patients/<patient_id>', methods=['GET', 'PUT', 'DELETE'])
|
|
|
|
|
def handle_patient(patient_id):
|
|
|
|
|
"""单个患者操作"""
|
|
|
|
|
if flask_request.method == 'GET':
|
|
|
|
|
# 获取患者详情
|
|
|
|
|
try:
|
|
|
|
|
patient = self.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:
|
|
|
|
|
self.logger.error(f'获取患者详情失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
elif flask_request.method == 'PUT':
|
|
|
|
|
# 更新患者信息
|
|
|
|
|
try:
|
|
|
|
|
data = flask_request.get_json()
|
|
|
|
|
|
2025-08-19 15:37:56 +08:00
|
|
|
|
patient_data = {
|
|
|
|
|
'name': data.get('name'),
|
|
|
|
|
'gender': data.get('gender'),
|
|
|
|
|
'age': data.get('age'),
|
|
|
|
|
'birth_date': data.get('birth_date'),
|
|
|
|
|
'nationality': data.get('nationality'),
|
|
|
|
|
'residence': data.get('residence'),
|
|
|
|
|
'height': data.get('height'),
|
|
|
|
|
'weight': data.get('weight'),
|
|
|
|
|
'shoe_size': data.get('shoe_size'),
|
|
|
|
|
'phone': data.get('phone'),
|
|
|
|
|
'email': data.get('email'),
|
|
|
|
|
'occupation': data.get('occupation'),
|
|
|
|
|
'workplace': data.get('workplace'),
|
|
|
|
|
'medical_history': data.get('medical_history'),
|
|
|
|
|
'notes': data.get('notes')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.db_manager.update_patient(patient_id, patient_data)
|
|
|
|
|
result = True
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
return jsonify({'success': True, 'message': '患者信息更新成功'})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': '患者不存在'}), 404
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'更新患者信息失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
elif flask_request.method == 'DELETE':
|
|
|
|
|
# 删除患者
|
|
|
|
|
try:
|
|
|
|
|
result = self.db_manager.delete_patient(patient_id)
|
|
|
|
|
if result:
|
|
|
|
|
return jsonify({'success': True, 'message': '患者已删除'})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': '患者不存在'}), 404
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'删除患者失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
# ==================== 设备管理API ====================
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/devices/status', methods=['GET'])
|
|
|
|
|
def get_device_status():
|
|
|
|
|
"""获取设备状态"""
|
|
|
|
|
try:
|
|
|
|
|
if self.device_coordinator:
|
|
|
|
|
status = self.device_coordinator.get_device_status()
|
|
|
|
|
return jsonify({'success': True, 'data': status})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': '设备协调器未初始化'}), 500
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'获取设备状态失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/devices/refresh', methods=['POST'])
|
|
|
|
|
def refresh_devices():
|
|
|
|
|
"""刷新设备"""
|
2025-07-28 11:59:56 +08:00
|
|
|
|
try:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
if self.device_coordinator:
|
|
|
|
|
result = self.device_coordinator.refresh_all_devices()
|
|
|
|
|
return jsonify({'success': True, 'data': result})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': '设备协调器未初始化'}), 500
|
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
except Exception as e:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.logger.error(f'刷新设备失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-19 15:58:49 +08:00
|
|
|
|
# ==================== 设备配置API ====================
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/config/devices', methods=['GET'])
|
|
|
|
|
def get_all_device_configs():
|
|
|
|
|
"""获取所有设备配置"""
|
|
|
|
|
try:
|
|
|
|
|
if self.config_manager:
|
|
|
|
|
configs = self.config_manager.get_all_device_configs()
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'data': configs
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': '配置管理器未初始化'}), 500
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f"获取设备配置失败: {e}")
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': f'获取设备配置失败: {str(e)}'
|
|
|
|
|
}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/config/devices/all', methods=['POST'])
|
|
|
|
|
def set_all_device_configs():
|
|
|
|
|
"""批量设置所有设备配置"""
|
|
|
|
|
try:
|
|
|
|
|
if not self.config_manager:
|
|
|
|
|
return jsonify({'success': False, 'error': '配置管理器未初始化'}), 500
|
|
|
|
|
|
|
|
|
|
data = flask_request.get_json()
|
|
|
|
|
if not data:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': '请求数据不能为空'
|
|
|
|
|
}), 400
|
|
|
|
|
|
|
|
|
|
# 验证数据格式
|
|
|
|
|
supported_devices = ['imu', 'pressure', 'camera', 'femtobolt']
|
|
|
|
|
for device_name in data.keys():
|
|
|
|
|
if device_name not in supported_devices:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': f'不支持的设备类型: {device_name},支持的设备类型: {", ".join(supported_devices)}'
|
|
|
|
|
}), 400
|
|
|
|
|
|
|
|
|
|
result = self.config_manager.set_all_device_configs(data)
|
|
|
|
|
|
2025-08-19 18:18:41 +08:00
|
|
|
|
# 如果配置设置成功,异步重启设备数据推送
|
2025-08-19 15:58:49 +08:00
|
|
|
|
if result['success']:
|
2025-08-19 18:18:41 +08:00
|
|
|
|
def restart_devices_async():
|
|
|
|
|
"""异步重启设备数据推送"""
|
|
|
|
|
try:
|
|
|
|
|
self.logger.info("设备配置更新成功,异步重启设备数据推送...")
|
|
|
|
|
# 先停止当前的数据推送
|
|
|
|
|
if self.is_pushing_data:
|
|
|
|
|
self.stop_device_push_data()
|
|
|
|
|
time.sleep(1) # 等待停止完成
|
|
|
|
|
|
|
|
|
|
# 重新启动设备数据推送
|
|
|
|
|
self.start_device_push_data()
|
|
|
|
|
self.logger.info("设备配置更新并重启数据推送完成")
|
|
|
|
|
|
|
|
|
|
# 通过SocketIO通知前端重启完成
|
|
|
|
|
self.socketio.emit('device_restart_complete', {
|
|
|
|
|
'status': 'success',
|
|
|
|
|
'message': '设备重启完成'
|
|
|
|
|
}, namespace='/devices')
|
|
|
|
|
|
|
|
|
|
except Exception as restart_error:
|
|
|
|
|
self.logger.error(f"重启设备数据推送失败: {restart_error}")
|
|
|
|
|
# 通过SocketIO通知前端重启失败
|
|
|
|
|
self.socketio.emit('device_restart_complete', {
|
|
|
|
|
'status': 'error',
|
|
|
|
|
'message': f'设备重启失败: {str(restart_error)}'
|
|
|
|
|
}, namespace='/devices')
|
|
|
|
|
|
|
|
|
|
# 启动异步线程执行重启操作
|
|
|
|
|
restart_thread = threading.Thread(target=restart_devices_async)
|
|
|
|
|
restart_thread.daemon = True
|
|
|
|
|
restart_thread.start()
|
|
|
|
|
|
|
|
|
|
result['message'] = result.get('message', '') + ' 设备正在后台重启中,请稍候...'
|
2025-08-19 15:58:49 +08:00
|
|
|
|
|
|
|
|
|
status_code = 200 if result['success'] else 400
|
|
|
|
|
return jsonify(result), status_code
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f"批量设置设备配置失败: {e}")
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': f'批量设置设备配置失败: {str(e)}'
|
|
|
|
|
}), 500
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
@self.app.route('/api/devices/calibrate', methods=['POST'])
|
|
|
|
|
def calibrate_device():
|
|
|
|
|
"""校准设备"""
|
|
|
|
|
try:
|
|
|
|
|
data = flask_request.get_json()
|
|
|
|
|
device_type = data.get('device_type')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
if not device_type:
|
|
|
|
|
return jsonify({'success': False, 'error': '设备类型不能为空'}), 400
|
|
|
|
|
|
|
|
|
|
if device_type in self.device_managers and self.device_managers[device_type]:
|
|
|
|
|
device_manager = self.device_managers[device_type]
|
|
|
|
|
if hasattr(device_manager, 'calibrate'):
|
|
|
|
|
result = device_manager.calibrate()
|
|
|
|
|
return jsonify({'success': True, 'data': result})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': f'{device_type}设备不支持校准'}), 400
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': f'{device_type}设备管理器未初始化'}), 500
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'校准设备失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-21 17:35:09 +08:00
|
|
|
|
@self.app.route('/api/devices/calibrate/imu', methods=['POST'])
|
2025-08-17 12:48:10 +08:00
|
|
|
|
def calibrate_imu():
|
|
|
|
|
"""校准IMU"""
|
2025-07-28 11:59:56 +08:00
|
|
|
|
try:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
if self.device_managers['imu']:
|
|
|
|
|
result = self.device_managers['imu'].calibrate()
|
|
|
|
|
if result:
|
|
|
|
|
return jsonify({'success': True, 'message': 'IMU校准成功'})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': 'IMU校准失败'}), 500
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({'success': False, 'error': 'IMU设备管理器未初始化'}), 500
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'IMU校准失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
# ==================== 检测API ====================
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/detection/start', methods=['POST'])
|
|
|
|
|
def start_detection():
|
|
|
|
|
"""开始检测"""
|
|
|
|
|
try:
|
2025-08-20 09:11:55 +08:00
|
|
|
|
if not self.db_manager or not self.device_coordinator:
|
|
|
|
|
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
data = flask_request.get_json()
|
|
|
|
|
patient_id = data.get('patient_id')
|
2025-08-20 09:11:55 +08:00
|
|
|
|
creator_id = data.get('creator_id')
|
|
|
|
|
if not patient_id or not creator_id:
|
|
|
|
|
return jsonify({'success': False, 'error': '缺少患者ID或创建人ID'}), 400
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
# 调用create_detection_session方法,settings传空字典
|
|
|
|
|
session_id = self.db_manager.create_detection_session(patient_id, settings={}, creator_id=creator_id)
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
# 开始同步录制
|
|
|
|
|
recording_response = None
|
|
|
|
|
try:
|
2025-08-20 10:30:51 +08:00
|
|
|
|
recording_response = self.recording_manager.start_recording(session_id, patient_id)
|
2025-08-20 09:11:55 +08:00
|
|
|
|
except Exception as rec_e:
|
|
|
|
|
self.logger.error(f'开始同步录制失败: {rec_e}')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time, 'recording': recording_response})
|
2025-08-17 12:48:10 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'开始检测失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
@self.app.route('/api/detection/<session_id>/stop', methods=['POST'])
|
|
|
|
|
def stop_detection(session_id):
|
2025-08-17 12:48:10 +08:00
|
|
|
|
"""停止检测"""
|
|
|
|
|
try:
|
2025-08-20 09:11:55 +08:00
|
|
|
|
if not self.db_manager or not self.device_coordinator:
|
|
|
|
|
self.logger.error('数据库管理器或设备管理器未初始化')
|
|
|
|
|
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
if not session_id:
|
|
|
|
|
self.logger.error('缺少会话ID')
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '缺少会话ID'
|
|
|
|
|
}), 400
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
data = flask_request.get_json()
|
|
|
|
|
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)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'message': f'视频数据解码失败: {str(e)}'
|
|
|
|
|
}), 400
|
|
|
|
|
# 停止同步录制,传递视频数据
|
|
|
|
|
try:
|
2025-08-20 10:30:51 +08:00
|
|
|
|
restrt = self.recording_manager.stop_recording(session_id)
|
|
|
|
|
self.logger.info(f'停止录制结果: {restrt}')
|
2025-08-20 09:11:55 +08:00
|
|
|
|
except Exception as rec_e:
|
|
|
|
|
self.logger.error(f'停止同步录制失败: {rec_e}', exc_info=True)
|
|
|
|
|
raise
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
# 更新会话状态为已完成
|
|
|
|
|
success = self.db_manager.update_session_status(session_id, 'completed')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
if success:
|
|
|
|
|
self.logger.info(f'检测会话已停止 - 会话ID: {session_id}')
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': '检测已停止'
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
self.logger.error('停止检测失败,更新会话状态失败')
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '停止检测失败'
|
|
|
|
|
}), 500
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
except Exception as e:
|
2025-08-20 09:11:55 +08:00
|
|
|
|
self.logger.error(f'停止检测失败: {e}', exc_info=True)
|
2025-08-17 12:48:10 +08:00
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
@self.app.route('/api/detection/<session_id>/status', methods=['GET'])
|
|
|
|
|
def get_detection_status(session_id):
|
2025-08-17 12:48:10 +08:00
|
|
|
|
"""获取检测状态"""
|
|
|
|
|
try:
|
2025-08-20 09:11:55 +08:00
|
|
|
|
if not self.db_manager:
|
|
|
|
|
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
|
|
|
|
|
|
|
|
|
|
if not session_id:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '缺少会话ID'
|
|
|
|
|
}), 400
|
|
|
|
|
|
|
|
|
|
# 获取会话数据
|
|
|
|
|
session_data = self.db_manager.get_session_data(session_id)
|
|
|
|
|
|
|
|
|
|
if session_data:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
2025-08-20 09:11:55 +08:00
|
|
|
|
'data': session_data
|
2025-08-17 12:48:10 +08:00
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
return jsonify({
|
2025-08-20 09:11:55 +08:00
|
|
|
|
'success': False,
|
|
|
|
|
'error': '会话不存在'
|
|
|
|
|
}), 404
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
except Exception as e:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.logger.error(f'获取检测状态失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
@self.app.route('/api/detection/<session_id>/save-info', methods=['POST'])
|
|
|
|
|
def save_session_info(session_id):
|
|
|
|
|
"""保存会话信息(诊断、处理、建议、状态)"""
|
2025-08-17 12:48:10 +08:00
|
|
|
|
try:
|
2025-08-20 09:11:55 +08:00
|
|
|
|
if not self.db_manager:
|
|
|
|
|
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
if not session_id:
|
2025-08-20 09:11:55 +08:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
# 调用数据库管理器的批量更新方法
|
|
|
|
|
self.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})')
|
|
|
|
|
|
|
|
|
|
self.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
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'保存会话信息失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
@self.app.route('/api/detection/<session_id>/collect', methods=['POST'])
|
|
|
|
|
def collect_detection_data(session_id):
|
2025-08-17 12:48:10 +08:00
|
|
|
|
"""采集检测数据"""
|
|
|
|
|
try:
|
2025-08-20 09:11:55 +08:00
|
|
|
|
if not self.db_manager:
|
|
|
|
|
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
if not self.device_coordinator:
|
|
|
|
|
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
# 获取请求数据
|
|
|
|
|
data = flask_request.get_json() or {}
|
|
|
|
|
patient_id = data.get('patient_id')
|
|
|
|
|
screen_image_base64 = data.get('imageData')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
# 如果没有提供patient_id,从会话信息中获取
|
|
|
|
|
if not patient_id:
|
|
|
|
|
session_data = self.db_manager.get_session_data(session_id)
|
|
|
|
|
if not session_data:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '检测会话不存在'
|
|
|
|
|
}), 404
|
|
|
|
|
patient_id = session_data.get('patient_id')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 09:11:55 +08:00
|
|
|
|
if not patient_id:
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': '无法获取患者ID'
|
|
|
|
|
}), 400
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-20 16:04:38 +08:00
|
|
|
|
# 调用录制管理器采集数据
|
|
|
|
|
collected_data = self.recording_manager.collect_detection_data(
|
2025-08-20 09:11:55 +08:00
|
|
|
|
session_id=session_id,
|
2025-08-20 16:04:38 +08:00
|
|
|
|
patient_id=patient_id
|
2025-08-20 09:11:55 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 将采集的数据保存到数据库
|
|
|
|
|
if collected_data:
|
|
|
|
|
self.db_manager.save_detection_data(session_id, collected_data)
|
|
|
|
|
self.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': '数据采集成功'
|
|
|
|
|
})
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-08-20 09:11:55 +08:00
|
|
|
|
self.logger.error(f'采集检测数据失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
@self.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))
|
2025-08-20 17:38:38 +08:00
|
|
|
|
patient_id = flask_request.args.get('patient_id')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
sessions = self.db_manager.get_detection_sessions(page, size, patient_id)
|
|
|
|
|
total = self.db_manager.get_sessions_count(patient_id)
|
|
|
|
|
|
2025-08-20 17:38:38 +08:00
|
|
|
|
# 为每个会话补充最新的检测数据
|
|
|
|
|
for session in sessions:
|
|
|
|
|
session_id = session.get('id')
|
|
|
|
|
if session_id:
|
|
|
|
|
latest_data = self.db_manager.get_latest_detection_data(session_id, 5)
|
|
|
|
|
session['latest_detection_data'] = latest_data
|
|
|
|
|
else:
|
|
|
|
|
session['latest_detection_data'] = []
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'data': {
|
|
|
|
|
'sessions': sessions,
|
|
|
|
|
'total': total,
|
|
|
|
|
'page': page,
|
|
|
|
|
'size': size
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'获取检测历史失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-20 09:34:06 +08:00
|
|
|
|
@self.app.route('/api/history/sessions/<session_id>', methods=['GET'])
|
|
|
|
|
def get_session_data(session_id):
|
|
|
|
|
"""获取会话详细数据"""
|
|
|
|
|
try:
|
|
|
|
|
session_data = self.db_manager.get_session_data(session_id)
|
|
|
|
|
if session_data is None:
|
|
|
|
|
return jsonify({'success': False, 'error': '会话不存在'}), 404
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'data': session_data
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'获取会话数据失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/detection/data/<session_id>/latest', methods=['GET'])
|
|
|
|
|
def get_latest_detection_data(session_id):
|
|
|
|
|
"""获取最新的检测数据"""
|
|
|
|
|
try:
|
|
|
|
|
limit = int(flask_request.args.get('limit', 5))
|
|
|
|
|
data = self.db_manager.get_latest_detection_data(session_id, limit)
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'data': data
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'获取最新检测数据失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/detection/data/detail/<data_id>', methods=['GET'])
|
|
|
|
|
def get_detection_data_by_id(data_id):
|
|
|
|
|
"""根据主键ID查询检测数据详情"""
|
|
|
|
|
try:
|
|
|
|
|
data = self.db_manager.get_detection_data_by_id(data_id)
|
|
|
|
|
if data is None:
|
|
|
|
|
return jsonify({'success': False, 'error': '检测数据不存在'}), 404
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'data': data
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'获取检测数据详情失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
|
|
|
|
@self.app.route('/api/detection/data/<data_id>', methods=['DELETE'])
|
|
|
|
|
def delete_detection_data(data_id):
|
|
|
|
|
"""删除检测数据记录"""
|
|
|
|
|
try:
|
|
|
|
|
self.db_manager.delete_detection_data(data_id)
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': '检测数据删除成功'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 404
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'删除检测数据失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-20 17:38:38 +08:00
|
|
|
|
@self.app.route('/api/detection/sessions/<session_id>', methods=['DELETE'])
|
|
|
|
|
def delete_detection_session(session_id):
|
|
|
|
|
"""删除检测会话及其相关的检测数据"""
|
|
|
|
|
try:
|
|
|
|
|
self.db_manager.delete_detection_session(session_id)
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': '检测会话删除成功'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 404
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'删除检测会话失败: {e}')
|
|
|
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# ==================== 错误处理 ====================
|
|
|
|
|
|
|
|
|
|
@self.app.errorhandler(404)
|
|
|
|
|
def not_found(error):
|
|
|
|
|
return jsonify({'success': False, 'error': 'API接口不存在'}), 404
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
@self.app.errorhandler(500)
|
|
|
|
|
def internal_error(error):
|
|
|
|
|
return jsonify({'success': False, 'error': '服务器内部错误'}), 500
|
2025-08-20 09:11:55 +08:00
|
|
|
|
|
|
|
|
|
# ==================== SOCKET事件 ====================
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
def _register_socketio_events(self):
|
|
|
|
|
"""注册SocketIO事件"""
|
|
|
|
|
if self.socketio is None:
|
2025-07-28 11:59:56 +08:00
|
|
|
|
return
|
|
|
|
|
|
2025-08-17 16:42:05 +08:00
|
|
|
|
# 注册统一设备命名空间的连接事件
|
|
|
|
|
@self.socketio.on('connect', namespace='/devices')
|
|
|
|
|
def handle_devices_connect():
|
|
|
|
|
self.logger.info('设备命名空间客户端连接')
|
|
|
|
|
emit('status', {'message': '设备命名空间连接成功'}, namespace='/devices')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-17 17:39:04 +08:00
|
|
|
|
# 连接时发送当前所有设备的状态
|
|
|
|
|
self.broadcast_all_device_status()
|
|
|
|
|
|
2025-08-17 16:42:05 +08:00
|
|
|
|
@self.socketio.on('disconnect', namespace='/devices')
|
|
|
|
|
def handle_devices_disconnect():
|
|
|
|
|
self.logger.info('设备命名空间客户端断开连接')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-17 16:42:05 +08:00
|
|
|
|
# 注册设备订阅事件
|
|
|
|
|
@self.socketio.on('subscribe_device', namespace='/devices')
|
|
|
|
|
def handle_subscribe_device(data):
|
|
|
|
|
"""订阅特定设备数据"""
|
|
|
|
|
device_type = data.get('device_type')
|
|
|
|
|
if device_type in ['camera', 'femtobolt', 'imu', 'pressure']:
|
|
|
|
|
self.logger.info(f'客户端订阅{device_type}设备数据')
|
|
|
|
|
emit('subscription_status', {
|
|
|
|
|
'device_type': device_type,
|
|
|
|
|
'status': 'subscribed',
|
|
|
|
|
'message': f'{device_type}设备数据订阅成功'
|
|
|
|
|
}, namespace='/devices')
|
|
|
|
|
else:
|
|
|
|
|
emit('subscription_status', {
|
|
|
|
|
'device_type': device_type,
|
|
|
|
|
'status': 'error',
|
|
|
|
|
'message': '不支持的设备类型'
|
|
|
|
|
}, namespace='/devices')
|
|
|
|
|
|
|
|
|
|
@self.socketio.on('unsubscribe_device', namespace='/devices')
|
|
|
|
|
def handle_unsubscribe_device(data):
|
|
|
|
|
"""取消订阅特定设备数据"""
|
|
|
|
|
device_type = data.get('device_type')
|
|
|
|
|
self.logger.info(f'客户端取消订阅{device_type}设备数据')
|
|
|
|
|
emit('subscription_status', {
|
|
|
|
|
'device_type': device_type,
|
|
|
|
|
'status': 'unsubscribed',
|
|
|
|
|
'message': f'{device_type}设备数据取消订阅成功'
|
|
|
|
|
}, namespace='/devices')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
2025-08-17 16:42:05 +08:00
|
|
|
|
@self.socketio.on('start_push_data', namespace='/devices')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
def handle_start_push_data():
|
|
|
|
|
"""启动数据推送"""
|
2025-07-28 11:59:56 +08:00
|
|
|
|
try:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.start_device_push_data()
|
2025-08-17 16:42:05 +08:00
|
|
|
|
emit('test_status', {'status': 'started', 'message': '数据推送已开始'}, namespace='/devices')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
except Exception as e:
|
2025-08-17 16:42:05 +08:00
|
|
|
|
emit('test_status', {'status': 'error', 'message': str(e)}, namespace='/devices')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 16:42:05 +08:00
|
|
|
|
@self.socketio.on('stop_push_data', namespace='/devices')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
def handle_stop_push_data():
|
|
|
|
|
"""停止数据推送"""
|
2025-07-28 11:59:56 +08:00
|
|
|
|
try:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.stop_device_push_data()
|
2025-08-17 16:42:05 +08:00
|
|
|
|
emit('test_status', {'status': 'stopped', 'message': '数据推送已停止'}, namespace='/devices')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
except Exception as e:
|
2025-08-17 16:42:05 +08:00
|
|
|
|
emit('test_status', {'status': 'error', 'message': str(e)}, namespace='/devices')
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
def start_device_push_data(self):
|
|
|
|
|
"""开始设备数据推送"""
|
2025-08-17 16:42:05 +08:00
|
|
|
|
if self.is_pushing_data:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.logger.warning('设备数据推送已在运行')
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
self.logger.info('开始设备数据推送...')
|
2025-08-17 16:42:05 +08:00
|
|
|
|
self.is_pushing_data = True
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
# 并行启动真实设备管理器
|
|
|
|
|
failed_devices = []
|
|
|
|
|
device_threads = {}
|
|
|
|
|
device_results = {}
|
|
|
|
|
|
|
|
|
|
def initialize_device(device_name, manager):
|
|
|
|
|
"""设备初始化工作函数"""
|
|
|
|
|
try:
|
|
|
|
|
print(f"[DEBUG] 尝试初始化设备: {device_name}")
|
|
|
|
|
if manager.initialize():
|
|
|
|
|
print(f"[DEBUG] {device_name} 初始化成功,开始启动流")
|
|
|
|
|
manager.start_streaming()
|
|
|
|
|
device_results[device_name] = True
|
|
|
|
|
self.logger.info(f'{device_name}设备启动成功')
|
|
|
|
|
else:
|
|
|
|
|
print(f"[DEBUG] {device_name} 初始化失败")
|
|
|
|
|
device_results[device_name] = False
|
|
|
|
|
self.logger.error(f'{device_name}设备启动失败')
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"[DEBUG] {device_name} 初始化异常: {e}")
|
|
|
|
|
device_results[device_name] = False
|
|
|
|
|
self.logger.error(f'{device_name}设备启动异常: {e}')
|
|
|
|
|
|
|
|
|
|
# 为每个设备创建初始化线程
|
|
|
|
|
for device_name, manager in self.device_managers.items():
|
|
|
|
|
if manager is not None: # 确保管理器已初始化
|
|
|
|
|
thread = threading.Thread(
|
|
|
|
|
target=initialize_device,
|
|
|
|
|
args=(device_name, manager),
|
|
|
|
|
name=f'Init-{device_name}',
|
|
|
|
|
daemon=True
|
|
|
|
|
)
|
|
|
|
|
device_threads[device_name] = thread
|
|
|
|
|
thread.start()
|
|
|
|
|
else:
|
|
|
|
|
self.logger.warning(f'{device_name}管理器未初始化,跳过启动')
|
|
|
|
|
device_results[device_name] = False
|
|
|
|
|
|
|
|
|
|
# 等待所有设备初始化完成(最多等待30秒)
|
|
|
|
|
for device_name, thread in device_threads.items():
|
|
|
|
|
thread.join(timeout=30.0)
|
|
|
|
|
if thread.is_alive():
|
|
|
|
|
self.logger.warning(f'{device_name}设备初始化超时')
|
|
|
|
|
device_results[device_name] = False
|
|
|
|
|
|
|
|
|
|
# 收集失败的设备
|
|
|
|
|
for device_name, success in device_results.items():
|
|
|
|
|
if not success:
|
|
|
|
|
failed_devices.append(device_name)
|
|
|
|
|
|
|
|
|
|
# 输出启动结果摘要
|
|
|
|
|
successful_devices = [name for name, success in device_results.items() if success]
|
2025-08-17 17:39:04 +08:00
|
|
|
|
|
|
|
|
|
# 广播设备状态更新
|
|
|
|
|
for device_name, success in device_results.items():
|
|
|
|
|
self.broadcast_device_status(device_name, success)
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
if successful_devices:
|
|
|
|
|
self.logger.info(f'成功启动的设备: {", ".join(successful_devices)}')
|
|
|
|
|
if failed_devices:
|
|
|
|
|
self.logger.warning(f'启动失败的设备: {", ".join(failed_devices)}')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.logger.info('设备数据推送已启动')
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'启动设备数据推送失败: {e}')
|
2025-08-17 16:42:05 +08:00
|
|
|
|
self.is_pushing_data = False
|
2025-08-17 12:48:10 +08:00
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
def stop_device_push_data(self):
|
|
|
|
|
"""停止设备数据推送"""
|
2025-08-17 16:42:05 +08:00
|
|
|
|
if not self.is_pushing_data:
|
2025-08-17 12:48:10 +08:00
|
|
|
|
self.logger.warning('设备数据推送未运行')
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
self.logger.info('停止设备数据推送...')
|
2025-08-17 16:42:05 +08:00
|
|
|
|
self.is_pushing_data = False
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
# 停止设备管理器
|
|
|
|
|
for device_name, manager in self.device_managers.items():
|
|
|
|
|
if manager is not None:
|
|
|
|
|
try:
|
|
|
|
|
manager.stop_streaming()
|
|
|
|
|
manager.disconnect()
|
|
|
|
|
self.logger.info(f'{device_name}设备已停止')
|
2025-08-17 17:39:04 +08:00
|
|
|
|
# 广播设备状态为未连接
|
|
|
|
|
self.broadcast_device_status(device_name, False)
|
2025-08-17 12:48:10 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'停止{device_name}设备失败: {e}')
|
2025-08-17 17:39:04 +08:00
|
|
|
|
# 即使停止失败也广播为未连接状态
|
|
|
|
|
self.broadcast_device_status(device_name, False)
|
2025-08-17 12:48:10 +08:00
|
|
|
|
|
|
|
|
|
self.logger.info('设备数据推送已停止')
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'停止设备数据推送失败: {e}')
|
2025-08-17 17:39:04 +08:00
|
|
|
|
|
|
|
|
|
def broadcast_device_status(self, device_name: str, is_connected: bool):
|
|
|
|
|
"""广播单个设备状态"""
|
|
|
|
|
if self.socketio:
|
|
|
|
|
try:
|
|
|
|
|
status_data = {
|
|
|
|
|
'device_type': device_name,
|
|
|
|
|
'status': is_connected,
|
|
|
|
|
'timestamp': datetime.now().isoformat()
|
|
|
|
|
}
|
|
|
|
|
self.socketio.emit('device_status', status_data, namespace='/devices')
|
|
|
|
|
self.logger.info(f'广播设备状态: {device_name} -> {"已连接" if is_connected else "未连接"}')
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'广播设备状态失败: {e}')
|
|
|
|
|
|
|
|
|
|
def broadcast_all_device_status(self):
|
|
|
|
|
"""广播所有设备状态"""
|
|
|
|
|
for device_name, manager in self.device_managers.items():
|
|
|
|
|
if manager is not None:
|
|
|
|
|
try:
|
|
|
|
|
# 检查设备是否连接(使用is_connected属性)
|
|
|
|
|
is_connected = hasattr(manager, 'is_connected') and getattr(manager, 'is_connected', False)
|
|
|
|
|
self.broadcast_device_status(device_name, is_connected)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'检查{device_name}设备状态失败: {e}')
|
|
|
|
|
self.broadcast_device_status(device_name, False)
|
|
|
|
|
|
|
|
|
|
def _on_device_status_change(self, device_name: str, is_connected: bool):
|
|
|
|
|
"""
|
|
|
|
|
设备状态变化回调函数
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
device_name: 设备名称
|
|
|
|
|
is_connected: 连接状态
|
|
|
|
|
"""
|
|
|
|
|
self.logger.info(f'设备状态变化: {device_name} -> {"已连接" if is_connected else "未连接"}')
|
|
|
|
|
self.broadcast_device_status(device_name, is_connected)
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
def _detection_worker(self, detection_id, duration):
|
|
|
|
|
"""检测工作线程"""
|
|
|
|
|
try:
|
|
|
|
|
self.logger.info(f'检测线程启动 - ID: {detection_id}, 持续时间: {duration}秒')
|
|
|
|
|
|
|
|
|
|
# 模拟检测过程
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
while time.time() - start_time < duration:
|
|
|
|
|
if not self.current_detection:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# 发送检测进度
|
|
|
|
|
elapsed = time.time() - start_time
|
|
|
|
|
progress = min(100, (elapsed / duration) * 100)
|
|
|
|
|
|
|
|
|
|
if self.socketio:
|
|
|
|
|
self.socketio.emit('detection_progress', {
|
|
|
|
|
'detection_id': detection_id,
|
|
|
|
|
'progress': progress,
|
|
|
|
|
'elapsed_time': elapsed,
|
|
|
|
|
'remaining_time': max(0, duration - elapsed)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
|
|
# 检测完成
|
|
|
|
|
if self.current_detection and self.current_detection['id'] == detection_id:
|
|
|
|
|
self.db_manager.end_detection_session(detection_id)
|
|
|
|
|
self.current_detection = None
|
|
|
|
|
|
|
|
|
|
if self.socketio:
|
|
|
|
|
self.socketio.emit('detection_complete', {
|
|
|
|
|
'detection_id': detection_id,
|
|
|
|
|
'message': '检测已完成'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
self.logger.info(f'检测完成 - ID: {detection_id}')
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'检测线程异常: {e}')
|
|
|
|
|
if self.current_detection:
|
|
|
|
|
self.current_detection = None
|
2025-07-28 11:59:56 +08:00
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
def run(self, debug=None):
|
|
|
|
|
"""运行服务器"""
|
|
|
|
|
if debug is not None:
|
|
|
|
|
self.debug = debug
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
self.logger.info('正在启动AppServer...')
|
|
|
|
|
self.init_app()
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
self.logger.info('服务被用户中断')
|
|
|
|
|
except Exception as e:
|
|
|
|
|
self.logger.error(f'服务启动失败: {e}')
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
finally:
|
|
|
|
|
self.logger.info('AppServer已停止')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
"""主函数"""
|
|
|
|
|
# 解析命令行参数
|
|
|
|
|
parser = argparse.ArgumentParser(description='Body Balance Evaluation System Backend')
|
|
|
|
|
parser.add_argument('--host', default='localhost', help='Host address to bind to')
|
|
|
|
|
parser.add_argument('--port', type=int, default=5000, help='Port number to bind to')
|
|
|
|
|
parser.add_argument('--debug', action='store_true', help='Enable debug mode')
|
2025-07-28 11:59:56 +08:00
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
2025-08-17 12:48:10 +08:00
|
|
|
|
# 创建并运行服务器
|
|
|
|
|
server = AppServer(host=args.host, port=args.port, debug=args.debug)
|
|
|
|
|
server.run()
|
|
|
|
|
|
|
|
|
|
|
2025-07-28 11:59:56 +08:00
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|