Browse Source

feat: 用户忙线状态改为在后端维护

master
xsx 2 years ago
parent
commit
b046eb245a
  1. 35
      im-platform/src/main/java/com/bx/implatform/controller/SystemController.java
  2. 7
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcGroupController.java
  3. 10
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java
  4. 6
      im-platform/src/main/java/com/bx/implatform/service/IWebrtcGroupService.java
  5. 3
      im-platform/src/main/java/com/bx/implatform/service/IWebrtcPrivateService.java
  6. 7
      im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcGroupServiceImpl.java
  7. 33
      im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java
  8. 22
      im-platform/src/main/java/com/bx/implatform/vo/SystemConfigVO.java
  9. 23
      im-ui/src/components/chat/ChatPrivateVideo.vue
  10. 4
      im-ui/src/store/index.js
  11. 2
      im-uniapp/App.vue
  12. 16
      im-uniapp/components/group-member-selector/group-member-selector.vue
  13. 1
      im-uniapp/pages/chat/chat-box.vue
  14. 1
      im-uniapp/pages/chat/chat-group-video.vue
  15. 3
      im-uniapp/pages/chat/chat-private-video.vue
  16. 10
      im-uniapp/store/index.js
  17. 3
      im-uniapp/store/userStore.js

35
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<SystemConfigVO> loadConfig() {
return ResultUtils.success(new SystemConfigVO(webrtcConfig));
}
}

7
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<WebrtcConfig> loadConfig() {
return ResultUtils.success(webrtcGroupService.loadConfig());
}
}
}

10
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<List<ICEServer>> iceservers() {
return ResultUtils.success(webrtcPrivateService.getIceServers());
@ApiOperation(httpMethod = "POST", value = "获取通话信息")
@PostMapping("/heartbeat")
public Result heartbeat(@RequestParam Long uid) {
webrtcPrivateService.heartbeat(uid);
return ResultUtils.success();
}
}

6
im-platform/src/main/java/com/bx/implatform/service/IWebrtcGroupService.java

@ -76,10 +76,4 @@ public interface IWebrtcGroupService {
*/
void heartbeat(Long groupId);
/**
* 加载配置
*/
WebrtcConfig loadConfig();
}

3
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<ICEServer> getIceServers();
void heartbeat(Long uid);
}

7
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<Long> recvIds = getRecvIds(dto.getUserInfos());
List<Long> 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);

33
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<String, Object> 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<ICEServer> 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) {

22
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;
}

23
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: {

4
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);
})
},

2
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;

16
im-uniapp/components/group-member-selector/group-member-selector.vue

@ -19,7 +19,7 @@
</view>
<view class="member-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="m in members" v-show="!m.quit && m.aliasName.startsWith(searchText)" :key="m.userId">
<view v-for="m in members" v-show="!m.quit && m.aliasName.startsWith(searchText)" :key="m.userId">
<view class="member-item" @click="onSwitchChecked(m)">
<head-image :name="m.aliasName" :online="m.online" :url="m.headImage"
:size="90"></head-image>
@ -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) => {

1
im-uniapp/pages/chat/chat-box.vue

@ -109,6 +109,7 @@
@complete="onAtComplete"></chat-at-box>
<!-- 群语音通话时选择成员 -->
<group-member-selector ref="selBox" :members="groupMembers"
:maxSize="$store.state.configStore.webrtc.maxChannel"
@complete="onSelectMember"></group-member-selector>
<group-rtc-join ref="rtcJoin" :groupId="group.id"></group-rtc-join>
</view>

1
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() {

3
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() {

10
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);
})
},

3
im-uniapp/store/userStore.js

@ -5,6 +5,9 @@ import http from '../common/request'
export default {
state: {
userInfo: {},
config:{
webrtc:{}
},
state: USER_STATE.FREE
},

Loading…
Cancel
Save