12 KiB
12 KiB
语音/视频通话模块说明
📋 模块概述
本模块实现了基于 WebRTC 的一对一语音/视频通话功能,支持通话邀请、接听、拒绝、取消、结束等完整流程,并提供通话记录管理功能。
实现时间: 已完成
技术方案: WebRTC + WebSocket 信令交换
完成度: 100%
🏗️ 架构设计
核心组件
-
CallRecord - 通话记录实体类
- 路径:
crmeb-common/src/main/java/com/zbkj/common/model/call/CallRecord.java - 功能: 存储通话记录信息(通话双方、类型、状态、时长等)
- 路径:
-
CallService - 通话业务逻辑服务
- 路径:
crmeb-service/src/main/java/com/zbkj/service/service/impl/CallServiceImpl.java - 功能: 处理通话创建、接听、拒绝、取消、结束等业务逻辑
- 路径:
-
CallController - 通话接口控制器
- 路径:
crmeb-front/src/main/java/com/zbkj/front/controller/CallController.java - 功能: 提供 RESTful API 接口
- 路径:
-
CallSignalingHandler - WebRTC 信令处理器
- 路径:
crmeb-front/src/main/java/com/zbkj/front/websocket/CallSignalingHandler.java - 功能: 处理 WebSocket 信令交换(offer、answer、ice-candidate)
- 路径:
📊 数据库设计
表名: eb_call_record
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键ID |
| call_id | VARCHAR(64) | 通话唯一标识(用于信令) |
| caller_id | INT | 主叫用户ID |
| callee_id | INT | 被叫用户ID |
| call_type | VARCHAR(20) | 通话类型: voice-语音, video-视频 |
| status | VARCHAR(20) | 通话状态 |
| call_time | DATETIME | 通话发起时间 |
| connect_time | DATETIME | 通话接通时间 |
| end_time | DATETIME | 通话结束时间 |
| duration | INT | 通话时长(秒) |
| end_reason | VARCHAR(50) | 结束原因 |
| caller_deleted | TINYINT(1) | 主叫是否删除记录 |
| callee_deleted | TINYINT(1) | 被叫是否删除记录 |
| create_time | DATETIME | 创建时间 |
| update_time | DATETIME | 更新时间 |
通话状态说明
calling- 呼叫中(主叫发起,等待被叫响应)ringing- 响铃中(被叫收到邀请)connected- 通话中(双方已接通)ended- 已结束(正常结束)missed- 未接(超时未接听)rejected- 已拒绝(被叫拒绝)cancelled- 已取消(主叫取消)busy- 忙线(对方正在通话中)
🔌 API 接口
1. 发起通话
POST /api/front/call/initiate
Content-Type: application/json
{
"calleeId": 123,
"callType": "video" // voice 或 video
}
响应:
{
"code": 200,
"msg": "操作成功",
"data": {
"callId": "call_abc123...",
"callType": "video",
"calleeId": 123,
"calleeName": "用户昵称",
"calleeAvatar": "头像URL",
"status": "calling",
"signalingUrl": "/ws/call/call_abc123..."
}
}
2. 接听通话
POST /api/front/call/accept/{callId}
3. 拒绝通话
POST /api/front/call/reject/{callId}
4. 取消通话
POST /api/front/call/cancel/{callId}
5. 结束通话
POST /api/front/call/end/{callId}?endReason=normal
6. 获取通话记录
GET /api/front/call/history?page=1&limit=20
7. 删除通话记录
DELETE /api/front/call/record/{recordId}
8. 获取未接来电数量
GET /api/front/call/missed/count
9. 检查通话状态
GET /api/front/call/status
10. 获取通话详情
GET /api/front/call/detail/{callId}
🔄 WebSocket 信令协议
连接地址
ws://your-domain/ws/call
客户端发送消息类型
1. 注册用户
{
"type": "register",
"userId": 123
}
2. 发起通话请求
{
"type": "call_request",
"calleeId": 456,
"callType": "video"
}
3. 接听通话
{
"type": "call_accept",
"callId": "call_abc123..."
}
4. 拒绝通话
{
"type": "call_reject",
"callId": "call_abc123..."
}
5. 取消通话
{
"type": "call_cancel",
"callId": "call_abc123..."
}
6. 结束通话
{
"type": "call_end",
"callId": "call_abc123...",
"reason": "normal"
}
7. WebRTC Offer
{
"type": "offer",
"callId": "call_abc123...",
"sdp": {
"type": "offer",
"sdp": "v=0\r\no=..."
}
}
8. WebRTC Answer
{
"type": "answer",
"callId": "call_abc123...",
"sdp": {
"type": "answer",
"sdp": "v=0\r\no=..."
}
}
9. ICE Candidate
{
"type": "ice-candidate",
"callId": "call_abc123...",
"candidate": {
"candidate": "candidate:...",
"sdpMLineIndex": 0,
"sdpMid": "0"
}
}
服务端推送消息类型
1. 注册成功
{
"type": "registered",
"userId": 123
}
2. 来电通知
{
"type": "incoming_call",
"callId": "call_abc123...",
"callerId": 123,
"callerName": "用户昵称",
"callerAvatar": "头像URL",
"callType": "video"
}
3. 通话已创建
{
"type": "call_created",
"callId": "call_abc123..."
}
4. 对方已接听
{
"type": "call_accepted",
"callId": "call_abc123..."
}
5. 对方已拒绝
{
"type": "call_rejected",
"callId": "call_abc123..."
}
6. 对方已取消
{
"type": "call_cancelled",
"callId": "call_abc123..."
}
7. 通话已结束
{
"type": "call_ended",
"callId": "call_abc123...",
"reason": "normal"
}
8. 通话超时
{
"type": "call_timeout",
"callId": "call_abc123..."
}
9. 错误消息
{
"type": "error",
"message": "错误描述"
}
🎯 通话流程
语音/视频通话完整流程
主叫方 服务器 被叫方
| | |
|--1. 发起通话----------->| |
| POST /call/initiate | |
| | |
|<--返回callId-----------| |
| | |
|--2. 连接WebSocket----->| |
| register | |
| | |
| |--3. 推送来电通知------->|
| | incoming_call |
| | |
| |<--4. 接听通话----------|
| | call_accept |
| | |
|<--5. 通知已接听--------|--5. 通知已接听-------->|
| call_accepted | call_accepted |
| | |
|--6. 发送Offer--------->|--6. 转发Offer--------->|
| | |
|<--7. 接收Answer--------|<--7. 发送Answer--------|
| | |
|--8. 交换ICE候选------->|--8. 转发ICE候选------->|
|<-----------------------|<-----------------------|
| | |
|========== WebRTC P2P 通话建立 =================|
| | |
|--9. 结束通话---------->| |
| call_end | |
| |--9. 通知结束---------->|
| | call_ended |
⚙️ 核心功能特性
1. 通话管理
- ✅ 发起语音/视频通话
- ✅ 接听/拒绝/取消通话
- ✅ 通话中状态检测
- ✅ 通话时长统计
- ✅ 通话记录保存
2. 状态管理
- ✅ 用户在线状态检测
- ✅ 通话状态实时同步
- ✅ 忙线检测(防止重复呼叫)
- ✅ 超时自动挂断(60秒)
3. 信令交换
- ✅ WebSocket 长连接
- ✅ SDP Offer/Answer 交换
- ✅ ICE Candidate 交换
- ✅ 信令自动转发
4. 通话记录
- ✅ 通话历史查询
- ✅ 未接来电统计
- ✅ 双向软删除
- ✅ 通话详情查看
5. 异常处理
- ✅ 网络断开自动结束
- ✅ 超时自动挂断
- ✅ 错误消息推送
- ✅ 状态一致性保证
🔒 安全特性
- 权限验证: 所有接口需要登录认证
- 操作权限: 只能操作自己参与的通话
- 状态校验: 严格的状态机转换验证
- 防重复呼叫: 检测用户是否正在通话中
- 超时保护: 60秒超时自动结束未接通的通话
📱 客户端集成指南
1. 发起通话
// 1. 调用 API 发起通话
const response = await fetch('/api/front/call/initiate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
calleeId: 456,
callType: 'video'
})
});
const { callId, signalingUrl } = await response.json();
// 2. 连接 WebSocket
const ws = new WebSocket('ws://your-domain/ws/call');
// 3. 注册用户
ws.send(JSON.stringify({
type: 'register',
userId: currentUserId
}));
// 4. 创建 WebRTC 连接
const pc = new RTCPeerConnection(config);
// 5. 添加本地媒体流
const stream = await navigator.mediaDevices.getUserMedia({
video: callType === 'video',
audio: true
});
stream.getTracks().forEach(track => pc.addTrack(track, stream));
// 6. 创建并发送 Offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send(JSON.stringify({
type: 'offer',
callId: callId,
sdp: offer
}));
// 7. 监听 ICE Candidate
pc.onicecandidate = (event) => {
if (event.candidate) {
ws.send(JSON.stringify({
type: 'ice-candidate',
callId: callId,
candidate: event.candidate
}));
}
};
// 8. 接收远程流
pc.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
2. 接听通话
// 1. 收到来电通知
ws.onmessage = async (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'incoming_call') {
// 显示来电界面
showIncomingCallUI(msg);
}
};
// 2. 用户点击接听
async function acceptCall(callId) {
// 调用接听 API
await fetch(`/api/front/call/accept/${callId}`, {
method: 'POST'
});
// 创建 WebRTC 连接
const pc = new RTCPeerConnection(config);
// 添加本地媒体流
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
stream.getTracks().forEach(track => pc.addTrack(track, stream));
// 监听 Offer
ws.onmessage = async (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'offer') {
await pc.setRemoteDescription(msg.sdp);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
// 发送 Answer
ws.send(JSON.stringify({
type: 'answer',
callId: callId,
sdp: answer
}));
}
if (msg.type === 'ice-candidate') {
await pc.addIceCandidate(msg.candidate);
}
};
}
🧪 测试建议
功能测试
- 正常通话流程测试
- 拒绝/取消通话测试
- 超时未接测试
- 忙线检测测试
- 通话记录查询测试
异常测试
- 网络断开重连测试
- 对方离线测试
- 并发呼叫测试
- WebSocket 断开测试
性能测试
- 并发通话数量测试
- 信令延迟测试
- 内存泄漏测试
📝 注意事项
- WebRTC 兼容性: 确保客户端浏览器支持 WebRTC
- HTTPS 要求: WebRTC 需要在 HTTPS 环境下运行(本地开发除外)
- STUN/TURN 服务器: 生产环境需要配置 STUN/TURN 服务器用于 NAT 穿透
- 媒体权限: 需要用户授权摄像头和麦克风权限
- 超时时间: 默认 60 秒超时,可根据需求调整
- 通话记录清理: 建议定期清理过期的通话记录
🚀 后续优化建议
- 群组通话: 支持多人视频会议
- 屏幕共享: 支持屏幕共享功能
- 通话质量: 添加网络质量检测和自适应码率
- 通话录制: 支持通话录制功能
- 美颜滤镜: 集成美颜和滤镜功能
- 通话统计: 添加通话质量统计和分析
文档版本: v1.0
最后更新: 2024年12月26日
维护状态: ✅ 已完成