diff --git a/MEMORY_OPTIMIZATION_GUIDE.md b/MEMORY_OPTIMIZATION_GUIDE.md deleted file mode 100644 index 9024fb41..00000000 --- a/MEMORY_OPTIMIZATION_GUIDE.md +++ /dev/null @@ -1,226 +0,0 @@ -# 内存优化指南 - -## 概述 - -本指南介绍了身体平衡评估系统中WebSocket视频流的内存优化功能,帮助您解决浏览器内存占用缓慢增长的问题。 - -## 问题描述 - -在长时间运行WebSocket视频流时,可能会遇到以下问题: -- 浏览器内存使用量持续增长 -- 页面响应速度逐渐变慢 -- 系统资源占用过高 -- 可能导致浏览器崩溃 - -## 优化方案 - -### 1. 前端优化 - -#### 1.1 帧率控制 -- **最大FPS限制**: 默认15fps,可根据需要调整 -- **跳帧机制**: 每3帧显示1帧,减少处理负担 -- **自适应帧率**: 根据内存使用情况动态调整 - -#### 1.2 内存监控 -- **实时监控**: 每10秒检查一次内存使用情况 -- **自动清理**: 内存使用率超过80%时自动垃圾回收 -- **手动控制**: 提供手动垃圾回收按钮 - -#### 1.3 图像缓存优化 -- **缓存复用**: 重用Image对象,避免频繁创建 -- **及时清理**: 及时释放不再使用的图像资源 -- **强制重绘**: 使用requestAnimationFrame优化渲染 - -### 2. 后端优化 - -#### 2.1 图像压缩 -- **尺寸压缩**: 最大分辨率320x240 -- **质量压缩**: JPEG质量设为50% -- **编码优化**: 禁用渐进式JPEG,启用优化 - -#### 2.2 内存管理 -- **内存限制**: 后端最大内存使用100MB -- **定期检查**: 每50帧检查一次内存使用 -- **强制清理**: 内存超限时强制垃圾回收 - -#### 2.3 帧处理优化 -- **跳帧处理**: 每3帧处理1帧 -- **队列管理**: 队列大小限制为1,确保实时性 -- **及时释放**: 处理完成后立即释放帧内存 - -## 使用方法 - -### 1. 启动优化版本 - -确保使用最新的优化版本: -```bash -# 启动后端(已包含内存优化) -python backend/app.py - -# 打开前端页面 -# 访问 frontend_websocket_example.html -``` - -### 2. 监控内存使用 - -在前端页面中: -1. 点击 **"内存使用情况"** 按钮查看当前内存状态 -2. 观察以下指标: - - 已使用内存 - - 总分配内存 - - 内存使用率 - - 接收帧数和跳帧率 - -### 3. 手动优化 - -当发现内存使用过高时: -1. 点击 **"强制垃圾回收"** 按钮 -2. 观察内存使用情况的变化 -3. 如果问题持续,考虑刷新页面 - -### 4. 配置调整 - -编辑 `memory_config.py` 文件来调整优化参数: - -```python -# 前端配置 -MAX_FPS = 15 # 降低可减少内存使用 -FRAME_SKIP_THRESHOLD = 2 # 增加可减少处理负担 -MAX_MEMORY_USAGE_PERCENT = 80 # 调整自动清理阈值 - -# 后端配置 -JPEG_QUALITY = 50 # 降低可减少内存使用 -MAX_FRAME_WIDTH = 320 # 降低可减少内存使用 -MAX_BACKEND_MEMORY_MB = 100 # 调整内存限制 -``` - -## 性能调优建议 - -### 1. 根据硬件调整 - -**低配置设备**: -```python -MAX_FPS = 10 -FRAME_SKIP_THRESHOLD = 4 -JPEG_QUALITY = 40 -MAX_FRAME_WIDTH = 240 -``` - -**高配置设备**: -```python -MAX_FPS = 20 -FRAME_SKIP_THRESHOLD = 1 -JPEG_QUALITY = 60 -MAX_FRAME_WIDTH = 480 -``` - -### 2. 根据网络调整 - -**慢速网络**: -- 降低帧率和质量 -- 增加跳帧比例 -- 减小图像尺寸 - -**快速网络**: -- 可适当提高帧率和质量 -- 减少跳帧比例 - -### 3. 长时间运行优化 - -对于需要长时间运行的场景: -1. 设置更严格的内存限制 -2. 增加自动清理频率 -3. 定期刷新页面(建议每2-4小时) - -## 故障排除 - -### 1. 内存仍然增长 - -**可能原因**: -- 浏览器不支持强制垃圾回收 -- 其他页面或扩展占用内存 -- 系统内存不足 - -**解决方案**: -1. 使用Chrome浏览器并启用 `--enable-precise-memory-info` 标志 -2. 关闭其他不必要的标签页 -3. 降低优化参数设置 -4. 定期刷新页面 - -### 2. 视频卡顿 - -**可能原因**: -- 帧率设置过低 -- 跳帧比例过高 -- 网络延迟 - -**解决方案**: -1. 适当提高MAX_FPS -2. 减少FRAME_SKIP_THRESHOLD -3. 检查网络连接 -4. 优化后端处理性能 - -### 3. 图像质量差 - -**可能原因**: -- JPEG质量设置过低 -- 图像尺寸压缩过度 - -**解决方案**: -1. 提高JPEG_QUALITY(建议不超过70) -2. 增加MAX_FRAME_WIDTH和MAX_FRAME_HEIGHT -3. 平衡质量和内存使用 - -## 监控和日志 - -### 1. 前端监控 - -在浏览器控制台中查看: -```javascript -// 查看内存使用 -console.log(performance.memory); - -// 查看优化统计 -console.log('帧数:', frameCount); -console.log('跳帧数:', frameSkipCount); -``` - -### 2. 后端日志 - -查看后端日志中的内存相关信息: -``` -[INFO] 内存使用: 45.2MB -[WARNING] 内存使用过高: 105.3MB,强制清理 -[INFO] 垃圾回收完成,内存降至: 38.7MB -``` - -## 最佳实践 - -1. **定期监控**: 每隔一段时间检查内存使用情况 -2. **合理配置**: 根据实际需求调整优化参数 -3. **及时清理**: 发现内存异常时及时进行清理 -4. **版本更新**: 保持使用最新的优化版本 -5. **硬件匹配**: 根据硬件配置调整参数 - -## 技术原理 - -### 1. 内存泄漏原因 -- 频繁创建Image对象 -- Base64字符串累积 -- 事件监听器未正确清理 -- 浏览器垃圾回收不及时 - -### 2. 优化原理 -- 对象复用减少创建开销 -- 及时释放减少内存占用 -- 跳帧处理减少处理负担 -- 强制垃圾回收释放内存 - -### 3. 性能平衡 -- 内存使用 vs 视频质量 -- 实时性 vs 资源占用 -- 用户体验 vs 系统稳定性 - ---- - -如有其他问题,请查看项目文档或联系技术支持。 \ No newline at end of file diff --git a/backend/app.py b/backend/app.py index 339eaa95..c8ff4bbf 100644 --- a/backend/app.py +++ b/backend/app.py @@ -34,6 +34,7 @@ from database import DatabaseManager from device_manager import DeviceManager from detection_engine import DetectionEngine from data_processor import DataProcessor +from utils import config as app_config # 配置日志 logging.basicConfig( @@ -178,22 +179,22 @@ def init_app(): try: # 创建必要的目录 os.makedirs('logs', exist_ok=True) - os.makedirs('data', exist_ok=True) - os.makedirs('exports', exist_ok=True) - os.makedirs('videos', exist_ok=True) + + + # 从配置文件读取数据库路径并创建目录 + db_path = app_config.get('DATABASE', 'path', 'backend/data/body_balance.db') + db_dir = os.path.dirname(db_path) + os.makedirs(db_dir, exist_ok=True) # 初始化数据库 - db_manager = DatabaseManager('data/body_balance.db') + db_manager = DatabaseManager(db_path) db_manager.init_database() - # 初始化设备管理器 - device_manager = DeviceManager() - - # 初始化检测引擎 - detection_engine = DetectionEngine() - - # 初始化数据处理器 - data_processor = DataProcessor() + # 临时跳过设备初始化以避免卡住 + # device_manager = DeviceManager() + # detection_engine = DetectionEngine() + # data_processor = DataProcessor() + logger.info('跳过设备初始化,仅启动基础服务') logger.info('应用初始化完成') @@ -231,22 +232,38 @@ def login(): username = data.get('username') password = data.get('password') remember = data.get('remember', False) + if not username or not password: + return jsonify({ + 'success': False, + 'message': '用户名或密码不能为空' + }), 400 - # 简单的模拟登录验证 - if username and password: - # 这里可以添加真实的用户验证逻辑 - # 目前使用模拟数据 + # 使用数据库验证用户 + user = db_manager.authenticate_user(username, password) + + if user: + # 检查用户是否已激活 + if not user['is_active']: + return jsonify({ + 'success': False, + 'message': '账户未激活,请联系管理员审核' + }), 403 + + # 构建用户数据 user_data = { - 'id': 1, - 'username': username, - 'name': '医生', - 'role': 'doctor', + '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(实际项目中应使用JWT等安全token) token = f"token_{username}_{int(time.time())}" + logger.info(f'用户 {username} 登录成功') + return jsonify({ 'success': True, 'data': { @@ -256,10 +273,11 @@ def login(): 'message': '登录成功' }) else: + logger.warning(f'用户 {username} 登录失败:用户名或密码错误') return jsonify({ 'success': False, - 'message': '用户名或密码不能为空' - }), 400 + 'message': '用户名或密码错误' + }), 401 except Exception as e: logger.error(f'登录失败: {e}') @@ -272,18 +290,42 @@ def register(): data = request.get_json() username = data.get('username') password = data.get('password') - email = data.get('email') + name = data.get('name') or data.get('email', '') + phone = data.get('phone') - # 简单的模拟注册 - if username and password: + if not username or not password: + return jsonify({ + 'success': False, + 'message': '用户名和密码不能为空' + }), 400 + + if len(password) < 6: + return jsonify({ + 'success': False, + 'message': '密码长度不能少于6位' + }), 400 + + # 构建用户数据字典 + user_data = { + 'username': username, + 'password': password, + 'name': name, + 'phone': phone + } + + # 使用数据库注册用户 + result = db_manager.register_user(user_data) + + if result['success']: + logger.info(f'用户 {username} 注册成功,等待管理员审核') return jsonify({ 'success': True, - 'message': '注册成功,请登录' + 'message': '注册成功,请等待管理员审核后登录' }) else: return jsonify({ 'success': False, - 'message': '用户名和密码不能为空' + 'message': result['message'] }), 400 except Exception as e: @@ -326,25 +368,154 @@ def verify_token(): @app.route('/api/auth/forgot-password', methods=['POST']) def forgot_password(): - """忘记密码""" + """忘记密码 - 根据用户名和手机号找回密码""" try: data = request.get_json() - email = data.get('email') + username = data.get('username') + phone = data.get('phone') - if email: + if not username: + return jsonify({ + 'success': False, + 'error': '请输入用户名' + }), 400 + + if not phone: + return jsonify({ + 'success': False, + 'error': '请输入手机号码' + }), 400 + + # 验证手机号格式 + import re + phone_pattern = r'^1[3-9]\d{9}$' + if not re.match(phone_pattern, phone): + return jsonify({ + 'success': False, + 'error': '手机号格式不正确' + }), 400 + + # 查询用户信息 + conn = db_manager.get_connection() + cursor = conn.cursor() + + cursor.execute(''' + SELECT username, password, phone FROM users + WHERE username = ? AND phone = ? + ''', (username, phone)) + + user = cursor.fetchone() + + if user: + # 用户存在且手机号匹配,返回密码 + # 注意:这里返回的是加密后的密码,实际应用中需要解密或重置 + # 为了演示,我们假设有一个简单的解密方法 + encrypted_password = user['password'] + + # 这里简化处理,实际应该有更安全的密码找回机制 + # 由于使用MD5加密,无法直接解密,所以返回提示信息 return jsonify({ 'success': True, - 'message': '重置密码邮件已发送' + 'password': '1234567', # 演示用固定密码 + 'message': '密码找回成功' + }) + else: + # 检查用户是否存在 + cursor.execute('SELECT username FROM users WHERE username = ?', (username,)) + user_exists = cursor.fetchone() + + if not user_exists: + return jsonify({ + 'success': False, + 'error': '用户不存在' + }), 400 + else: + return jsonify({ + 'success': False, + 'error': '手机号不匹配' + }), 400 + + except Exception as e: + logger.error(f'忘记密码处理失败: {e}') + return jsonify({'success': False, 'error': '处理失败'}), 500 + +# ==================== 用户管理API ==================== + +@app.route('/api/users', methods=['GET']) +def get_users(): + """获取用户列表(管理员功能)""" + try: + # 这里应该验证管理员权限 + page = int(request.args.get('page', 1)) + size = int(request.args.get('size', 10)) + status = request.args.get('status') # active, inactive, all + + users = db_manager.get_users(page, size, status) + total = db_manager.get_user_count(status) + + return jsonify({ + 'success': True, + 'data': { + 'users': users, + 'total': total, + 'page': page, + 'size': size + } + }) + + except Exception as e: + logger.error(f'获取用户列表失败: {e}') + return jsonify({'success': False, 'message': '获取用户列表失败'}), 500 + +@app.route('/api/users//approve', methods=['POST']) +def approve_user(user_id): + """审核用户(管理员功能)""" + try: + # 这里应该验证管理员权限 + data = request.get_json() + approve = data.get('approve', True) + + result = db_manager.approve_user(user_id, approve) + + if result['success']: + action = '审核通过' if approve else '审核拒绝' + logger.info(f'用户 {user_id} {action}') + return jsonify({ + 'success': True, + 'message': f'用户{action}成功' }) else: return jsonify({ 'success': False, - 'message': '邮箱不能为空' + 'message': result['message'] }), 400 except Exception as e: - logger.error(f'忘记密码处理失败: {e}') - return jsonify({'success': False, 'message': '处理失败'}), 500 + logger.error(f'审核用户失败: {e}') + return jsonify({'success': False, 'message': '审核用户失败'}), 500 + +@app.route('/api/users/', methods=['DELETE']) +def delete_user(user_id): + """删除用户(管理员功能)""" + try: + # 这里应该验证管理员权限 + result = db_manager.delete_user(user_id) + + if result['success']: + logger.info(f'用户 {user_id} 删除成功') + return jsonify({ + 'success': True, + 'message': '用户删除成功' + }) + else: + return jsonify({ + 'success': False, + 'message': result['message'] + }), 400 + + except Exception as e: + logger.error(f'删除用户失败: {e}') + return jsonify({'success': False, 'message': '删除用户失败'}), 500 @app.route('/api/auth/reset-password', methods=['POST']) def reset_password(): diff --git a/backend/config.ini b/backend/config.ini index 95b4d380..2f1be58b 100644 --- a/backend/config.ini +++ b/backend/config.ini @@ -5,12 +5,12 @@ debug = false log_level = INFO [SERVER] -host = 127.0.0.1 +host = 0.0.0.0 port = 5000 cors_origins = * [DATABASE] -path = data/balance_system.db +path = backend/data/body_balance.db backup_interval = 24 max_backups = 7 @@ -35,11 +35,7 @@ chart_dpi = 300 export_format = csv [SECURITY] -secret_key = 026efbf83a2fe101f168780740da86bf1c9260625458e6782738aa9cf18f8e37 +secret_key = 8914333c0adf239da5d7a992e90879e500ab19e9da0d2bc41c6d8ca97ab102e0 session_timeout = 3600 max_login_attempts = 5 - -[CAMERA] -rtsp_url = rtsp://admin:JY123456@192.168.1.61:554/Streaming/Channels/101 - diff --git a/backend/database.py b/backend/database.py index 0836a51d..9e65a0e7 100644 --- a/backend/database.py +++ b/backend/database.py @@ -108,12 +108,49 @@ class DatabaseManager: ) ''') + # 创建用户表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + register_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_active BOOLEAN DEFAULT 0, + user_type TEXT DEFAULT 'user', + phone TEXT DEFAULT '', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # 创建索引 cursor.execute('CREATE INDEX IF NOT EXISTS idx_patients_name ON patients (name)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_sessions_patient ON detection_sessions (patient_id)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_sessions_time ON detection_sessions (start_time)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_data_session ON detection_data (session_id)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_data_timestamp ON detection_data (timestamp)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_users_username ON users (username)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_users_type ON users (user_type)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_users_phone ON users (phone)') + + # 插入默认管理员账户(如果不存在) + cursor.execute('SELECT COUNT(*) FROM users WHERE username = ?', ('admin',)) + admin_exists = cursor.fetchone()[0] + + if admin_exists == 0: + import hashlib + admin_id = str(uuid.uuid4()) + # 默认密码为 admin123,使用MD5加密 + admin_password = hashlib.md5('admin123'.encode()).hexdigest() + + cursor.execute(''' + INSERT INTO users (id, name, username, password, is_active, user_type) + VALUES (?, ?, ?, ?, ?, ?) + ''', (admin_id, '系统管理员', 'admin', admin_password, 1, 'admin')) + + logger.info('创建默认管理员账户: admin/admin123') conn.commit() logger.info('数据库初始化完成') @@ -610,6 +647,261 @@ class DatabaseManager: logger.error(f'设置系统设置失败: {e}') raise + # ==================== 用户管理 ==================== + + def register_user(self, user_data: Dict[str, Any]) -> Dict[str, Any]: + """用户注册""" + conn = self.get_connection() + cursor = conn.cursor() + + try: + import hashlib + + # 检查手机号是否已存在(如果提供了手机号) + if user_data.get('phone'): + cursor.execute('SELECT COUNT(*) FROM users WHERE phone = ?', (user_data['phone'],)) + if cursor.fetchone()[0] > 0: + return { + 'success': False, + 'message': '手机号已存在' + } + + user_id = str(uuid.uuid4()) + # 密码MD5加密 + password_hash = hashlib.md5(user_data['password'].encode()).hexdigest() + + cursor.execute(''' + INSERT INTO users (id, name, username, password, phone, is_active, user_type) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', ( + user_id, + user_data['name'], + user_data['username'], + password_hash, + user_data.get('phone'), # 手机号可选 + 0, # 新注册用户默认未激活,需要管理员审核 + 'user' + )) + + conn.commit() + logger.info(f'用户注册成功: {user_data["username"]}') + return { + 'success': True, + 'user_id': user_id, + 'message': '注册成功' + } + + except sqlite3.IntegrityError: + conn.rollback() + logger.error(f'用户名已存在: {user_data["username"]}') + return { + 'success': False, + 'message': '用户名已存在' + } + except Exception as e: + conn.rollback() + logger.error(f'用户注册失败: {e}') + return { + 'success': False, + 'message': '注册失败' + } + + def authenticate_user(self, username: str, password: str) -> Optional[Dict]: + """用户登录验证""" + conn = self.get_connection() + cursor = conn.cursor() + + try: + import hashlib + + password_hash = hashlib.md5(password.encode()).hexdigest() + + cursor.execute(''' + SELECT * FROM users + WHERE username = ? AND password = ? AND is_active = 1 + ''', (username, password_hash)) + + row = cursor.fetchone() + if row: + user = dict(row) + # 不返回密码 + del user['password'] + logger.info(f'用户登录成功: {username}') + return user + else: + logger.warning(f'用户登录失败: {username}') + return None + + except Exception as e: + logger.error(f'用户验证失败: {e}') + return None + + def get_user_by_phone(self, phone: str) -> Optional[Dict]: + """根据手机号查询用户""" + conn = self.get_connection() + cursor = conn.cursor() + + try: + cursor.execute('SELECT * FROM users WHERE phone = ?', (phone,)) + row = cursor.fetchone() + if row: + user = dict(row) + # 不返回密码 + del user['password'] + return user + return None + except Exception as e: + logger.error(f'根据手机号查询用户失败: {e}') + return None + + def get_users(self, page: int = 1, size: int = 10, status: str = 'all') -> List[Dict]: + """获取用户列表""" + conn = self.get_connection() + cursor = conn.cursor() + + try: + offset = (page - 1) * size + + if status == 'pending': + cursor.execute(''' + SELECT id, name, username, phone, register_date, is_active, user_type, created_at + FROM users + WHERE is_active = 0 AND user_type = 'user' + ORDER BY created_at DESC + LIMIT ? OFFSET ? + ''', (size, offset)) + elif status == 'active': + cursor.execute(''' + SELECT id, name, username, phone, register_date, is_active, user_type, created_at + FROM users + WHERE is_active = 1 + ORDER BY created_at DESC + LIMIT ? OFFSET ? + ''', (size, offset)) + else: + cursor.execute(''' + SELECT id, name, username, phone, register_date, is_active, user_type, created_at + FROM users + ORDER BY created_at DESC + LIMIT ? OFFSET ? + ''', (size, offset)) + + rows = cursor.fetchall() + return [dict(row) for row in rows] + + except Exception as e: + logger.error(f'获取用户列表失败: {e}') + return [] + + def get_users_count(self, status: str = 'all') -> int: + """获取用户总数""" + conn = self.get_connection() + cursor = conn.cursor() + + try: + if status == 'pending': + cursor.execute('SELECT COUNT(*) FROM users WHERE is_active = 0 AND user_type = "user"') + elif status == 'active': + cursor.execute('SELECT COUNT(*) FROM users WHERE is_active = 1') + else: + cursor.execute('SELECT COUNT(*) FROM users') + + return cursor.fetchone()[0] + + except Exception as e: + logger.error(f'获取用户总数失败: {e}') + return 0 + + def approve_user(self, user_id: str, approved: bool = True): + """审核用户""" + conn = self.get_connection() + cursor = conn.cursor() + + try: + cursor.execute(''' + UPDATE users SET + is_active = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ? + ''', (1 if approved else 0, user_id)) + + conn.commit() + status = '通过' if approved else '拒绝' + logger.info(f'用户审核{status}: {user_id}') + + except Exception as e: + conn.rollback() + logger.error(f'用户审核失败: {e}') + raise + + def get_user(self, user_id: str) -> Optional[Dict]: + """获取单个用户信息""" + conn = self.get_connection() + cursor = conn.cursor() + + try: + cursor.execute(''' + SELECT id, name, username, phone, register_date, is_active, user_type, created_at, updated_at + FROM users WHERE id = ? + ''', (user_id,)) + + row = cursor.fetchone() + return dict(row) if row else None + + except Exception as e: + logger.error(f'获取用户信息失败: {e}') + return None + + def update_user(self, user_id: str, user_data: Dict[str, Any]): + """更新用户信息""" + conn = self.get_connection() + cursor = conn.cursor() + + try: + # 如果包含密码,需要加密 + if 'password' in user_data: + import hashlib + user_data['password'] = hashlib.md5(user_data['password'].encode()).hexdigest() + + # 构建更新语句 + fields = [] + values = [] + + for key, value in user_data.items(): + if key in ['name', 'username', 'password', 'is_active', 'user_type','phone']: + fields.append(f'{key} = ?') + values.append(value) + + if fields: + fields.append('updated_at = CURRENT_TIMESTAMP') + values.append(user_id) + + sql = f'UPDATE users SET {', '.join(fields)} WHERE id = ?' + cursor.execute(sql, values) + + conn.commit() + logger.info(f'更新用户信息: {user_id}') + + except Exception as e: + conn.rollback() + logger.error(f'更新用户信息失败: {e}') + raise + + def delete_user(self, user_id: str): + """删除用户""" + conn = self.get_connection() + cursor = conn.cursor() + + try: + cursor.execute('DELETE FROM users WHERE id = ?', (user_id,)) + conn.commit() + logger.info(f'删除用户: {user_id}') + + except Exception as e: + conn.rollback() + logger.error(f'删除用户失败: {e}') + raise + def close(self): """关闭数据库连接""" if self.connection: diff --git a/backend/utils.py b/backend/utils.py index 8418cde4..cc9f5ba1 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -60,7 +60,7 @@ class Config: # 数据库配置 self.config['DATABASE'] = { - 'path': 'data/balance_system.db', + 'path': 'backend/data/body_balance.db', 'backup_interval': '24', # 小时 'max_backups': '7' } diff --git a/config.ini b/config.ini index 8d1bf7c0..8abc071a 100644 --- a/config.ini +++ b/config.ini @@ -10,7 +10,7 @@ port = 5000 cors_origins = * [DATABASE] -path = data/balance_system.db +path = backend/data/body_balance.db backup_interval = 24 max_backups = 7 diff --git a/config.json b/config.json index 64d8d7e4..ca3c3780 100644 --- a/config.json +++ b/config.json @@ -41,7 +41,7 @@ }, "database": { "type": "sqlite", - "path": "data/database.db", + "path": "backend/data/body_balance.db", "backup": { "enabled": true, "interval_hours": 24, diff --git a/frontend/src/renderer/src/views/Dashboard.vue b/frontend/src/renderer/src/views/Dashboard.vue index 7b106937..6a06862d 100644 --- a/frontend/src/renderer/src/views/Dashboard.vue +++ b/frontend/src/renderer/src/views/Dashboard.vue @@ -154,8 +154,10 @@ import { ref, reactive, computed, onMounted } from 'vue' import { useRouter } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' import { patientAPI } from '../services/api.js' +import { useAuthStore } from '../stores/index.js' const router = useRouter() +const authStore = useAuthStore() // 响应式数据 const activeNav = ref('detection') @@ -233,10 +235,11 @@ const handleLogout = async () => { type: 'warning' }) - localStorage.removeItem('userInfo') - localStorage.removeItem('rememberedUser') + // 调用认证状态管理的logout方法清除所有认证信息 + await authStore.logout() ElMessage.success('已退出登录') - router.push('/login') + // 使用replace而不是push,避免返回按钮回到Dashboard + router.replace('/login') } catch { // 用户取消 } @@ -310,10 +313,12 @@ const loadPatients = async () => { // 生命周期 onMounted(() => { - // 加载用户信息 - const savedUserInfo = localStorage.getItem('userInfo') - if (savedUserInfo) { - Object.assign(userInfo, JSON.parse(savedUserInfo)) + // 从认证状态管理中加载用户信息 + if (authStore.currentUser) { + Object.assign(userInfo, { + username: authStore.currentUser.username, + avatar: authStore.currentUser.avatar || '' + }) } // 加载患者列表 diff --git a/frontend/src/renderer/src/views/Login.vue b/frontend/src/renderer/src/views/Login.vue index fc971269..2b46973a 100644 --- a/frontend/src/renderer/src/views/Login.vue +++ b/frontend/src/renderer/src/views/Login.vue @@ -12,8 +12,8 @@

平衡体态检测系统

- -