From 5db025b10fb8bc02711949340b144dd3d8fe390c Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Sat, 27 Dec 2025 20:08:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BB=E6=8C=81=E4=BA=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docker/urbanlifeline/docker-compose.yml | 132 ++++-------------- .../xyzh/api/workcase/vo/VideoMeetingVO.java | 2 +- .../service/JitsiTokenServiceImpl.java | 21 ++- .../service/VideoMeetingServiceImpl.java | 15 ++ 4 files changed, 66 insertions(+), 104 deletions(-) diff --git a/urbanLifelineServ/.bin/docker/urbanlifeline/docker-compose.yml b/urbanLifelineServ/.bin/docker/urbanlifeline/docker-compose.yml index a0cb4e24..add6154d 100644 --- a/urbanLifelineServ/.bin/docker/urbanlifeline/docker-compose.yml +++ b/urbanLifelineServ/.bin/docker/urbanlifeline/docker-compose.yml @@ -1,8 +1,5 @@ version: '3.8' -# urban-lifeline 开发环境 Docker Compose 配置 -# 使用主机的 MySQL 数据库 - networks: urban-lifeline: driver: bridge @@ -10,85 +7,65 @@ networks: services: nacos: + # 保持原有配置不变 image: nacos/nacos-server:v3.1.0 container_name: urban-lifeline-nacos restart: unless-stopped networks: - urban-lifeline ports: - - "8081:8080" # Nacos Console (Web UI) - 映射到主机 8081 - - "8848:8848" # Nacos HTTP API - - "9848:9848" # Nacos gRPC 客户端请求 - - "9849:9849" # Nacos gRPC 服务间同步 + - "8081:8080" + - "8848:8848" + - "9848:9848" + - "9849:9849" environment: - # 运行模式 MODE: standalone - - # 数据库配置 - 使用主机 MySQL SPRING_DATASOURCE_PLATFORM: mysql - MYSQL_SERVICE_HOST: host.docker.internal # Docker Desktop - # MYSQL_SERVICE_HOST: 172.17.0.1 # Linux 使用此行,注释上一行 + MYSQL_SERVICE_HOST: host.docker.internal MYSQL_SERVICE_PORT: 3306 MYSQL_SERVICE_DB_NAME: nacos_config MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: "123456" MYSQL_SERVICE_DB_PARAM: allowPublicKeyRetrieval=true&useSSL=false - - # JVM 配置 JVM_XMS: 512m JVM_XMX: 512m JVM_XMN: 256m - - # 认证配置(开发环境关闭) NACOS_AUTH_ENABLE: "false" NACOS_AUTH_TOKEN: ZlRkR2ZxR3BvZ1F0a3JxY2V6RUx2cUh1Rkx6V1ZQbE9kUVd1R1VOcWFFS2t3dG5hS0E9PQ== NACOS_AUTH_IDENTITY_KEY: ZlRkR2ZxR3BvZ1F0a3JxY2V6RUx2cUh1Rkx6V1ZQbE9kUVd1R1VOcWFFS2t3dG5hS0E9PQ== NACOS_AUTH_IDENTITY_VALUE: ZlRkR2ZxR3BvZ1F0a3JxY2V6RUx2cUh1Rkx6V1ZQbE9kUVd1R1VOcWFFS2t3dG5hS0E9PQ== - volumes: - # 数据持久化到主机目录 - ../../../.data/docker/nacos/data:/home/nacos/data - ../../../.data/docker/nacos/logs:/home/nacos/logs - healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/"] interval: 30s timeout: 10s retries: 5 start_period: 60s - - # Linux 需要添加 extra_hosts 来访问主机服务 extra_hosts: - "host.docker.internal:host-gateway" minio: + # 保持原有配置不变 image: minio/minio:latest container_name: urban-lifeline-minio restart: unless-stopped networks: - urban-lifeline ports: - - "9000:9000" # MinIO API 端口 - - "9001:9001" # MinIO Console (Web UI) 端口 + - "9000:9000" + - "9001:9001" environment: - # 管理员账户配置 MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin123 - - # Console 地址配置 MINIO_CONSOLE_ADDRESS: ":9001" MINIO_ADDRESS: ":9000" - - # 时区设置 TZ: Asia/Shanghai - volumes: - # 数据持久化到主机目录 - ../../../.data/docker/minio/data:/data - ../../../.data/docker/minio/config:/root/.minio - command: server /data --console-address ":9001" - healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s @@ -96,47 +73,35 @@ services: retries: 3 start_period: 30s - # ====================== Jitsi Meet 视频会议服务 ====================== - + # ====================== Jitsi 核心修改开始 ====================== jitsi-web: + # ✅ 保持原有配置不变,无需修改 image: jitsi/web:stable-9584 container_name: urban-lifeline-jitsi-web restart: unless-stopped networks: - urban-lifeline ports: - - "8280:80" # HTTP 端口(通过 Nginx 反向代理) - - "8443:443" # HTTPS 端口 + - "8280:80" + - "8443:443" environment: - # 基础配置(Jitsi容器使用HTTP,由Nginx提供HTTPS) TZ: Asia/Shanghai - # PUBLIC_URL设置为独立子域名 PUBLIC_URL: https://org.xyzh.yslg.jitsi - - # 禁用容器内部HTTPS(由Nginx统一处理HTTPS) ENABLE_HTTPS: 0 ENABLE_HTTP_REDIRECT: 0 DISABLE_HTTPS: 1 - - # XMPP 配置 XMPP_DOMAIN: meet.jitsi XMPP_AUTH_DOMAIN: auth.meet.jitsi XMPP_BOSH_URL_BASE: http://jitsi-prosody:5280 XMPP_MUC_DOMAIN: muc.meet.jitsi XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi XMPP_GUEST_DOMAIN: guest.meet.jitsi - - # Jicofo 配置(完全保留原设置) JICOFO_COMPONENT_SECRET: jicofo-secret JICOFO_AUTH_USER: focus - - # JVB 配置(完全保留原设置) JVB_AUTH_USER: jvb JVB_AUTH_PASSWORD: jvb-password - - # JWT 认证配置(完全保留原设置) ENABLE_AUTH: 1 - ENABLE_GUESTS: 1 + ENABLE_GUESTS: 0 AUTH_TYPE: jwt JWT_APP_ID: urbanLifeline JWT_APP_SECRET: urbanLifeline-jitsi-secret-key-2025-production-safe-hs256 @@ -146,18 +111,13 @@ services: JWT_ALLOW_EMPTY: 0 JWT_AUTH_TYPE: token JWT_TOKEN_AUTH_MODULE: token_verification - - # 界面/功能配置(完全保留原设置) ENABLE_RECORDING: 0 ENABLE_TRANSCRIPTIONS: 0 ENABLE_SUBDOMAINS: 0 ENABLE_XMPP_WEBSOCKET: 1 ENABLE_SCTP: 1 - - # 不使用Let's Encrypt(使用mkcert证书) ENABLE_LETSENCRYPT: 0 LETSENCRYPT_DOMAIN: org.xyzh.yslg.jitsi - volumes: - ../../../.data/docker/jitsi/web:/config - ../../../.data/docker/jitsi/web/crontabs:/var/spool/cron/crontabs @@ -171,7 +131,6 @@ services: retries: 3 start_period: 60s - # XMPP 服务(Prosody)- 完全保留原配置 jitsi-prosody: image: jitsi/prosody:stable-9584 container_name: urban-lifeline-jitsi-prosody @@ -179,31 +138,23 @@ services: networks: - urban-lifeline expose: - - "5222" # XMPP客户端连接(内部) - - "5347" # XMPP组件连接(内部) - - "5280" # BOSH/WebSocket(内部) + - "5222" + - "5347" + - "5280" environment: TZ: Asia/Shanghai - - # XMPP域配置(完全保留) XMPP_DOMAIN: meet.jitsi XMPP_AUTH_DOMAIN: auth.meet.jitsi XMPP_MUC_DOMAIN: muc.meet.jitsi XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi XMPP_GUEST_DOMAIN: guest.meet.jitsi - - # Jicofo组件密钥(完全保留) JICOFO_COMPONENT_SECRET: jicofo-secret JICOFO_AUTH_USER: focus JICOFO_AUTH_PASSWORD: focus-password - - # JVB认证(完全保留) JVB_AUTH_USER: jvb JVB_AUTH_PASSWORD: jvb-password - - # JWT认证(完全保留) ENABLE_AUTH: 1 - ENABLE_GUESTS: 1 + ENABLE_GUESTS: 0 AUTH_TYPE: jwt JWT_APP_ID: urbanLifeline JWT_APP_SECRET: urbanLifeline-jitsi-secret-key-2025-production-safe-hs256 @@ -212,13 +163,10 @@ services: JWT_ALLOW_EMPTY: 0 JWT_AUTH_TYPE: token JWT_TOKEN_AUTH_MODULE: token_verification - - # 日志配置(完全保留) LOG_LEVEL: info - - # 公共URL(局域网访问) PUBLIC_URL: https://org.xyzh.yslg.jitsi - + # 🔥 新增1 - Prosody层禁用JWT自动授予主持人权限(JWT模式核心!) + JWT_DISABLE_AUTO_MODERATOR: true volumes: - ../../../.data/docker/jitsi/prosody/config:/config - ../../../.data/docker/jitsi/prosody/prosody-plugins-custom:/prosody-plugins-custom @@ -229,7 +177,6 @@ services: retries: 3 start_period: 90s - # 会议焦点控制器(Jicofo)- 完全保留原配置 jitsi-jicofo: image: jitsi/jicofo:stable-9584 container_name: urban-lifeline-jitsi-jicofo @@ -238,28 +185,22 @@ services: - urban-lifeline environment: TZ: Asia/Shanghai - - # XMPP配置(完全保留) XMPP_DOMAIN: meet.jitsi XMPP_AUTH_DOMAIN: auth.meet.jitsi XMPP_MUC_DOMAIN: muc.meet.jitsi XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi XMPP_SERVER: jitsi-prosody - - # Jicofo认证(完全保留) JICOFO_COMPONENT_SECRET: jicofo-secret JICOFO_AUTH_USER: focus JICOFO_AUTH_PASSWORD: focus-password - - # JWT配置(完全保留) AUTH_TYPE: jwt - - # JVB配置(完全保留) JVB_BREWERY_MUC: jvbbrewery - - # 日志级别(完全保留) - JICOFO_ENABLE_HEALTH_CHECKS: "true" - + JICOFO_ENABLE_HEALTH_CHECKS: true + # 保留原有配置 + JICOFO_ENABLE_AUTO_OWNER: false + JICOFO_ENABLE_AUTO_LOGIN: false + # 🔥 新增2 - 兜底:强制清空初始主持人,杜绝所有自动分配可能 + JICOFO_CONFERENCE_INITIAL_OWNER: "" volumes: - ../../../.data/docker/jitsi/jicofo:/config depends_on: @@ -271,46 +212,33 @@ services: retries: 3 start_period: 90s - # 视频桥接服务(JVB)- 仅修复 WebSocket 相关,保留IP/端口 jitsi-jvb: + # ✅ 保持原有配置不变,无需修改 image: jitsi/jvb:stable-9584 container_name: urban-lifeline-jitsi-jvb restart: unless-stopped networks: - urban-lifeline ports: - - "10000:10000/udp" # 保留原 UDP 端口 - - "4443:4443/tcp" # 保留原 TCP 端口 + - "10000:10000/udp" + - "4443:4443/tcp" environment: TZ: Asia/Shanghai - - # XMPP配置(完全保留) XMPP_DOMAIN: meet.jitsi XMPP_AUTH_DOMAIN: auth.meet.jitsi XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi XMPP_SERVER: jitsi-prosody - - # JVB认证(完全保留) JVB_AUTH_USER: jvb JVB_AUTH_PASSWORD: jvb-password - - # JVB配置(完全保留) JVB_BREWERY_MUC: jvbbrewery JVB_PORT: 10000 JVB_STUN_SERVERS: stun.l.google.com:19302,stun1.l.google.com:19302 - - # 本地IP配置(局域网IP - 关键配置!) DOCKER_HOST_ADDRESS: 192.168.0.253 JVB_ADVERTISE_IPS: 192.168.0.253 - - # 启用统计(完全保留) JVB_ENABLE_APIS: rest,colibri - - # 性能优化(完全保留) JVB_TCP_HARVESTER_DISABLED: "false" JVB_TCP_PORT: 4443 JVB_TCP_MAPPED_PORT: 4443 - volumes: - ../../../.data/docker/jitsi/jvb:/config depends_on: @@ -320,4 +248,4 @@ services: interval: 30s timeout: 10s retries: 3 - start_period: 90s + start_period: 90s \ No newline at end of file diff --git a/urbanLifelineServ/apis/api-workcase/src/main/java/org/xyzh/api/workcase/vo/VideoMeetingVO.java b/urbanLifelineServ/apis/api-workcase/src/main/java/org/xyzh/api/workcase/vo/VideoMeetingVO.java index 285823c6..2241dd1f 100644 --- a/urbanLifelineServ/apis/api-workcase/src/main/java/org/xyzh/api/workcase/vo/VideoMeetingVO.java +++ b/urbanLifelineServ/apis/api-workcase/src/main/java/org/xyzh/api/workcase/vo/VideoMeetingVO.java @@ -48,7 +48,7 @@ public class VideoMeetingVO extends BaseVO { @Schema(description = "状态:scheduled-已安排 ongoing-进行中 ended-已结束 cancelled-已取消") private String status; - + @Schema(description = "创建者类型:guest-来客 agent-客服") private String creatorType; diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/JitsiTokenServiceImpl.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/JitsiTokenServiceImpl.java index 7ffce9c9..6369228d 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/JitsiTokenServiceImpl.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/JitsiTokenServiceImpl.java @@ -44,15 +44,34 @@ public class JitsiTokenServiceImpl implements JitsiTokenService { userContext.put("name", userName); userContext.put("moderator", isModerator); + // 构建affiliation (这是Jitsi识别主持人的关键字段) + Map user = new HashMap<>(); + user.put("id", userId); + user.put("name", userName); + user.put("moderator", isModerator); + user.put("affiliation", isModerator ? "owner" : "member"); // owner=主持人, member=普通成员 + + // 构建context + Map context = new HashMap<>(); + context.put("user", user); + + // 【调试日志】打印用户上下文 + logger.info("【JWT用户上下文】roomName={}, userId={}, userName={}, isModerator={}, affiliation={}, user={}", + roomName, userId, userName, isModerator, isModerator ? "owner" : "member", user); + // 构建JWT claims Map claims = new HashMap<>(); - claims.put("context", Map.of("user", userContext)); + claims.put("context", context); claims.put("room", roomName); claims.put("iss", jitsiProperties.getApp().getId()); claims.put("aud", "jitsi"); claims.put("sub", jitsiProperties.getServer().getUrl()); claims.put("exp", exp / 1000); // 秒级时间戳 claims.put("nbf", now / 1000); + claims.put("moderator", isModerator); // 在顶层也添加moderator字段 + + // 【调试日志】打印完整claims + logger.info("【JWT Claims】roomName={}, claims={}", roomName, claims); // 构建JWT Header,必须包含 typ: JWT Map header = new HashMap<>(); diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/VideoMeetingServiceImpl.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/VideoMeetingServiceImpl.java index 2c4a9f93..128f9056 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/VideoMeetingServiceImpl.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/service/VideoMeetingServiceImpl.java @@ -126,6 +126,11 @@ public class VideoMeetingServiceImpl implements VideoMeetingService { meetingDTO.setCreator(userId); meetingDTO.setCreatorType(userType); meetingDTO.setCreatorName(userName); + + // 【调试日志】打印会议创建者信息 + logger.info("【创建会议】meetingId={}, 创建者userId=[{}](类型:{}), creatorName={}, creatorType={}", + meetingId, userId, userId.getClass().getSimpleName(), userName, userType); + meetingDTO.setParticipantCount(0); meetingDTO.setOptsn(IdUtil.getOptsn()); @@ -330,6 +335,13 @@ public class VideoMeetingServiceImpl implements VideoMeetingService { // 会议创建人才是主持人 boolean isModerator = userId.equals(meeting.getCreator()); + // 【调试日志】打印主持人判断详情 + logger.info("【主持人判断】meetingId={}, 当前用户ID=[{}](类型:{}), 创建者ID=[{}](类型:{}), isModerator={}", + meetingId, + userId, userId.getClass().getSimpleName(), + meeting.getCreator(), meeting.getCreator() != null ? meeting.getCreator().getClass().getSimpleName() : "null", + isModerator); + if (members != null && !members.isEmpty()) { ChatMemberVO member = members.get(0); userName = member.getUserName(); @@ -343,6 +355,9 @@ public class VideoMeetingServiceImpl implements VideoMeetingService { isModerator ); + logger.info("【JWT Token生成】meetingId={}, userId={}, userName={}, isModerator={}", + meetingId, userId, userName, isModerator); + // 7. 构建真正的Jitsi iframe URL String jitsiIframeUrl = jitsiTokenService.buildIframeUrl( meeting.getJitsiRoomName(),