暂存
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package org.xyzh.workcase.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -393,6 +394,53 @@ public class WorkcaseChatContorller {
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "会议入口(支持URL参数token认证,用于小程序和外部链接)")
|
||||
@GetMapping("/meeting/{meetingId}/entry")
|
||||
public ResultDomain<VideoMeetingVO> getMeetingEntry(
|
||||
@PathVariable(value = "meetingId") String meetingId,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
HttpServletRequest request) {
|
||||
// 优先从URL参数获取token,其次从Header获取
|
||||
if (token == null || token.trim().isEmpty()) {
|
||||
token = request.getHeader("Authorization");
|
||||
}
|
||||
|
||||
try {
|
||||
return videoMeetingService.getMeetingEntryByToken(meetingId, token);
|
||||
} catch (Exception e) {
|
||||
return ResultDomain.failure(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "生成会议入口URL(用于分享给小程序用户)")
|
||||
@PreAuthorize("hasAuthority('meeting:url:any')")
|
||||
@GetMapping("/meeting/{meetingId}/share-url")
|
||||
public ResultDomain<String> generateMeetingShareUrl(
|
||||
@PathVariable(value = "meetingId") String meetingId,
|
||||
@RequestParam(value = "baseUrl", defaultValue = "") String baseUrl,
|
||||
HttpServletRequest request) {
|
||||
// 如果没有提供baseUrl,则从请求中构建
|
||||
if (baseUrl == null || baseUrl.trim().isEmpty()) {
|
||||
String scheme = request.getScheme();
|
||||
String serverName = request.getServerName();
|
||||
int serverPort = request.getServerPort();
|
||||
String contextPath = request.getContextPath();
|
||||
|
||||
if ((scheme.equals("http") && serverPort == 80) ||
|
||||
(scheme.equals("https") && serverPort == 443)) {
|
||||
baseUrl = scheme + "://" + serverName + contextPath + "/workcase";
|
||||
} else {
|
||||
baseUrl = scheme + "://" + serverName + ":" + serverPort + contextPath + "/workcase";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return videoMeetingService.generateMeetingEntryUrl(meetingId, baseUrl);
|
||||
} catch (Exception e) {
|
||||
return ResultDomain.failure(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= 微信客服消息回调 =========================
|
||||
|
||||
// @Operation(summary = "微信客服消息回调验证(GET)")
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package org.xyzh.workcase.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.xyzh.api.auth.service.AuthService;
|
||||
import org.xyzh.api.workcase.dto.TbChatRoomMemberDTO;
|
||||
import org.xyzh.api.workcase.dto.TbChatRoomMessageDTO;
|
||||
import org.xyzh.api.workcase.dto.TbVideoMeetingDTO;
|
||||
@@ -51,6 +53,9 @@ public class VideoMeetingServiceImpl implements VideoMeetingService {
|
||||
@Autowired
|
||||
private ChatRoomService chatRoomService;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "auth", timeout = 30000, retries = 0)
|
||||
private AuthService authService;
|
||||
|
||||
// 会议创建锁映射表:每个meetingId对应一个ReentrantLock
|
||||
private final ConcurrentHashMap<String, ReentrantLock> meetingLocks = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -326,7 +331,7 @@ public class VideoMeetingServiceImpl implements VideoMeetingService {
|
||||
userName = member.getUserName();
|
||||
}
|
||||
|
||||
// 6. 生成用户专属JWT Token
|
||||
// 6. 生成用户专属JWT Token(用于Jitsi内部认证)
|
||||
String userJwtToken = jitsiTokenService.generateJwtToken(
|
||||
meeting.getJitsiRoomName(),
|
||||
userId,
|
||||
@@ -334,16 +339,24 @@ public class VideoMeetingServiceImpl implements VideoMeetingService {
|
||||
isModerator
|
||||
);
|
||||
|
||||
// 7. 构建用户专属iframe URL
|
||||
String userIframeUrl = jitsiTokenService.buildIframeUrl(
|
||||
// 7. 构建真正的Jitsi iframe URL
|
||||
String jitsiIframeUrl = jitsiTokenService.buildIframeUrl(
|
||||
meeting.getJitsiRoomName(),
|
||||
userJwtToken,
|
||||
meeting.getConfig()
|
||||
);
|
||||
|
||||
// 8. 更新VO
|
||||
// 8. 构建会议页面URL(用于Web端路由跳转和小程序外部访问)
|
||||
// 获取当前用户的登录token(用于页面token认证)
|
||||
String userToken = LoginUtil.getToken();
|
||||
// 注意:URL不包含/workcase前缀,因为workcase应用的路由base已经是/workcase
|
||||
String meetingPageUrl = "/meeting?meetingId=" + meetingId +
|
||||
"&token=" + (userToken != null ? userToken : "");
|
||||
|
||||
// 9. 更新VO
|
||||
meeting.setJwtToken(userJwtToken);
|
||||
meeting.setIframeUrl(userIframeUrl);
|
||||
meeting.setJitsiIframeUrl(jitsiIframeUrl); // 真正的Jitsi URL
|
||||
meeting.setIframeUrl(meetingPageUrl); // 会议页面URL(用于router跳转)
|
||||
|
||||
logger.info("生成用户专属会议URL成功: meetingId={}, userId={}, status={}",
|
||||
meetingId, userId, meeting.getStatus());
|
||||
@@ -534,4 +547,177 @@ public class VideoMeetingServiceImpl implements VideoMeetingService {
|
||||
// 时间段1的结束时间 > 时间段2的开始时间 AND 时间段1的开始时间 < 时间段2的结束时间
|
||||
return end1.after(start2) && start1.before(end2);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResultDomain<VideoMeetingVO> getMeetingEntryByToken(String meetingId, String token) {
|
||||
logger.info("通过token获取会议入口: meetingId={}", meetingId);
|
||||
|
||||
try {
|
||||
// 1. 验证token并获取用户信息
|
||||
if (token == null || token.trim().isEmpty()) {
|
||||
logger.warn("token为空: meetingId={}", meetingId);
|
||||
return ResultDomain.failure("认证token不能为空");
|
||||
}
|
||||
|
||||
// 去除Bearer前缀(如果有)
|
||||
if (token.startsWith("Bearer ")) {
|
||||
token = token.substring(7);
|
||||
}
|
||||
|
||||
ResultDomain<LoginDomain> loginResult = authService.getLoginByToken(token);
|
||||
if (!loginResult.getSuccess() || loginResult.getData() == null) {
|
||||
logger.warn("token验证失败: meetingId={}, error={}", meetingId, loginResult.getMessage());
|
||||
return ResultDomain.failure("认证失败: " + loginResult.getMessage());
|
||||
}
|
||||
|
||||
LoginDomain loginDomain = loginResult.getData();
|
||||
String userId = loginDomain.getUser().getUserId();
|
||||
logger.info("token验证成功: meetingId={}, userId={}", meetingId, userId);
|
||||
|
||||
// 2. 调用现有的generateUserMeetingUrl方法生成会议URL
|
||||
// 但需要先设置当前登录上下文(因为generateUserMeetingUrl可能依赖LoginUtil)
|
||||
// 这里直接复用generateUserMeetingUrl的核心逻辑
|
||||
|
||||
// 获取会议信息
|
||||
TbVideoMeetingDTO filter = new TbVideoMeetingDTO();
|
||||
filter.setMeetingId(meetingId);
|
||||
List<VideoMeetingVO> meetings = videoMeetingMapper.selectVideoMeetingList(filter);
|
||||
|
||||
if (meetings == null || meetings.isEmpty()) {
|
||||
logger.warn("会议不存在: meetingId={}", meetingId);
|
||||
return ResultDomain.failure("会议不存在");
|
||||
}
|
||||
|
||||
VideoMeetingVO meeting = meetings.get(0);
|
||||
|
||||
// 3. 验证访问权限(用户必须是聊天室成员)
|
||||
if (!isMemberOfRoom(meeting.getRoomId(), userId)) {
|
||||
logger.warn("用户无权访问会议: meetingId={}, userId={}", meetingId, userId);
|
||||
return ResultDomain.failure("您无权访问此会议");
|
||||
}
|
||||
|
||||
// 4. 检查会议状态和时间窗口
|
||||
if ("ended".equals(meeting.getStatus())) {
|
||||
logger.warn("会议已结束: meetingId={}", meetingId);
|
||||
return ResultDomain.failure("会议已结束");
|
||||
}
|
||||
|
||||
if ("scheduled".equals(meeting.getStatus())) {
|
||||
Date now = new Date();
|
||||
|
||||
// 计算提前入会时间点
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(meeting.getStartTime());
|
||||
calendar.add(Calendar.MINUTE, -meeting.getAdvance());
|
||||
Date advanceTime = calendar.getTime();
|
||||
|
||||
if (now.before(advanceTime)) {
|
||||
logger.warn("会议未到入会时间: meetingId={}", meetingId);
|
||||
return ResultDomain.failure("会议未到入会时间,请在 " + advanceTime + " 之后加入");
|
||||
}
|
||||
|
||||
if (now.after(meeting.getEndTime())) {
|
||||
logger.warn("会议已过期: meetingId={}", meetingId);
|
||||
return ResultDomain.failure("会议已结束");
|
||||
}
|
||||
|
||||
// 首次入会时更新会议状态
|
||||
ReentrantLock lock = meetingLocks.computeIfAbsent(meetingId, k -> new ReentrantLock());
|
||||
lock.lock();
|
||||
try {
|
||||
List<VideoMeetingVO> recheck = videoMeetingMapper.selectVideoMeetingList(filter);
|
||||
if (recheck != null && !recheck.isEmpty() && "scheduled".equals(recheck.get(0).getStatus())) {
|
||||
TbVideoMeetingDTO updateDTO = new TbVideoMeetingDTO();
|
||||
updateDTO.setMeetingId(meetingId);
|
||||
updateDTO.setStatus("ongoing");
|
||||
updateDTO.setActualStartTime(new Date());
|
||||
videoMeetingMapper.updateVideoMeeting(updateDTO);
|
||||
meeting.setStatus("ongoing");
|
||||
meeting.setActualStartTime(new Date());
|
||||
logger.info("会议状态已更新为进行中: meetingId={}", meetingId);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
if (!lock.hasQueuedThreads()) {
|
||||
meetingLocks.remove(meetingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 获取用户信息
|
||||
TbChatRoomMemberDTO memberFilter = new TbChatRoomMemberDTO();
|
||||
memberFilter.setRoomId(meeting.getRoomId());
|
||||
memberFilter.setUserId(userId);
|
||||
List<ChatMemberVO> members = chatRoomMemberMapper.selectChatRoomMemberList(memberFilter);
|
||||
|
||||
String userName = loginDomain.getUserInfo().getUsername();
|
||||
boolean isModerator = userId.equals(meeting.getCreator());
|
||||
|
||||
if (members != null && !members.isEmpty()) {
|
||||
userName = members.get(0).getUserName();
|
||||
}
|
||||
|
||||
// 6. 生成用户专属JWT Token
|
||||
String userJwtToken = jitsiTokenService.generateJwtToken(
|
||||
meeting.getJitsiRoomName(),
|
||||
userId,
|
||||
userName,
|
||||
isModerator
|
||||
);
|
||||
|
||||
// 7. 构建真正的Jitsi iframe URL
|
||||
String jitsiIframeUrl = jitsiTokenService.buildIframeUrl(
|
||||
meeting.getJitsiRoomName(),
|
||||
userJwtToken,
|
||||
meeting.getConfig()
|
||||
);
|
||||
|
||||
// 8. 构建会议页面URL(用于Web端路由跳转和小程序外部访问)
|
||||
// 注意:使用提供的token参数而非LoginUtil.getToken()
|
||||
String meetingPageUrl = "/meeting?meetingId=" + meetingId +
|
||||
"&token=" + token;
|
||||
|
||||
// 9. 更新VO并返回
|
||||
meeting.setJwtToken(userJwtToken);
|
||||
meeting.setJitsiIframeUrl(jitsiIframeUrl); // 真正的Jitsi URL
|
||||
meeting.setIframeUrl(meetingPageUrl); // 会议页面URL(用于router跳转)
|
||||
|
||||
logger.info("通过token获取会议入口成功: meetingId={}, userId={}, isModerator={}",
|
||||
meetingId, userId, isModerator);
|
||||
return ResultDomain.success("获取会议入口成功", meeting);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("通过token获取会议入口异常: meetingId={}, error={}", meetingId, e.getMessage(), e);
|
||||
return ResultDomain.failure("获取会议入口失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<String> generateMeetingEntryUrl(String meetingId, String baseUrl) {
|
||||
logger.info("生成会议入口URL: meetingId={}, baseUrl={}", meetingId, baseUrl);
|
||||
|
||||
try {
|
||||
// 获取当前用户token
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (loginDomain == null || loginDomain.getToken() == null) {
|
||||
logger.warn("无法获取当前用户token: meetingId={}", meetingId);
|
||||
return ResultDomain.failure("无法获取当前用户认证信息");
|
||||
}
|
||||
|
||||
// 构建完整URL: {baseUrl}/meeting/{meetingId}?token={token}
|
||||
String entryUrl = String.format("%s/meeting/%s?token=%s",
|
||||
baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl,
|
||||
meetingId,
|
||||
loginDomain.getToken()
|
||||
);
|
||||
|
||||
logger.info("会议入口URL生成成功: meetingId={}", meetingId);
|
||||
return ResultDomain.success("生成会议入口URL成功", entryUrl);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("生成会议入口URL异常: meetingId={}, error={}", meetingId, e.getMessage(), e);
|
||||
return ResultDomain.failure("生成会议入口URL失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user