实现基于UDP发送语音

main
wang 2 months ago
parent e8002e1513
commit 36c92b7faf

@ -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 = {}
# 存储在线用户和他们的sidSocket 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()

@ -2,6 +2,7 @@ import os
import logging
from dotenv import load_dotenv
from app import app, socketio, init_db
import voice_udp_server
# 加载环境变量
load_dotenv()
@ -22,6 +23,13 @@ if __name__ == '__main__':
logger.error(f"数据库初始化失败: {e}")
# 继续执行,因为可能会在应用启动后自动重试
# 启动UDP语音服务器
udp_started = voice_udp_server.start_udp_server()
if udp_started:
logger.info("UDP语音服务器启动成功")
else:
logger.warning("UDP语音服务器启动失败实时语音功能将不可用")
# 从环境变量获取主机和端口,如果没有则使用默认值
host = os.environ.get('HOST', '0.0.0.0')
port = int(os.environ.get('PORT', 5000))
@ -29,6 +37,7 @@ if __name__ == '__main__':
logger.info(f"环境: {os.environ.get('FLASK_ENV', 'production')}")
logger.info(f"启动服务器,监听 {host}:{port}")
logger.info(f"UDP语音服务器状态: {'在线' if udp_started else '离线'}")
logger.info(f"SECRET_KEY已设置: {app.config['SECRET_KEY'][:3]}..." if 'SECRET_KEY' in app.config else "SECRET_KEY未设置")
logger.info(f"SESSION_COOKIE_SECURE: {app.config.get('SESSION_COOKIE_SECURE', False)}")
logger.info(f"CORS supports_credentials: {app.config.get('CORS_SUPPORTS_CREDENTIALS', False)}")

@ -0,0 +1,210 @@
import socket
import threading
import json
import time
import logging
import base64
from collections import defaultdict
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger('voice_udp_server')
# 配置
UDP_HOST = '0.0.0.0' # 监听所有网络接口
UDP_PORT = 5001 # UDP端口
MAX_PACKET_SIZE = 65507 # UDP包最大大小
ROOM_CLIENTS = defaultdict(set) # 存储房间和客户端的映射关系
CLIENT_ADDRESSES = {} # 存储客户端ID和地址的映射关系
HEARTBEAT_INTERVAL = 5 # 心跳检测间隔(秒)
CLIENT_TIMEOUT = 15 # 客户端超时时间(秒)
CLIENT_LAST_SEEN = {} # 记录客户端最后活跃时间
# UDP套接字
udp_socket = None
def start_udp_server():
"""启动UDP服务器"""
global udp_socket
try:
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind((UDP_HOST, UDP_PORT))
logger.info(f"UDP语音服务器已启动监听于 {UDP_HOST}:{UDP_PORT}")
# 启动心跳检测线程
heartbeat_thread = threading.Thread(target=check_client_heartbeats)
heartbeat_thread.daemon = True
heartbeat_thread.start()
# 启动UDP监听线程
udp_thread = threading.Thread(target=udp_listener)
udp_thread.daemon = True
udp_thread.start()
return True
except Exception as e:
logger.error(f"启动UDP服务器失败: {str(e)}")
return False
def udp_listener():
"""UDP监听线程处理接收到的UDP数据包"""
global udp_socket
logger.info("UDP监听线程已启动")
while True:
try:
data, addr = udp_socket.recvfrom(MAX_PACKET_SIZE)
# 解析数据
process_udp_packet(data, addr)
except Exception as e:
logger.error(f"处理UDP数据包时出错: {str(e)}")
def process_udp_packet(data, addr):
"""处理UDP数据包"""
try:
# 尝试解析为JSON
packet = json.loads(data.decode('utf-8'))
packet_type = packet.get('type')
client_id = packet.get('client_id')
room_id = packet.get('room_id')
# 更新客户端最后活跃时间
if client_id:
CLIENT_LAST_SEEN[client_id] = time.time()
CLIENT_ADDRESSES[client_id] = addr
# 根据数据包类型处理
if packet_type == 'join':
# 客户端加入房间
if room_id and client_id:
join_voice_room(client_id, room_id)
logger.info(f"客户端 {client_id} 加入语音房间 {room_id}")
elif packet_type == 'leave':
# 客户端离开房间
if room_id and client_id:
leave_voice_room(client_id, room_id)
logger.info(f"客户端 {client_id} 离开语音房间 {room_id}")
elif packet_type == 'heartbeat':
# 心跳包,只需更新最后活跃时间,已在上面处理
send_udp_packet({
'type': 'heartbeat_ack',
'server_time': time.time()
}, addr)
elif packet_type == 'voice':
# 语音数据包,转发给房间内其他用户
voice_data = packet.get('data')
if room_id and client_id and voice_data:
forward_voice_data(room_id, client_id, voice_data)
except json.JSONDecodeError:
logger.warning(f"收到无效的JSON数据: {data[:100]}...")
except Exception as e:
logger.error(f"处理UDP数据包时出错: {str(e)}")
def join_voice_room(client_id, room_id):
"""客户端加入语音房间"""
ROOM_CLIENTS[room_id].add(client_id)
# 通知房间内其他用户有新用户加入
notify_room_clients(room_id, {
'type': 'user_joined',
'client_id': client_id,
'room_id': room_id,
'time': time.time()
}, exclude_client=client_id)
def leave_voice_room(client_id, room_id):
"""客户端离开语音房间"""
if client_id in ROOM_CLIENTS[room_id]:
ROOM_CLIENTS[room_id].remove(client_id)
# 如果房间空了,删除房间
if not ROOM_CLIENTS[room_id]:
del ROOM_CLIENTS[room_id]
else:
# 通知房间内其他用户有用户离开
notify_room_clients(room_id, {
'type': 'user_left',
'client_id': client_id,
'room_id': room_id,
'time': time.time()
}, exclude_client=client_id)
def forward_voice_data(room_id, sender_id, voice_data):
"""转发语音数据给房间内其他用户"""
packet = {
'type': 'voice',
'client_id': sender_id,
'room_id': room_id,
'data': voice_data,
'time': time.time()
}
notify_room_clients(room_id, packet, exclude_client=sender_id)
def notify_room_clients(room_id, packet, exclude_client=None):
"""通知房间内的所有客户端"""
if room_id not in ROOM_CLIENTS:
return
for client_id in ROOM_CLIENTS[room_id]:
if client_id != exclude_client and client_id in CLIENT_ADDRESSES:
send_udp_packet(packet, CLIENT_ADDRESSES[client_id])
def send_udp_packet(packet, addr):
"""发送UDP数据包"""
try:
data = json.dumps(packet).encode('utf-8')
udp_socket.sendto(data, addr)
except Exception as e:
logger.error(f"发送UDP数据包失败: {str(e)}")
def check_client_heartbeats():
"""检查客户端心跳,移除超时的客户端"""
while True:
try:
current_time = time.time()
timeout_clients = []
# 检查所有客户端
for client_id, last_seen in list(CLIENT_LAST_SEEN.items()):
if current_time - last_seen > CLIENT_TIMEOUT:
timeout_clients.append(client_id)
# 处理超时的客户端
for client_id in timeout_clients:
logger.info(f"客户端 {client_id} 超时,移除")
# 从所有房间中移除
for room_id in list(ROOM_CLIENTS.keys()):
if client_id in ROOM_CLIENTS[room_id]:
leave_voice_room(client_id, room_id)
# 删除客户端记录
if client_id in CLIENT_ADDRESSES:
del CLIENT_ADDRESSES[client_id]
if client_id in CLIENT_LAST_SEEN:
del CLIENT_LAST_SEEN[client_id]
# 等待下一次检查
time.sleep(HEARTBEAT_INTERVAL)
except Exception as e:
logger.error(f"心跳检测线程出错: {str(e)}")
time.sleep(HEARTBEAT_INTERVAL)
def get_voice_room_status():
"""获取语音房间状态"""
result = {}
for room_id, clients in ROOM_CLIENTS.items():
result[room_id] = list(clients)
return result
# 当模块直接运行时启动UDP服务器
if __name__ == "__main__":
start_udp_server()
try:
# 保持主线程运行
while True:
time.sleep(1)
except KeyboardInterrupt:
logger.info("UDP语音服务器已关闭")

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -48,6 +48,12 @@ export default {
isLoading: false
}
},
mounted() {
// 401
if (this.$route.query.expired) {
this.error = '会话已过期,请重新登录'
}
},
methods: {
async login() {
if (!this.username || !this.password) {
@ -60,15 +66,31 @@ export default {
try {
console.log('正在发送登录请求...')
const response = await axios.post('/api/login', {
username: this.username,
password: this.password
}, {
withCredentials: true,
//
if (!navigator.onLine) {
throw new Error('网络连接已断开,请检查网络连接后重试')
}
//
const requestConfig = {
url: '/api/login',
method: 'post',
data: {
username: this.username,
password: this.password
},
headers: {
'Content-Type': 'application/json',
}
})
'Accept': 'application/json'
},
withCredentials: true
}
console.log('登录请求配置:', JSON.stringify(requestConfig))
//
const response = await axios(requestConfig)
console.log('登录成功,响应数据:', response.data)
@ -109,10 +131,37 @@ export default {
}
} catch (error) {
console.error('登录失败:', error)
if (error.response && error.response.data) {
this.error = error.response.data.error || '登录失败,请重试'
console.error('错误详情:', {
message: error.message,
response: error.response ? {
status: error.response.status,
statusText: error.response.statusText,
data: error.response.data
} : '无响应数据',
request: error.request ? '请求已发送但无响应' : '请求未发送'
})
if (error.response) {
//
switch (error.response.status) {
case 400:
this.error = error.response.data.error || '请求格式不正确,请重试'
break
case 401:
this.error = '用户名或密码不正确'
break
case 500:
this.error = '服务器内部错误,请稍后重试'
break
default:
this.error = error.response.data.error || '登录失败,请重试'
}
} else if (error.request) {
//
this.error = '服务器无响应,请检查网络连接或稍后重试'
} else {
this.error = '网络错误,请稍后重试'
//
this.error = error.message || '网络错误,请稍后重试'
}
} finally {
this.isLoading = false

@ -12,17 +12,50 @@ axios.defaults.baseURL = process.env.NODE_ENV === 'production'
// 确保axios请求发送凭证(cookies)
axios.defaults.withCredentials = true
// 添加请求拦截器自动添加Authorization头
// 设置默认头部
axios.defaults.headers.common['Content-Type'] = 'application/json'
// 添加请求拦截器自动添加Authorization头和检查请求数据
axios.interceptors.request.use(config => {
const token = localStorage.getItem('auth_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
// 确保POST和PUT请求都有Content-Type设置
if (['post', 'put'].includes(config.method?.toLowerCase())) {
config.headers['Content-Type'] = 'application/json'
}
console.log('发送请求:', config.url, 'Headers:', config.headers, 'Method:', config.method)
return config
}, error => {
console.error('请求拦截器错误:', error)
return Promise.reject(error)
})
// 添加响应拦截器,处理登录错误
axios.interceptors.response.use(
response => response,
error => {
console.error('响应错误:', error.response?.status, error.response?.data)
// 处理401错误
if (error.response && error.response.status === 401) {
console.log('用户未授权,清除本地存储')
localStorage.removeItem('user')
localStorage.removeItem('auth_token')
// 如果不是在登录页面,重定向到登录
if (router.currentRoute.value.path !== '/login') {
router.push('/login')
}
}
return Promise.reject(error)
}
)
// 检查并尝试恢复用户会话
const checkAndRestoreSession = async () => {
const userData = localStorage.getItem('user')

Loading…
Cancel
Save