From b046eb245a2b62d91023154adc7851dde6107239 Mon Sep 17 00:00:00 2001 From: xsx <825657193@qq.com> Date: Mon, 10 Jun 2024 19:46:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E5=BF=99=E7=BA=BF?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=94=B9=E4=B8=BA=E5=9C=A8=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SystemController.java | 35 +++++++++++++++++++ .../controller/WebrtcGroupController.java | 7 +--- .../controller/WebrtcPrivateController.java | 10 +++--- .../service/IWebrtcGroupService.java | 6 ---- .../service/IWebrtcPrivateService.java | 3 +- .../service/impl/WebrtcGroupServiceImpl.java | 7 +--- .../impl/WebrtcPrivateServiceImpl.java | 33 ++++++++++++++--- .../com/bx/implatform/vo/SystemConfigVO.java | 22 ++++++++++++ .../src/components/chat/ChatPrivateVideo.vue | 23 ++++++++---- im-ui/src/store/index.js | 4 ++- im-uniapp/App.vue | 2 +- .../group-member-selector.vue | 16 +++++++-- im-uniapp/pages/chat/chat-box.vue | 1 + im-uniapp/pages/chat/chat-group-video.vue | 1 + im-uniapp/pages/chat/chat-private-video.vue | 3 +- im-uniapp/store/index.js | 10 +++--- im-uniapp/store/userStore.js | 3 ++ 17 files changed, 141 insertions(+), 45 deletions(-) create mode 100644 im-platform/src/main/java/com/bx/implatform/controller/SystemController.java create mode 100644 im-platform/src/main/java/com/bx/implatform/vo/SystemConfigVO.java diff --git a/im-platform/src/main/java/com/bx/implatform/controller/SystemController.java b/im-platform/src/main/java/com/bx/implatform/controller/SystemController.java new file mode 100644 index 0000000..d3cd30b --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/SystemController.java @@ -0,0 +1,35 @@ +package com.bx.implatform.controller; + +import com.bx.implatform.config.WebrtcConfig; +import com.bx.implatform.dto.PrivateMessageDTO; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.vo.SystemConfigVO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + + + +/** + * @author: blue + * @date: 2024-06-10 + * @version: 1.0 + */ +@Api(tags = "系统相关") +@RestController +@RequestMapping("/system") +@RequiredArgsConstructor +public class SystemController { + + private final WebrtcConfig webrtcConfig; + + @GetMapping("/config") + @ApiOperation(value = "加载系统配置", notes = "加载系统配置") + public Result loadConfig() { + return ResultUtils.success(new SystemConfigVO(webrtcConfig)); + } + + +} diff --git a/im-platform/src/main/java/com/bx/implatform/controller/WebrtcGroupController.java b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcGroupController.java index 93e19a4..03361be 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/WebrtcGroupController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcGroupController.java @@ -123,9 +123,4 @@ public class WebrtcGroupController { return ResultUtils.success(); } - @GetMapping("/config") - @ApiOperation(httpMethod = "GET", value = "获取系统配置") - public Result loadConfig() { - return ResultUtils.success(webrtcGroupService.loadConfig()); - } -} + } diff --git a/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java index 8f37619..40cca08 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java @@ -70,10 +70,10 @@ public class WebrtcPrivateController { return ResultUtils.success(); } - - @GetMapping("/iceservers") - @ApiOperation(httpMethod = "GET", value = "获取iceservers") - public Result> iceservers() { - return ResultUtils.success(webrtcPrivateService.getIceServers()); + @ApiOperation(httpMethod = "POST", value = "获取通话信息") + @PostMapping("/heartbeat") + public Result heartbeat(@RequestParam Long uid) { + webrtcPrivateService.heartbeat(uid); + return ResultUtils.success(); } } diff --git a/im-platform/src/main/java/com/bx/implatform/service/IWebrtcGroupService.java b/im-platform/src/main/java/com/bx/implatform/service/IWebrtcGroupService.java index 9a257f4..edef9e6 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IWebrtcGroupService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IWebrtcGroupService.java @@ -76,10 +76,4 @@ public interface IWebrtcGroupService { */ void heartbeat(Long groupId); - - /** - * 加载配置 - */ - WebrtcConfig loadConfig(); - } diff --git a/im-platform/src/main/java/com/bx/implatform/service/IWebrtcPrivateService.java b/im-platform/src/main/java/com/bx/implatform/service/IWebrtcPrivateService.java index 7eea5db..04dd4ad 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IWebrtcPrivateService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IWebrtcPrivateService.java @@ -25,7 +25,6 @@ public interface IWebrtcPrivateService { void candidate(Long uid, String candidate); - List getIceServers(); - + void heartbeat(Long uid); } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcGroupServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcGroupServiceImpl.java index e19835d..3a30277 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcGroupServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcGroupServiceImpl.java @@ -110,7 +110,7 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService { sendRtcMessage2(MessageType.RTC_GROUP_FAILED, dto.getGroupId(), reciver, JSON.toJSONString(vo)); } // 向被邀请的用户广播消息,发起呼叫 - List recvIds = getRecvIds(dto.getUserInfos()); + List recvIds = getRecvIds(userInfos); sendRtcMessage1(MessageType.RTC_GROUP_SETUP, dto.getGroupId(), recvIds, JSON.toJSONString(userInfos)); // 发送文字提示信息 WebrtcUserInfo mineInfo = findUserInfo(webrtcSession,userSession.getUserId()); @@ -465,11 +465,6 @@ public class WebrtcGroupServiceImpl implements IWebrtcGroupService { userStateUtils.expire(userSession.getUserId()); } - @Override - public WebrtcConfig loadConfig() { - return webrtcConfig; - } - private WebrtcGroupSession getWebrtcSession(Long groupId) { String key = buildWebrtcSessionKey(groupId); WebrtcGroupSession webrtcSession = (WebrtcGroupSession)redisTemplate.opsForValue().get(key); diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java index 0d2ecde..b05fc6c 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java @@ -12,6 +12,7 @@ import com.bx.implatform.service.IWebrtcPrivateService; import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.UserSession; import com.bx.implatform.session.WebrtcPrivateSession; +import com.bx.implatform.util.UserStateUtils; import com.bx.implatform.vo.PrivateMessageVO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -31,6 +32,7 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService { private final IMClient imClient; private final RedisTemplate redisTemplate; private final WebrtcConfig iceServerConfig; + private final UserStateUtils userStateUtils; @Override public void call(Long uid, String mode, String offer) { @@ -38,12 +40,18 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService { if (!imClient.isOnline(uid)) { throw new GlobalException("对方目前不在线"); } + if(userStateUtils.isBusy(uid)){ + throw new GlobalException("对方正忙"); + } // 创建webrtc会话 WebrtcPrivateSession webrtcSession = new WebrtcPrivateSession(); webrtcSession.setCallerId(session.getUserId()); webrtcSession.setCallerTerminal(session.getTerminal()); String key = getWebRtcSessionKey(session.getUserId(), uid); - redisTemplate.opsForValue().set(key, webrtcSession, 12, TimeUnit.HOURS); + redisTemplate.opsForValue().set(key, webrtcSession, 60, TimeUnit.SECONDS); + // 设置用户忙线状态 + userStateUtils.setBusy(uid); + userStateUtils.setBusy(session.getUserId()); // 向对方所有终端发起呼叫 PrivateMessageVO messageInfo = new PrivateMessageVO(); MessageType messageType = mode.equals("video") ? MessageType.RTC_CALL_VIDEO : MessageType.RTC_CALL_VOICE; @@ -71,7 +79,7 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService { webrtcSession.setAcceptorId(session.getUserId()); webrtcSession.setAcceptorTerminal(session.getTerminal()); String key = getWebRtcSessionKey(session.getUserId(), uid); - redisTemplate.opsForValue().set(key, webrtcSession, 12, TimeUnit.HOURS); + redisTemplate.opsForValue().set(key, webrtcSession, 60, TimeUnit.SECONDS); // 向发起人推送接受通话信令 PrivateMessageVO messageInfo = new PrivateMessageVO(); messageInfo.setType(MessageType.RTC_ACCEPT.code()); @@ -97,6 +105,9 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService { WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid); // 删除会话信息 removeWebrtcSession(uid, session.getUserId()); + // 设置用户空闲状态 + userStateUtils.setFree(uid); + userStateUtils.setFree(session.getUserId()); // 向发起人推送拒绝通话信令 PrivateMessageVO messageInfo = new PrivateMessageVO(); messageInfo.setType(MessageType.RTC_REJECT.code()); @@ -119,6 +130,9 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService { UserSession session = SessionContext.getSession(); // 删除会话信息 removeWebrtcSession(session.getUserId(), uid); + // 设置用户空闲状态 + userStateUtils.setFree(uid); + userStateUtils.setFree(session.getUserId()); // 向对方所有终端推送取消通话信令 PrivateMessageVO messageInfo = new PrivateMessageVO(); messageInfo.setType(MessageType.RTC_CANCEL.code()); @@ -142,6 +156,9 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService { WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid); // 删除会话信息 removeWebrtcSession(uid, session.getUserId()); + // 设置用户空闲状态 + userStateUtils.setFree(uid); + userStateUtils.setFree(session.getUserId()); // 向发起方推送通话失败信令 PrivateMessageVO messageInfo = new PrivateMessageVO(); messageInfo.setType(MessageType.RTC_FAILED.code()); @@ -168,6 +185,9 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService { WebrtcPrivateSession webrtcSession = getWebrtcSession(session.getUserId(), uid); // 删除会话信息 removeWebrtcSession(uid, session.getUserId()); + // 设置用户空闲状态 + userStateUtils.setFree(uid); + userStateUtils.setFree(session.getUserId()); // 向对方推送挂断通话信令 PrivateMessageVO messageInfo = new PrivateMessageVO(); messageInfo.setType(MessageType.RTC_HANDUP.code()); @@ -210,8 +230,13 @@ public class WebrtcPrivateServiceImpl implements IWebrtcPrivateService { } @Override - public List getIceServers() { - return iceServerConfig.getIceServers(); + public void heartbeat(Long uid) { + UserSession session = SessionContext.getSession(); + // 会话续命 + String key = getWebRtcSessionKey(session.getUserId(), uid); + redisTemplate.expire(key,60,TimeUnit.SECONDS); + // 用户状态续命 + userStateUtils.expire(session.getUserId()); } private WebrtcPrivateSession getWebrtcSession(Long userId, Long uid) { diff --git a/im-platform/src/main/java/com/bx/implatform/vo/SystemConfigVO.java b/im-platform/src/main/java/com/bx/implatform/vo/SystemConfigVO.java new file mode 100644 index 0000000..f8e936e --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/vo/SystemConfigVO.java @@ -0,0 +1,22 @@ +package com.bx.implatform.vo; + +import com.bx.implatform.config.WebrtcConfig; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @author: blue + * @date: 2024-06-10 + * @version: 1.0 + */ +@Data +@ApiModel("系统配置VO") +@AllArgsConstructor +public class SystemConfigVO { + + @ApiModelProperty(value = "webrtc配置") + private WebrtcConfig webrtc; + +} diff --git a/im-ui/src/components/chat/ChatPrivateVideo.vue b/im-ui/src/components/chat/ChatPrivateVideo.vue index c85a932..cdaecb2 100644 --- a/im-ui/src/components/chat/ChatPrivateVideo.vue +++ b/im-ui/src/components/chat/ChatPrivateVideo.vue @@ -49,6 +49,7 @@ peerConnection: null, videoTime: 0, videoTimer: null, + heartbeatTimer: null, candidates: [], configuration: { iceServers: [] @@ -78,6 +79,8 @@ this.accept(this.rtcInfo.offer); } }); + // 开启心跳 + this.startHeartBeat(); }, openCamera(callback) { navigator.getUserMedia({ @@ -275,6 +278,7 @@ this.loading = false; this.videoTime = 0; this.videoTimer && clearInterval(this.videoTimer); + this.heartbeatTimer && clearInterval(this.heartbeatTimer); this.audio.pause(); this.candidates = []; if (this.peerConnection) { @@ -295,6 +299,16 @@ this.videoTime++; }, 1000) }, + startHeartBeat() { + // 每15s推送一次心跳 + this.heartbeatTimer && clearInterval(this.heartbeatTimer); + this.heartbeatTimer = setInterval(() => { + this.$http({ + url: `/webrtc/private/heartbeat?uid=${this.rtcInfo.friend.id}`, + method: 'post' + }) + }, 15000) + }, handleClose() { if (this.isAccepted) { this.handup() @@ -325,14 +339,9 @@ this.audio.loop = true; }, initICEServers() { - this.$http({ - url: '/webrtc/private/iceservers', - method: 'get' - }).then((servers) => { - this.configuration.iceServers = servers; - }) + let iceServers = this.$store.state.configStore.webrtc.iceServers; + this.configuration.iceServers = iceServers; } - }, watch: { rtcState: { diff --git a/im-ui/src/store/index.js b/im-ui/src/store/index.js index 6b136bf..37426b5 100644 --- a/im-ui/src/store/index.js +++ b/im-ui/src/store/index.js @@ -4,12 +4,13 @@ import chatStore from './chatStore.js'; import friendStore from './friendStore.js'; import userStore from './userStore.js'; import groupStore from './groupStore.js'; +import configStore from './configStore.js'; import uiStore from './uiStore.js'; Vue.use(Vuex) export default new Vuex.Store({ - modules: {chatStore,friendStore,userStore,groupStore,uiStore}, + modules: {chatStore,friendStore,userStore,groupStore,configStore,uiStore}, state: {}, mutations: { }, @@ -20,6 +21,7 @@ export default new Vuex.Store({ promises.push(this.dispatch("loadFriend")); promises.push(this.dispatch("loadGroup")); promises.push(this.dispatch("loadChat")); + promises.push(this.dispatch("loadConfig")); return Promise.all(promises); }) }, diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue index 3fd1604..a2ba339 100644 --- a/im-uniapp/App.vue +++ b/im-uniapp/App.vue @@ -191,7 +191,7 @@ return; // #endif // 被呼叫,弹出视频页面 - let delayTime = 10; + let delayTime = 100; if(msg.type == enums.MESSAGE_TYPE.RTC_GROUP_SETUP){ let pages = getCurrentPages(); let curPage = pages[pages.length-1].route; diff --git a/im-uniapp/components/group-member-selector/group-member-selector.vue b/im-uniapp/components/group-member-selector/group-member-selector.vue index 2addd13..cced730 100644 --- a/im-uniapp/components/group-member-selector/group-member-selector.vue +++ b/im-uniapp/components/group-member-selector/group-member-selector.vue @@ -19,7 +19,7 @@ - + @@ -42,6 +42,10 @@ props: { members: { type: Array + }, + maxSize: { + type: Number, + default: -1 } }, data() { @@ -60,9 +64,17 @@ this.$refs.popup.open(); }, onSwitchChecked(m) { - if(!m.locked){ + if (!m.locked) { m.checked = !m.checked; } + // 达到选择上限 + if (this.maxSize > 0 && this.checkedIds.length > this.maxSize) { + m.checked = false; + uni.showToast({ + title: `最多选择${this.maxSize}位用户`, + icon: "none" + }) + } }, onClean() { this.members.forEach((m) => { diff --git a/im-uniapp/pages/chat/chat-box.vue b/im-uniapp/pages/chat/chat-box.vue index 0c50723..146fafc 100644 --- a/im-uniapp/pages/chat/chat-box.vue +++ b/im-uniapp/pages/chat/chat-box.vue @@ -109,6 +109,7 @@ @complete="onAtComplete"> diff --git a/im-uniapp/pages/chat/chat-group-video.vue b/im-uniapp/pages/chat/chat-group-video.vue index 9b11494..de42480 100644 --- a/im-uniapp/pages/chat/chat-group-video.vue +++ b/im-uniapp/pages/chat/chat-group-video.vue @@ -76,6 +76,7 @@ this.url += "&isHost=" + this.isHost; this.url += "&loginInfo=" + JSON.stringify(uni.getStorageSync("loginInfo")); this.url += "&userInfos=" + JSON.stringify(this.userInfos); + this.url += "&config=" + JSON.stringify(this.$store.state.configStore.webrtc); }, }, onBackPress() { diff --git a/im-uniapp/pages/chat/chat-private-video.vue b/im-uniapp/pages/chat/chat-private-video.vue index 801d17b..afb405b 100644 --- a/im-uniapp/pages/chat/chat-private-video.vue +++ b/im-uniapp/pages/chat/chat-private-video.vue @@ -72,13 +72,14 @@ // #endif }, initUrl(){ - this.url = "/hybrid/html/index.html"; + this.url = "/hybrid/html/rtc-private/index.html"; this.url += "?mode="+this.mode; this.url += "&isHost="+this.isHost; this.url += "&baseUrl="+UNI_APP.BASE_URL; this.url += "&loginInfo="+JSON.stringify(uni.getStorageSync("loginInfo")); this.url += "&userInfo="+JSON.stringify(this.$store.state.userStore.userInfo); this.url += "&friend="+JSON.stringify(this.friend); + this.url += "&config=" + JSON.stringify(this.$store.state.configStore.webrtc); }, }, onBackPress() { diff --git a/im-uniapp/store/index.js b/im-uniapp/store/index.js index 34db94b..fa52aac 100644 --- a/im-uniapp/store/index.js +++ b/im-uniapp/store/index.js @@ -2,15 +2,16 @@ import chatStore from './chatStore.js'; import friendStore from './friendStore.js'; import userStore from './userStore.js'; import groupStore from './groupStore.js'; -import { - createStore -} from 'vuex'; +import configStore from './configStore.js'; +import { createStore } from 'vuex'; + const store = createStore({ modules: { chatStore, friendStore, userStore, - groupStore + groupStore, + configStore }, state: {}, actions: { @@ -20,6 +21,7 @@ const store = createStore({ promises.push(this.dispatch("loadFriend")); promises.push(this.dispatch("loadGroup")); promises.push(this.dispatch("loadChat")); + promises.push(this.dispatch("loadConfig")); return Promise.all(promises); }) }, diff --git a/im-uniapp/store/userStore.js b/im-uniapp/store/userStore.js index 42056e5..a2e5a5a 100644 --- a/im-uniapp/store/userStore.js +++ b/im-uniapp/store/userStore.js @@ -5,6 +5,9 @@ import http from '../common/request' export default { state: { userInfo: {}, + config:{ + webrtc:{} + }, state: USER_STATE.FREE },