|
|
|
@ -9,6 +9,8 @@ from datetime import datetime
|
|
|
|
|
from sqlalchemy.exc import OperationalError
|
|
|
|
|
import logging
|
|
|
|
|
from functools import wraps
|
|
|
|
|
import voice_udp_server # 导入UDP语音服务器模块
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@ -66,12 +68,22 @@ socketio = SocketIO(app,
|
|
|
|
|
ping_interval=25,
|
|
|
|
|
always_connect=True)
|
|
|
|
|
|
|
|
|
|
# 跟踪所有活跃游戏会话
|
|
|
|
|
active_games = {} # 用户ID -> 游戏状态
|
|
|
|
|
sid_to_user = {} # Socket ID -> 用户ID映射
|
|
|
|
|
# 存储用户令牌
|
|
|
|
|
user_tokens = {}
|
|
|
|
|
|
|
|
|
|
# 添加一个简单的令牌存储
|
|
|
|
|
user_tokens = {} # token -> user_id 映射
|
|
|
|
|
# 存储活跃游戏状态
|
|
|
|
|
active_games = {}
|
|
|
|
|
|
|
|
|
|
# 存储在线用户和他们的sid(Socket ID)映射
|
|
|
|
|
online_users = {}
|
|
|
|
|
sid_to_user = {} # sid -> user_id映射,保留此变量以兼容现有代码
|
|
|
|
|
|
|
|
|
|
# 启动UDP语音服务器
|
|
|
|
|
voice_server_started = voice_udp_server.start_udp_server()
|
|
|
|
|
if voice_server_started:
|
|
|
|
|
logger.info("UDP语音服务器已成功启动")
|
|
|
|
|
else:
|
|
|
|
|
logger.error("UDP语音服务器启动失败")
|
|
|
|
|
|
|
|
|
|
# 用户模型
|
|
|
|
|
class User(db.Model):
|
|
|
|
@ -219,49 +231,58 @@ def register():
|
|
|
|
|
# 登录API
|
|
|
|
|
@app.route('/api/login', methods=['POST'])
|
|
|
|
|
def login():
|
|
|
|
|
data = request.get_json()
|
|
|
|
|
logger.info(f"收到登录请求: {data.get('username') if data else 'No data'}")
|
|
|
|
|
|
|
|
|
|
# 检查必要的字段
|
|
|
|
|
if not data or not data.get('username') or not data.get('password'):
|
|
|
|
|
logger.warning("登录请求缺少用户名或密码")
|
|
|
|
|
return jsonify({'error': '用户名和密码是必填项'}), 400
|
|
|
|
|
|
|
|
|
|
# 查找用户
|
|
|
|
|
user = User.query.filter_by(username=data['username']).first()
|
|
|
|
|
|
|
|
|
|
# 检查密码
|
|
|
|
|
if user and user.check_password(data['password']):
|
|
|
|
|
# 生成会话ID和令牌
|
|
|
|
|
session_id = str(uuid.uuid4())
|
|
|
|
|
token = str(uuid.uuid4())
|
|
|
|
|
try:
|
|
|
|
|
data = request.get_json()
|
|
|
|
|
logger.info(f"收到登录请求: {data.get('username') if data else 'No data'}")
|
|
|
|
|
|
|
|
|
|
# 存储到会话和令牌映射
|
|
|
|
|
session['user_id'] = user.id
|
|
|
|
|
session['session_id'] = session_id
|
|
|
|
|
user_tokens[token] = user.id
|
|
|
|
|
# 检查请求数据
|
|
|
|
|
if request.content_type != 'application/json':
|
|
|
|
|
logger.warning(f"登录请求Content-Type不正确: {request.content_type}")
|
|
|
|
|
return jsonify({'error': '请求格式必须为JSON'}), 400
|
|
|
|
|
|
|
|
|
|
# 更新最后登录时间
|
|
|
|
|
user.last_login = datetime.utcnow()
|
|
|
|
|
db.session.commit()
|
|
|
|
|
# 检查必要的字段
|
|
|
|
|
if not data or not data.get('username') or not data.get('password'):
|
|
|
|
|
logger.warning("登录请求缺少用户名或密码")
|
|
|
|
|
return jsonify({'error': '用户名和密码是必填项'}), 400
|
|
|
|
|
|
|
|
|
|
logger.info(f"用户 {user.username} (ID: {user.id}) 登录成功,设置会话ID: {session_id} 和令牌: {token[:8]}...")
|
|
|
|
|
logger.info(f"当前会话内容: {dict(session)}")
|
|
|
|
|
# 查找用户
|
|
|
|
|
user = User.query.filter_by(username=data['username']).first()
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'message': '登录成功',
|
|
|
|
|
'user': {
|
|
|
|
|
'id': user.id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'high_score': user.high_score,
|
|
|
|
|
'is_admin': user.is_admin
|
|
|
|
|
},
|
|
|
|
|
'session_id': session_id,
|
|
|
|
|
'token': token
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
logger.warning(f"用户名或密码不正确: {data.get('username')}")
|
|
|
|
|
return jsonify({'error': '用户名或密码不正确'}), 401
|
|
|
|
|
# 检查密码
|
|
|
|
|
if user and user.check_password(data['password']):
|
|
|
|
|
# 生成会话ID和令牌
|
|
|
|
|
session_id = str(uuid.uuid4())
|
|
|
|
|
token = str(uuid.uuid4())
|
|
|
|
|
|
|
|
|
|
# 存储到会话和令牌映射
|
|
|
|
|
session['user_id'] = user.id
|
|
|
|
|
session['session_id'] = session_id
|
|
|
|
|
user_tokens[token] = user.id
|
|
|
|
|
|
|
|
|
|
# 更新最后登录时间
|
|
|
|
|
user.last_login = datetime.utcnow()
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
|
|
logger.info(f"用户 {user.username} (ID: {user.id}) 登录成功,设置会话ID: {session_id} 和令牌: {token[:8]}...")
|
|
|
|
|
logger.info(f"当前会话内容: {dict(session)}")
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'message': '登录成功',
|
|
|
|
|
'user': {
|
|
|
|
|
'id': user.id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'high_score': user.high_score,
|
|
|
|
|
'is_admin': user.is_admin
|
|
|
|
|
},
|
|
|
|
|
'session_id': session_id,
|
|
|
|
|
'token': token
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
logger.warning(f"用户名或密码不正确: {data.get('username')}")
|
|
|
|
|
return jsonify({'error': '用户名或密码不正确'}), 401
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"登录处理时发生错误: {str(e)}")
|
|
|
|
|
return jsonify({'error': '服务器处理请求时出错'}), 500
|
|
|
|
|
|
|
|
|
|
# 登出API
|
|
|
|
|
@app.route('/api/logout', methods=['POST'])
|
|
|
|
@ -517,6 +538,12 @@ def handle_connect():
|
|
|
|
|
|
|
|
|
|
# 记录用户的Socket ID
|
|
|
|
|
sid_to_user[request.sid] = user_id
|
|
|
|
|
|
|
|
|
|
# 同时更新online_users映射
|
|
|
|
|
if user_id not in online_users:
|
|
|
|
|
online_users[user_id] = []
|
|
|
|
|
online_users[user_id].append(request.sid)
|
|
|
|
|
|
|
|
|
|
print(f'Client connected: {request.sid}, User ID: {user_id}')
|
|
|
|
|
|
|
|
|
|
# 发送当前的排行榜和活跃游戏状态
|
|
|
|
@ -533,18 +560,19 @@ def handle_disconnect():
|
|
|
|
|
if request.sid in sid_to_user:
|
|
|
|
|
user_id = sid_to_user.pop(request.sid)
|
|
|
|
|
|
|
|
|
|
# 更新online_users映射
|
|
|
|
|
if user_id in online_users:
|
|
|
|
|
if request.sid in online_users[user_id]:
|
|
|
|
|
online_users[user_id].remove(request.sid)
|
|
|
|
|
# 如果用户没有其他连接,从映射中删除
|
|
|
|
|
if not online_users[user_id]:
|
|
|
|
|
del online_users[user_id]
|
|
|
|
|
|
|
|
|
|
# 如果该用户没有其他活跃连接,从游戏状态中移除
|
|
|
|
|
if user_id in active_games:
|
|
|
|
|
user_still_active = False
|
|
|
|
|
for sid, uid in sid_to_user.items():
|
|
|
|
|
if uid == user_id:
|
|
|
|
|
user_still_active = True
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if not user_still_active:
|
|
|
|
|
active_games.pop(user_id, None)
|
|
|
|
|
# 广播活跃游戏状态更新
|
|
|
|
|
send_active_games_update()
|
|
|
|
|
if user_id in active_games and user_id not in online_users:
|
|
|
|
|
active_games.pop(user_id, None)
|
|
|
|
|
# 广播活跃游戏状态更新
|
|
|
|
|
send_active_games_update()
|
|
|
|
|
|
|
|
|
|
@socketio.on('join_room')
|
|
|
|
|
def handle_join_room(data):
|
|
|
|
@ -649,37 +677,52 @@ def handle_send_audio_message(data):
|
|
|
|
|
audio_duration = data.get('audio_duration', 0) # 音频持续时间(秒)
|
|
|
|
|
|
|
|
|
|
if not room_id or not audio_data:
|
|
|
|
|
logger.error("语音消息缺少必要参数: room_id或audio_data")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 获取用户和房间信息
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
room = ChatRoom.query.get(room_id)
|
|
|
|
|
|
|
|
|
|
if not user or not room:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 保存消息到数据库
|
|
|
|
|
message = ChatMessage(
|
|
|
|
|
room_id=room_id,
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
message=audio_data, # 存储Base64编码的音频数据
|
|
|
|
|
message_type='audio',
|
|
|
|
|
audio_duration=audio_duration
|
|
|
|
|
)
|
|
|
|
|
# 检查audio_data是否为有效的base64编码数据
|
|
|
|
|
if not audio_data.startswith('data:audio'):
|
|
|
|
|
logger.warning(f"接收到非标准格式的音频数据,前缀: {audio_data[:20]}")
|
|
|
|
|
|
|
|
|
|
db.session.add(message)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
logger.info(f"处理语音消息: 用户ID={user_id}, 房间ID={room_id}, 音频时长={audio_duration}秒, 数据长度={len(audio_data)}")
|
|
|
|
|
|
|
|
|
|
# 广播消息给房间内的所有用户
|
|
|
|
|
emit('new_message', {
|
|
|
|
|
'id': message.id,
|
|
|
|
|
'user_id': user_id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'message': audio_data,
|
|
|
|
|
'message_type': 'audio',
|
|
|
|
|
'audio_duration': audio_duration,
|
|
|
|
|
'created_at': message.created_at.isoformat()
|
|
|
|
|
}, room=str(room_id))
|
|
|
|
|
try:
|
|
|
|
|
# 获取用户和房间信息
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
room = ChatRoom.query.get(room_id)
|
|
|
|
|
|
|
|
|
|
if not user or not room:
|
|
|
|
|
logger.error(f"用户或房间不存在: user_id={user_id}, room_id={room_id}")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 保存消息到数据库
|
|
|
|
|
message = ChatMessage(
|
|
|
|
|
room_id=room_id,
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
message=audio_data, # 存储Base64编码的音频数据
|
|
|
|
|
message_type='audio',
|
|
|
|
|
audio_duration=audio_duration
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
db.session.add(message)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
logger.info(f"语音消息已保存到数据库: ID={message.id}")
|
|
|
|
|
|
|
|
|
|
# 广播消息给房间内的所有用户
|
|
|
|
|
emit('new_message', {
|
|
|
|
|
'id': message.id,
|
|
|
|
|
'user_id': user_id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'message': audio_data,
|
|
|
|
|
'message_type': 'audio',
|
|
|
|
|
'audio_duration': audio_duration,
|
|
|
|
|
'created_at': message.created_at.isoformat()
|
|
|
|
|
}, room=str(room_id))
|
|
|
|
|
logger.info(f"语音消息已广播到房间: {room_id}")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"处理语音消息时出错: {e}")
|
|
|
|
|
db.session.rollback()
|
|
|
|
|
|
|
|
|
|
# 更新游戏状态处理
|
|
|
|
|
@socketio.on('update_game_status')
|
|
|
|
@ -1023,6 +1066,282 @@ def test_admin_status():
|
|
|
|
|
def handle_options(path):
|
|
|
|
|
return '', 200
|
|
|
|
|
|
|
|
|
|
# 添加UDP语音服务器状态API
|
|
|
|
|
@app.route('/api/voice/status', methods=['GET'])
|
|
|
|
|
def get_voice_status():
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if not user_id:
|
|
|
|
|
return jsonify({'error': '未登录'}), 401
|
|
|
|
|
|
|
|
|
|
status = voice_udp_server.get_voice_room_status()
|
|
|
|
|
return jsonify({
|
|
|
|
|
'status': 'online' if voice_server_started else 'offline',
|
|
|
|
|
'rooms': status,
|
|
|
|
|
'port': voice_udp_server.UDP_PORT
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# 添加UDP语音数据包发送API
|
|
|
|
|
@app.route('/api/voice/send', methods=['POST'])
|
|
|
|
|
def send_voice_packet():
|
|
|
|
|
"""模拟将数据包通过UDP发送"""
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if not user_id:
|
|
|
|
|
return jsonify({'error': '未登录'}), 401
|
|
|
|
|
|
|
|
|
|
if not voice_server_started:
|
|
|
|
|
return jsonify({'error': 'UDP语音服务器未启动'}), 503
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
data = request.get_json()
|
|
|
|
|
if not data:
|
|
|
|
|
return jsonify({'error': '无效的请求数据'}), 400
|
|
|
|
|
|
|
|
|
|
# 从请求中获取必要信息
|
|
|
|
|
client_id = data.get('client_id')
|
|
|
|
|
room_id = data.get('room_id')
|
|
|
|
|
packet_type = data.get('type')
|
|
|
|
|
|
|
|
|
|
# 验证用户身份
|
|
|
|
|
if str(user_id) != str(client_id):
|
|
|
|
|
logger.warning(f"用户ID不匹配: {user_id} vs {client_id}")
|
|
|
|
|
return jsonify({'error': '用户身份验证失败'}), 403
|
|
|
|
|
|
|
|
|
|
# 如果是语音数据,直接转发
|
|
|
|
|
if packet_type == 'voice':
|
|
|
|
|
voice_data = data.get('data')
|
|
|
|
|
if voice_data and room_id:
|
|
|
|
|
# 转发到UDP服务器的相应函数
|
|
|
|
|
voice_udp_server.forward_voice_data(room_id, client_id, voice_data)
|
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
|
|
|
|
# 加入/离开房间
|
|
|
|
|
elif packet_type in ['join', 'leave']:
|
|
|
|
|
if packet_type == 'join':
|
|
|
|
|
voice_udp_server.join_voice_room(client_id, room_id)
|
|
|
|
|
else:
|
|
|
|
|
voice_udp_server.leave_voice_room(client_id, room_id)
|
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
|
|
|
|
# 心跳包
|
|
|
|
|
elif packet_type == 'heartbeat':
|
|
|
|
|
# 更新客户端最后活跃时间
|
|
|
|
|
voice_udp_server.CLIENT_LAST_SEEN[client_id] = time.time()
|
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
|
|
|
|
return jsonify({'error': '不支持的数据包类型'}), 400
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"处理语音数据包时出错: {str(e)}")
|
|
|
|
|
return jsonify({'error': '服务器处理请求时出错'}), 500
|
|
|
|
|
|
|
|
|
|
# 添加UDP语音事件通知函数
|
|
|
|
|
def notify_voice_event(event_type, data):
|
|
|
|
|
"""通过WebSocket通知客户端UDP语音相关事件"""
|
|
|
|
|
event_data = {
|
|
|
|
|
'type': event_type,
|
|
|
|
|
**data
|
|
|
|
|
}
|
|
|
|
|
socketio.emit('voice_event', event_data, room=f"voice_room_{data.get('room_id')}")
|
|
|
|
|
|
|
|
|
|
# 添加UDP语音WebSocket事件处理
|
|
|
|
|
@socketio.on('join_voice_room')
|
|
|
|
|
def handle_join_voice_room(data):
|
|
|
|
|
"""用户加入语音房间"""
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if not user_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room_id = data.get('room_id')
|
|
|
|
|
if not room_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 加入WebSocket房间(用于通知事件)
|
|
|
|
|
join_room(f"voice_room_{room_id}")
|
|
|
|
|
|
|
|
|
|
# 获取用户信息
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 通知其他用户
|
|
|
|
|
socketio.emit('voice_event', {
|
|
|
|
|
'type': 'user_joined_voice',
|
|
|
|
|
'user_id': user_id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'room_id': room_id
|
|
|
|
|
}, room=f"voice_room_{room_id}", skip_sid=request.sid)
|
|
|
|
|
|
|
|
|
|
logger.info(f"用户 {user.username} (ID: {user_id}) 加入语音房间 {room_id}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('leave_voice_room')
|
|
|
|
|
def handle_leave_voice_room(data):
|
|
|
|
|
"""用户离开语音房间"""
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if not user_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room_id = data.get('room_id')
|
|
|
|
|
if not room_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 离开WebSocket房间
|
|
|
|
|
leave_room(f"voice_room_{room_id}")
|
|
|
|
|
|
|
|
|
|
# 获取用户信息
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 通知其他用户
|
|
|
|
|
socketio.emit('voice_event', {
|
|
|
|
|
'type': 'user_left_voice',
|
|
|
|
|
'user_id': user_id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'room_id': room_id
|
|
|
|
|
}, room=f"voice_room_{room_id}")
|
|
|
|
|
|
|
|
|
|
logger.info(f"用户 {user.username} (ID: {user_id}) 离开语音房间 {room_id}")
|
|
|
|
|
|
|
|
|
|
# 添加WebRTC信令处理
|
|
|
|
|
@socketio.on('webrtc_signal')
|
|
|
|
|
def handle_webrtc_signal(data):
|
|
|
|
|
"""处理WebRTC信令"""
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if not user_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room_id = data.get('room_id')
|
|
|
|
|
target_id = data.get('target_id')
|
|
|
|
|
signal_type = data.get('signal_type')
|
|
|
|
|
signal_data = data.get('signal_data')
|
|
|
|
|
|
|
|
|
|
if not room_id or not signal_type:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 获取用户信息
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 创建要发送的信令数据
|
|
|
|
|
signal_message = {
|
|
|
|
|
'sender_id': user_id,
|
|
|
|
|
'sender_name': user.username,
|
|
|
|
|
'room_id': room_id,
|
|
|
|
|
'signal_type': signal_type,
|
|
|
|
|
'signal_data': signal_data
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 如果指定了目标用户,只发送给目标用户
|
|
|
|
|
if target_id:
|
|
|
|
|
# 查找目标用户的会话ID列表
|
|
|
|
|
target_sids = online_users.get(int(target_id), [])
|
|
|
|
|
|
|
|
|
|
if target_sids:
|
|
|
|
|
# 向目标用户的所有会话发送信令
|
|
|
|
|
for target_sid in target_sids:
|
|
|
|
|
emit('webrtc_signal', signal_message, room=target_sid)
|
|
|
|
|
else:
|
|
|
|
|
# 如果找不到目标用户的会话ID,则发送到整个房间
|
|
|
|
|
emit('webrtc_signal', signal_message, room=f"voice_room_{room_id}", skip_sid=request.sid)
|
|
|
|
|
else:
|
|
|
|
|
# 如果没有指定目标用户,则发送到整个房间
|
|
|
|
|
emit('webrtc_signal', signal_message, room=f"voice_room_{room_id}", skip_sid=request.sid)
|
|
|
|
|
|
|
|
|
|
logger.info(f"WebRTC信令: {user.username} (ID: {user_id}) 发送 {signal_type} 到房间 {room_id}" +
|
|
|
|
|
(f", 目标用户: {target_id}" if target_id else ""))
|
|
|
|
|
|
|
|
|
|
# WebRTC语音聊天相关处理
|
|
|
|
|
@socketio.on('join_voice_chat')
|
|
|
|
|
def handle_join_voice_chat(data):
|
|
|
|
|
"""用户加入语音聊天"""
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if not user_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room_id = data.get('room_id')
|
|
|
|
|
if not room_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 获取用户信息
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 加入WebSocket房间(用于通知语音相关事件)
|
|
|
|
|
voice_room = f"voice_room_{room_id}"
|
|
|
|
|
join_room(voice_room)
|
|
|
|
|
|
|
|
|
|
# 通知房间内其他用户
|
|
|
|
|
emit('voice_chat_event', {
|
|
|
|
|
'type': 'user_joined',
|
|
|
|
|
'user_id': user_id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'room_id': room_id
|
|
|
|
|
}, room=voice_room, skip_sid=request.sid)
|
|
|
|
|
|
|
|
|
|
logger.info(f"用户 {user.username} (ID: {user_id}) 加入语音聊天 {room_id}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('leave_voice_chat')
|
|
|
|
|
def handle_leave_voice_chat(data):
|
|
|
|
|
"""用户离开语音聊天"""
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if not user_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room_id = data.get('room_id')
|
|
|
|
|
if not room_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 获取用户信息
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 离开WebSocket房间
|
|
|
|
|
voice_room = f"voice_room_{room_id}"
|
|
|
|
|
leave_room(voice_room)
|
|
|
|
|
|
|
|
|
|
# 通知房间内其他用户
|
|
|
|
|
emit('voice_chat_event', {
|
|
|
|
|
'type': 'user_left',
|
|
|
|
|
'user_id': user_id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'room_id': room_id
|
|
|
|
|
}, room=voice_room)
|
|
|
|
|
|
|
|
|
|
logger.info(f"用户 {user.username} (ID: {user_id}) 离开语音聊天 {room_id}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('voice_status_change')
|
|
|
|
|
def handle_voice_status_change(data):
|
|
|
|
|
"""用户更改语音状态(如静音)"""
|
|
|
|
|
user_id = session.get('user_id')
|
|
|
|
|
if not user_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room_id = data.get('room_id')
|
|
|
|
|
muted = data.get('muted', False)
|
|
|
|
|
|
|
|
|
|
if not room_id:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 获取用户信息
|
|
|
|
|
user = User.query.get(user_id)
|
|
|
|
|
if not user:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 通知房间内其他用户
|
|
|
|
|
voice_room = f"voice_room_{room_id}"
|
|
|
|
|
emit('voice_chat_event', {
|
|
|
|
|
'type': 'mute_change',
|
|
|
|
|
'user_id': user_id,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'room_id': room_id,
|
|
|
|
|
'muted': muted
|
|
|
|
|
}, room=voice_room)
|
|
|
|
|
|
|
|
|
|
logger.info(f"用户 {user.username} (ID: {user_id}) 在语音聊天 {room_id} 中{'静音' if muted else '取消静音'}")
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
# 在启动应用前初始化数据库
|
|
|
|
|
init_db()
|
|
|
|
|