zhibo/Zhibo/离线消息功能实现说明.md

392 lines
8.8 KiB
Markdown
Raw Normal View History

# 离线消息功能实现说明
## 📋 功能概述
离线消息服务是直播IM系统的核心功能之一用于在用户离线时保存消息并在用户重新上线时推送给用户。
## 🎯 核心功能
### 1. 消息保存
- 当用户离线时系统自动将发送给该用户的消息保存到Redis队列中
- 每个用户最多保存100条离线消息可配置
- 离线消息保存7天后自动过期可配置
### 2. 消息推送
- 用户上线时,系统自动推送所有离线消息
- 推送完成后自动清除已推送的离线消息
### 3. 消息管理
- 获取离线消息数量
- 获取离线消息列表(支持分页)
- 删除指定数量的离线消息
- 清空所有离线消息
## 🏗️ 技术架构
### 数据存储
使用Redis List存储离线消息
```
Key格式: offline:msg:{userId}
数据结构: List
过期时间: 7天
最大长度: 100条
```
### 核心类说明
#### 1. OfflineMessageService (接口)
定义离线消息服务的所有方法:
- `saveOfflineMessage()` - 保存离线消息
- `getOfflineMessages()` - 获取离线消息(分页)
- `getAllOfflineMessages()` - 获取所有离线消息
- `clearOfflineMessages()` - 清空离线消息
- `getOfflineMessageCount()` - 获取消息数量
- `removeOfflineMessages()` - 删除指定数量的消息
#### 2. OfflineMessageServiceImpl (实现类)
实现离线消息服务的具体逻辑:
- 使用RedisUtil操作Redis
- 自动添加时间戳
- 限制消息数量
- 设置过期时间
#### 3. OfflineMessageController (控制器)
提供HTTP接口
- `GET /api/front/offline-messages/count/{userId}` - 获取消息数量
- `GET /api/front/offline-messages/list/{userId}` - 获取消息列表
- `GET /api/front/offline-messages/all/{userId}` - 获取所有消息
- `DELETE /api/front/offline-messages/clear/{userId}` - 清空消息
- `DELETE /api/front/offline-messages/remove/{userId}` - 删除指定数量
- `POST /api/front/offline-messages/save` - 保存消息(测试接口)
#### 4. OfflineMessageCleanupTask (定时任务)
定期清理和监控离线消息:
- 每天凌晨3点统计离线消息使用情况
- 每小时检查离线消息健康状况
- 发现异常大的消息队列时发出告警
## 🔄 工作流程
### 消息发送流程
```
1. 用户A发送消息给用户B
2. 检查用户B是否在线
3. 如果在线 → 直接推送消息
4. 如果离线 → 保存到离线消息队列
5. 用户B上线时 → 自动推送离线消息
6. 推送完成 → 清除已推送的消息
```
### WebSocket集成
在`PrivateChatHandler`中已经集成了离线消息功能:
```java
@Override
public void afterConnectionEstablished(WebSocketSession session) {
// ... 连接建立逻辑 ...
// 推送离线消息
List<String> offlineMessages = offlineMessageService.getAllOfflineMessages(userId);
if (!offlineMessages.isEmpty()) {
for (String offlineMsg : offlineMessages) {
sendToSession(session, offlineMsg);
}
// 清除已推送的离线消息
offlineMessageService.clearOfflineMessages(userId);
}
}
```
在消息发送时检查用户是否在线:
```java
// 检查对方是否在线
if (onlineStatusService.isUserOnline(otherUserId)) {
// 在线则直接推送
notifyUser(otherUserId, buildNewMessageNotification(conversationId, response));
} else {
// 离线则保存为离线消息
offlineMessageService.saveOfflineMessage(otherUserId, chatMsg);
}
```
## 📊 Redis数据结构
### 离线消息队列
```redis
# Key格式
offline:msg:{userId}
# 数据类型
List
# 示例数据
LRANGE offline:msg:1 0 -1
1) "{\"type\":\"chat\",\"content\":\"你好\",\"timestamp\":1234567890,\"savedAt\":1234567900}"
2) "{\"type\":\"chat\",\"content\":\"在吗\",\"timestamp\":1234567891,\"savedAt\":1234567901}"
# 过期时间
TTL offline:msg:1
604800 # 7天 = 7 * 24 * 3600秒
```
### 消息格式
```json
{
"type": "chat",
"messageId": "msg_123456",
"userId": 2,
"username": "张三",
"avatarUrl": "http://example.com/avatar.jpg",
"message": "你好,在吗?",
"timestamp": 1234567890,
"savedAt": 1234567900,
"status": "sent"
}
```
## 🔧 配置说明
### 可配置参数
在`OfflineMessageServiceImpl`中:
```java
// 离线消息最大保存数量(每个用户)
private static final int MAX_OFFLINE_MESSAGES = 100;
// 离线消息过期时间(秒)- 7天
private static final long OFFLINE_MESSAGE_EXPIRE_SECONDS = 7 * 24 * 3600;
```
### 修改配置
如果需要修改配置,可以:
1. 直接修改常量值
2. 或者改为从配置文件读取:
```java
@Value("${offline.message.max-count:100}")
private int maxOfflineMessages;
@Value("${offline.message.expire-seconds:604800}")
private long offlineMessageExpireSeconds;
```
然后在`application.yml`中配置:
```yaml
offline:
message:
max-count: 100 # 最大保存数量
expire-seconds: 604800 # 过期时间7天
```
## 🧪 测试方法
### 1. 使用测试HTML页面
打开`测试离线消息功能.html`文件,可以测试:
- 保存离线消息
- 获取离线消息
- 删除离线消息
- 清空离线消息
### 2. 使用Postman测试
#### 保存离线消息
```http
POST http://localhost:8081/api/front/offline-messages/save?userId=1
Content-Type: application/json
{
"type": "chat",
"content": "测试消息",
"timestamp": 1234567890
}
```
#### 获取消息数量
```http
GET http://localhost:8081/api/front/offline-messages/count/1
```
#### 获取消息列表
```http
GET http://localhost:8081/api/front/offline-messages/list/1?limit=50
```
#### 获取所有消息
```http
GET http://localhost:8081/api/front/offline-messages/all/1
```
#### 删除指定数量
```http
DELETE http://localhost:8081/api/front/offline-messages/remove/1?count=5
```
#### 清空所有消息
```http
DELETE http://localhost:8081/api/front/offline-messages/clear/1
```
### 3. 使用Redis CLI测试
```bash
# 查看用户1的离线消息
redis-cli LRANGE offline:msg:1 0 -1
# 查看消息数量
redis-cli LLEN offline:msg:1
# 查看过期时间
redis-cli TTL offline:msg:1
# 手动添加消息
redis-cli LPUSH offline:msg:1 '{"type":"chat","content":"测试"}'
# 手动删除消息
redis-cli DEL offline:msg:1
```
## 📈 性能优化
### 1. 批量操作
- 使用Redis Pipeline批量获取多个用户的离线消息
- 减少网络往返次数
### 2. 消息限制
- 限制每个用户最多100条离线消息
- 超过限制时自动删除最旧的消息
- 避免内存占用过大
### 3. 过期策略
- 设置7天过期时间
- Redis自动清理过期数据
- 减少手动清理的开销
### 4. 异步推送
- 用户上线时异步推送离线消息
- 不阻塞WebSocket连接建立
- 提高用户体验
## 🔒 安全考虑
### 1. 权限验证
- 只能获取自己的离线消息
- 需要在Controller中添加Token验证
- 防止越权访问
### 2. 消息加密
- 敏感消息可以加密存储
- 推送时解密
- 保护用户隐私
### 3. 限流保护
- 限制API调用频率
- 防止恶意刷接口
- 保护系统稳定性
## 🐛 常见问题
### 1. 离线消息没有推送?
**可能原因:**
- 用户没有真正离线(还有其他设备在线)
- WebSocket连接建立失败
- Redis连接异常
**解决方法:**
- 检查在线状态服务
- 查看WebSocket连接日志
- 检查Redis连接
### 2. 离线消息丢失?
**可能原因:**
- Redis数据过期
- 消息超过100条被删除
- Redis重启导致数据丢失
**解决方法:**
- 增加过期时间
- 增加最大消息数量
- 启用Redis持久化AOF/RDB
### 3. 离线消息重复推送?
**可能原因:**
- 清除消息失败
- 用户多次连接
- 并发问题
**解决方法:**
- 添加消息去重逻辑
- 使用分布式锁
- 检查清除逻辑
## 📝 后续优化建议
### 1. 消息持久化
- 将重要消息同时保存到MySQL
- Redis作为缓存MySQL作为持久化存储
- 提高数据可靠性
### 2. 消息优先级
- 支持消息优先级
- 重要消息优先推送
- 普通消息延迟推送
### 3. 消息分类
- 支持不同类型的离线消息
- 文本消息、图片消息、系统通知等
- 分别处理和展示
### 4. 消息统计
- 统计离线消息的发送量
- 分析用户活跃度
- 优化推送策略
### 5. 消息推送优化
- 支持增量推送
- 避免一次性推送大量消息
- 提高用户体验
## 📚 相关文档
- [直播IM系统开发指南.md](./直播IM系统开发指南.md)
- [WebSocket增强功能实现说明.md](./WebSocket增强功能实现说明.md)
- [功能验证清单.md](./功能验证清单.md)
## 🎉 总结
离线消息功能已经完整实现,包括:
✅ 消息保存和推送
✅ HTTP接口管理
✅ WebSocket集成
✅ 定时清理任务
✅ 测试页面
✅ 完整文档
可以开始测试和使用了!
---
**文档版本:** v1.0
**最后更新:** 2024-12-25
**作者:** Kiro AI Assistant