提交修改
This commit is contained in:
parent
bfdba3e91f
commit
7d9f44d124
243
DEBUG_GUIDE.md
Normal file
243
DEBUG_GUIDE.md
Normal 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. 参考项目文档和代码注释
|
133
backend/app.py
133
backend/app.py
@ -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
138
debug_server.py
Normal 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()
|
@ -711,9 +711,6 @@ onUnmounted(() => {
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
}
|
||||
.gauge-group-box-left{
|
||||
|
||||
}
|
||||
.gauge-table-title{
|
||||
padding: 10px;
|
||||
|
383
frontend_websocket_example.html
Normal file
383
frontend_websocket_example.html
Normal 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>
|
382
javascript_websocket_example.js
Normal file
382
javascript_websocket_example.js
Normal 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
100
start_debug.bat
Normal 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
53
test_websocket.py
Normal 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
421
vue_websocket_example.vue
Normal 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>
|
Loading…
Reference in New Issue
Block a user