基于 Spring Boot + Netty + WebRTC 的视频会议信令服务器后端,支持多人实时音视频会议、会议管理、用户体系等功能。
现在仓库中已经额外提供了一个独立的 frontend/ 浏览器演示,用来结合当前后端的 REST 接口和 WebSocket 信令完成 WebRTC P2P 视频传输 验证。
| 组件 | 版本 |
|---|---|
| Java | 1.8 |
| Spring Boot | 2.7.18 |
| Netty | 4.1.50 |
| MySQL | 8.0.23 |
| Redis / Redisson | 3.12.3 |
| RabbitMQ (amqp-client) | 5.14.2 |
| MyBatis | 1.3.2 |
| Lombok | 1.18.22 |
| Frontend Demo | Vite + Vanilla JS |
┌─────────────────────────────────────────┐
│ 客户端 (前端) │
│ HTTP REST API WebSocket │
└────────┬────────────────────┬───────────┘
│ port 5050 │ port 6061
▼ ▼
┌─────────────────┐ ┌──────────────────────┐
│ Spring Boot │ │ Netty WebSocket │
│ REST 接口层 │ │ 信令服务器 (/ws) │
│ │ │ - Token 鉴权 │
│ /api/account │ │ - 心跳检测 │
│ /api/meeting │ │ - WebRTC 信令转发 │
│ /api/userInfo │ └──────────┬───────────┘
└────────┬────────┘ │
│ │
▼ ▼
┌─────────────────────────────────────────┐
│ 消息总线 (可切换) │
│ Redis Pub/Sub 或 RabbitMQ │
└──────────────┬──────────────────────────┘
│
┌───────┴────────┐
▼ ▼
MySQL Redis
(持久化) (缓存/Token)
- 图形验证码生成 (
/api/account/checkCode) - 用户注册 (
/api/account/register) - 用户登录 (
/api/account/login) - 用户信息管理、联系人管理
- 即时会议:快速创建,支持个人会议号或随机会议号
- 预约会议:查看今日会议列表
- 加入会议:支持密码验证加入
- 会议管理:踢出成员、加入黑名单、结束会议
- 实时状态:查询当前进行中会议
- 基于 Netty 的 WebSocket 服务器,路径
/ws - 支持 WebRTC 的 Offer/Answer/ICE Candidate 信令转发
- Token 鉴权拦截器
- 心跳检测(20秒空闲检测)
- Peer 连接数据转发
- 浏览器中直接采集摄像头和麦克风
- 通过现有
/api/account/*和/api/meeting/*接口完成登录、建会、入会 - 通过现有
/ws信令通道转发offer / answer / candidate - 在浏览器之间建立 P2P 音视频连接
- 支持展示本地视频、远端视频、参会者列表和调试日志
- 支持退出会议与结束会议后的本地资源清理
通过 message.handle.channel 配置项切换:
redis:使用 Redis Pub/Sub(默认)rabbitmq:使用 RabbitMQ
- JDK 1.8+
- Maven 3.6+
- MySQL 8.0+
- Redis
- RabbitMQ(可选,默认使用 Redis)
- Node.js 18+(用于前端 demo)
创建数据库 easymeeting 并导入 SQL 脚本(位于 sql/ 目录,如果你本地仓库没有该目录,需要自行补齐初始化 SQL)。
编辑 src/main/resources/application.properties:
# HTTP 服务端口
server.port=5050
# WebSocket 信令端口
ws.port=6061
# 数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/easymeeting?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=your_password
# Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
# 消息通道: redis 或 rabbitmq
message.handle.channel=redis
# 超级管理员邮箱
admin.emails=admin@example.com
# 项目文件目录
project.folder=/your/project/folder/mvn clean package -DskipTests
java -jar target/easymeeting-1.0.jar或直接在 IDE 中运行 EasyMeetingApplication.java。
cd frontend
npm install
npm run dev默认启动后访问:
http://localhost:5173
默认配置下:
- API Base:
/api(通过 Vite 代理到http://localhost:5050) - WS Base:
ws://localhost:6061
这个项目中的 WebSocket 不传输视频流本身,只负责 WebRTC 信令交换:
offeranswerICE candidate
实际的音视频媒体流:
- 由浏览器通过 WebRTC 建立
RTCPeerConnection - 优先走浏览器之间的 P2P 直连
- 本仓库当前未内置 TURN 服务,因此复杂网络环境下可能无法成功建立媒体链路
- 用户登录后拿到
token - 浏览器连接:
ws://host:6061/ws?token=<token> - 主持人创建会议后调用
joinMeeting - 参会者通过
preJoinMeeting + joinMeeting进入房间 - 后端广播
ADD_MEETING_ROOM - 新加入者向房间中已有成员逐个发送
offer - 对方返回
answer - 双方继续交换
candidate - 浏览器之间建立 P2P 媒体连接并显示远端视频
前端通过 WebSocket 发送的数据结构与后端 PeerConnectionDataDto 对齐:
{
"token": "user-token",
"sendUserId": "sender-id",
"receiveUserId": "receiver-id",
"signalType": "offer",
"signalData": "{...json string...}"
}其中 signalType 支持:
offeranswercandidateheartbeat(前端为了兼容后端 20 秒空闲断连而补的保活消息)
建议使用两个不同浏览器窗口或两个不同浏览器进行测试。
- 打开
http://localhost:5173 - 注册两个账号,分别在两个浏览器中登录
- 在浏览器 A 中点击“打开摄像头/麦克风”
- 在浏览器 A 中填写会议信息,点击“创建会议并入会”
- 浏览器 A 会连接 WebSocket,并等待其他成员加入
- 在浏览器 B 中点击“打开摄像头/麦克风”
- 输入会议号、昵称、密码(如有)
- 点击“加入会议”
- 前端会先调用
preJoinMeeting,再调用joinMeeting - 加入成功后,浏览器 B 会主动向已有成员发起
offer - 双方交换
answer和candidate后看到远端视频
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /checkCode |
获取图形验证码 |
| POST | /register |
注册(email / nickName / password) |
| POST | /login |
登录(email / password) |
| 方法 | 路径 | 说明 | 需鉴权 |
|---|---|---|---|
| POST | /loadingMeeting |
分页查询会议列表 | ✓ |
| POST | /quickMeeting |
快速创建会议 | ✓ |
| POST | /preJoinMeeting |
加入前校验会议号/密码,并写入当前会议状态 | ✓ |
| POST | /joinMeeting |
正式加入会议 | ✓ |
| POST | /exitMeeting |
退出会议 | ✓ |
| POST | /finishMeeting |
结束会议(主持人) | ✓ |
| POST | /getCurrentMeeting |
获取当前会议信息 | ✓ |
| POST | /kickOutMeeting |
踢出成员 | ✓ |
| POST | /blackMeeting |
将成员加入黑名单 | ✓ |
ws://host:6061/ws?token=<your_token>
src/main/java/com/easymeeting/
├── EasyMeetingApplication.java # 启动类
├── annotation/ # 自定义注解(鉴权、限流)
├── aspect/ # AOP 切面
├── config/ # 配置类
├── controller/ # REST 控制器
├── entity/
│ ├── constants/ # 常量
│ ├── dto/ # 数据传输对象
│ ├── enums/ # 枚举
│ ├── po/ # 数据库实体
│ ├── query/ # 查询条件
│ └── vo/ # 视图对象
├── exception/ # 异常处理
├── mappers/ # MyBatis Mapper
├── redis/ # Redis 工具类
├── service/ # 业务逻辑层
│ └── impl/
├── utils/ # 工具类
└── websocket/
├── ChannelContextUtils.java # Channel 上下文管理
├── InitRun.java # 启动时初始化
├── message/ # 消息处理(Redis/RabbitMQ)
└── netty/ # Netty WebSocket 服务器
frontend/
├── index.html # 前端演示页面
├── package.json # 前端依赖与脚本
├── vite.config.js # Vite 开发代理配置
└── src/
├── api.js # REST 接口封装
├── main.js # 页面逻辑、状态管理、事件绑定
├── signaling.js # WebSocket 信令客户端
├── webrtc.js # RTCPeerConnection 管理
└── styles.css # 页面样式
本次新增的前端 Demo 做了这些事情:
- 新增独立
frontend/目录,不侵入现有 Java 后端结构 - 使用现有后端登录、建会、入会、离会、结束会议接口
- 使用现有后端
/ws信令服务完成 WebRTC 协商 - 在前端实现了
offer / answer / candidate的发送与接收 - 在前端实现了“新加入者主动发 offer”的建链策略
- 为每个远端用户维护一个独立的
RTCPeerConnection - 在 UI 中展示本地视频、远端视频、参会者和调试日志
- 增加了 WebSocket keepalive,避免后端空闲 20 秒断开连接
- 通过
npm run build验证前端可以成功构建
- 当前前端是 Mesh P2P 模式,多人会议人数较多时带宽和 CPU 压力会上升
- 当前仓库未提供 TURN 服务器,跨复杂 NAT 环境时音视频直连可能失败
- 后端当前更偏向演示/实验性质,部分踢人/房间清理逻辑需要后续继续完善
quickMeeting接口返回的是meetingId,前端目前无法直接从该接口得到会议号,因此主持人创建会议后,会议号展示仍依赖后端后续补充或从其他接口读取
MIT