提交修改

This commit is contained in:
zhaozilong12 2025-07-29 18:28:40 +08:00
parent bfdba3e91f
commit 7d9f44d124
9 changed files with 1825 additions and 31 deletions

243
DEBUG_GUIDE.md Normal file
View File

@ -0,0 +1,243 @@
# 调试指南
本文档介绍如何在开发过程中进行调试,包括断点调试、日志调试等方法。
## 调试方式概览
### 1. 批处理文件调试 (推荐新手)
```bash
# 启动调试模式
./start_debug.bat
```
### 2. Python脚本调试 (推荐)
```bash
# 直接运行调试服务器
python debug_server.py
```
### 3. VS Code调试 (推荐开发者)
- 打开VS Code
- 按F5或点击调试按钮
- 选择"Debug Backend Server"配置
### 4. 命令行调试
```bash
# 激活虚拟环境
backend\venv\Scripts\activate
# 设置环境变量
set FLASK_ENV=development
set FLASK_DEBUG=1
set PYTHONPATH=%cd%
# 启动调试服务器
python -u backend\app.py
```
## 详细调试方法
### VS Code 断点调试
1. **安装Python扩展**
- 确保VS Code已安装Python扩展
2. **打开项目**
```bash
code .
```
3. **设置断点**
- 在代码行号左侧点击设置断点
- 红色圆点表示断点已设置
4. **启动调试**
- 按F5或点击调试面板的播放按钮
- 选择"Debug Backend Server"配置
5. **调试操作**
- F10: 单步跳过
- F11: 单步进入
- Shift+F11: 单步跳出
- F5: 继续执行
- Shift+F5: 停止调试
### PyCharm 调试
1. **打开项目**
- File -> Open -> 选择项目目录
2. **配置Python解释器**
- File -> Settings -> Project -> Python Interpreter
- 选择backend/venv/Scripts/python.exe
3. **创建运行配置**
- Run -> Edit Configurations
- 添加新的Python配置
- Script path: debug_server.py
- Working directory: 项目根目录
4. **设置断点并调试**
- 点击行号左侧设置断点
- 点击调试按钮启动
### 命令行调试
1. **使用pdb调试器**
```python
import pdb
pdb.set_trace() # 在需要调试的地方插入
```
2. **使用ipdb (增强版pdb)**
```bash
pip install ipdb
```
```python
import ipdb
ipdb.set_trace()
```
## 调试配置说明
### 环境变量
- `FLASK_ENV=development`: 启用开发模式
- `FLASK_DEBUG=1`: 启用调试模式
- `PYTHONPATH`: 设置Python模块搜索路径
### 调试端口
- 后端服务: http://127.0.0.1:5000
- 健康检查: http://127.0.0.1:5000/health
- WebSocket: ws://127.0.0.1:5000/socket.io/
## 常见调试场景
### 1. WebSocket连接问题
```python
# 在handle_connect函数中设置断点
@socketio.on('connect')
def handle_connect():
print(f'客户端连接: {request.sid}') # 添加调试输出
# 设置断点在这里
emit('connect_status', {'status': 'connected'})
```
### 2. RTSP流问题
```python
# 在generate_rtsp_frames函数中设置断点
def generate_rtsp_frames():
print(f'RTSP URL: {rtsp_url}') # 调试输出
# 设置断点检查rtsp_url值
cap = cv2.VideoCapture(rtsp_url)
```
### 3. API请求问题
```python
# 在API路由中设置断点
@app.route('/api/patients', methods=['GET'])
def get_patients():
print(f'请求参数: {request.args}') # 调试输出
# 设置断点检查请求参数
```
### 4. 数据库操作问题
```python
# 在数据库操作中设置断点
def get_patients(self, page, size, keyword):
print(f'查询参数: page={page}, size={size}, keyword={keyword}')
# 设置断点检查SQL查询
```
## 日志调试
### 查看日志文件
```bash
# 实时查看日志
tail -f logs/debug.log
tail -f logs/backend.log
```
### 调整日志级别
```python
# 在代码中临时调整日志级别
import logging
logging.getLogger().setLevel(logging.DEBUG)
```
## 前端调试
### 浏览器开发者工具
1. 按F12打开开发者工具
2. Console标签页查看JavaScript错误
3. Network标签页查看网络请求
4. WebSocket连接在Network -> WS中查看
### 前端调试技巧
```javascript
// 在浏览器控制台中测试WebSocket连接
const socket = io('http://127.0.0.1:5000');
socket.on('connect', () => console.log('连接成功'));
socket.emit('start_rtsp', {});
```
## 性能调试
### 使用cProfile
```bash
python -m cProfile -o profile_output.prof debug_server.py
```
### 内存使用监控
```bash
pip install memory-profiler
python -m memory_profiler debug_server.py
```
## 故障排除
### 常见问题
1. **端口被占用**
```bash
netstat -ano | findstr :5000
taskkill /PID <PID> /F
```
2. **模块导入错误**
- 检查PYTHONPATH设置
- 确认虚拟环境已激活
3. **权限问题**
- 以管理员身份运行
- 检查文件夹权限
4. **依赖包问题**
```bash
pip install -r backend/requirements.txt --force-reinstall
```
### 调试检查清单
- [ ] Python虚拟环境已激活
- [ ] 所有依赖包已安装
- [ ] 环境变量设置正确
- [ ] 端口5000未被占用
- [ ] config.ini文件存在且配置正确
- [ ] 日志文件可以正常写入
- [ ] 断点设置在正确位置
## 调试最佳实践
1. **逐步调试**: 从简单的断点开始,逐步深入
2. **日志记录**: 在关键位置添加详细的日志输出
3. **单元测试**: 编写测试用例验证功能
4. **代码审查**: 定期检查代码逻辑
5. **版本控制**: 使用Git跟踪代码变更
## 获取帮助
如果遇到调试问题,可以:
1. 查看logs目录下的日志文件
2. 检查控制台输出信息
3. 使用浏览器开发者工具
4. 参考项目文档和代码注释

View File

@ -627,7 +627,7 @@ if __name__ == '__main__':
# 启动Flask+SocketIO服务
logger.info('启动后端服务...')
socketio.run(app,
host=config.get('SERVER', 'host', fallback='127.0.0.1'),
host=config.get('SERVER', 'host', fallback='0.0.0.0'),
port=config.getint('SERVER', 'port', fallback=5000),
debug=config.getboolean('APP', 'debug', fallback=False),
allow_unsafe_werkzeug=True
@ -644,37 +644,114 @@ if __name__ == '__main__':
def generate_rtsp_frames():
global rtsp_running
cap = cv2.VideoCapture(rtsp_url)
if not cap.isOpened():
logger.error(f'无法打开RTSP流: {rtsp_url}')
return
rtsp_running = True
while rtsp_running:
ret, frame = cap.read()
if not ret:
logger.warning('RTSP读取帧失败尝试重连...')
frame_count = 0
error_count = 0
logger.info(f'开始生成RTSP帧URL: {rtsp_url}')
try:
cap = cv2.VideoCapture(rtsp_url)
if not cap.isOpened():
logger.error(f'无法打开RTSP流: {rtsp_url}')
socketio.emit('rtsp_status', {'status': 'error', 'message': f'无法打开RTSP流: {rtsp_url}'})
return
logger.info('RTSP流已打开开始推送帧')
rtsp_running = True
while rtsp_running:
ret, frame = cap.read()
if not ret:
error_count += 1
logger.warning(f'RTSP读取帧失败{error_count}次),尝试重连...')
cap.release()
if error_count > 5:
logger.error('RTSP连接失败次数过多停止推流')
socketio.emit('rtsp_status', {'status': 'error', 'message': 'RTSP连接失败次数过多'})
break
time.sleep(1)
cap = cv2.VideoCapture(rtsp_url)
continue
error_count = 0 # 重置错误计数
frame_count += 1
try:
_, buffer = cv2.imencode('.jpg', frame)
jpg_as_text = base64.b64encode(buffer).decode('utf-8')
socketio.emit('rtsp_frame', {'image': jpg_as_text})
if frame_count % 150 == 0: # 每150帧约10秒记录一次
logger.info(f'已推送 {frame_count}')
except Exception as e:
logger.error(f'编码帧失败: {e}')
time.sleep(1/15) # 推送帧率可调
except Exception as e:
logger.error(f'RTSP推流异常: {e}')
socketio.emit('rtsp_status', {'status': 'error', 'message': f'推流异常: {str(e)}'})
finally:
if 'cap' in locals():
cap.release()
time.sleep(1)
cap = cv2.VideoCapture(rtsp_url)
continue
_, buffer = cv2.imencode('.jpg', frame)
jpg_as_text = base64.b64encode(buffer).decode('utf-8')
socketio.emit('rtsp_frame', {'image': jpg_as_text})
time.sleep(1/15) # 推送帧率可调
cap.release()
rtsp_running = False
logger.info(f'RTSP推流结束总共推送了 {frame_count}')
@socketio.on('start_rtsp')
def handle_start_rtsp():
def handle_start_rtsp(data=None):
global rtsp_thread, rtsp_running
if rtsp_thread and rtsp_thread.is_alive():
emit('rtsp_status', {'status': 'already_running'})
return
rtsp_thread = threading.Thread(target=generate_rtsp_frames)
rtsp_thread.start()
emit('rtsp_status', {'status': 'started'})
logger.info(f'收到start_rtsp事件客户端ID: {request.sid}, 数据: {data}')
try:
if rtsp_thread and rtsp_thread.is_alive():
logger.warning('RTSP线程已在运行')
emit('rtsp_status', {'status': 'already_running', 'message': 'RTSP已在运行'})
return
if not rtsp_url:
logger.error('RTSP URL未配置')
emit('rtsp_status', {'status': 'error', 'message': 'RTSP URL未配置'})
return
logger.info(f'启动RTSP线程URL: {rtsp_url}')
rtsp_thread = threading.Thread(target=generate_rtsp_frames)
rtsp_thread.daemon = True
rtsp_thread.start()
logger.info('RTSP线程已启动')
emit('rtsp_status', {'status': 'started', 'message': 'RTSP推流已启动'})
except Exception as e:
logger.error(f'启动RTSP失败: {e}')
emit('rtsp_status', {'status': 'error', 'message': f'启动失败: {str(e)}'})
@socketio.on('stop_rtsp')
def handle_stop_rtsp():
def handle_stop_rtsp(data=None):
global rtsp_running
rtsp_running = False
emit('rtsp_status', {'status': 'stopped'})
logger.info(f'收到stop_rtsp事件客户端ID: {request.sid}, 数据: {data}')
try:
rtsp_running = False
logger.info('RTSP推流已停止')
emit('rtsp_status', {'status': 'stopped', 'message': 'RTSP推流已停止'})
except Exception as e:
logger.error(f'停止RTSP失败: {e}')
emit('rtsp_status', {'status': 'error', 'message': f'停止失败: {str(e)}'})
@socketio.on('connect')
def handle_connect():
logger.info(f'客户端连接: {request.sid}')
emit('connect_status', {'status': 'connected', 'sid': request.sid, 'message': '连接成功'})
@socketio.on('disconnect')
def handle_disconnect():
logger.info(f'客户端断开连接: {request.sid}')
@socketio.on('ping')
def handle_ping(data=None):
logger.info(f'收到ping事件客户端ID: {request.sid}, 数据: {data}')
emit('pong', {'timestamp': time.time(), 'message': 'pong'})

138
debug_server.py Normal file
View File

@ -0,0 +1,138 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
身体平衡评估系统 - 调试服务器启动脚本
这个脚本专门用于调试模式提供更好的调试体验
1. 支持IDE断点调试
2. 详细的错误信息输出
3. 热重载功能
4. 调试日志输出
"""
import os
import sys
import logging
from pathlib import Path
# 添加项目路径
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
sys.path.insert(0, str(project_root / 'backend'))
def setup_debug_logging():
"""设置调试日志"""
# 创建logs目录
logs_dir = project_root / 'logs'
logs_dir.mkdir(exist_ok=True)
# 配置日志格式
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
handlers=[
logging.FileHandler(logs_dir / 'debug.log', encoding='utf-8'),
logging.StreamHandler(sys.stdout)
]
)
# 设置Flask和SocketIO的日志级别
logging.getLogger('werkzeug').setLevel(logging.DEBUG)
logging.getLogger('socketio').setLevel(logging.DEBUG)
logging.getLogger('engineio').setLevel(logging.DEBUG)
logger = logging.getLogger(__name__)
logger.info('调试日志已启用')
return logger
def check_debug_environment():
"""检查调试环境"""
logger = logging.getLogger(__name__)
# 检查Python版本
if sys.version_info < (3, 8):
logger.error('需要Python 3.8或更高版本')
return False
# 检查必要文件
required_files = [
'backend/app.py',
'backend/config.ini',
'backend/requirements.txt'
]
for file_path in required_files:
if not (project_root / file_path).exists():
logger.error(f'缺少必要文件: {file_path}')
return False
logger.info('调试环境检查通过')
return True
def start_debug_server():
"""启动调试服务器"""
logger = logging.getLogger(__name__)
try:
# 设置环境变量
os.environ['FLASK_ENV'] = 'development'
os.environ['FLASK_DEBUG'] = '1'
os.environ['PYTHONPATH'] = str(project_root)
# 导入Flask应用
from backend.app import app, socketio, init_app
# 初始化应用
logger.info('初始化应用...')
init_app()
# 启动调试服务器
logger.info('启动调试服务器...')
logger.info('调试模式已启用 - 可以在IDE中设置断点')
logger.info('服务器地址: http://127.0.0.1:5000')
logger.info('健康检查: http://127.0.0.1:5000/health')
logger.info('按 Ctrl+C 停止服务器')
# 启动SocketIO服务器支持调试
socketio.run(
app,
host='127.0.0.1',
port=5000,
debug=True,
use_reloader=True, # 启用热重载
log_output=True, # 输出详细日志
allow_unsafe_werkzeug=True
)
except KeyboardInterrupt:
logger.info('服务器被用户中断')
except Exception as e:
logger.error(f'服务器启动失败: {e}', exc_info=True)
return False
return True
def main():
"""主函数"""
print('='*50)
print('身体平衡评估系统 - 调试模式')
print('='*50)
print()
# 设置调试日志
logger = setup_debug_logging()
# 检查环境
if not check_debug_environment():
input('按任意键退出...')
sys.exit(1)
# 启动调试服务器
success = start_debug_server()
if not success:
input('按任意键退出...')
sys.exit(1)
if __name__ == '__main__':
main()

View File

@ -711,9 +711,6 @@ onUnmounted(() => {
justify-content: center;
color: #ffffff;
font-size: 12px;
}
.gauge-group-box-left{
}
.gauge-table-title{
padding: 10px;

View File

@ -0,0 +1,383 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端WebSocket连接示例</title>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.status {
padding: 10px;
margin: 10px 0;
border-radius: 4px;
font-weight: bold;
}
.connected { background-color: #d4edda; color: #155724; }
.disconnected { background-color: #f8d7da; color: #721c24; }
.error { background-color: #fff3cd; color: #856404; }
button {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
margin: 5px;
border-radius: 4px;
cursor: pointer;
}
button:hover { background-color: #0056b3; }
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
#videoContainer {
margin-top: 20px;
text-align: center;
}
#rtspImage {
max-width: 100%;
height: auto;
border: 2px solid #ddd;
border-radius: 4px;
}
.log {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
margin-top: 20px;
height: 200px;
overflow-y: auto;
font-family: monospace;
font-size: 12px;
}
</style>
</head>
<body>
<div class="container">
<h1>前端WebSocket连接示例</h1>
<div id="status" class="status disconnected">未连接</div>
<div>
<button id="connectBtn" onclick="connectWebSocket()">连接WebSocket</button>
<button id="disconnectBtn" onclick="disconnectWebSocket()" disabled>断开连接</button>
<button id="startRtspBtn" onclick="startRtsp()" disabled>启动RTSP</button>
<button id="stopRtspBtn" onclick="stopRtsp()" disabled>停止RTSP</button>
</div>
<div style="margin-top: 10px;">
<button onclick="checkBackendStatus()">检查后端状态</button>
<button onclick="testSocketConnection()">测试Socket连接</button>
<button onclick="clearLog()">清空日志</button>
<button onclick="showDebugInfo()">显示调试信息</button>
</div>
<div id="debugInfo" style="display: none; margin-top: 10px; padding: 10px; background-color: #e9ecef; border-radius: 4px;">
<h4>调试信息</h4>
<div id="debugContent"></div>
</div>
<div id="videoContainer">
<h3>RTSP视频流</h3>
<img id="rtspImage" src="" alt="RTSP视频流" style="display: none;">
<div id="noVideo">暂无视频流</div>
</div>
<div class="log" id="logContainer"></div>
</div>
<script>
let socket = null;
let frameCount = 0;
// 后端服务器地址配置
const BACKEND_URL = 'http://localhost:5000'; // 根据实际情况修改
// 如果是远程服务器,使用: 'http://192.168.1.173:5000'
function log(message) {
const logContainer = document.getElementById('logContainer');
const timestamp = new Date().toLocaleTimeString();
logContainer.innerHTML += `[${timestamp}] ${message}<br>`;
logContainer.scrollTop = logContainer.scrollHeight;
}
function updateStatus(status, className) {
const statusEl = document.getElementById('status');
statusEl.textContent = status;
statusEl.className = `status ${className}`;
}
function updateButtons(connected, rtspRunning = false) {
document.getElementById('connectBtn').disabled = connected;
document.getElementById('disconnectBtn').disabled = !connected;
document.getElementById('startRtspBtn').disabled = !connected || rtspRunning;
document.getElementById('stopRtspBtn').disabled = !connected || !rtspRunning;
}
function connectWebSocket() {
try {
log('正在连接到 ' + BACKEND_URL);
// 创建Socket.IO连接
socket = io(BACKEND_URL, {
transports: ['websocket', 'polling'],
timeout: 10000,
forceNew: true
});
// 连接成功事件
socket.on('connect', () => {
log('✅ WebSocket连接成功Socket ID: ' + socket.id);
log('🔍 连接详情: ' + JSON.stringify({
connected: socket.connected,
id: socket.id,
transport: socket.io.engine.transport.name
}));
updateStatus('已连接', 'connected');
updateButtons(true);
});
// 连接失败事件
socket.on('connect_error', (error) => {
log('❌ 连接失败: ' + error.message);
log('🔍 错误详情: ' + JSON.stringify(error));
updateStatus('连接失败', 'error');
updateButtons(false);
});
// 断开连接事件
socket.on('disconnect', (reason) => {
log('⚠️ 连接断开: ' + reason);
updateStatus('已断开', 'disconnected');
updateButtons(false);
hideVideo();
});
// 监听所有事件(调试用)
socket.onAny((eventName, ...args) => {
log(`📨 收到事件: ${eventName}, 数据: ${JSON.stringify(args)}`);
});
// 监听RTSP状态事件
socket.on('rtsp_status', (data) => {
log('📺 RTSP状态: ' + JSON.stringify(data));
if (data.status === 'started') {
updateButtons(true, true);
} else if (data.status === 'stopped') {
updateButtons(true, false);
hideVideo();
}
});
// 监听RTSP帧数据
socket.on('rtsp_frame', (data) => {
if (data.image) {
frameCount++;
displayFrame(data.image);
if (frameCount % 30 === 0) { // 每30帧记录一次
log(`🎬 已接收 ${frameCount} 帧`);
}
} else {
log('⚠️ 收到rtsp_frame事件但无图像数据: ' + JSON.stringify(data));
}
});
// 监听错误事件
socket.on('error', (error) => {
log('❌ Socket错误: ' + JSON.stringify(error));
});
} catch (error) {
log('💥 连接异常: ' + error.message);
log('🔍 异常堆栈: ' + error.stack);
updateStatus('连接异常', 'error');
}
}
function disconnectWebSocket() {
if (socket) {
socket.disconnect();
socket = null;
log('主动断开连接');
updateStatus('已断开', 'disconnected');
updateButtons(false);
hideVideo();
}
}
function startRtsp() {
if (socket && socket.connected) {
log('🚀 发送start_rtsp事件');
log('🔍 Socket状态: ' + JSON.stringify({
connected: socket.connected,
id: socket.id,
transport: socket.io.engine.transport.name
}));
// 发送事件并监听确认
socket.emit('start_rtsp', {}, (ack) => {
if (ack) {
log('✅ start_rtsp事件已确认: ' + JSON.stringify(ack));
} else {
log('⚠️ start_rtsp事件无确认响应');
}
});
frameCount = 0;
// 设置超时检查
setTimeout(() => {
if (frameCount === 0) {
log('⏰ 5秒后仍未收到视频帧可能存在问题');
checkBackendStatus();
}
}, 5000);
} else {
log('❌ WebSocket未连接无法启动RTSP');
log('🔍 Socket状态: ' + (socket ? '存在但未连接' : '不存在'));
}
}
function stopRtsp() {
if (socket && socket.connected) {
log('🛑 发送stop_rtsp事件');
socket.emit('stop_rtsp', {}, (ack) => {
if (ack) {
log('✅ stop_rtsp事件已确认: ' + JSON.stringify(ack));
} else {
log('⚠️ stop_rtsp事件无确认响应');
}
});
} else {
log('❌ WebSocket未连接无法停止RTSP');
}
}
function displayFrame(base64Image) {
const img = document.getElementById('rtspImage');
const noVideo = document.getElementById('noVideo');
img.src = 'data:image/jpeg;base64,' + base64Image;
img.style.display = 'block';
noVideo.style.display = 'none';
}
function hideVideo() {
const img = document.getElementById('rtspImage');
const noVideo = document.getElementById('noVideo');
img.style.display = 'none';
img.src = '';
noVideo.style.display = 'block';
}
// 检查后端服务状态
function checkBackendStatus() {
log('🔍 检查后端服务状态...');
fetch(BACKEND_URL + '/health')
.then(response => {
if (response.ok) {
log('✅ 后端HTTP服务正常');
return response.text();
} else {
log('⚠️ 后端HTTP服务响应异常: ' + response.status);
}
})
.then(data => {
if (data) {
log('📄 后端响应: ' + data);
}
})
.catch(error => {
log('❌ 无法连接到后端服务: ' + error.message);
log('💡 请检查后端服务是否已启动,地址是否正确');
});
}
// 测试Socket.IO连接
function testSocketConnection() {
log('🧪 测试Socket.IO连接...');
const testSocket = io(BACKEND_URL + '/socket.io/', {
transports: ['polling'],
timeout: 5000
});
testSocket.on('connect', () => {
log('✅ Socket.IO测试连接成功');
testSocket.disconnect();
});
testSocket.on('connect_error', (error) => {
log('❌ Socket.IO测试连接失败: ' + error.message);
});
}
// 清空日志
function clearLog() {
document.getElementById('logContainer').innerHTML = '';
log('🧹 日志已清空');
}
// 显示调试信息
function showDebugInfo() {
const debugInfo = document.getElementById('debugInfo');
const debugContent = document.getElementById('debugContent');
let info = '<strong>当前状态:</strong><br>';
info += `Socket对象: ${socket ? '存在' : '不存在'}<br>`;
if (socket) {
info += `连接状态: ${socket.connected ? '已连接' : '未连接'}<br>`;
info += `Socket ID: ${socket.id || '无'}<br>`;
info += `传输方式: ${socket.io?.engine?.transport?.name || '未知'}<br>`;
info += `URL: ${socket.io?.uri || '未知'}<br>`;
}
info += `<br><strong>配置信息:</strong><br>`;
info += `后端地址: ${BACKEND_URL}<br>`;
info += `接收帧数: ${frameCount}<br>`;
info += `页面URL: ${window.location.href}<br>`;
info += `用户代理: ${navigator.userAgent}<br>`;
debugContent.innerHTML = info;
debugInfo.style.display = debugInfo.style.display === 'none' ? 'block' : 'none';
}
// 页面加载完成后的初始化
window.onload = function() {
log('📄 页面加载完成可以开始连接WebSocket');
log('🌐 后端地址: ' + BACKEND_URL);
log('⚠️ 请确保后端服务已启动');
// 自动检查后端状态
setTimeout(() => {
checkBackendStatus();
testSocketConnection();
}, 1000);
};
// 页面关闭时清理连接
window.onbeforeunload = function() {
if (socket) {
socket.disconnect();
}
};
</script>
</body>
</html>

View File

@ -0,0 +1,382 @@
/**
* 前端WebSocket连接示例 - JavaScript版本
* 使用Socket.IO客户端连接Flask-SocketIO后端
*/
// 引入Socket.IO客户端库
// 在HTML中添加: <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
// 或者使用npm安装: npm install socket.io-client
class WebSocketManager {
constructor() {
this.socket = null;
this.isConnected = false;
this.isRtspRunning = false;
this.frameCount = 0;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = 3000;
// 后端服务器配置
this.BACKEND_URL = 'http://localhost:5000'; // 根据实际情况修改
// 如果是远程服务器,使用: 'http://192.168.1.173:5000'
}
/**
* 连接WebSocket服务器
*/
connect() {
try {
console.log(`正在连接到 ${this.BACKEND_URL}`);
// 创建Socket.IO连接
this.socket = io(this.BACKEND_URL, {
transports: ['websocket', 'polling'], // 支持WebSocket和轮询
timeout: 10000, // 连接超时时间
forceNew: true, // 强制创建新连接
autoConnect: true // 自动连接
});
this.setupEventListeners();
} catch (error) {
console.error('连接异常:', error);
this.onError(error);
}
}
/**
* 设置事件监听器
*/
setupEventListeners() {
// 连接成功事件
this.socket.on('connect', () => {
this.isConnected = true;
this.reconnectAttempts = 0;
console.log(`WebSocket连接成功Socket ID: ${this.socket.id}`);
this.onConnect();
});
// 连接失败事件
this.socket.on('connect_error', (error) => {
this.isConnected = false;
console.error('连接失败:', error.message);
this.onConnectError(error);
this.attemptReconnect();
});
// 断开连接事件
this.socket.on('disconnect', (reason) => {
this.isConnected = false;
this.isRtspRunning = false;
console.log('连接断开:', reason);
this.onDisconnect(reason);
// 如果不是主动断开,尝试重连
if (reason !== 'io client disconnect') {
this.attemptReconnect();
}
});
// 监听RTSP状态事件
this.socket.on('rtsp_status', (data) => {
console.log('RTSP状态:', data);
this.handleRtspStatus(data);
});
// 监听RTSP帧数据
this.socket.on('rtsp_frame', (data) => {
if (data.image) {
this.frameCount++;
this.onRtspFrame(data.image);
// 每30帧记录一次
if (this.frameCount % 30 === 0) {
console.log(`已接收 ${this.frameCount}`);
}
}
});
}
/**
* 断开WebSocket连接
*/
disconnect() {
if (this.socket) {
this.socket.disconnect();
this.socket = null;
this.isConnected = false;
this.isRtspRunning = false;
console.log('主动断开连接');
}
}
/**
* 启动RTSP视频流
*/
startRtsp() {
if (this.socket && this.isConnected) {
console.log('发送start_rtsp事件');
this.socket.emit('start_rtsp');
this.frameCount = 0;
} else {
console.warn('WebSocket未连接无法启动RTSP');
}
}
/**
* 停止RTSP视频流
*/
stopRtsp() {
if (this.socket && this.isConnected) {
console.log('发送stop_rtsp事件');
this.socket.emit('stop_rtsp');
} else {
console.warn('WebSocket未连接无法停止RTSP');
}
}
/**
* 处理RTSP状态变化
*/
handleRtspStatus(data) {
switch (data.status) {
case 'started':
this.isRtspRunning = true;
this.onRtspStarted();
break;
case 'stopped':
this.isRtspRunning = false;
this.onRtspStopped();
break;
case 'already_running':
console.log('RTSP已在运行中');
this.isRtspRunning = true;
break;
case 'error':
console.error('RTSP错误:', data.message);
this.onRtspError(data.message);
break;
}
}
/**
* 尝试重连
*/
attemptReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
setTimeout(() => {
this.connect();
}, this.reconnectInterval);
} else {
console.error('WebSocket重连失败已达到最大重试次数');
this.onReconnectFailed();
}
}
// ========== 事件回调函数(可以被重写) ==========
/**
* 连接成功回调
*/
onConnect() {
// 可以在这里添加连接成功后的逻辑
console.log('WebSocket连接成功回调');
}
/**
* 连接错误回调
*/
onConnectError(error) {
// 可以在这里添加连接错误处理逻辑
console.log('WebSocket连接错误回调:', error);
}
/**
* 断开连接回调
*/
onDisconnect(reason) {
// 可以在这里添加断开连接处理逻辑
console.log('WebSocket断开连接回调:', reason);
}
/**
* 通用错误回调
*/
onError(error) {
// 可以在这里添加错误处理逻辑
console.log('WebSocket错误回调:', error);
}
/**
* RTSP启动回调
*/
onRtspStarted() {
console.log('RTSP视频流已启动');
}
/**
* RTSP停止回调
*/
onRtspStopped() {
console.log('RTSP视频流已停止');
}
/**
* RTSP错误回调
*/
onRtspError(message) {
console.error('RTSP错误:', message);
}
/**
* RTSP帧数据回调
* @param {string} base64Image - Base64编码的图像数据
*/
onRtspFrame(base64Image) {
// 在这里处理接收到的视频帧
// 例如显示在img元素中
const imgElement = document.getElementById('rtspImage');
if (imgElement) {
imgElement.src = 'data:image/jpeg;base64,' + base64Image;
}
}
/**
* 重连失败回调
*/
onReconnectFailed() {
console.error('WebSocket重连失败');
}
}
// ========== 使用示例 ==========
// 创建WebSocket管理器实例
const wsManager = new WebSocketManager();
// 重写回调函数(可选)
wsManager.onConnect = function() {
console.log('自定义连接成功处理');
// 连接成功后可以自动启动RTSP
// this.startRtsp();
};
wsManager.onRtspFrame = function(base64Image) {
// 自定义帧处理逻辑
const imgElement = document.getElementById('videoDisplay');
if (imgElement) {
imgElement.src = 'data:image/jpeg;base64,' + base64Image;
imgElement.style.display = 'block';
}
};
// ========== 具体使用方法 ==========
/**
* 1. 连接WebSocket
*/
function connectWebSocket() {
wsManager.connect();
}
/**
* 2. 断开WebSocket
*/
function disconnectWebSocket() {
wsManager.disconnect();
}
/**
* 3. 启动RTSP视频流
*/
function startRtspStream() {
wsManager.startRtsp();
}
/**
* 4. 停止RTSP视频流
*/
function stopRtspStream() {
wsManager.stopRtsp();
}
/**
* 5. 检查连接状态
*/
function checkConnectionStatus() {
console.log('连接状态:', wsManager.isConnected);
console.log('RTSP状态:', wsManager.isRtspRunning);
console.log('接收帧数:', wsManager.frameCount);
}
// ========== HTML页面中的使用示例 ==========
/*
<!DOCTYPE html>
<html>
<head>
<title>WebSocket RTSP示例</title>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
</head>
<body>
<div>
<button onclick="connectWebSocket()">连接</button>
<button onclick="disconnectWebSocket()">断开</button>
<button onclick="startRtspStream()">启动RTSP</button>
<button onclick="stopRtspStream()">停止RTSP</button>
<button onclick="checkConnectionStatus()">检查状态</button>
</div>
<div>
<h3>视频显示</h3>
<img id="videoDisplay" style="max-width: 100%; display: none;" />
</div>
<script src="javascript_websocket_example.js"></script>
</body>
</html>
*/
// ========== Node.js环境中的使用示例 ==========
/*
// 安装依赖: npm install socket.io-client
const { io } = require('socket.io-client');
// 然后使用上面的WebSocketManager类
const wsManager = new WebSocketManager();
wsManager.connect();
// 5秒后启动RTSP
setTimeout(() => {
wsManager.startRtsp();
}, 5000);
// 30秒后停止RTSP并断开连接
setTimeout(() => {
wsManager.stopRtsp();
setTimeout(() => {
wsManager.disconnect();
}, 2000);
}, 30000);
*/
// 导出类(用于模块化)
if (typeof module !== 'undefined' && module.exports) {
module.exports = WebSocketManager;
}
// 全局暴露(用于浏览器环境)
if (typeof window !== 'undefined') {
window.WebSocketManager = WebSocketManager;
window.wsManager = wsManager;
}
console.log('WebSocket管理器已加载可以使用 wsManager 实例或创建新的 WebSocketManager 实例');
console.log('使用方法:');
console.log('1. wsManager.connect() - 连接WebSocket');
console.log('2. wsManager.startRtsp() - 启动RTSP视频流');
console.log('3. wsManager.stopRtsp() - 停止RTSP视频流');
console.log('4. wsManager.disconnect() - 断开WebSocket连接');

100
start_debug.bat Normal file
View File

@ -0,0 +1,100 @@
@echo off
echo ====================================
echo Body Balance Evaluation System - Debug Mode
echo ====================================
echo.
:: Check Python
python --version >nul 2>&1
if %errorlevel% neq 0 (
echo [Error] Python not found, please install Python 3.8 or higher
pause
exit /b 1
)
:: Show version info
echo [Info] Checking environment versions...
for /f "tokens=*" %%i in ('python --version') do echo Python: %%i
echo.
:: Check virtual environment
if not exist "backend\venv" (
echo [Info] Creating Python virtual environment...
python -m venv backend\venv
if %errorlevel% neq 0 (
echo [Error] Failed to create virtual environment
pause
exit /b 1
)
)
:: Activate virtual environment
echo [Info] Activating virtual environment...
call backend\venv\Scripts\activate.bat
if %errorlevel% neq 0 (
echo [Error] Failed to activate virtual environment
pause
exit /b 1
)
:: Install Python dependencies
echo [Info] Checking and installing Python dependencies...
if not exist "backend\requirements.txt" (
echo [Error] requirements.txt file not found
pause
exit /b 1
)
pip install -r backend\requirements.txt
if %errorlevel% neq 0 (
echo [Error] Failed to install Python dependencies
pause
exit /b 1
)
:: Create necessary directories
echo [Info] Creating necessary directories...
if not exist "data" mkdir data
if not exist "data\patients" mkdir data\patients
if not exist "data\sessions" mkdir data\sessions
if not exist "data\exports" mkdir data\exports
if not exist "data\backups" mkdir data\backups
if not exist "logs" mkdir logs
if not exist "temp" mkdir temp
:: Start application in debug mode
echo.
echo ====================================
echo Starting Debug Mode
echo ====================================
echo [Info] Starting backend server in debug mode...
echo [Info] Backend address: http://127.0.0.1:5000
echo [Info] Debug mode enabled - you can set breakpoints in your IDE
echo [Info] Press Ctrl+C to stop service
echo.
echo [Debug Tips]:
echo 1. Open your IDE (VS Code, PyCharm, etc.)
echo 2. Set breakpoints in backend\app.py or other Python files
echo 3. Attach debugger to the running process if needed
echo 4. Use browser dev tools for frontend debugging
echo.
:: Set environment variables for debugging
set FLASK_ENV=development
set FLASK_DEBUG=1
set PYTHONPATH=%cd%
:: Start main program with debug flags
python -u backend\app.py
if %errorlevel% neq 0 (
echo.
echo [Error] Application startup failed
echo [Tip] Please check error messages and fix issues
pause
exit /b 1
)
echo.
echo [Info] Debug session ended
pause

53
test_websocket.py Normal file
View File

@ -0,0 +1,53 @@
import socketio
import time
# 创建SocketIO客户端
sio = socketio.SimpleClient()
print('连接WebSocket并监听事件...')
try:
# 连接到服务器
print('正在连接到192.168.1.173:5000...')
sio.connect('http://192.168.1.173:5000', wait_timeout=10)
print('WebSocket连接成功!')
# 发送启动RTSP事件
sio.emit('start_rtsp')
print('已发送start_rtsp事件等待5秒接收数据...')
# 等待并接收事件
for i in range(5):
try:
# 接收事件
event = sio.receive(timeout=1)
if event:
event_name, data = event
print(f'收到事件: {event_name}, 数据类型: {type(data)}')
if event_name == 'rtsp_frame' and isinstance(data, dict) and 'image' in data:
print(f'收到图像数据,长度: {len(data["image"])} 字符')
elif event_name == 'rtsp_status':
print(f'RTSP状态: {data}')
except socketio.exceptions.TimeoutError:
print(f'等待事件超时 ({i+1}/5)')
# 发送停止RTSP事件
sio.emit('stop_rtsp')
print('已发送stop_rtsp事件')
# 等待停止状态事件
try:
event = sio.receive(timeout=2)
if event:
event_name, data = event
print(f'收到停止事件: {event_name}, 数据: {data}')
except socketio.exceptions.TimeoutError:
print('等待停止事件超时')
sio.disconnect()
print('WebSocket连接已断开')
except Exception as e:
print(f'测试过程中发生错误: {e}')
print('WebSocket测试完成')

421
vue_websocket_example.vue Normal file
View File

@ -0,0 +1,421 @@
<template>
<div class="websocket-example">
<div class="header">
<h2>Vue WebSocket连接示例</h2>
<div class="status" :class="connectionStatus">
{{ statusText }}
</div>
</div>
<div class="controls">
<button
@click="connectWebSocket"
:disabled="isConnected"
class="btn btn-primary"
>
连接WebSocket
</button>
<button
@click="disconnectWebSocket"
:disabled="!isConnected"
class="btn btn-secondary"
>
断开连接
</button>
<button
@click="startRtsp"
:disabled="!isConnected || isRtspRunning"
class="btn btn-success"
>
启动RTSP
</button>
<button
@click="stopRtsp"
:disabled="!isConnected || !isRtspRunning"
class="btn btn-danger"
>
停止RTSP
</button>
</div>
<div class="video-container">
<h3>RTSP视频流</h3>
<div v-if="rtspImageSrc" class="video-wrapper">
<img :src="rtspImageSrc" alt="RTSP视频流" class="rtsp-image" />
<div class="frame-info">已接收帧数: {{ frameCount }}</div>
</div>
<div v-else class="no-video">
暂无视频流
</div>
</div>
<div class="log-container">
<h3>连接日志</h3>
<div class="log-content" ref="logContainer">
<div v-for="(log, index) in logs" :key="index" class="log-item">
<span class="log-time">[{{ log.time }}]</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { io } from 'socket.io-client'
//
const socket = ref(null)
const isConnected = ref(false)
const isRtspRunning = ref(false)
const rtspImageSrc = ref('')
const frameCount = ref(0)
const logs = ref([])
const logContainer = ref(null)
//
const BACKEND_URL = 'http://localhost:5000' //
// 使: 'http://192.168.1.173:5000'
//
const connectionStatus = computed(() => {
if (isConnected.value) return 'connected'
return 'disconnected'
})
const statusText = computed(() => {
if (isConnected.value) {
return isRtspRunning.value ? '已连接 - RTSP运行中' : '已连接'
}
return '未连接'
})
//
const addLog = (message) => {
const now = new Date()
const time = now.toLocaleTimeString()
logs.value.push({ time, message })
//
if (logs.value.length > 100) {
logs.value.shift()
}
//
nextTick(() => {
if (logContainer.value) {
logContainer.value.scrollTop = logContainer.value.scrollHeight
}
})
}
// WebSocket
const connectWebSocket = () => {
try {
addLog(`正在连接到 ${BACKEND_URL}`)
// Socket.IO
socket.value = io(BACKEND_URL, {
transports: ['websocket', 'polling'],
timeout: 10000,
forceNew: true
})
//
socket.value.on('connect', () => {
isConnected.value = true
addLog(`WebSocket连接成功Socket ID: ${socket.value.id}`)
})
//
socket.value.on('connect_error', (error) => {
isConnected.value = false
addLog(`连接失败: ${error.message}`)
})
//
socket.value.on('disconnect', (reason) => {
isConnected.value = false
isRtspRunning.value = false
rtspImageSrc.value = ''
addLog(`连接断开: ${reason}`)
})
// RTSP
socket.value.on('rtsp_status', (data) => {
addLog(`RTSP状态: ${JSON.stringify(data)}`)
if (data.status === 'started') {
isRtspRunning.value = true
frameCount.value = 0
} else if (data.status === 'stopped') {
isRtspRunning.value = false
rtspImageSrc.value = ''
} else if (data.status === 'already_running') {
addLog('RTSP已在运行中')
}
})
// RTSP
socket.value.on('rtsp_frame', (data) => {
if (data.image) {
frameCount.value++
rtspImageSrc.value = 'data:image/jpeg;base64,' + data.image
// 30
if (frameCount.value % 30 === 0) {
addLog(`已接收 ${frameCount.value}`)
}
}
})
} catch (error) {
addLog(`连接异常: ${error.message}`)
}
}
// WebSocket
const disconnectWebSocket = () => {
if (socket.value) {
socket.value.disconnect()
socket.value = null
isConnected.value = false
isRtspRunning.value = false
rtspImageSrc.value = ''
addLog('主动断开连接')
}
}
// RTSP
const startRtsp = () => {
if (socket.value && isConnected.value) {
addLog('发送start_rtsp事件')
socket.value.emit('start_rtsp')
} else {
addLog('WebSocket未连接无法启动RTSP')
}
}
// RTSP
const stopRtsp = () => {
if (socket.value && isConnected.value) {
addLog('发送stop_rtsp事件')
socket.value.emit('stop_rtsp')
} else {
addLog('WebSocket未连接无法停止RTSP')
}
}
//
onMounted(() => {
addLog('组件已挂载可以开始连接WebSocket')
addLog(`后端地址: ${BACKEND_URL}`)
addLog('请确保后端服务已启动')
})
onUnmounted(() => {
//
if (socket.value) {
socket.value.disconnect()
}
})
</script>
<style scoped>
.websocket-example {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #e9ecef;
}
.header h2 {
margin: 0;
color: #333;
}
.status {
padding: 8px 16px;
border-radius: 20px;
font-weight: bold;
font-size: 14px;
}
.status.connected {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.disconnected {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover:not(:disabled) {
background-color: #545b62;
}
.btn-success {
background-color: #28a745;
color: white;
}
.btn-success:hover:not(:disabled) {
background-color: #1e7e34;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover:not(:disabled) {
background-color: #c82333;
}
.video-container {
margin-bottom: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
}
.video-container h3 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
}
.video-wrapper {
text-align: center;
position: relative;
}
.rtsp-image {
max-width: 100%;
height: auto;
border: 2px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.frame-info {
margin-top: 10px;
font-size: 14px;
color: #666;
font-weight: 500;
}
.no-video {
text-align: center;
padding: 40px;
color: #6c757d;
font-style: italic;
background-color: #e9ecef;
border-radius: 8px;
}
.log-container {
margin-top: 20px;
}
.log-container h3 {
margin-bottom: 10px;
color: #333;
}
.log-content {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 15px;
height: 200px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
.log-item {
margin-bottom: 5px;
word-wrap: break-word;
}
.log-time {
color: #6c757d;
font-weight: bold;
}
.log-message {
margin-left: 8px;
color: #333;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.controls {
flex-direction: column;
}
.btn {
width: 100%;
}
}
</style>