# 工单服务 + Jitsi Meet 视频会议 技术方案 ## 📋 目录 1. [业务流程分析](#业务流程分析) 2. [数据表结构设计](#数据表结构设计) 3. [核心服务功能](#核心服务功能) 4. [API接口设计](#api接口设计) 5. [前端集成方案](#前端集成方案) 6. [安全方案](#安全方案) --- ## 业务流程分析 ### 完整业务流程 ``` 用户进入小程序 ↓ AI客服对话(默认,存储在 ai.tb_chat 表) ↓ 连续3次AI对话后询问是否转人工 ↓ 用户触发转人工(可能一开始就手动触发,没有聊天记录) ↓ 【核心变更】创建IM聊天室(取代微信客服) ↓ 同步 ai.tb_chat 对话记录到聊天室 ↓ 客服人员加入聊天室(AI退出) ↓ 客服与客户IM对话(员工续接AI对话) ↓ 【可选】发起Jitsi Meet视频会议 ↓ 【可选】用户/员工在聊天室内创建工单(也可在web管理端) ↓ 工单处理和状态更新 ↓ 工单完成/撤销,生成总结和词云 ``` ### 关键改动点 **AI对话 → 转人工 → 创建聊天室** - AI对话存储在 `ai.tb_chat` 表(通过WorkcaseChatService调用AI接口) - 转人工时创建IM聊天室,同步AI对话记录 - 工单和会议没有前置关系,可在聊天室内随时创建 **取消微信客服 → 使用自建IM + Jitsi Meet** - IM聊天室:文字、图片、文件、语音消息 - 视频会议:通过iframe嵌入Jitsi Meet,最简实现 - 权限控制:只有聊天室成员才能创建/加入会议 --- ## 数据表结构设计 ### 核心表结构(6+1张表) #### 1. **tb_chat_room** - 聊天室表 ⭐核心表 **用途**:转人工时创建的聊天室,可关联工单 ```sql room_id -- 聊天室ID(主键) workcase_id -- 关联工单ID(可选,可后续绑定) room_name -- 聊天室名称 status -- 状态:active-活跃 closed-已关闭 archived-已归档 guest_id -- 来客ID guest_name -- 来客姓名 ai_session_id -- AI对话会话ID(从ai.tb_chat同步) current_agent_id -- 当前负责客服ID agent_count -- 已加入客服人数 message_count -- 消息总数 unread_count -- 未读消息数(客服端) last_message_time -- 最后消息时间 last_message -- 最后消息内容(列表展示用) ``` **业务规则**: - 转人工时创建聊天室,同步 ai.tb_chat 对话记录 - 用户/员工可在聊天室内创建工单,绑定 workcase_id - 聊天室可独立存在,无需绑定工单 - 工单完成后聊天室归档 --- #### 2. **tb_chat_room_member** - 聊天室成员表 **用途**:记录聊天室内的所有成员 ```sql member_id -- 成员记录ID(主键) room_id -- 聊天室ID user_id -- 用户ID(来客ID或员工ID) user_type -- 用户类型:guest-来客 agent-客服 ai-AI助手 user_name -- 用户名称 role -- 角色:owner-创建者 admin-管理员 member-普通成员 status -- 状态:active-活跃 left-已离开 removed-被移除 unread_count -- 该成员的未读消息数 last_read_time -- 最后阅读时间 last_read_msg_id -- 最后阅读的消息ID join_time -- 加入时间 leave_time -- 离开时间 ``` **业务规则**: - 来客自动加入(创建者) - 客服手动加入或系统分配 - 用于权限校验:只有成员能发消息和发起会议 --- #### 3. **tb_chat_message** - 聊天室消息表 **用途**:存储所有聊天消息(AI对话+人工客服对话) ```sql message_id -- 消息ID(主键) room_id -- 聊天室ID sender_id -- 发送者ID sender_type -- 发送者类型:guest-来客 agent-客服 ai-AI助手 system-系统消息 sender_name -- 发送者名称 message_type -- 消息类型:text image file voice video system meeting content -- 消息内容 content_extra -- 扩展内容(JSONB:图片URL、文件信息、会议链接等) reply_to_msg_id -- 回复的消息ID(引用回复) is_ai_message -- 是否AI消息 ai_message_id -- AI原始消息ID(追溯用) status -- 状态:sending sent delivered read failed recalled read_count -- 已读人数 send_time -- 发送时间 ``` **业务规则**: - 从ai.tb_chat同步AI对话时设置 `is_ai_message=true` - 会议通知作为系统消息 `message_type=meeting` - 支持引用回复和消息撤回 --- #### 4. **tb_video_meeting** - 视频会议表 ⭐Jitsi Meet **用途**:记录聊天室内创建的视频会议 ```sql meeting_id -- 会议ID(主键,也是Jitsi房间名) room_id -- 关联聊天室ID workcase_id -- 关联工单ID meeting_name -- 会议名称 meeting_password -- 会议密码(可选) jwt_token -- JWT Token(身份验证) jitsi_room_name -- Jitsi房间名(格式:workcase_{workcase_id}_{timestamp}) jitsi_server_url -- Jitsi服务器地址(默认:https://meet.jit.si) status -- 状态:scheduled ongoing ended cancelled creator_type -- 创建者类型:guest-来客 agent-客服 creator_name -- 创建者名称 participant_count -- 参与人数 max_participants -- 最大参与人数 start_time -- 实际开始时间 end_time -- 实际结束时间 duration_seconds -- 会议时长(秒) iframe_url -- iframe嵌入URL(生成后存储) config -- Jitsi配置项(JSONB自定义配置) ``` **业务规则**: - 只有聊天室成员能创建会议 - `jitsi_room_name`唯一,格式:`workcase_{workcaseId}_{timestamp}` - `iframe_url`在创建时生成,前端直接使用 --- #### 5. **tb_meeting_participant** - 会议参与记录表(可选) **用途**:用于审计和统计 ```sql participant_id -- 参与记录ID(主键) meeting_id -- 会议ID user_id -- 用户ID user_type -- 用户类型:guest-来客 agent-客服 user_name -- 用户名称 join_time -- 加入时间 leave_time -- 离开时间 duration_seconds -- 参与时长(秒) is_moderator -- 是否主持人 join_method -- 加入方式:web mobile desktop device_info -- 设备信息 ``` **业务规则**: - 记录每个参与者的加入和离开时间 - 用于统计会议时长和参与情况 - 可用于生成会议报告 --- #### 6. **tb_customer_service** - 客服人员配置表(可选) **用途**:管理有客服权限的员工 ```sql user_id username --- #### 7. **tb_word_cloud** - 词云统计表(已有) **用途**:记录聊天和工单中的关键词 ```sql word_id -- 词条ID(主键) word -- 词语 frequency -- 词频 source_type -- 来源类型:chat workcase global source_id -- 来源ID(room_id/workcase_id) category -- 分类:fault device emotion等 stat_date -- 统计日期(按天聚合) ``` --- ## 核心服务功能 ### Service层设计 #### 1. **ChatRoomService** - 聊天室服务 ```java // 创建聊天室(工单创建时调用) ChatRoomVO createChatRoom(CreateChatRoomDTO dto); // 关闭聊天室(工单完成时调用) void closeChatRoom(String roomId, String closedBy); // 获取聊天室详情 ChatRoomVO getChatRoomByWorkcaseId(String workcaseId); // 同步AI对话记录到聊天室 void syncAiMessages(String roomId, String aiSessionId); // 更新聊天室统计信息 void updateChatRoomStats(String roomId); ``` --- #### 2. **ChatMemberService** - 聊天室成员服务 ```java // 添加成员到聊天室 void addMember(String roomId, String userId, String userType); // 移除成员 void removeMember(String roomId, String userId); // 检查用户是否是聊天室成员(权限校验) boolean isMemberOfRoom(String roomId, String userId); // 获取聊天室成员列表 List getRoomMembers(String roomId); // 更新成员未读数 void updateMemberUnreadCount(String roomId, String userId); ``` --- #### 3. **ChatMessageService** - 聊天消息服务 ```java // 发送消息 ChatMessageVO sendMessage(SendMessageDTO dto); // 获取聊天历史 PageResult getChatHistory(String roomId, PageParam pageParam); // 标记消息已读 void markMessagesAsRead(String roomId, String userId, List messageIds); // 撤回消息 void recallMessage(String messageId, String userId); // 同步AI消息(从ai.tb_chat) void syncAiMessages(String roomId, String aiSessionId); ``` --- #### 4. **VideoMeetingService** - 视频会议服务 ⭐核心 ```java // 创建会议 VideoMeetingVO createMeeting(CreateMeetingDTO dto); // 验证加入会议权限 boolean validateMeetingAccess(String meetingId, String userId); // 生成会议iframe URL String generateMeetingIframeUrl(String meetingId, String userId); // 开始会议 void startMeeting(String meetingId); // 结束会议 void endMeeting(String meetingId); // 获取会议详情 VideoMeetingVO getMeetingInfo(String meetingId); // 记录参与者加入 void recordParticipantJoin(String meetingId, String userId); // 记录参与者离开 void recordParticipantLeave(String meetingId, String userId); ``` --- #### 5. **JitsiTokenService** - Jitsi JWT Token服务 ```java // 生成JWT Token(用于身份验证) String generateJwtToken(String roomName, String userId, String userName, boolean isModerator); // 验证JWT Token boolean validateJwtToken(String token); // 生成iframe嵌入URL String buildIframeUrl(String roomName, String jwtToken, JitsiConfig config); ``` --- ## API接口设计 ### 聊天室相关接口 #### 1. 创建聊天室 ``` POST /api/workcase/chat-room/create 请求体:{ "workcaseId": "WC20231220001", "guestId": "GUEST001", "guestName": "张三", "aiSessionId": "AI_SESSION_123" } 响应:{ "code": 0, "data": { "roomId": "ROOM001", "roomName": "工单#WC20231220001的客服支持", "status": "active" } } ``` #### 2. 发送消息 ``` POST /api/workcase/chat-room/send-message 请求体:{ "roomId": "ROOM001", "senderId": "USER001", "senderType": "agent", "messageType": "text", "content": "您好,我是客服小李,请问有什么可以帮到您?" } 响应:{ "code": 0, "data": { "messageId": "MSG001", "sendTime": "2023-12-20T10:00:00Z" } } ``` #### 3. 获取聊天历史 ``` GET /api/workcase/chat-room/messages?roomId=ROOM001&page=1&size=50 响应:{ "code": 0, "data": { "total": 120, "list": [ { "messageId": "MSG001", "senderId": "USER001", "senderName": "客服小李", "senderType": "agent", "messageType": "text", "content": "您好,我是客服小李", "sendTime": "2023-12-20T10:00:00Z", "status": "read" } ] } } ``` --- ### 视频会议相关接口 ⭐ #### 1. 创建视频会议 ``` POST /api/workcase/meeting/create 请求头:Authorization: Bearer 请求体:{ "roomId": "ROOM001", "workcaseId": "WC20231220001", "meetingName": "工单技术支持会议", "maxParticipants": 10 } 响应:{ "code": 0, "data": { "meetingId": "MEET001", "jitsiRoomName": "workcase_WC20231220001_1703059200", "iframeUrl": "https://meet.jit.si/workcase_WC20231220001_1703059200?jwt=eyJhbGc...", "status": "scheduled" } } ``` **业务逻辑**: 1. 验证请求用户是否是聊天室成员 2. 生成唯一的 `jitsi_room_name` 3. 生成JWT Token(包含用户身份和权限) 4. 构建iframe URL 5. 发送系统消息到聊天室通知会议创建 --- #### 2. 获取会议信息(用于加入会议) ``` GET /api/workcase/meeting/info/{meetingId} 请求头:Authorization: Bearer 响应:{ "code": 0, "data": { "meetingId": "MEET001", "meetingName": "工单技术支持会议", "jitsiRoomName": "workcase_WC20231220001_1703059200", "iframeUrl": "https://meet.jit.si/workcase_WC20231220001_1703059200?jwt=eyJhbGc...", "status": "ongoing", "participantCount": 2, "maxParticipants": 10, "canJoin": true } } ``` **业务逻辑**: 1. 验证请求用户是否是聊天室成员 2. 生成用户专属的JWT Token 3. 返回带Token的iframe URL 4. 记录参与者加入时间 --- #### 3. 开始会议 ``` POST /api/workcase/meeting/start/{meetingId} 响应:{ "code": 0, "message": "会议已开始" } ``` #### 4. 结束会议 ``` POST /api/workcase/meeting/end/{meetingId} 响应:{ "code": 0, "data": { "durationSeconds": 1800, "participantCount": 3 } } ``` --- ## 前端集成方案 ### 最简iframe嵌入实现 #### Vue 3 组件示例 ```vue ``` --- ### API请求封装 ```typescript // src/api/workcase/meeting.ts import { http } from '@/utils/http'; export interface CreateMeetingParams { roomId: string; workcaseId: string; meetingName: string; maxParticipants?: number; } export interface VideoMeetingVO { meetingId: string; meetingName: string; jitsiRoomName: string; iframeUrl: string; status: string; participantCount: number; maxParticipants: number; } // 创建视频会议 export const createVideoMeeting = (params: CreateMeetingParams) => { return http.post('/api/workcase/meeting/create', params); }; // 获取会议信息 export const getMeetingInfo = (meetingId: string) => { return http.get(`/api/workcase/meeting/info/${meetingId}`); }; // 结束会议 export const endVideoMeeting = (meetingId: string) => { return http.post(`/api/workcase/meeting/end/${meetingId}`); }; ``` --- ### 移动端适配 ```vue ``` --- ## 安全方案 ### 1. **会议权限控制** #### 后端验证逻辑 ```java public boolean validateMeetingAccess(String meetingId, String userId) { // 1. 获取会议信息 VideoMeetingDO meeting = meetingMapper.selectById(meetingId); if (meeting == null) { throw new BusinessException("会议不存在"); } // 2. 检查用户是否是聊天室成员 ChatRoomMemberDO member = memberMapper.selectByRoomAndUser( meeting.getRoomId(), userId ); if (member == null || !"active".equals(member.getStatus())) { throw new BusinessException("您不是聊天室成员,无法加入会议"); } return true; } ``` --- ### 2. **JWT Token生成** ```java public String generateJwtToken(String roomName, String userId, String userName, boolean isModerator) { long now = System.currentTimeMillis(); long exp = now + (2 * 60 * 60 * 1000); // 2小时有效期 return Jwts.builder() .setIssuer("urbanLifeline") .setSubject(roomName) .setAudience("jitsi") .claim("context", Map.of( "user", Map.of( "id", userId, "name", userName, "moderator", isModerator ) )) .claim("room", roomName) .setIssuedAt(new Date(now)) .setExpiration(new Date(exp)) .signWith(SignatureAlgorithm.HS256, jitsiSecretKey) .compact(); } ``` --- ### 3. **iframe URL构建** ```java public String buildIframeUrl(String roomName, String jwtToken, JitsiConfig config) { StringBuilder url = new StringBuilder(); url.append(jitsiServerUrl).append("/").append(roomName); // JWT认证 url.append("?jwt=").append(jwtToken); // Jitsi配置项 url.append("&config.startWithAudioMuted=").append(config.isStartWithAudioMuted()); url.append("&config.startWithVideoMuted=").append(config.isStartWithVideoMuted()); url.append("&config.enableWelcomePage=false"); url.append("&config.prejoinPageEnabled=false"); url.append("&config.disableDeepLinking=true"); // 界面配置 url.append("&interfaceConfig.SHOW_JITSI_WATERMARK=false"); url.append("&interfaceConfig.SHOW_WATERMARK_FOR_GUESTS=false"); url.append("&interfaceConfig.DISABLE_JOIN_LEAVE_NOTIFICATIONS=true"); return url.toString(); } ``` --- ### 4. **防止未授权访问** ```java @PostMapping("/create") @PreAuthorize("hasAuthority('workcase:meeting:create')") public ResultDomain createMeeting( @RequestBody @Valid CreateMeetingDTO dto, @RequestHeader("Authorization") String token ) { // 1. 从token解析用户信息 String userId = JwtUtil.getUserIdFromToken(token); // 2. 验证是否是聊天室成员 if (!chatMemberService.isMemberOfRoom(dto.getRoomId(), userId)) { return ResultDomain.failure("您不是聊天室成员,无法创建会议"); } // 3. 创建会议 VideoMeetingVO meeting = videoMeetingService.createMeeting(dto, userId); return ResultDomain.success(meeting); } ``` --- ## 技术要点总结 ### ✅ 优势 1. **最简实现**:iframe嵌入,无需深度集成Jitsi服务端 2. **权限安全**:JWT Token + 聊天室成员校验 3. **无状态**:Jitsi Meet本身无状态,只记录必要的会议元数据 4. **可扩展**:可后续升级为自建Jitsi服务器 5. **成本低**:使用官方meet.jit.si服务器,免费 ### ⚠️ 注意事项 1. **会议记录**:使用官方服务器无法录制,需自建服务器 2. **并发限制**:官方服务器有并发限制,建议后续自建 3. **网络要求**:需要良好的网络环境,可能需要科学上网 4. **数据隐私**:敏感场景建议自建Jitsi服务器 --- ## 下一步工作 1. ✅ 数据表已创建(createTableWorkcase.sql) 2. ⏳ 实现Service层业务逻辑 3. ⏳ 实现Controller层API接口 4. ⏳ 实现前端IM聊天室组件 5. ⏳ 实现前端视频会议组件 6. ⏳ 测试和调优 --- ## 附录:Jitsi Meet配置参考 ### 推荐配置项 ```javascript const jitsiConfig = { // 音视频设置 startWithAudioMuted: false, startWithVideoMuted: false, // 功能开关 enableWelcomePage: false, prejoinPageEnabled: false, disableDeepLinking: true, // 录制和直播 recordingEnabled: false, liveStreamingEnabled: false, // 聊天和屏幕共享 enableChat: true, enableScreenSharing: true, // 界面定制 hideConferenceSubject: false, hideConferenceTimer: false, // 安全设置 enableE2EE: false, // 端到端加密 requireDisplayName: true }; ``` --- **文档版本**:v1.0 **最后更新**:2023-12-20 **作者**:Cascade AI Assistant