This commit is contained in:
zhaozilong12 2025-07-29 16:44:17 +08:00
parent 167524b758
commit dd695cfcca
4 changed files with 107 additions and 24 deletions

View File

@ -16,6 +16,10 @@ from flask_cors import CORS
import sqlite3 import sqlite3
import logging import logging
from pathlib import Path from pathlib import Path
from flask_socketio import SocketIO, emit
import cv2
import base64
import configparser
# 添加当前目录到Python路径 # 添加当前目录到Python路径
sys.path.append(os.path.dirname(os.path.abspath(__file__))) sys.path.append(os.path.dirname(os.path.abspath(__file__)))
@ -40,10 +44,16 @@ logger = logging.getLogger(__name__)
# 创建Flask应用 # 创建Flask应用
app = Flask(__name__) app = Flask(__name__)
app.config['SECRET_KEY'] = 'body-balance-detection-system-2024' app.config['SECRET_KEY'] = 'body-balance-detection-system-2024'
socketio = SocketIO(app, cors_allowed_origins='*', async_mode='threading')
# 启用CORS支持 # 启用CORS支持
CORS(app, origins=['http://localhost:3000', 'http://localhost:3001', 'file://*']) CORS(app, origins=['http://localhost:3000', 'http://localhost:3001', 'file://*'])
# 读取RTSP配置
config = configparser.ConfigParser()
config.read(os.path.join(os.path.dirname(__file__), 'config.ini'), encoding='utf-8')
rtsp_url = config.get('CAMERA', 'rtsp_url', fallback=None)
# 全局变量 # 全局变量
db_manager = None db_manager = None
device_manager = None device_manager = None
@ -51,6 +61,8 @@ detection_engine = None
data_processor = None data_processor = None
current_detection = None current_detection = None
detection_thread = None detection_thread = None
rtsp_thread = None
rtsp_running = False
def init_app(): def init_app():
"""初始化应用""" """初始化应用"""
@ -612,19 +624,57 @@ if __name__ == '__main__':
# 初始化应用 # 初始化应用
init_app() init_app()
# 启动Flask服务 # 启动Flask+SocketIO服务
logger.info('启动后端服务...') logger.info('启动后端服务...')
app.run( socketio.run(app,
host='127.0.0.1', host=config.get('SERVER', 'host', fallback='127.0.0.1'),
port=5000, port=config.getint('SERVER', 'port', fallback=5000),
debug=False, debug=config.getboolean('APP', 'debug', fallback=False),
threaded=True allow_unsafe_werkzeug=True
) )
except KeyboardInterrupt: except KeyboardInterrupt:
logger.info('服务被用户中断') logger.info('服务被用户中断')
except Exception as e: except Exception as e:
logger.error(f'服务启动失败: {e}') logger.error(f'服务启动失败: {e}')
sys.exit(1) sys.exit(1)
finally: finally:
logger.info('后端服务已停止') logger.info('后端服务已停止')
# ==================== WebSocket 实时推送RTSP帧 ====================
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读取帧失败尝试重连...')
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()
@socketio.on('start_rtsp')
def handle_start_rtsp():
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'})
@socketio.on('stop_rtsp')
def handle_stop_rtsp():
global rtsp_running
rtsp_running = False
emit('rtsp_status', {'status': 'stopped'})

View File

@ -39,3 +39,7 @@ secret_key = 026efbf83a2fe101f168780740da86bf1c9260625458e6782738aa9cf18f8e37
session_timeout = 3600 session_timeout = 3600
max_login_attempts = 5 max_login_attempts = 5
[CAMERA]
rtsp_url = rtsp://admin:password@192.168.1.100:554/Streaming/Channels/101

View File

@ -265,7 +265,7 @@ const formatDate = (date) => {
const loadPatients = async () => { const loadPatients = async () => {
try { try {
const response = await patientAPI.getList() const response = await patientAPI.getPatients()
if (response.success) { if (response.success) {
patients.value = response.data patients.value = response.data
} }

View File

@ -142,13 +142,13 @@
<el-table-column prop="rotLeft" label="左" min-width="60" align="center"/> <el-table-column prop="rotLeft" label="左" min-width="60" align="center"/>
<el-table-column prop="rotRight" label="右" min-width="60" align="center"/> <el-table-column prop="rotRight" label="右" min-width="60" align="center"/>
</el-table-column> </el-table-column>
<el-table-column label="最大旋转角" align="center"> <el-table-column label="最大倾斜角" align="center">
<el-table-column prop="rotLeft" label="左" min-width="60" align="center"/> <el-table-column prop="tiltLeft" label="左" min-width="60" align="center"/>
<el-table-column prop="rotRight" label="右" min-width="60" align="center"/> <el-table-column prop="tiltRight" label="右" min-width="60" align="center"/>
</el-table-column> </el-table-column>
<el-table-column label="最大旋转角" align="center"> <el-table-column label="最大仰视角" align="center">
<el-table-column prop="rotLeft" label="左" min-width="60" align="center"/> <el-table-column prop="pitchDown" label="下" min-width="60" align="center"/>
<el-table-column prop="rotRight" label="右" min-width="60" align="center"/> <el-table-column prop="pitchUp" label="上" min-width="60" align="center"/>
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
@ -289,7 +289,7 @@
<div class="module-title">视频</div> <div class="module-title">视频</div>
</div> </div>
</div> </div>
<video src="@/assets/1.mp4" autoplay style="width: 100%;height: 268px;"></video> <img :src="rtspImgSrc" alt="RTSP视频流" style="width: 100%;height: 268px;object-fit:contain;background:#000;" />
</div> </div>
</div> </div>
</div> </div>
@ -298,15 +298,9 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, onMounted, onUnmounted } from 'vue'
import {
ArrowLeft,
UserFilled,
Clock,
Grid,
Edit
} from '@element-plus/icons-vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { wsManager } from '@/services/api'
const router = useRouter() const router = useRouter()
const isStart = ref(false) const isStart = ref(false)
@ -325,6 +319,41 @@ const handleBack = () => {
// //
console.log('返回上一页') console.log('返回上一页')
} }
const rtspImgSrc = ref('')
let ws = null
onMounted(() => {
// 使Socket.IO
if (window.electronAPI) {
window.electronAPI.getBackendUrl().then(url => {
// 使wsManager
wsManager.connect(url)
//
wsManager.on('connect', () => {
console.log('WebSocket连接成功')
wsManager.emit('start_rtsp', {})
})
// RTSP
wsManager.on('rtsp_frame', (data) => {
if (data.image) {
rtspImgSrc.value = 'data:image/jpeg;base64,' + data.image
}
})
//
wsManager.on('connect_error', (error) => {
console.error('WebSocket连接失败:', error)
})
})
}
})
onUnmounted(() => {
// RTSP
wsManager.emit('stop_rtsp', {})
wsManager.disconnect()
})
</script> </script>
<style scoped> <style scoped>