# 现在需要修复的问题 1. 媒体预览的组件有问题:Uncaught TypeError: $setup.previewMedia is not a function at _createElementBlock.onClick._cache.._cache. (MessageBubble.vue:27:21) 2. 点击视频聊天或者语音聊天后应该请求弹框(等待对方接听,对方传输接收则开始聊天,对方拒绝则给出消息提示然后存在聊天记录中),对方也要弹出视频/语音通话邀请,可以接收或者拒绝。 3. 根据{前端获取信令示例}和{后端交互代码}来完成整个聊天功能,并且要考虑用户掉线、断网、网络异常等情况,以及用户退出登录、用户被删除等情况。 4. 在好友管理点击聊天后要吧侧边导航变成聊天 5. 聊天记录列表和好友管理中中,点击对方的头像要有对方的信息 6. 完整的基于WebRTC实现语音/视频通话功能 # 前端获取信令示例 ```javascript // 发起通话邀请 function startCall(targetUserId, isVideo) { const callId = generateUniqueId(); const callType = isVideo ? 6 : 7; // 6=视频,7=语音 const callSignal = { action: "invite", call_type: callType, call_id: callId, caller_id: currentUserId, callee_id: targetUserId, data: JSON.stringify(offer) // SDP offer }; const payload = { sender_user_id: currentUserId, receiver_user_id: targetUserId, message_type: callType, message_content: JSON.stringify(callSignal) }; // 调用/send-to-user接口 fetch('/send-to-user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).then(response => { if (!response.ok) { throw new Error('Failed to send call invite'); } return response.json(); }).then(data => { console.log('Call invite sent:', data); }).catch(error => { console.error('Error sending call invite:', error); }); } // 接受通话 function acceptCall(callId, callType) { const callSignal = { action: "accept", call_id: callId, data: JSON.stringify(answer) // SDP answer }; const payload = { sender_user_id: currentUserId, receiver_user_id: callerUserId, message_type: callType, message_content: JSON.stringify(callSignal) }; fetch('/send-to-user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); } // 拒绝通话 function rejectCall(callId, callType, reason) { const callSignal = { action: "reject", call_id: callId, data: reason || "用户忙" }; const payload = { sender_user_id: currentUserId, receiver_user_id: callerUserId, message_type: callType, message_content: JSON.stringify(callSignal) }; fetch('/send-to-user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); } // 结束通话 function endCall(callId, callType) { const callSignal = { action: "end", call_id: callId }; const payload = { sender_user_id: currentUserId, receiver_user_id: otherUserId, message_type: callType, message_content: JSON.stringify(callSignal) }; fetch('/send-to-user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); } // 发送ICE候选 function sendCandidate(callId, callType, candidate, targetUserId) { const callSignal = { action: "candidate", call_id: callId, data: JSON.stringify(candidate) }; const payload = { sender_user_id: currentUserId, receiver_user_id: targetUserId, message_type: callType, message_content: JSON.stringify(callSignal) }; fetch('/send-to-user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); } ``` ## 后端交互代码 1. {websocket_controller.go} ## 消息结构 1. {messages.go} ## 通话流程图 ```mermaid graph TD A[用户A点击通话按钮] --> B{选择通话类型} B --> |语音通话| C1[设置 message_type=7] B --> |视频通话| C2[设置 message_type=6] C1 --> D[生成唯一CallID] D --> E[创建PeerConnection对象] E --> F[获取本地媒体流] F --> G[创建SDP offer] G --> H[构造信令对象] H --> I[发送邀请信令] I --> J[后端转发给用户B] J --> K[用户B收到来电通知] K --> L{用户B选择} L --> |接听| M[创建SDP answer] L --> |拒绝| N[发送拒绝信令] M --> O[发送接受信令] O --> P[后端转发给用户A] P --> Q[用户A设置远程描述] Q --> R[交换ICE候选] R --> S[建立P2P连接] S --> T[开始语音通话] N --> U[用户A收到拒绝通知] U --> V[结束通话流程] subgraph 信令交换 I[发送邀请信令] O[发送接受信令] R[交换ICE候选] end subgraph WebRTC连接 E[创建PeerConnection] F[获取本地媒体流] G[创建SDP offer] M[创建SDP answer] Q[设置远程描述] S[建立P2P连接] end ```