# Jitsi 会议独立页面实现方案
## 问题背景
1. **Web端问题**:ChatRoom 弹窗关闭按钮不应该触发 `endMeeting`,只有在 Jitsi iframe 中点击"结束会议"才应该结束会议
2. **主持人问题**:主持人应该是数据库中的会议创建人,而不是第一个进入会议的人
3. **小程序问题**:无法捕捉结束会议事件
## 解决方案
### 1. 后端修改(已完成)
#### 1.1 新增路由配置 (`initDataPermission.sql`)
```sql
-- Jitsi视频会议独立页面(支持URL参数token认证,用于小程序和外部链接访问)
('VIEW-W003', 'view_workcase_jitsi_meeting', 'Jitsi视频会议', NULL, '/meeting/:meetingId',
'public/Meeting/JitsiMeetingView.vue', 'Video', 1,
'route', NULL, 'workcase', 'BlankLayout', 25,
'Jitsi视频会议独立页面,支持token参数认证', 'system', now(), false);
```
#### 1.2 新增 API 接口
| 接口 | 方法 | 说明 |
|------|------|------|
| `/meeting/{meetingId}/entry?token=xxx` | GET | 会议入口(支持URL参数token认证) |
| `/meeting/{meetingId}/share-url` | GET | 生成会议分享URL |
#### 1.3 Gateway 白名单
```yaml
whitelist:
- /urban-lifeline/workcase/meeting/*/entry
```
#### 1.4 主持人逻辑
主持人判断逻辑已正确实现:`userId.equals(meeting.getCreator())`
### 2. 前端实现(需要在前端仓库实现)
#### 2.1 创建 `JitsiMeetingView.vue`
路径:`src/views/public/Meeting/JitsiMeetingView.vue`
```vue
```
#### 2.2 API 接口定义
```typescript
// src/api/meeting.ts
import request from '@/utils/request'
// 获取会议入口信息(支持token参数)
export function getMeetingEntry(meetingId: string, token?: string) {
return request({
url: `/workcase/meeting/${meetingId}/entry`,
method: 'get',
params: { token },
// 不使用默认的Authorization header
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
})
}
// 结束会议
export function endMeeting(meetingId: string) {
return request({
url: `/workcase/meeting/${meetingId}/end`,
method: 'post'
})
}
// 生成会议分享URL
export function generateMeetingShareUrl(meetingId: string, baseUrl?: string) {
return request({
url: `/workcase/meeting/${meetingId}/share-url`,
method: 'get',
params: { baseUrl }
})
}
```
#### 2.3 路由配置
```typescript
// src/router/workcase.ts
{
path: '/meeting/:meetingId',
name: 'JitsiMeeting',
component: () => import('@/views/public/Meeting/JitsiMeetingView.vue'),
meta: {
layout: 'BlankLayout',
requiresAuth: false, // 允许通过URL token认证
title: '视频会议'
}
}
```
#### 2.4 修改 ChatRoom 组件
在 ChatRoom 组件中,点击"进入会议"时:
```typescript
// 进入会议
const joinMeeting = async (meetingId: string) => {
// 生成会议入口URL
const response = await generateMeetingShareUrl(meetingId)
if (response.success) {
// 在新窗口打开会议页面
window.open(response.data, '_blank', 'width=1200,height=800')
}
}
```
#### 2.5 Web 初始化逻辑修改
在 workcase 前端的初始化逻辑中,添加从 URL token 恢复登录状态的功能:
```typescript
// src/utils/auth.ts
export async function initAuth() {
// 1. 检查 localStorage 中是否有登录信息
const storedToken = localStorage.getItem('token')
if (storedToken) {
// 验证token是否有效
const isValid = await validateToken(storedToken)
if (isValid) {
return true
}
}
// 2. 检查 URL 参数中是否有 token
const urlParams = new URLSearchParams(window.location.search)
const urlToken = urlParams.get('token')
if (urlToken) {
try {
// 调用 refresh 接口验证 token 并获取用户信息
const response = await refreshToken(urlToken)
if (response.success) {
// 保存登录信息到 localStorage
localStorage.setItem('token', response.data.token)
localStorage.setItem('loginDomain', JSON.stringify(response.data))
return true
}
} catch (e) {
console.error('URL token 验证失败:', e)
}
}
return false
}
```
### 3. 小程序端实现
小程序端直接给出会议链接,用户在手机浏览器中打开:
```javascript
// 小程序中获取会议链接
const getMeetingUrl = async (meetingId) => {
const token = wx.getStorageSync('token')
const baseUrl = 'https://your-domain.com/workcase'
// 构建会议入口URL
const meetingUrl = `${baseUrl}/meeting/${meetingId}?token=${token}`
// 复制到剪贴板或显示给用户
wx.setClipboardData({
data: meetingUrl,
success: () => {
wx.showToast({
title: '会议链接已复制,请在浏览器中打开',
icon: 'none'
})
}
})
}
```
### 4. 关键流程
#### 4.1 Web 端进入会议流程
```
用户点击"进入会议"
↓
调用 generateMeetingShareUrl 获取带token的URL
↓
window.open 打开 JitsiMeetingView.vue
↓
JitsiMeetingView 从URL获取token
↓
调用 /meeting/{id}/entry?token=xxx 获取会议信息
↓
初始化 Jitsi External API
↓
监听 readyToClose/hangup 事件
↓
主持人结束会议时调用 endMeeting API
```
#### 4.2 小程序端进入会议流程
```
用户点击"进入会议"
↓
获取当前用户token
↓
构建会议URL: /meeting/{id}?token=xxx
↓
复制URL到剪贴板
↓
用户在手机浏览器打开URL
↓
JitsiMeetingView 从URL获取token
↓
调用 /meeting/{id}/entry?token=xxx 获取会议信息
↓
初始化 Jitsi External API
```
### 5. 注意事项
1. **主持人权限**:只有会议创建人(`meeting.creator`)才是主持人,JWT token 中的 `moderator` 字段会正确设置
2. **结束会议**:只有主持人点击"结束会议"或挂断时才会调用 `endMeeting` API
3. **Token 安全**:URL 中的 token 应该有时效性,建议使用短期 token 或一次性 token
4. **跨域问题**:确保 Jitsi 服务器配置了正确的 CORS 策略
5. **HTTPS**:生产环境必须使用 HTTPS,否则浏览器可能阻止摄像头/麦克风访问