# 离线消息功能实现说明 ## 📋 功能概述 离线消息服务是直播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 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