# 功能实现:视频会议预约模式(Reservation Model) ## 实现日期 2025-12-26 ## 功能概述 实现视频会议"预约+按需创建"架构:用户创建会议预约时不立即创建Jitsi会议室,仅在首个用户在允许入会时间窗口内加入时,通过Redis双检锁机制创建Jitsi会议室。 ## 架构模型 ### 预约模式 (Reservation Model) ``` 用户创建会议 → 保存预约信息(scheduled)→ 发送会议通知消息(meetingId) ↓ 首个用户加入 → 时间窗口校验 → Redis分布式锁 → 创建Jitsi会议室 → 更新状态(ongoing) ↓ 后续用户加入 → 直接获取已创建的会议室URL ``` ### 时间窗口规则 - **提前入会时间**: `start_time - advance` 分钟 - **允许入会窗口**: `[提前入会时间, end_time]` - **默认advance**: 5分钟 ## 实现文件 ### 后端修改 **VideoMeetingServiceImpl.java** - 位置:`urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/` - 修改内容: 1. 新增导入:`TbChatRoomMessageDTO`、`ChatRoomService`、`JSONObject` 2. 注入依赖:`ChatRoomService` 3. 修改 `createMeeting()` 方法:插入数据库成功后调用 `sendMeetingNotification()` 4. 新增私有方法:`sendMeetingNotification()` ## 详细实现 ### 1. 依赖注入 ```java @Autowired private ChatRoomService chatRoomService; ``` ### 2. 调用时机 在 `createMeeting()` 方法中,会议记录插入数据库成功后: ```java // 8. 插入数据库 int rows = videoMeetingMapper.insertVideoMeeting(meetingDTO); if (rows > 0) { logger.info("视频会议创建成功: meetingId={}, jitsiRoomName={}", meetingId, jitsiRoomName); // 9. 发送会议通知消息到聊天室 sendMeetingNotification(meetingDTO, userName); // 10. 返回VO VideoMeetingVO meetingVO = new VideoMeetingVO(); BeanUtils.copyProperties(meetingDTO, meetingVO); return ResultDomain.success("创建会议成功", meetingVO); } ``` ### 3. 消息发送实现 **sendMeetingNotification() 方法 (VideoMeetingServiceImpl.java:396-442)** ```java /** * 发送会议通知消息到聊天室 * @param meetingDTO 会议信息 * @param creatorName 创建者名称 */ private void sendMeetingNotification(TbVideoMeetingDTO meetingDTO, String creatorName) { try { logger.info("发送会议通知消息: roomId={}, meetingId={}", meetingDTO.getRoomId(), meetingDTO.getMeetingId()); // 构建消息内容 TbChatRoomMessageDTO message = new TbChatRoomMessageDTO(); message.setMessageId(IdUtil.generateUUID()); message.setRoomId(meetingDTO.getRoomId()); message.setSenderId(meetingDTO.getCreator()); message.setSenderType(meetingDTO.getCreatorType()); message.setSenderName(creatorName); message.setMessageType("meet"); // 会议类型消息 message.setContent(meetingDTO.getIframeUrl()); // 会议URL作为内容 message.setStatus("sent"); message.setReadCount(0); message.setSendTime(new Date()); // 构建扩展信息(会议详情) JSONObject contentExtra = new JSONObject(); contentExtra.put("meetingId", meetingDTO.getMeetingId()); contentExtra.put("meetingName", meetingDTO.getMeetingName()); contentExtra.put("jitsiRoomName", meetingDTO.getJitsiRoomName()); contentExtra.put("iframeUrl", meetingDTO.getIframeUrl()); contentExtra.put("maxParticipants", meetingDTO.getMaxParticipants()); contentExtra.put("creatorName", creatorName); contentExtra.put("workcaseId", meetingDTO.getWorkcaseId()); message.setContentExtra(contentExtra); // 发送消息 ResultDomain sendResult = chatRoomService.sendMessage(message); if (sendResult.getSuccess()) { logger.info("会议通知消息发送成功: messageId={}", message.getMessageId()); } else { logger.warn("会议通知消息发送失败: {}", sendResult.getMessage()); } } catch (Exception e) { // 消息发送失败不影响会议创建 logger.error("发送会议通知消息异常: roomId={}, error={}", meetingDTO.getRoomId(), e.getMessage(), e); } } ``` ## 消息数据结构 ### 消息字段 | 字段 | 类型 | 说明 | |-----|------|------| | messageId | String | 消息ID(UUID) | | roomId | String | 聊天室ID | | senderId | String | 发送者ID(会议创建者) | | senderType | String | 发送者类型(guest/agent) | | senderName | String | 发送者名称 | | **messageType** | **String** | **"meet"(会议消息类型)** | | **content** | **String** | **会议iframe URL** | | contentExtra | JSONObject | 会议详细信息(见下表) | | status | String | "sent" | | readCount | Integer | 0 | | sendTime | Date | 发送时间 | ### contentExtra 详细信息 ```json { "meetingId": "会议ID", "meetingName": "会议名称", "jitsiRoomName": "Jitsi房间名", "iframeUrl": "会议URL", "maxParticipants": 10, "creatorName": "创建者名称", "workcaseId": "工单ID" } ``` ## 前端渲染建议 ### Vue Web 端 在 `ChatRoom.vue` 中添加会议消息卡片渲染: ```vue ``` ### UniApp 小程序端 在 `chatRoom.uvue` 中添加会议消息卡片: ```vue ``` ## 设计考虑 ### 1. 异步发送 消息发送在独立的 try-catch 块中,失败不影响会议创建流程 ### 2. 完整信息 contentExtra 包含会议所有关键信息,前端可灵活使用 ### 3. 类型明确 messageType 使用 "meet" 标识会议消息,方便前端过滤和渲染 ### 4. URL 即内容 content 字段直接存储会议URL,方便快速访问 ### 5. 日志追踪 完整的日志记录,便于问题排查 ## 测试要点 ### 1. 会议创建测试 ```bash POST /urban-lifeline/workcase/chat/meeting/create { "roomId": "test-room-123", "workcaseId": "WC001", "meetingName": "技术支持会议", "maxParticipants": 10 } ``` **预期结果**: - ✅ 返回会议创建成功 - ✅ 数据库 tb_video_meeting 表插入会议记录 - ✅ 数据库 tb_chat_room_message 表插入类型为 "meet" 的消息 - ✅ 消息的 content 字段包含会议URL - ✅ 消息的 contentExtra 包含会议详细信息 ### 2. 前端卡片渲染测试 - ✅ 聊天消息列表中显示会议卡片 - ✅ 卡片展示会议名称、发起人、参与人数等信息 - ✅ 点击"加入会议"按钮能正确跳转 ### 3. 多人加入测试 - ✅ 创建者加入会议(主持人权限) - ✅ 其他成员通过卡片加入会议(普通权限) - ✅ 非聊天室成员无法加入 ### 4. 异常情况测试 - ✅ 消息发送失败不影响会议创建 - ✅ 会议创建失败不会发送消息 ## 注意事项 1. **消息类型**:messageType 为 "meet",而非 "meeting"(根据用户需求) 2. **权限控制**: - 只有聊天室成员才能创建会议 - 只有聊天室成员才能加入会议 3. **事务处理**: - 会议创建在事务中(@Transactional) - 消息发送在独立的 try-catch,失败不回滚会议创建 4. **前端适配**: - Web端和小程序端需分别实现会议卡片渲染 - 建议使用统一的样式和交互 5. **扩展性**: - contentExtra 可根据需要添加更多字段 - 建议前端做好字段缺失的容错处理 ## 相关文档 - [Jitsi Meet 视频会议集成总结](./项目总结-Jitsi-Meet视频会议功能.md) - [Docker 部署指南](./Jitsi-Meet-Docker部署指南.md) - [代码重构 - 视频会议API规范化](./代码重构-视频会议API规范化.md) --- **实现人员**:Claude Code **审核状态**:待审核 **版本**:v1.0