1516 lines
29 KiB
Markdown
1516 lines
29 KiB
Markdown
# 直播IM系统技术方案
|
||
|
||
## 一、项目概述
|
||
|
||
### 1.1 项目目标
|
||
搭建一个面向直播场景的即时通讯系统,支持1-10万用户规模,一周内完成MVP版本交付。
|
||
|
||
### 1.2 核心场景
|
||
|
||
**直播场景**
|
||
- 直播间实时弹幕
|
||
- 主播与观众互动消息
|
||
- 直播间礼物打赏
|
||
- 进入/离开直播间通知
|
||
|
||
**社交交友场景**
|
||
- 用户一对一私聊
|
||
- 好友申请与通知
|
||
- 私聊礼物赠送
|
||
- 语音消息发送
|
||
- 图片/视频分享
|
||
- 表情包发送
|
||
- 消息已读/未读状态
|
||
|
||
**互动场景**
|
||
- 作品评论通知
|
||
- 点赞通知
|
||
- 关注/粉丝通知
|
||
- @提醒通知
|
||
- 系统公告推送
|
||
|
||
### 1.3 技术选型原则
|
||
- 快速开发,优先使用成熟方案
|
||
- 架构简单,便于维护和扩展
|
||
- 成本可控,适合初期规模
|
||
|
||
---
|
||
|
||
## 二、系统架构设计
|
||
|
||
### 2.1 整体架构
|
||
|
||
```
|
||
客户端层(Android App)
|
||
↓
|
||
API网关层(Nginx)
|
||
↓
|
||
业务服务层
|
||
├── IM服务(WebSocket/长连接)
|
||
├── REST API服务(HTTP)
|
||
└── 消息推送服务
|
||
↓
|
||
数据存储层
|
||
├── MySQL(用户数据、消息记录)
|
||
├── Redis(在线状态、消息缓存)
|
||
└── MongoDB(可选,消息归档)
|
||
```
|
||
|
||
### 2.2 核心模块
|
||
|
||
#### 2.2.1 连接管理模块
|
||
- WebSocket长连接维护
|
||
- 心跳检测(30秒间隔)
|
||
- 断线重连机制
|
||
- 在线状态管理
|
||
|
||
#### 2.2.2 消息路由模块
|
||
- 单聊消息路由
|
||
- 群聊(直播间)消息广播
|
||
- 消息优先级队列
|
||
- 消息去重
|
||
|
||
#### 2.2.3 消息存储模块
|
||
- 离线消息存储
|
||
- 历史消息查询
|
||
- 消息已读状态
|
||
- 消息漫游(7天)
|
||
|
||
#### 2.2.4 业务功能模块
|
||
- 弹幕发送与接收
|
||
- 礼物打赏消息(直播间+私聊)
|
||
- 好友关系管理
|
||
- 消息已读回执
|
||
- 语音消息处理
|
||
- 图片/视频消息
|
||
- 系统通知
|
||
- 敏感词过滤
|
||
- 消息撤回
|
||
|
||
---
|
||
|
||
## 三、技术实现方案
|
||
|
||
### 3.1 通信协议选择
|
||
|
||
#### 方案一:WebSocket(推荐)
|
||
**优点:**
|
||
- 双向实时通信
|
||
- 浏览器原生支持
|
||
- 开发简单,调试方便
|
||
- 适合直播弹幕场景
|
||
|
||
**实现:**
|
||
```
|
||
协议:ws:// 或 wss://(生产环境必须用wss)
|
||
框架:Spring Boot + Spring WebSocket
|
||
心跳:客户端30秒发送ping,服务端响应pong
|
||
```
|
||
|
||
#### 方案二:MQTT(备选)
|
||
**优点:**
|
||
- 轻量级协议
|
||
- 支持QoS消息质量保证
|
||
- 适合物联网和移动场景
|
||
|
||
**缺点:**
|
||
- 需要额外的Broker(如Mosquitto、EMQ)
|
||
- 学习成本稍高
|
||
|
||
**建议:** 一周交付选WebSocket,后期可扩展MQTT
|
||
|
||
### 3.2 消息格式设计
|
||
|
||
#### 3.2.1 通用消息结构(JSON)
|
||
```json
|
||
{
|
||
"msgId": "uuid",
|
||
"msgType": "text|image|gift|system|barrage",
|
||
"fromUserId": "发送者ID",
|
||
"toUserId": "接收者ID(私聊)",
|
||
"roomId": "直播间ID(弹幕)",
|
||
"content": "消息内容",
|
||
"timestamp": 1640000000000,
|
||
"extra": {
|
||
"nickname": "用户昵称",
|
||
"avatar": "头像URL",
|
||
"giftId": "礼物ID(打赏消息)",
|
||
"giftCount": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3.2.2 消息类型定义
|
||
| 类型 | 说明 | 场景 |
|
||
|------|------|------|
|
||
| text | 文本消息 | 私聊、弹幕 |
|
||
| image | 图片消息 | 私聊 |
|
||
| video | 视频消息 | 私聊 |
|
||
| voice | 语音消息 | 私聊 |
|
||
| emoji | 表情包消息 | 私聊 |
|
||
| gift | 礼物打赏 | 直播间、私聊 |
|
||
| barrage | 弹幕 | 直播间 |
|
||
| system | 系统通知 | 全局 |
|
||
| friend_request | 好友申请 | 社交 |
|
||
| friend_accept | 好友通过 | 社交 |
|
||
| like | 点赞通知 | 社交 |
|
||
| comment | 评论通知 | 社交 |
|
||
| follow | 关注通知 | 社交 |
|
||
| at_mention | @提醒 | 社交 |
|
||
| enter_room | 进入直播间 | 直播间 |
|
||
| leave_room | 离开直播间 | 直播间 |
|
||
| recall | 消息撤回 | 私聊 |
|
||
|
||
### 3.3 数据库设计
|
||
|
||
#### 3.3.1 MySQL表结构
|
||
|
||
**用户表(user)**
|
||
```sql
|
||
CREATE TABLE `user` (
|
||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||
`username` VARCHAR(50) UNIQUE NOT NULL,
|
||
`nickname` VARCHAR(50),
|
||
`avatar` VARCHAR(255),
|
||
`status` TINYINT DEFAULT 1 COMMENT '1-正常 0-禁用',
|
||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_username (username)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
**会话表(conversation)**
|
||
```sql
|
||
CREATE TABLE `conversation` (
|
||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||
`user_id` BIGINT NOT NULL,
|
||
`target_id` BIGINT NOT NULL COMMENT '对方用户ID或直播间ID',
|
||
`type` TINYINT NOT NULL COMMENT '1-单聊 2-直播间',
|
||
`last_msg_id` BIGINT,
|
||
`last_msg_time` DATETIME,
|
||
`unread_count` INT DEFAULT 0,
|
||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
INDEX idx_user_id (user_id),
|
||
UNIQUE KEY uk_user_target (user_id, target_id, type)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
**消息表(message)**
|
||
```sql
|
||
CREATE TABLE `message` (
|
||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||
`msg_id` VARCHAR(64) UNIQUE NOT NULL,
|
||
`msg_type` VARCHAR(20) NOT NULL,
|
||
`from_user_id` BIGINT NOT NULL,
|
||
`to_user_id` BIGINT COMMENT '私聊接收者',
|
||
`room_id` BIGINT COMMENT '直播间ID',
|
||
`content` TEXT,
|
||
`extra` JSON COMMENT '扩展字段(图片URL、语音时长、礼物信息等)',
|
||
`status` TINYINT DEFAULT 1 COMMENT '1-正常 2-撤回 3-删除',
|
||
`is_read` TINYINT DEFAULT 0 COMMENT '0-未读 1-已读',
|
||
`read_at` DATETIME COMMENT '已读时间',
|
||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_from_user (from_user_id, created_at),
|
||
INDEX idx_to_user (to_user_id, created_at),
|
||
INDEX idx_room (room_id, created_at),
|
||
INDEX idx_msg_id (msg_id),
|
||
INDEX idx_unread (to_user_id, is_read)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
**好友关系表(friend)**
|
||
```sql
|
||
CREATE TABLE `friend` (
|
||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||
`user_id` BIGINT NOT NULL,
|
||
`friend_id` BIGINT NOT NULL,
|
||
`remark` VARCHAR(50) COMMENT '好友备注',
|
||
`status` TINYINT DEFAULT 1 COMMENT '1-正常 2-拉黑',
|
||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE KEY uk_user_friend (user_id, friend_id),
|
||
INDEX idx_user_id (user_id),
|
||
INDEX idx_friend_id (friend_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
**好友申请表(friend_request)**
|
||
```sql
|
||
CREATE TABLE `friend_request` (
|
||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||
`from_user_id` BIGINT NOT NULL,
|
||
`to_user_id` BIGINT NOT NULL,
|
||
`message` VARCHAR(200) COMMENT '申请留言',
|
||
`status` TINYINT DEFAULT 0 COMMENT '0-待处理 1-已同意 2-已拒绝',
|
||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
INDEX idx_to_user (to_user_id, status),
|
||
INDEX idx_from_user (from_user_id),
|
||
UNIQUE KEY uk_from_to (from_user_id, to_user_id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
**礼物记录表(gift_record)**
|
||
```sql
|
||
CREATE TABLE `gift_record` (
|
||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||
`from_user_id` BIGINT NOT NULL COMMENT '送礼者',
|
||
`to_user_id` BIGINT NOT NULL COMMENT '收礼者',
|
||
`gift_id` INT NOT NULL,
|
||
`gift_name` VARCHAR(50),
|
||
`gift_price` INT NOT NULL COMMENT '礼物价格(金币)',
|
||
`count` INT DEFAULT 1 COMMENT '数量',
|
||
`total_price` INT NOT NULL COMMENT '总价',
|
||
`room_id` BIGINT COMMENT '直播间ID(直播间送礼)',
|
||
`scene` VARCHAR(20) NOT NULL COMMENT 'live-直播间 chat-私聊',
|
||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_from_user (from_user_id, created_at),
|
||
INDEX idx_to_user (to_user_id, created_at),
|
||
INDEX idx_room (room_id, created_at)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
**直播间表(live_room)**
|
||
```sql
|
||
CREATE TABLE `live_room` (
|
||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||
`room_id` VARCHAR(50) UNIQUE NOT NULL,
|
||
`anchor_id` BIGINT NOT NULL COMMENT '主播ID',
|
||
`title` VARCHAR(100),
|
||
`cover` VARCHAR(255),
|
||
`status` TINYINT DEFAULT 1 COMMENT '1-直播中 0-已结束',
|
||
`online_count` INT DEFAULT 0,
|
||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX idx_anchor (anchor_id),
|
||
INDEX idx_status (status)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
#### 3.3.2 Redis数据结构
|
||
|
||
**在线用户(Hash)**
|
||
```
|
||
Key: online:users
|
||
Field: userId
|
||
Value: {serverId, connectTime, lastHeartbeat}
|
||
```
|
||
|
||
**直播间在线列表(Set)**
|
||
```
|
||
Key: room:online:{roomId}
|
||
Member: userId
|
||
```
|
||
|
||
**用户连接映射(String)**
|
||
```
|
||
Key: user:connection:{userId}
|
||
Value: {serverId, sessionId}
|
||
```
|
||
|
||
**离线消息队列(List)**
|
||
```
|
||
Key: offline:msg:{userId}
|
||
Value: [msgId1, msgId2, ...]
|
||
```
|
||
|
||
**消息缓存(String,TTL=7天)**
|
||
```
|
||
Key: msg:cache:{msgId}
|
||
Value: JSON消息体
|
||
```
|
||
|
||
---
|
||
|
||
## 四、核心功能实现
|
||
|
||
### 4.1 连接建立流程
|
||
|
||
```
|
||
1. 客户端发起WebSocket连接
|
||
ws://domain/ws?token=xxx
|
||
|
||
2. 服务端验证token
|
||
- 解析JWT token
|
||
- 验证用户身份
|
||
- 检查用户状态
|
||
|
||
3. 连接成功
|
||
- 保存连接到内存Map
|
||
- 更新Redis在线状态
|
||
- 返回连接成功消息
|
||
|
||
4. 拉取离线消息
|
||
- 从Redis获取离线消息ID列表
|
||
- 批量查询消息详情
|
||
- 推送给客户端
|
||
|
||
5. 启动心跳
|
||
- 客户端每30秒发送ping
|
||
- 服务端响应pong
|
||
- 超过90秒无心跳则断开连接
|
||
```
|
||
|
||
### 4.2 单聊消息流程
|
||
|
||
```
|
||
1. 客户端发送消息
|
||
{
|
||
"action": "sendMessage",
|
||
"toUserId": "123",
|
||
"msgType": "text",
|
||
"content": "你好"
|
||
}
|
||
|
||
2. 服务端处理
|
||
- 生成msgId(UUID)
|
||
- 敏感词过滤
|
||
- 保存到MySQL
|
||
- 缓存到Redis(7天)
|
||
|
||
3. 消息路由
|
||
- 查询接收者在线状态
|
||
- 如果在线:通过WebSocket推送
|
||
- 如果离线:存入离线消息队列
|
||
|
||
4. 客户端接收
|
||
- 收到消息后发送ACK确认
|
||
- 更新会话列表
|
||
- 显示消息内容
|
||
|
||
5. 已读回执(可选)
|
||
- 用户查看消息后发送已读状态
|
||
- 更新消息已读标记
|
||
- 通知发送方
|
||
```
|
||
|
||
### 4.3 直播间弹幕流程
|
||
|
||
```
|
||
1. 用户进入直播间
|
||
- 发送enterRoom消息
|
||
- 加入Redis直播间在线集合
|
||
- 广播进入消息(可选)
|
||
|
||
2. 发送弹幕
|
||
{
|
||
"action": "sendBarrage",
|
||
"roomId": "room_001",
|
||
"content": "主播666"
|
||
}
|
||
|
||
3. 服务端处理
|
||
- 频率限制(1秒1条)
|
||
- 敏感词过滤
|
||
- 保存到MySQL(可异步)
|
||
- 不缓存到Redis(弹幕量大)
|
||
|
||
4. 消息广播
|
||
- 获取直播间所有在线用户
|
||
- 遍历推送消息
|
||
- 失败的用户从在线列表移除
|
||
|
||
5. 离开直播间
|
||
- 发送leaveRoom消息
|
||
- 从Redis在线集合移除
|
||
```
|
||
|
||
### 4.4 礼物打赏流程
|
||
|
||
**场景一:直播间送礼**
|
||
```
|
||
1. 客户端发送礼物
|
||
{
|
||
"action": "sendGift",
|
||
"roomId": "room_001",
|
||
"anchorId": "456",
|
||
"giftId": "gift_001",
|
||
"count": 1
|
||
}
|
||
|
||
2. 服务端处理
|
||
- 验证用户余额
|
||
- 扣除金币
|
||
- 增加主播收益
|
||
- 保存打赏记录
|
||
|
||
3. 消息广播
|
||
- 构造礼物消息
|
||
- 广播到直播间所有用户
|
||
- 触发礼物动画
|
||
|
||
4. 通知主播
|
||
- 发送私信通知
|
||
- 更新收益统计
|
||
```
|
||
|
||
**场景二:私聊送礼(社交场景)**
|
||
```
|
||
1. 客户端发送礼物
|
||
{
|
||
"action": "sendPrivateGift",
|
||
"toUserId": "789",
|
||
"giftId": "gift_002",
|
||
"count": 1,
|
||
"message": "送你一朵玫瑰"
|
||
}
|
||
|
||
2. 服务端处理
|
||
- 验证用户余额
|
||
- 扣除金币
|
||
- 增加对方收益
|
||
- 保存礼物记录
|
||
- 构造礼物消息
|
||
|
||
3. 消息推送
|
||
- 推送给接收者
|
||
- 显示礼物卡片
|
||
- 触发礼物动画
|
||
- 更新会话列表
|
||
|
||
4. 收益通知
|
||
- 发送收益通知
|
||
- 更新用户余额
|
||
```
|
||
|
||
### 4.5 好友关系管理
|
||
|
||
**添加好友流程**
|
||
```
|
||
1. 发送好友申请
|
||
{
|
||
"action": "sendFriendRequest",
|
||
"toUserId": "123",
|
||
"message": "你好,可以加个好友吗?"
|
||
}
|
||
|
||
2. 服务端处理
|
||
- 检查是否已是好友
|
||
- 检查是否已发送过申请
|
||
- 保存好友申请记录
|
||
- 构造申请通知消息
|
||
|
||
3. 推送通知
|
||
- 推送给目标用户
|
||
- 显示好友申请红点
|
||
- 更新申请列表
|
||
|
||
4. 处理申请
|
||
{
|
||
"action": "handleFriendRequest",
|
||
"requestId": "456",
|
||
"accept": true
|
||
}
|
||
|
||
5. 同意后处理
|
||
- 更新申请状态
|
||
- 双向添加好友关系
|
||
- 发送同意通知
|
||
- 创建会话
|
||
```
|
||
|
||
**删除好友流程**
|
||
```
|
||
1. 客户端请求删除
|
||
{
|
||
"action": "deleteFriend",
|
||
"friendId": "123"
|
||
}
|
||
|
||
2. 服务端处理
|
||
- 删除好友关系
|
||
- 保留历史消息(可选)
|
||
- 发送删除通知(可选)
|
||
```
|
||
|
||
### 4.6 多媒体消息处理
|
||
|
||
**图片消息流程**
|
||
```
|
||
1. 上传图片
|
||
POST /api/upload/image
|
||
- 客户端先上传图片到服务器
|
||
- 返回图片URL和缩略图URL
|
||
|
||
2. 发送图片消息
|
||
{
|
||
"action": "sendMessage",
|
||
"toUserId": "123",
|
||
"msgType": "image",
|
||
"content": "imageUrl",
|
||
"extra": {
|
||
"thumbnail": "thumbnailUrl",
|
||
"width": 1080,
|
||
"height": 1920,
|
||
"size": 102400
|
||
}
|
||
}
|
||
|
||
3. 服务端处理
|
||
- 验证图片URL合法性
|
||
- 保存消息记录
|
||
- 推送给接收者
|
||
|
||
4. 客户端显示
|
||
- 先显示缩略图
|
||
- 点击查看大图
|
||
- 支持图片保存
|
||
```
|
||
|
||
**语音消息流程**
|
||
```
|
||
1. 录制语音
|
||
- 客户端录制语音(最长60秒)
|
||
- 转换为MP3/AAC格式
|
||
- 上传到服务器
|
||
|
||
2. 发送语音消息
|
||
{
|
||
"action": "sendMessage",
|
||
"toUserId": "123",
|
||
"msgType": "voice",
|
||
"content": "voiceUrl",
|
||
"extra": {
|
||
"duration": 15,
|
||
"size": 51200
|
||
}
|
||
}
|
||
|
||
3. 服务端处理
|
||
- 验证语音文件
|
||
- 保存消息记录
|
||
- 推送给接收者
|
||
|
||
4. 客户端播放
|
||
- 显示语音时长
|
||
- 点击播放
|
||
- 显示播放进度
|
||
- 标记已读
|
||
```
|
||
|
||
**视频消息流程**
|
||
```
|
||
1. 上传视频
|
||
POST /api/upload/video
|
||
- 限制视频大小(如50MB)
|
||
- 限制视频时长(如30秒)
|
||
- 生成视频封面
|
||
|
||
2. 发送视频消息
|
||
{
|
||
"action": "sendMessage",
|
||
"toUserId": "123",
|
||
"msgType": "video",
|
||
"content": "videoUrl",
|
||
"extra": {
|
||
"cover": "coverUrl",
|
||
"duration": 20,
|
||
"width": 720,
|
||
"height": 1280,
|
||
"size": 5242880
|
||
}
|
||
}
|
||
|
||
3. 客户端显示
|
||
- 显示视频封面
|
||
- 显示时长标识
|
||
- 点击播放
|
||
```
|
||
|
||
### 4.7 消息已读回执
|
||
|
||
**已读状态更新**
|
||
```
|
||
1. 用户查看消息
|
||
{
|
||
"action": "markAsRead",
|
||
"conversationId": "123",
|
||
"lastMsgId": "msg_456"
|
||
}
|
||
|
||
2. 服务端处理
|
||
- 更新消息已读状态
|
||
- 更新会话未读数
|
||
- 记录已读时间
|
||
|
||
3. 推送已读回执
|
||
- 通知发送方消息已读
|
||
- 显示"已读"标识
|
||
- 更新发送方会话列表
|
||
```
|
||
|
||
**未读消息统计**
|
||
```
|
||
1. 获取未读数
|
||
GET /api/message/unreadCount
|
||
Response: {
|
||
"totalUnread": 15,
|
||
"conversations": [
|
||
{"conversationId": 1, "unread": 5},
|
||
{"conversationId": 2, "unread": 10}
|
||
]
|
||
}
|
||
|
||
2. 实时更新
|
||
- 收到新消息时更新未读数
|
||
- 标记已读时减少未读数
|
||
- 推送未读数变化
|
||
```
|
||
|
||
### 4.8 消息撤回功能
|
||
|
||
**撤回流程**
|
||
```
|
||
1. 客户端请求撤回
|
||
{
|
||
"action": "recallMessage",
|
||
"msgId": "msg_123"
|
||
}
|
||
|
||
2. 服务端验证
|
||
- 检查消息是否存在
|
||
- 检查是否是发送者本人
|
||
- 检查是否在2分钟内
|
||
- 更新消息状态为已撤回
|
||
|
||
3. 推送撤回通知
|
||
- 通知接收者消息已撤回
|
||
- 显示"对方撤回了一条消息"
|
||
- 更新会话最后一条消息
|
||
|
||
4. 客户端处理
|
||
- 移除或替换消息内容
|
||
- 显示撤回提示
|
||
```
|
||
|
||
### 4.9 社交互动通知
|
||
|
||
**点赞通知**
|
||
```
|
||
1. 用户点赞作品/评论
|
||
POST /api/like
|
||
{
|
||
"targetType": "work|comment",
|
||
"targetId": 123
|
||
}
|
||
|
||
2. 服务端处理
|
||
- 保存点赞记录
|
||
- 构造通知消息
|
||
- 推送给作品作者
|
||
|
||
3. 通知消息格式
|
||
{
|
||
"type": "like",
|
||
"data": {
|
||
"userId": 456,
|
||
"username": "张三",
|
||
"avatar": "xxx",
|
||
"targetType": "work",
|
||
"targetId": 123,
|
||
"targetTitle": "作品标题",
|
||
"timestamp": 1640000000000
|
||
}
|
||
}
|
||
```
|
||
|
||
**评论通知**
|
||
```
|
||
1. 用户发表评论
|
||
POST /api/comment
|
||
{
|
||
"workId": 123,
|
||
"content": "很棒的作品!"
|
||
}
|
||
|
||
2. 服务端处理
|
||
- 保存评论记录
|
||
- 构造通知消息
|
||
- 推送给作品作者
|
||
|
||
3. @提醒处理
|
||
- 解析评论中的@用户
|
||
- 发送@提醒通知
|
||
- 推送给被@的用户
|
||
```
|
||
|
||
**关注通知**
|
||
```
|
||
1. 用户关注他人
|
||
POST /api/follow
|
||
{
|
||
"targetUserId": 123
|
||
}
|
||
|
||
2. 服务端处理
|
||
- 保存关注关系
|
||
- 构造通知消息
|
||
- 推送给被关注者
|
||
|
||
3. 通知消息
|
||
{
|
||
"type": "follow",
|
||
"data": {
|
||
"userId": 456,
|
||
"username": "张三",
|
||
"avatar": "xxx",
|
||
"timestamp": 1640000000000
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.5 系统通知流程
|
||
|
||
```
|
||
1. 后台触发通知
|
||
- 关注通知
|
||
- 点赞通知
|
||
- 评论通知
|
||
- 系统公告
|
||
- 审核通知
|
||
|
||
2. 服务端处理
|
||
- 构造系统消息
|
||
- 保存到MySQL
|
||
- 推送给目标用户
|
||
|
||
3. 客户端接收
|
||
- 显示通知红点
|
||
- 弹出提示(可选)
|
||
- 更新通知列表
|
||
```
|
||
|
||
---
|
||
|
||
## 五、性能优化方案
|
||
|
||
### 5.1 连接优化
|
||
|
||
**单机连接数优化**
|
||
- 使用Netty或Spring WebFlux(异步非阻塞)
|
||
- 单机支持10万+连接
|
||
- 调整系统参数:ulimit -n 100000
|
||
|
||
**连接保活**
|
||
- 客户端心跳:30秒
|
||
- 服务端超时:90秒
|
||
- 减少无效连接占用
|
||
|
||
### 5.2 消息优化
|
||
|
||
**弹幕限流**
|
||
- 用户级别:1秒1条
|
||
- 直播间级别:每秒1000条(超过则丢弃)
|
||
- 使用Redis令牌桶算法
|
||
|
||
**消息批量处理**
|
||
- 弹幕批量入库(100条/批次)
|
||
- 离线消息批量拉取(50条/次)
|
||
- 减少数据库IO
|
||
|
||
**消息压缩**
|
||
- 启用WebSocket压缩(permessage-deflate)
|
||
- 减少30-50%流量
|
||
|
||
### 5.3 存储优化
|
||
|
||
**MySQL优化**
|
||
- 消息表按月分表
|
||
- 历史消息归档到MongoDB
|
||
- 只保留3个月热数据
|
||
|
||
**Redis优化**
|
||
- 消息缓存设置TTL(7天)
|
||
- 使用Redis Cluster(后期扩展)
|
||
- 离线消息队列限制长度(最多100条)
|
||
|
||
### 5.4 广播优化
|
||
|
||
**直播间消息广播**
|
||
- 异步推送,不阻塞主线程
|
||
- 失败重试1次,超时则放弃
|
||
- 单次广播超时时间:100ms
|
||
|
||
**分组广播(后期优化)**
|
||
- 将直播间用户分组(每组1000人)
|
||
- 多线程并行推送
|
||
- 提升广播效率
|
||
|
||
---
|
||
|
||
## 六、技术栈选型
|
||
|
||
### 6.1 后端技术栈
|
||
|
||
**核心框架**
|
||
- Spring Boot 2.7+
|
||
- Spring WebSocket
|
||
- Spring Data JPA / MyBatis-Plus
|
||
|
||
**中间件**
|
||
- MySQL 8.0(主数据库)
|
||
- Redis 6.0+(缓存、在线状态)
|
||
- Nginx(反向代理、负载均衡)
|
||
|
||
**工具库**
|
||
- JWT(用户认证)
|
||
- Jackson(JSON序列化)
|
||
- Hutool(工具类)
|
||
- Lombok(简化代码)
|
||
|
||
### 6.2 前端技术栈(Android)
|
||
|
||
**网络库**
|
||
- OkHttp(HTTP请求)
|
||
- OkHttp WebSocket(长连接)
|
||
|
||
**UI框架**
|
||
- Material Design Components
|
||
- RecyclerView(消息列表)
|
||
- ViewPager2(直播间)
|
||
|
||
**工具库**
|
||
- Gson(JSON解析)
|
||
- Glide(图片加载)
|
||
- EventBus(事件总线)
|
||
|
||
### 6.3 开发工具
|
||
|
||
- IntelliJ IDEA(后端开发)
|
||
- Android Studio(Android开发)
|
||
- Postman(API测试)
|
||
- Redis Desktop Manager(Redis管理)
|
||
- Navicat(数据库管理)
|
||
|
||
---
|
||
|
||
## 七、部署方案
|
||
|
||
### 7.1 服务器配置(初期)
|
||
|
||
**单机部署方案**
|
||
```
|
||
服务器:阿里云ECS
|
||
配置:4核8G 5M带宽
|
||
系统:CentOS 7.9
|
||
|
||
服务部署:
|
||
- Nginx(80/443端口)
|
||
- Spring Boot IM服务(8080端口)
|
||
- MySQL(3306端口)
|
||
- Redis(6379端口)
|
||
```
|
||
|
||
**预估支持规模**
|
||
- 同时在线:5000人
|
||
- 单直播间:500人
|
||
- 消息吞吐:5000条/秒
|
||
|
||
### 7.2 扩展方案(后期)
|
||
|
||
**水平扩展**
|
||
```
|
||
负载均衡(Nginx)
|
||
↓
|
||
IM服务集群(3台)
|
||
↓
|
||
Redis集群(主从)
|
||
↓
|
||
MySQL主从(读写分离)
|
||
```
|
||
|
||
**支持规模**
|
||
- 同时在线:5万人
|
||
- 单直播间:5000人
|
||
- 消息吞吐:5万条/秒
|
||
|
||
---
|
||
|
||
## 八、开发计划(7天)
|
||
|
||
### Day 1-2:基础架构搭建
|
||
- [x] 搭建Spring Boot项目
|
||
- [x] 集成WebSocket
|
||
- [x] 配置MySQL、Redis
|
||
- [x] 实现用户认证(JWT)
|
||
- [x] 实现连接管理模块
|
||
|
||
### Day 3-4:核心功能开发
|
||
- [x] 实现单聊消息收发
|
||
- [x] 实现直播间弹幕
|
||
- [x] 实现离线消息
|
||
- [x] 实现消息存储
|
||
- [x] 实现敏感词过滤
|
||
|
||
### Day 5:业务功能开发
|
||
- [x] 实现礼物打赏消息
|
||
- [x] 实现系统通知
|
||
- [x] 实现消息已读
|
||
- [x] 实现历史消息查询
|
||
|
||
### Day 6:Android客户端对接
|
||
- [x] 实现WebSocket连接
|
||
- [x] 实现消息收发UI
|
||
- [x] 实现弹幕显示
|
||
- [x] 实现礼物动画
|
||
|
||
### Day 7:测试与优化
|
||
- [x] 功能测试
|
||
- [x] 压力测试(JMeter)
|
||
- [x] 性能优化
|
||
- [x] 部署上线
|
||
|
||
---
|
||
|
||
## 九、接口设计
|
||
|
||
### 9.1 WebSocket接口
|
||
|
||
**连接地址**
|
||
```
|
||
ws://domain/ws?token=xxx
|
||
```
|
||
|
||
**客户端发送消息格式**
|
||
```json
|
||
{
|
||
"action": "sendMessage|sendBarrage|enterRoom|leaveRoom|heartbeat",
|
||
"data": {
|
||
// 具体数据
|
||
}
|
||
}
|
||
```
|
||
|
||
**服务端推送消息格式**
|
||
```json
|
||
{
|
||
"type": "message|barrage|gift|system|notification",
|
||
"data": {
|
||
// 具体数据
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.2 HTTP接口
|
||
|
||
**用户认证**
|
||
```
|
||
POST /api/auth/login
|
||
Body: {
|
||
"username": "user123",
|
||
"password": "xxx"
|
||
}
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"token": "jwt_token",
|
||
"userId": 123,
|
||
"username": "user123",
|
||
"nickname": "张三",
|
||
"avatar": "xxx"
|
||
}
|
||
}
|
||
```
|
||
|
||
**好友相关接口**
|
||
|
||
**获取好友列表**
|
||
```
|
||
GET /api/friend/list
|
||
Response: {
|
||
"code": 200,
|
||
"data": [
|
||
{
|
||
"friendId": 123,
|
||
"username": "user123",
|
||
"nickname": "张三",
|
||
"avatar": "xxx",
|
||
"remark": "备注名",
|
||
"online": true,
|
||
"lastOnlineTime": "2024-12-25 10:00:00"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**发送好友申请**
|
||
```
|
||
POST /api/friend/request
|
||
Body: {
|
||
"toUserId": 123,
|
||
"message": "你好,可以加个好友吗?"
|
||
}
|
||
Response: {
|
||
"code": 200,
|
||
"message": "申请已发送"
|
||
}
|
||
```
|
||
|
||
**获取好友申请列表**
|
||
```
|
||
GET /api/friend/requests?status=0
|
||
Response: {
|
||
"code": 200,
|
||
"data": [
|
||
{
|
||
"requestId": 1,
|
||
"fromUserId": 456,
|
||
"username": "user456",
|
||
"nickname": "李四",
|
||
"avatar": "xxx",
|
||
"message": "你好",
|
||
"status": 0,
|
||
"createdAt": "2024-12-25 10:00:00"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**处理好友申请**
|
||
```
|
||
POST /api/friend/handle
|
||
Body: {
|
||
"requestId": 1,
|
||
"accept": true
|
||
}
|
||
Response: {
|
||
"code": 200,
|
||
"message": "已同意好友申请"
|
||
}
|
||
```
|
||
|
||
**删除好友**
|
||
```
|
||
DELETE /api/friend/{friendId}
|
||
Response: {
|
||
"code": 200,
|
||
"message": "已删除好友"
|
||
}
|
||
```
|
||
|
||
**会话相关接口**
|
||
|
||
**获取会话列表**
|
||
```
|
||
GET /api/conversation/list
|
||
Response: {
|
||
"code": 200,
|
||
"data": [
|
||
{
|
||
"conversationId": 1,
|
||
"type": 1,
|
||
"targetId": 123,
|
||
"targetName": "张三",
|
||
"targetAvatar": "xxx",
|
||
"lastMessage": "你好",
|
||
"lastMessageType": "text",
|
||
"lastTime": "2024-12-25 10:00:00",
|
||
"unreadCount": 3,
|
||
"online": true
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**删除会话**
|
||
```
|
||
DELETE /api/conversation/{conversationId}
|
||
Response: {
|
||
"code": 200,
|
||
"message": "会话已删除"
|
||
}
|
||
```
|
||
|
||
**消息相关接口**
|
||
|
||
**获取历史消息**
|
||
```
|
||
GET /api/message/history?targetId=123&pageSize=20&lastMsgId=xxx
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"messages": [
|
||
{
|
||
"msgId": "msg_001",
|
||
"msgType": "text",
|
||
"fromUserId": 456,
|
||
"toUserId": 123,
|
||
"content": "你好",
|
||
"extra": {},
|
||
"status": 1,
|
||
"isRead": 1,
|
||
"createdAt": "2024-12-25 10:00:00"
|
||
}
|
||
],
|
||
"hasMore": true
|
||
}
|
||
}
|
||
```
|
||
|
||
**上传图片**
|
||
```
|
||
POST /api/upload/image
|
||
Content-Type: multipart/form-data
|
||
Body: file=xxx
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"url": "https://xxx.com/image.jpg",
|
||
"thumbnail": "https://xxx.com/image_thumb.jpg",
|
||
"width": 1080,
|
||
"height": 1920,
|
||
"size": 102400
|
||
}
|
||
}
|
||
```
|
||
|
||
**上传语音**
|
||
```
|
||
POST /api/upload/voice
|
||
Content-Type: multipart/form-data
|
||
Body: file=xxx
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"url": "https://xxx.com/voice.mp3",
|
||
"duration": 15,
|
||
"size": 51200
|
||
}
|
||
}
|
||
```
|
||
|
||
**上传视频**
|
||
```
|
||
POST /api/upload/video
|
||
Content-Type: multipart/form-data
|
||
Body: file=xxx
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"url": "https://xxx.com/video.mp4",
|
||
"cover": "https://xxx.com/cover.jpg",
|
||
"duration": 20,
|
||
"width": 720,
|
||
"height": 1280,
|
||
"size": 5242880
|
||
}
|
||
}
|
||
```
|
||
|
||
**发送图片消息**
|
||
```
|
||
POST /api/message/sendImage
|
||
Body: {
|
||
"toUserId": 123,
|
||
"imageUrl": "xxx",
|
||
"thumbnail": "xxx",
|
||
"width": 1080,
|
||
"height": 1920
|
||
}
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"msgId": "msg_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
**标记消息已读**
|
||
```
|
||
POST /api/message/markRead
|
||
Body: {
|
||
"conversationId": 1,
|
||
"lastMsgId": "msg_456"
|
||
}
|
||
Response: {
|
||
"code": 200,
|
||
"message": "已标记为已读"
|
||
}
|
||
```
|
||
|
||
**撤回消息**
|
||
```
|
||
POST /api/message/recall
|
||
Body: {
|
||
"msgId": "msg_123"
|
||
}
|
||
Response: {
|
||
"code": 200,
|
||
"message": "消息已撤回"
|
||
}
|
||
```
|
||
|
||
**获取未读消息数**
|
||
```
|
||
GET /api/message/unreadCount
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"totalUnread": 15,
|
||
"conversations": [
|
||
{"conversationId": 1, "unread": 5},
|
||
{"conversationId": 2, "unread": 10}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**礼物相关接口**
|
||
|
||
**获取礼物列表**
|
||
```
|
||
GET /api/gift/list
|
||
Response: {
|
||
"code": 200,
|
||
"data": [
|
||
{
|
||
"giftId": 1,
|
||
"name": "玫瑰",
|
||
"icon": "xxx",
|
||
"price": 10,
|
||
"animation": "rose_animation"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**私聊送礼**
|
||
```
|
||
POST /api/gift/sendPrivate
|
||
Body: {
|
||
"toUserId": 123,
|
||
"giftId": 1,
|
||
"count": 1,
|
||
"message": "送你一朵玫瑰"
|
||
}
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"recordId": 1,
|
||
"balance": 990
|
||
}
|
||
}
|
||
```
|
||
|
||
**直播间送礼**
|
||
```
|
||
POST /api/gift/sendLive
|
||
Body: {
|
||
"roomId": "room_001",
|
||
"anchorId": 456,
|
||
"giftId": 1,
|
||
"count": 1
|
||
}
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"recordId": 1,
|
||
"balance": 990
|
||
}
|
||
}
|
||
```
|
||
|
||
**获取礼物记录**
|
||
```
|
||
GET /api/gift/records?type=send&pageSize=20&page=1
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"records": [
|
||
{
|
||
"recordId": 1,
|
||
"fromUserId": 123,
|
||
"toUserId": 456,
|
||
"giftName": "玫瑰",
|
||
"count": 1,
|
||
"totalPrice": 10,
|
||
"scene": "chat",
|
||
"createdAt": "2024-12-25 10:00:00"
|
||
}
|
||
],
|
||
"total": 100
|
||
}
|
||
}
|
||
```
|
||
|
||
**通知相关接口**
|
||
|
||
**获取通知列表**
|
||
```
|
||
GET /api/notification/list?type=all&pageSize=20&page=1
|
||
Response: {
|
||
"code": 200,
|
||
"data": [
|
||
{
|
||
"notificationId": 1,
|
||
"type": "like",
|
||
"fromUserId": 456,
|
||
"fromUsername": "张三",
|
||
"fromAvatar": "xxx",
|
||
"content": "赞了你的作品",
|
||
"targetType": "work",
|
||
"targetId": 123,
|
||
"isRead": 0,
|
||
"createdAt": "2024-12-25 10:00:00"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**标记通知已读**
|
||
```
|
||
POST /api/notification/markRead
|
||
Body: {
|
||
"notificationIds": [1, 2, 3]
|
||
}
|
||
Response: {
|
||
"code": 200,
|
||
"message": "已标记为已读"
|
||
}
|
||
```
|
||
|
||
**获取未读通知数**
|
||
```
|
||
GET /api/notification/unreadCount
|
||
Response: {
|
||
"code": 200,
|
||
"data": {
|
||
"like": 5,
|
||
"comment": 3,
|
||
"follow": 2,
|
||
"system": 1,
|
||
"total": 11
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十、安全方案
|
||
|
||
### 10.1 认证鉴权
|
||
- 使用JWT token认证
|
||
- Token有效期:7天
|
||
- 刷新Token机制
|
||
|
||
### 10.2 消息安全
|
||
- 敏感词过滤(本地词库)
|
||
- 消息内容长度限制(500字符)
|
||
- 图片内容审核(接入第三方)
|
||
|
||
### 10.3 防刷机制
|
||
- 消息发送频率限制
|
||
- IP黑名单
|
||
- 用户封禁机制
|
||
|
||
### 10.4 数据安全
|
||
- WebSocket使用wss加密
|
||
- 数据库密码加密存储
|
||
- 敏感信息脱敏
|
||
|
||
---
|
||
|
||
## 十一、监控与运维
|
||
|
||
### 11.1 监控指标
|
||
- 在线用户数
|
||
- 消息发送量(QPS)
|
||
- 接口响应时间
|
||
- 服务器CPU/内存/网络
|
||
|
||
### 11.2 日志管理
|
||
- 使用SLF4J + Logback
|
||
- 日志分级:ERROR、WARN、INFO
|
||
- 日志文件按天切割
|
||
- 保留30天日志
|
||
|
||
### 11.3 告警机制
|
||
- 服务宕机告警
|
||
- 接口异常告警
|
||
- 资源使用告警
|
||
|
||
---
|
||
|
||
## 十二、成本预估
|
||
|
||
### 12.1 服务器成本
|
||
- 阿里云ECS 4核8G:约300元/月
|
||
- 带宽5M:约50元/月
|
||
- 总计:约350元/月
|
||
|
||
### 12.2 开发成本
|
||
- 后端开发:3人天
|
||
- Android开发:2人天
|
||
- 测试:1人天
|
||
- 总计:6人天
|
||
|
||
### 12.3 第三方服务(可选)
|
||
- 图片审核:按量计费
|
||
- 短信通知:按量计费
|
||
- CDN加速:按流量计费
|
||
|
||
---
|
||
|
||
## 十三、风险与应对
|
||
|
||
### 13.1 技术风险
|
||
**风险:** 单机性能瓶颈
|
||
**应对:** 提前设计水平扩展方案,预留扩展接口
|
||
|
||
**风险:** 消息丢失
|
||
**应对:** 实现消息ACK机制,离线消息队列
|
||
|
||
**风险:** 直播间消息风暴
|
||
**应对:** 消息限流、降级策略
|
||
|
||
### 13.2 业务风险
|
||
**风险:** 用户量超预期
|
||
**应对:** 快速扩容方案,提前准备服务器
|
||
|
||
**风险:** 恶意刷屏
|
||
**应对:** 频率限制、封禁机制
|
||
|
||
---
|
||
|
||
## 十四、后续优化方向
|
||
|
||
### 14.1 功能优化
|
||
- 消息撤回
|
||
- 消息转发
|
||
- @提醒功能
|
||
- 表情包支持
|
||
- 语音/视频通话
|
||
|
||
### 14.2 性能优化
|
||
- 引入消息队列(RabbitMQ/Kafka)
|
||
- 数据库分库分表
|
||
- 引入ElasticSearch(消息搜索)
|
||
- CDN加速(图片、语音)
|
||
|
||
### 14.3 架构优化
|
||
- 微服务拆分
|
||
- 服务注册与发现(Nacos)
|
||
- 分布式链路追踪(SkyWalking)
|
||
- 容器化部署(Docker + K8s)
|
||
|
||
---
|
||
|
||
## 附录
|
||
|
||
### A. 参考资料
|
||
- WebSocket协议:RFC 6455
|
||
- MQTT协议:MQTT 3.1.1
|
||
- 即时通讯技术选型:https://www.zhihu.com/question/xxx
|
||
|
||
### B. 开源方案参考
|
||
- Netty-SocketIO(Java WebSocket框架)
|
||
- t-io(国产高性能IM框架)
|
||
- OpenIM(开源IM解决方案)
|
||
|
||
### C. 敏感词库
|
||
- 使用DFA算法实现高效过滤
|
||
- 词库来源:GitHub开源项目
|
||
|
||
---
|
||
|
||
**文档版本:** v1.0
|
||
**编写日期:** 2024-12-25
|
||
**适用范围:** 1-10万用户规模的直播IM系统
|