Files
urbanLifeline/urbanLifelineServ/workcase/聊天室实现文档.md
2025-12-22 13:08:08 +08:00

234 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 聊天室实现文档
## 1. 业务规则
- 一个工单只能创建一个聊天室,一个聊天室可以发起多次会议
- 创建聊天室时自动添加来客和所有在线客服到成员表
- 消息发送使用分布式锁保证时间戳递增,避免并发乱序
---
## 2. 核心文件结构
```
workcase/
├── src/main/java/org/xyzh/workcase/
│ ├── service/
│ │ ├── ChatRoomServiceImpl.java # 聊天室服务实现
│ │ └── MeetServiceImpl.java # 会议服务实现(伪代码)
│ ├── listener/
│ │ └── ChatMessageListener.java # Redis消息监听器
│ ├── config/
│ │ ├── WebSocketConfig.java # STOMP WebSocket配置
│ │ └── RedisSubscriberConfig.java # Redis订阅配置
│ └── mapper/
│ ├── TbChatRoomMapper.java
│ ├── TbChatRoomMemberMapper.java
│ ├── TbChatMessageMapper.java
│ └── TbCustomerServiceMapper.java
apis/api-workcase/
├── src/main/java/org/xyzh/api/workcase/
│ ├── constant/
│ │ └── WorkcaseConstant.java # 常量定义
│ ├── service/
│ │ ├── ChatRoomService.java # 聊天室服务接口
│ │ └── MeetService.java # 会议服务接口
│ ├── dto/
│ │ ├── TbChatRoomDTO.java
│ │ ├── TbChatRoomMemberDTO.java
│ │ └── TbChatMessageDTO.java
│ └── vo/
│ ├── ChatRoomVO.java
│ ├── ChatMemberVO.java
│ └── ChatMessageVO.java
```
---
## 3. Redis Key 设计
| Key | 说明 | 示例 |
|-----|------|------|
| `chat:room:{roomId}` | 聊天室消息Pub/Sub频道 | `chat:room:abc123` |
| `chat:room:online:{roomId}` | 在线用户Set | `chat:room:online:abc123` |
| `chat:room:lock:{roomId}` | 消息发送锁 | `chat:room:lock:abc123` |
| `chat:room:lasttime:{roomId}` | 最后消息时间戳 | `chat:room:lasttime:abc123` |
| `chat:list:update` | 聊天室列表更新通知频道 | `chat:list:update` |
---
## 4. 消息流转架构
```
┌─────────────────────────────────────────────────────────────────┐
│ 消息发送流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 客户端 ──HTTP POST──> ChatRoomServiceImpl.sendMessage() │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 获取分布式锁 │ │
│ │ chat:room:lock │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 递增时间戳保证 │ │
│ │ 消息顺序 │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 保存到数据库 │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ redisService.publish("chat:room:{roomId}") │
│ │ │
│ ▼ │
│ redisService.publish("chat:list:update") │
│ │ │
└──────────────────────────────┼──────────────────────────────────┘
┌──────────────────────────────┼──────────────────────────────────┐
│ 消息接收流程 │
├──────────────────────────────┼──────────────────────────────────┤
│ │ │
│ RedisSubscriberConfig (订阅 chat:room:*, chat:list:update)│
│ │ │
│ ▼ │
│ ChatMessageListener.onMessage() │
│ ┌────┴────┐ │
│ │ │ │
│ ▼ ▼ │
│ /topic/chat/{roomId} /topic/chat/list-update │
│ (聊天窗口消息) (聊天室列表更新) │
│ │ │ │
│ ▼ ▼ │
│ 聊天窗口订阅者 列表页面订阅者 │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 5. 用户类型常量
| 常量 | 值 | 说明 |
|------|-----|------|
| `USER_TYPE_GUEST` | `guest` | 来客 |
| `USER_TYPE_STAFF` | `staff` | 客服 |
| `USER_TYPE_AI` | `ai` | AI助手 |
---
## 6. 状态常量
### 聊天室状态
| 常量 | 值 | 说明 |
|------|-----|------|
| `ROOM_STATUS_ACTIVE` | `active` | 活跃 |
| `ROOM_STATUS_CLOSED` | `closed` | 已关闭 |
### 成员状态
| 常量 | 值 | 说明 |
|------|-----|------|
| `MEMBER_STATUS_ACTIVE` | `active` | 活跃 |
| `MEMBER_STATUS_LEFT` | `left` | 已离开 |
| `MEMBER_STATUS_REMOVED` | `removed` | 被移除 |
### 消息状态
| 常量 | 值 | 说明 |
|------|-----|------|
| `MESSAGE_STATUS_SENT` | `sent` | 已发送 |
| `MESSAGE_STATUS_RECALLED` | `recalled` | 已撤回 |
---
## 7. 并发消息处理
```java
// 获取分布式锁
String lockKey = WorkcaseConstant.REDIS_CHAT_LOCK + roomId;
redisService.setIfAbsent(lockKey, "1", 5); // 5秒过期
// 保证时间戳递增
String timeKey = WorkcaseConstant.REDIS_CHAT_LASTTIME + roomId;
long lastTime = redisService.get(timeKey);
if (currentTime <= lastTime) {
currentTime = lastTime + 1;
}
message.setSendTime(new Date(currentTime));
redisService.set(timeKey, currentTime);
// 释放锁
redisService.delete(lockKey);
```
---
## 8. 前端WebSocket连接示例
```javascript
// 连接WebSocket
const socket = new SockJS('/ws/chat');
const stompClient = Stomp.over(socket);
stompClient.connect({}, () => {
// 1. 订阅聊天室消息(聊天窗口使用)
stompClient.subscribe('/topic/chat/' + roomId, (message) => {
const chatMessage = JSON.parse(message.body);
// 处理收到的消息,添加到聊天窗口
console.log('收到消息:', chatMessage);
});
// 2. 订阅聊天室列表更新(列表页面使用)
stompClient.subscribe('/topic/chat/list-update', (message) => {
const chatMessage = JSON.parse(message.body);
// 根据roomId更新对应聊天室的lastMessage和lastMessageTime
updateChatRoomInList(chatMessage.roomId, {
lastMessage: chatMessage.content,
lastMessageTime: chatMessage.sendTime,
senderName: chatMessage.senderName
});
});
});
// 断开连接
stompClient.disconnect();
```
---
## 9. API接口
### ChatRoomService
| 方法 | 说明 |
|------|------|
| `createChatRoom(dto)` | 创建聊天室,自动添加来客和客服 |
| `updateChatRoom(dto)` | 更新聊天室信息 |
| `closeChatRoom(roomId, closedBy)` | 关闭聊天室 |
| `deleteChatRoom(roomId)` | 删除聊天室 |
| `getChatRoomById(roomId)` | 获取聊天室详情 |
| `getChatRoomPage(pageRequest)` | 分页查询聊天室 |
| `addChatRoomMember(member)` | 添加成员 |
| `removeChatRoomMember(memberId)` | 移除成员 |
| `sendMessage(message)` | 发送消息(含并发处理) |
| `getChatMessagePage(pageRequest)` | 分页查询消息 |
| `assignCustomerService(roomId)` | 分配所有客服到聊天室 |
### MeetService伪代码
| 方法 | 说明 |
|------|------|
| `createMeeting(dto)` | 创建会议 |
| `startMeeting(meetingId)` | 开始会议 |
| `endMeeting(meetingId)` | 结束会议 |
| `joinMeeting(participant)` | 参与者加入 |
| `leaveMeeting(participantId)` | 参与者离开 |
| `generateMeetingJoinUrl(...)` | 生成Jitsi加入链接 |
| `addTranscription(dto)` | 添加转录记录 |