更改了文件和视频存储方法

This commit is contained in:
root 2025-10-14 11:32:35 +08:00
parent 21c6611619
commit 566c199413
10 changed files with 2482 additions and 218 deletions

2404
.gitignore vendored

File diff suppressed because it is too large Load Diff

View File

@ -10,13 +10,16 @@ port = 5000
cors_origins = * cors_origins = *
[DATABASE] [DATABASE]
path = data/body_balance.db path = D:/BodyCheck/data/body_balance.db
backup_interval = 24 backup_interval = 24
max_backups = 7 max_backups = 7
[FILEPATH]
path = D:/BodyCheck/file/
[CAMERA] [CAMERA]
enabled = True enabled = True
device_index = 1 device_index = 0
width = 1280 width = 1280
height = 720 height = 720
fps = 30 fps = 30

View File

@ -25,15 +25,18 @@ try:
from .camera_manager import CameraManager from .camera_manager import CameraManager
from .femtobolt_manager import FemtoBoltManager from .femtobolt_manager import FemtoBoltManager
from .pressure_manager import PressureManager from .pressure_manager import PressureManager
from .utils.config_manager import ConfigManager
except ImportError: except ImportError:
from camera_manager import CameraManager from camera_manager import CameraManager
from femtobolt_manager import FemtoBoltManager from femtobolt_manager import FemtoBoltManager
from pressure_manager import PressureManager from pressure_manager import PressureManager
from utils.config_manager import ConfigManager
class RecordingManager: class RecordingManager:
def __init__(self, camera_manager: Optional[CameraManager] = None, db_manager=None, def __init__(self, camera_manager: Optional[CameraManager] = None, db_manager=None,
femtobolt_manager: Optional[FemtoBoltManager] = None, femtobolt_manager: Optional[FemtoBoltManager] = None,
pressure_manager: Optional[PressureManager] = None): pressure_manager: Optional[PressureManager] = None,
config_manager: Optional[ConfigManager] = None):
""" """
初始化录制管理器 初始化录制管理器
@ -42,12 +45,16 @@ class RecordingManager:
db_manager: 数据库管理器实例 db_manager: 数据库管理器实例
femtobolt_manager: FemtoBolt深度相机管理器实例 femtobolt_manager: FemtoBolt深度相机管理器实例
pressure_manager: 压力传感器管理器实例 pressure_manager: 压力传感器管理器实例
config_manager: 配置管理器实例
""" """
self.camera_manager = camera_manager self.camera_manager = camera_manager
self.db_manager = db_manager self.db_manager = db_manager
self.femtobolt_manager = femtobolt_manager self.femtobolt_manager = femtobolt_manager
self.pressure_manager = pressure_manager self.pressure_manager = pressure_manager
# 配置管理
self.config_manager = config_manager or ConfigManager()
# 录制状态 # 录制状态
self.sync_recording = False self.sync_recording = False
self.is_recording = False self.is_recording = False
@ -271,9 +278,6 @@ class RecordingManager:
# 设置默认录制类型 # 设置默认录制类型
recording_types = ['screen', 'feet'] recording_types = ['screen', 'feet']
# recording_types = ['screen']
# 验证录制区域参数(仅对启用的录制类型进行验证) # 验证录制区域参数(仅对启用的录制类型进行验证)
if 'screen' in recording_types: if 'screen' in recording_types:
@ -317,15 +321,12 @@ class RecordingManager:
self.global_recording_start_time = None self.global_recording_start_time = None
self.recording_start_time = datetime.now() self.recording_start_time = datetime.now()
db_base_path = os.path.join('data', 'patients', patient_id, session_id)
# 创建主存储目录
if getattr(sys, 'frozen', False):
# 打包后的exe文件路径
exe_dir = os.path.dirname(sys.executable)
base_path = os.path.join(exe_dir, 'data', 'patients', patient_id, session_id)
else:
base_path = os.path.join('data', 'patients', patient_id, session_id)
# 创建主存储目录
timestamp = datetime.now().strftime('%H%M%S%f')[:-3] # 精确到毫秒
file_dir = self.config_manager.get_config_value('FILEPATH', 'path')
base_path = os.path.join(file_dir, patient_id, session_id,f'video_{timestamp}')
db_base_path = os.path.join(patient_id, session_id,f'video_{timestamp}')
try: try:
# 设置目录权限 # 设置目录权限
@ -338,10 +339,10 @@ class RecordingManager:
result['message'] = f'创建录制目录失败: {dir_error}' result['message'] = f'创建录制目录失败: {dir_error}'
return result return result
# 定义视频文件路径
feet_video_path = os.path.join(base_path, 'feet.mp4') feet_video_path = os.path.join(base_path, 'feet.mp4')
screen_video_path = os.path.join(base_path, 'screen.mp4') screen_video_path = os.path.join(base_path, 'screen.mp4')
femtobolt_video_path = os.path.join(base_path, 'femtobolt.mp4') femtobolt_video_path = os.path.join(base_path, 'femtobolt.mp4')
# 准备数据库更新信息,返回给调用方统一处理 # 准备数据库更新信息,返回给调用方统一处理
@ -350,8 +351,8 @@ class RecordingManager:
'status': 'recording', 'status': 'recording',
'video_paths': { 'video_paths': {
'normal_video_path': os.path.join(db_base_path, 'feet.mp4'), 'normal_video_path': os.path.join(db_base_path, 'feet.mp4'),
'screen_video_path': os.path.join(db_base_path, 'screen.mp4'), 'screen_video_path': os.path.join(db_base_path, 'screen.mp4'),
'femtobolt_video_path': os.path.join(db_base_path, 'femtobolt.mp4') 'femtobolt_video_path': os.path.join(db_base_path, 'femtobolt.mp4')
} }
} }
self.logger.debug(f'数据库更新信息已准备 - 会话ID: {session_id}') self.logger.debug(f'数据库更新信息已准备 - 会话ID: {session_id}')
@ -801,13 +802,9 @@ class RecordingManager:
Dict: 包含所有采集数据的字典符合detection_data表结构 Dict: 包含所有采集数据的字典符合detection_data表结构
""" """
# 生成采集时间戳 # 生成采集时间戳
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3] # 精确到毫秒 timestamp = datetime.now().strftime('%H%M%S%f')[:-3] # 精确到毫秒
if getattr(sys, 'frozen', False): file_path = self.config_manager.get_config_value('FILEPATH', 'path')
# 打包后的exe文件路径 data_dir = Path(os.path.join(file_path,patient_id, session_id, f"image_{timestamp}"))
exe_dir = os.path.dirname(sys.executable)
data_dir = Path(os.path.join(exe_dir, 'data', 'patients', patient_id, session_id, timestamp))
else:
data_dir = Path(f'data/patients/{patient_id}/{session_id}/{timestamp}')
# 创建数据存储目录 # 创建数据存储目录
data_dir.mkdir(parents=True, exist_ok=True) data_dir.mkdir(parents=True, exist_ok=True)
@ -861,7 +858,7 @@ class RecordingManager:
# 更新数据字典中的图片路径 # 更新数据字典中的图片路径
data[field] = str(os.path.join('data', 'patients', patient_id, session_id, timestamp, filename)) data[field] = str(os.path.join(patient_id, session_id, f"image_{timestamp}", filename))
self.logger.debug(f'{field}保存成功: {filename}') self.logger.debug(f'{field}保存成功: {filename}')
except Exception as e: except Exception as e:
@ -869,7 +866,7 @@ class RecordingManager:
# 屏幕截图 # 屏幕截图
screen_image = self._capture_screen_image(data_dir,timestamp) screen_image = self._capture_screen_image(data_dir,timestamp)
if screen_image: if screen_image:
data['screen_image'] = str(os.path.join('data', 'patients', patient_id, session_id, timestamp, screen_image)) data['screen_image'] = str(os.path.join( patient_id, session_id, f"image_{timestamp}", screen_image))
self.logger.debug(f'数据保存完成: {session_id}, 时间戳: {timestamp}') self.logger.debug(f'数据保存完成: {session_id}, 时间戳: {timestamp}')

View File

@ -1,71 +0,0 @@
[APP]
name = Body Balance Evaluation System
version = 1.0.0
debug = True
log_level = INFO
[SERVER]
host = 0.0.0.0
port = 5000
cors_origins = *
[DATABASE]
path = data/body_balance.db
backup_interval = 24
max_backups = 7
[CAMERA]
enabled = True
device_index = 0
width = 1280
height = 720
fps = 30
buffer_size = 1
fourcc = MJPG
backend = directshow
[FEMTOBOLT]
enabled = True
algorithm_type = plt
color_resolution = 1080P
depth_mode = NFOV_2X2BINNED
camera_fps = 20
depth_range_min = 800
depth_range_max = 1200
fps = 15
synchronized_images_only = False
[DEVICES]
imu_enabled = True
imu_device_type = ble
imu_port = COM9
imu_mac_address = ef:3c:1a:0a:fe:02
imu_baudrate = 9600
pressure_enabled = True
pressure_device_type = real
pressure_use_mock = False
pressure_port = COM5
pressure_baudrate = 115200
[SYSTEM]
log_level = INFO
max_cache_size = 10
cache_timeout = 5.0
[DETECTION]
default_duration = 60
sampling_rate = 30
balance_threshold = 0.2
posture_threshold = 5.0
[DATA_PROCESSING]
filter_window = 5
outlier_threshold = 2.0
chart_dpi = 300
export_format = csv
[SECURITY]
secret_key = 79fcc4983d478c2ee672f3305d5e12c7c84fd1b58a18acb650e9f8125bfa805f
session_timeout = 3600
max_login_attempts = 5

View File

@ -56,9 +56,7 @@ class ConfigManager:
# 开发环境下的路径 # 开发环境下的路径
possible_paths.extend([ possible_paths.extend([
os.path.join(os.path.dirname(__file__), 'config.ini'),
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'config.ini'), # backend/config.ini os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'config.ini'), # backend/config.ini
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), 'config.ini') # 项目根目录/config.ini
]) ])
for path in possible_paths: for path in possible_paths:
@ -71,7 +69,7 @@ class ConfigManager:
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
default_path = os.path.join(os.path.dirname(sys.executable), 'config.ini') default_path = os.path.join(os.path.dirname(sys.executable), 'config.ini')
else: else:
default_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'config.ini') default_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'config.ini'), # backend/config.ini
self.logger.warning(f"未找到配置文件,使用默认路径: {default_path}") self.logger.warning(f"未找到配置文件,使用默认路径: {default_path}")
return default_path return default_path
@ -244,6 +242,8 @@ class ConfigManager:
'heartbeat_interval': self.config.getfloat('SYSTEM', 'heartbeat_interval', fallback=30.0) 'heartbeat_interval': self.config.getfloat('SYSTEM', 'heartbeat_interval', fallback=30.0)
} }
def get_config_value(self, section: str, key: str, fallback: Any = None) -> Any: def get_config_value(self, section: str, key: str, fallback: Any = None) -> Any:
""" """
获取配置值 获取配置值

View File

@ -164,25 +164,27 @@ class AppServer:
def init_app(self): def init_app(self):
"""初始化应用组件""" """初始化应用组件"""
try: try:
# 初始化配置管理器
self.logger.info('正在初始化配置管理器...')
self.config_manager = ConfigManager()
self.logger.info('配置管理器初始化完成')
# 初始化数据库管理器 # 初始化数据库管理器
self.logger.info('正在初始化数据库管理器...') self.logger.info('正在初始化数据库管理器...')
# 在打包环境中使用可执行文件的目录,在开发环境中使用脚本文件的目录 db_path = self.config_manager.get_config_value('DATABASE', 'path', fallback=None)
if getattr(sys, 'frozen', False): self.logger.info(f'数据库文件路径: {db_path}')
# 打包环境
base_dir = os.path.dirname(sys.executable) # 确保数据库目录存在
else: try:
# 开发环境 os.makedirs(os.path.dirname(db_path), exist_ok=True)
base_dir = os.path.dirname(__file__) except Exception as e:
db_path = os.path.join(base_dir, 'data', 'body_balance.db') self.logger.error(f'创建数据库目录失败: {e}')
os.makedirs(os.path.dirname(db_path), exist_ok=True) raise
self.db_manager = DatabaseManager(db_path) self.db_manager = DatabaseManager(db_path)
self.db_manager.init_database() self.db_manager.init_database()
self.logger.info('数据库管理器初始化完成') self.logger.info('数据库管理器初始化完成')
# 初始化配置管理器
self.logger.info('正在初始化配置管理器...')
self.config_manager = ConfigManager()
self.logger.info('配置管理器初始化完成')
# 初始化设备协调器(统一管理所有设备) # 初始化设备协调器(统一管理所有设备)
self.logger.info('正在初始化设备协调器...') self.logger.info('正在初始化设备协调器...')
@ -250,31 +252,35 @@ class AppServer:
# ==================== 静态文件服务 ==================== # ==================== 静态文件服务 ====================
@self.app.route('/data/<path:filename>', methods=['GET']) @self.app.route('/<path:filename>', methods=['GET'])
def serve_static_files(filename): def serve_static_files(filename):
"""提供静态文件服务代理backend/data/目录"""
try: try:
# 获取data目录的绝对路径 # 读取配置中的文件存储根目录(优先使用 FILEPATH.path
if getattr(sys, 'frozen', False): cfg_dir = self.config_manager.get_config_value('FILEPATH', 'path', fallback=None)
# 打包环境 if not cfg_dir:
data_dir = os.path.join(os.path.dirname(sys.executable), 'data') return jsonify({'error': '未配置文件存储路径'}), 500
else: # 规范化允许目录路径,消除末尾斜杠、大小写和符号链接影响
# 开发环境 cfg_dir = os.path.abspath(os.path.realpath(os.path.normpath(cfg_dir)))
data_dir = os.path.join(os.path.dirname(__file__), 'data')
# 安全检查:防止路径遍历攻击 # 安全检查:防止路径遍历攻击
safe_path = os.path.normpath(filename) safe_path = os.path.normpath(filename)
if '..' in safe_path or safe_path.startswith('/'): if (
'..' in safe_path or
safe_path.startswith('/') or # POSIX绝对路径
safe_path.startswith('\\\\') or # Windows UNC路径
os.path.isabs(safe_path) # Windows盘符绝对路径
):
return jsonify({'error': '非法路径'}), 400 return jsonify({'error': '非法路径'}), 400
file_path = os.path.join(data_dir, safe_path) file_path = os.path.join(cfg_dir, safe_path)
# 检查文件是否存在 # 检查文件是否存在
if not os.path.exists(file_path): if not os.path.exists(file_path):
return jsonify({'error': '文件不存在'}), 404 return jsonify({'error': '文件不存在'}), 404
# 检查是否在允许的目录内 # 检查是否在允许的目录内
if not os.path.commonpath([data_dir, file_path]) == data_dir: if os.path.commonpath([cfg_dir, file_path]) != cfg_dir:
return jsonify({'error': '访问被拒绝'}), 403 return jsonify({'error': '访问被拒绝'}), 403
# 返回文件 # 返回文件
@ -293,47 +299,7 @@ class AppServer:
self.logger.error(f'静态文件服务错误: {e}') self.logger.error(f'静态文件服务错误: {e}')
return jsonify({'error': '服务器内部错误'}), 500 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
@self.app.route('/test-socketio') @self.app.route('/test-socketio')
def test_socketio(): def test_socketio():

View File

@ -100,7 +100,7 @@ function createWindow() {
contextIsolation: true, contextIsolation: true,
preload: path.join(__dirname, 'preload.js') preload: path.join(__dirname, 'preload.js')
}, },
icon: path.join(__dirname, '../../assets/icon.ico'), icon: path.join(__dirname, '../public/logo.png'),
show: false, show: false,
fullscreen: false, fullscreen: false,
frame: true, frame: true,

View File

@ -1,7 +1,7 @@
{ {
"name": "body-balance-renderer", "name": "body-balance-system",
"version": "1.0.0", "version": "1.5.0",
"description": "平衡体态检测系统前端界面", "description": "平衡体态检测系统",
"main": "main/main.js", "main": "main/main.js",
"scripts": { "scripts": {
"dev": "concurrently \"npm run dev:renderer\" \"wait-on http://localhost:3000 && npm run dev:electron\"", "dev": "concurrently \"npm run dev:renderer\" \"wait-on http://localhost:3000 && npm run dev:electron\"",
@ -9,7 +9,7 @@
"dev:electron": "electron .", "dev:electron": "electron .",
"build": "npm run build:renderer && npm run build:electron", "build": "npm run build:renderer && npm run build:electron",
"build:renderer": "vite build", "build:renderer": "vite build",
"build:electron": "electron-builder", "build:electron": "electron-builder --config ./build/electron-builder.install.json",
"pack": "electron-packager . electron-browser --platform=win32 --arch=x64 --out=dist --overwrite", "pack": "electron-packager . electron-browser --platform=win32 --arch=x64 --out=dist --overwrite",
"preview": "vite preview" "preview": "vite preview"
}, },
@ -34,40 +34,5 @@
"electron-packager": "^17.1.2", "electron-packager": "^17.1.2",
"vite": "^4.4.9", "vite": "^4.4.9",
"wait-on": "^7.0.1" "wait-on": "^7.0.1"
},
"build": {
"appId": "com.bodybalance.evaluation",
"productName": "平衡体态检测系统",
"electronVersion": "27.3.11",
"electronDownload": {
"mirror": "https://npmmirror.com/mirrors/electron/"
},
"directories": {
"output": "dist-electron"
},
"files": [
"dist/**/*",
"main/**/*",
"package.json"
],
"extraFiles": [
{
"from": "../../../backend/dist",
"to": "resources/backend",
"filter": [
"**/*"
]
}
],
"win": {
"target": "nsis",
"icon": "../../../install/assets/icon.ico"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
}
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -2063,7 +2063,7 @@ const startRecord = async () => { // 开始录屏
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
patient_id: patientInfo.value.sessionId, patient_id: patientId.value,
// //
creator_id: creatorId.value, creator_id: creatorId.value,
screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height)], screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height)],