diff --git a/im-client/src/main/java/com/bx/imclient/sender/IMSender.java b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java index 9aa66d2..460dfa7 100644 --- a/im-client/src/main/java/com/bx/imclient/sender/IMSender.java +++ b/im-client/src/main/java/com/bx/imclient/sender/IMSender.java @@ -4,7 +4,7 @@ import com.bx.imclient.listener.MessageListenerMulticaster; import com.bx.imcommon.contant.RedisKey; import com.bx.imcommon.enums.IMCmdType; import com.bx.imcommon.enums.IMListenerType; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.model.GroupMessageInfo; import com.bx.imcommon.model.IMRecvInfo; import com.bx.imcommon.model.PrivateMessageInfo; @@ -54,8 +54,7 @@ public class IMSender { SendResult result = new SendResult(); result.setMessageInfo(messageInfo); result.setRecvId(recvId); - result.setStatus(IMSendStatus.FAIL); - result.setFailReason("用户不在线"); + result.setCode(IMSendCode.NOT_ONLINE); listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, result); } } @@ -103,8 +102,7 @@ public class IMSender { SendResult result = new SendResult(); result.setMessageInfo(messageInfo); result.setRecvId(id); - result.setStatus(IMSendStatus.FAIL); - result.setFailReason("用户不在线"); + result.setCode(IMSendCode.NOT_ONLINE); listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE,result); } } diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMSendStatus.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java similarity index 61% rename from im-commom/src/main/java/com/bx/imcommon/enums/IMSendStatus.java rename to im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java index cf82384..62ad254 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMSendStatus.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java @@ -1,16 +1,18 @@ package com.bx.imcommon.enums; -public enum IMSendStatus { +public enum IMSendCode { SUCCESS(0,"发送成功"), - FAIL(1,"发送失败"); + NOT_ONLINE(1,"对方当前不在线"), + NOT_FIND_CHANNEL(2,"未找到对方的channel"), + UNKONW_ERROR(9999,"未知异常"); private int code; private String desc; // 构造方法 - IMSendStatus(int code, String desc) { + IMSendCode(int code, String desc) { this.code = code; this.desc = desc; } diff --git a/im-commom/src/main/java/com/bx/imcommon/model/SendResult.java b/im-commom/src/main/java/com/bx/imcommon/model/SendResult.java index 66ea316..2a65b82 100644 --- a/im-commom/src/main/java/com/bx/imcommon/model/SendResult.java +++ b/im-commom/src/main/java/com/bx/imcommon/model/SendResult.java @@ -1,6 +1,6 @@ package com.bx.imcommon.model; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import lombok.Data; @Data @@ -14,12 +14,7 @@ public class SendResult { /* * 发送状态 */ - private IMSendStatus status; - - /* - * 失败原因 - */ - private String failReason=""; + private IMSendCode code; /* * 消息体(透传) diff --git a/im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java new file mode 100644 index 0000000..75c6cc5 --- /dev/null +++ b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java @@ -0,0 +1,118 @@ +package com.bx.implatform.controller; + + +import com.bx.imclient.IMClient; +import com.bx.imcommon.model.PrivateMessageInfo; +import com.bx.implatform.enums.MessageType; +import com.bx.implatform.result.Result; +import com.bx.implatform.result.ResultUtils; +import com.bx.implatform.session.SessionContext; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@Api(tags = "webrtc视频单人通话") +@RestController +@RequestMapping("/webrtc/private") +public class WebrtcController { + + @Autowired + private IMClient imClient; + + + @ApiOperation(httpMethod = "POST", value = "呼叫视频通话") + @PostMapping("/call") + public Result call(@RequestParam Long uid, @RequestBody String offer) { + Long userId = SessionContext.getSession().getId(); + + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_CALL.code()); + message.setRecvId(uid); + message.setSendId(userId); + message.setContent(offer); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + @ApiOperation(httpMethod = "POST", value = "接受视频通话") + @PostMapping("/accept") + public Result accept(@RequestParam Long uid,@RequestBody String answer) { + Long userId = SessionContext.getSession().getId(); + + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_ACCEPT.code()); + message.setRecvId(uid); + message.setSendId(userId); + message.setContent(answer); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + + @ApiOperation(httpMethod = "POST", value = "拒绝视频通话") + @PostMapping("/reject") + public Result reject(@RequestParam Long uid) { + Long userId = SessionContext.getSession().getId(); + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_REJECT.code()); + message.setRecvId(uid); + message.setSendId(userId); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + @ApiOperation(httpMethod = "POST", value = "取消呼叫") + @PostMapping("/cancel") + public Result cancel(@RequestParam Long uid) { + Long userId = SessionContext.getSession().getId(); + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_CANCEL.code()); + message.setRecvId(uid); + message.setSendId(userId); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + @ApiOperation(httpMethod = "POST", value = "呼叫失败") + @PostMapping("/failed") + public Result failed(@RequestParam Long uid,@RequestParam String reason) { + Long userId = SessionContext.getSession().getId(); + + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_FAILED.code()); + message.setRecvId(uid); + message.setSendId(userId); + message.setContent(reason); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + @ApiOperation(httpMethod = "POST", value = "挂断") + @PostMapping("/handup") + public Result leave(@RequestParam Long uid) { + Long userId = SessionContext.getSession().getId(); + + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_HANDUP.code()); + message.setRecvId(uid); + message.setSendId(userId); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } + + + @PostMapping("/candidate") + @ApiOperation(httpMethod = "POST", value = "同步candidate") + public Result candidate(@RequestParam Long uid,@RequestBody String candidate ) { + Long userId = SessionContext.getSession().getId(); + PrivateMessageInfo message = new PrivateMessageInfo(); + message.setType(MessageType.RTC_CANDIDATE.code()); + message.setRecvId(uid); + message.setSendId(userId); + message.setContent(candidate); + imClient.sendPrivateMessage(uid,message); + return ResultUtils.success(); + } +} diff --git a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java index f6a0622..cc7a07c 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java @@ -7,7 +7,15 @@ public enum MessageType { FILE(1,"文件"), IMAGE(2,"图片"), VIDEO(3,"视频"), - TIP(10,"系统提示"); + TIP(10,"系统提示"), + + RTC_CALL(101,"呼叫"), + RTC_ACCEPT(102,"接受"), + RTC_REJECT(103, "拒绝"), + RTC_CANCEL(104,"取消呼叫"), + RTC_FAILED(105,"呼叫失败"), + RTC_HANDUP(106,"挂断"), + RTC_CANDIDATE(107,"同步candidate"); private Integer code; diff --git a/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java index 3a8a745..1258781 100644 --- a/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java +++ b/im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java @@ -3,7 +3,7 @@ package com.bx.implatform.listener; import com.bx.imclient.annotation.IMListener; import com.bx.imclient.listener.MessageListener; import com.bx.imcommon.enums.IMListenerType; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.model.GroupMessageInfo; import com.bx.imcommon.model.SendResult; import com.bx.implatform.contant.RedisKey; @@ -29,7 +29,7 @@ public class GroupMessageListener implements MessageListener { } // 保存该用户已拉取的最大消息id - if(result.getStatus().equals(IMSendStatus.SUCCESS)) { + if(result.getCode().equals(IMSendCode.SUCCESS)) { String key = RedisKey.IM_GROUP_READED_POSITION + messageInfo.getGroupId() + ":" + result.getRecvId(); redisTemplate.opsForValue().set(key, messageInfo.getId()); } diff --git a/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java index 636ee4e..b7fad41 100644 --- a/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java +++ b/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java @@ -1,10 +1,11 @@ package com.bx.implatform.listener; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.bx.imclient.IMClient; import com.bx.imclient.annotation.IMListener; import com.bx.imclient.listener.MessageListener; import com.bx.imcommon.enums.IMListenerType; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.model.PrivateMessageInfo; import com.bx.imcommon.model.SendResult; import com.bx.implatform.entity.PrivateMessage; @@ -14,6 +15,8 @@ import com.bx.implatform.service.IPrivateMessageService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import java.util.Date; + @Slf4j @IMListener(type = IMListenerType.PRIVATE_MESSAGE) @@ -22,15 +25,33 @@ public class PrivateMessageListener implements MessageListener { @Autowired private IPrivateMessageService privateMessageService; + @Autowired + private IMClient imClient; + @Override public void process(SendResult result){ PrivateMessageInfo messageInfo = (PrivateMessageInfo) result.getMessageInfo(); - if(messageInfo.getType().equals(MessageType.TIP)){ - // 提示类数据不记录 + // 提示类数据不记录 + if(messageInfo.getType().equals(MessageType.TIP.code())){ + return; } + // 视频通话信令不记录 + if(messageInfo.getType() >= MessageType.RTC_CALL.code() && messageInfo.getType()< MessageType.RTC_CANDIDATE.code()){ + // 通知用户呼叫失败了 + if(messageInfo.getType() == MessageType.RTC_CALL.code() + && !result.getCode().equals(IMSendCode.SUCCESS)){ + PrivateMessageInfo sendMessage = new PrivateMessageInfo(); + sendMessage.setRecvId(messageInfo.getSendId()); + sendMessage.setSendId(messageInfo.getRecvId()); + sendMessage.setType(MessageType.RTC_FAILED.code()); + sendMessage.setContent(result.getCode().description()); + sendMessage.setSendTime(new Date()); + imClient.sendPrivateMessage(sendMessage.getRecvId(),sendMessage); + } + } // 更新消息状态 - if(result.getStatus().equals(IMSendStatus.SUCCESS)){ + if(result.getCode().equals(IMSendCode.SUCCESS)){ UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.lambda().eq(PrivateMessage::getId,messageInfo.getId()) .eq(PrivateMessage::getStatus, MessageStatus.UNREAD.code()) diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java index 2451779..3b589b9 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java @@ -2,7 +2,7 @@ package com.bx.imserver.netty.processor; import com.bx.imcommon.contant.RedisKey; import com.bx.imcommon.enums.IMCmdType; -import com.bx.imcommon.enums.IMSendStatus; +import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.model.GroupMessageInfo; import com.bx.imcommon.model.IMRecvInfo; import com.bx.imcommon.model.IMSendInfo; @@ -43,7 +43,7 @@ public class GroupMessageProcessor extends MessageProcessor {{title}} - + @@ -12,10 +11,8 @@
  • - +
@@ -26,24 +23,24 @@
-
- +
+
+
- +
发送
@@ -57,9 +54,7 @@
- + @@ -70,8 +65,7 @@ import Emotion from "../common/Emotion.vue"; import ChatVoice from "./ChatVoice.vue"; import ChatHistory from "./ChatHistory.vue"; - - + export default { name: "chatPrivate", components: { @@ -129,7 +123,7 @@ thumbUrl: url } let msgInfo = { - id:0, + id: 0, fileId: file.uid, sendId: this.mine.id, content: JSON.stringify(data), @@ -218,10 +212,17 @@ closeVoiceBox() { this.showVoice = false; }, - showHistoryBox(){ + showVideoBox() { + console.log(this.friend) + this.$store.commit("showChatPrivateVideoBox", { + friend: this.friend, + master: true + }); + }, + showHistoryBox() { this.showHistory = true; }, - closeHistoryBox(){ + closeHistoryBox() { this.showHistory = false; }, handleSendVoice(data) { @@ -292,17 +293,17 @@ return false; } }, - deleteMessage(msgInfo){ - this.$confirm( '确认删除消息?','删除消息', { + deleteMessage(msgInfo) { + this.$confirm('确认删除消息?', '删除消息', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { - this.$store.commit("deleteMessage",msgInfo); + this.$store.commit("deleteMessage", msgInfo); }); }, - recallMessage(msgInfo){ - this.$confirm('确认撤回消息?','撤回消息', { + recallMessage(msgInfo) { + this.$confirm('确认撤回消息?', '撤回消息', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' @@ -316,10 +317,10 @@ msgInfo = JSON.parse(JSON.stringify(msgInfo)); msgInfo.type = 10; msgInfo.content = '你撤回了一条消息'; - this.$store.commit("insertMessage",msgInfo); + this.$store.commit("insertMessage", msgInfo); }) }); - + }, loadGroup(groupId) { this.$http({ @@ -346,6 +347,7 @@ method: 'get' }).then((friend) => { this.friend = friend; + console.log(this.friend) this.$store.commit("updateChatFromFriend", friend); this.$store.commit("updateFriend", friend); }) diff --git a/im-ui/src/components/chat/ChatVoice.vue b/im-ui/src/components/chat/ChatVoice.vue index 9bb7363..48bdc2c 100644 --- a/im-ui/src/components/chat/ChatVoice.vue +++ b/im-ui/src/components/chat/ChatVoice.vue @@ -19,7 +19,6 @@ 重新录音 立即发送 - diff --git a/im-ui/src/main.js b/im-ui/src/main.js index 5fb1b03..34478a3 100644 --- a/im-ui/src/main.js +++ b/im-ui/src/main.js @@ -9,7 +9,7 @@ import * as socketApi from './api/wssocket'; import emotion from './api/emotion.js'; import element from './api/element.js'; import store from './store'; - +import * as enums from './api/enums.js'; Vue.use(ElementUI); @@ -18,7 +18,7 @@ Vue.prototype.$wsApi = socketApi; Vue.prototype.$http = httpRequest // http请求方法 Vue.prototype.$emo = emotion; // emo表情 Vue.prototype.$elm = element; // 元素操作 - +Vue.prototype.$enums = enums; // 枚举 Vue.config.productionTip = false; new Vue({ diff --git a/im-ui/src/store/uiStore.js b/im-ui/src/store/uiStore.js index 351f6f4..1fd5d5a 100644 --- a/im-ui/src/store/uiStore.js +++ b/im-ui/src/store/uiStore.js @@ -11,6 +11,17 @@ export default { fullImage: { // 全屏大图 show: false, url: "" + }, + chatPrivateVideo:{ // 私人视频聊天 + show: false, + master: false, // 是否房主 + friend:{}, + offer:{} // 对方发起带过过来的sdp信息 + }, + videoAcceptor:{ // 视频呼叫选择 + show: false, + + friend:{} } }, @@ -35,6 +46,23 @@ export default { }, closeFullImageBox(state){ state.fullImage.show = false; + }, + showChatPrivateVideoBox(state,info){ + state.chatPrivateVideo.show = true; + state.chatPrivateVideo.friend = info.friend; + state.chatPrivateVideo.master = info.master; + state.chatPrivateVideo.offer = info.offer; + }, + closeChatPrivateVideoBox(state){ + state.chatPrivateVideo.show = false; + }, + showVideoAcceptorBox(state,friend){ + state.videoAcceptor.show = true; + state.videoAcceptor.friend = friend; + + }, + closeVideoAcceptorBox(state){ + state.videoAcceptor.show = false; } - }, + } } \ No newline at end of file diff --git a/im-ui/src/store/userStore.js b/im-ui/src/store/userStore.js index 4ad45bd..478ce8f 100644 --- a/im-ui/src/store/userStore.js +++ b/im-ui/src/store/userStore.js @@ -1,7 +1,12 @@ +import {USER_STATE} from "../api/enums.js" + export default { state: { - userInfo: {} + userInfo: { + + }, + state: USER_STATE.FREE }, mutations: { @@ -12,7 +17,10 @@ export default { this.commit("resetChatStore"); } state.userInfo = userInfo; - } + }, + setUserState(state, userState) { + state.state = userState; + }, } } \ No newline at end of file diff --git a/im-ui/src/view/Home.vue b/im-ui/src/view/Home.vue index 86b836e..6ff0632 100644 --- a/im-ui/src/view/Home.vue +++ b/im-ui/src/view/Home.vue @@ -38,6 +38,17 @@ + + + + @@ -46,17 +57,23 @@ import Setting from '../components/setting/Setting.vue'; import UserInfo from '../components/common/UserInfo.vue'; import FullImage from '../components/common/FullImage.vue'; - + import ChatPrivateVideo from '../components/chat/ChatPrivateVideo.vue'; + import VideoAcceptor from '../components/chat/VideoAcceptor.vue'; + + export default { components: { HeadImage, Setting, UserInfo, - FullImage + FullImage, + ChatPrivateVideo, + VideoAcceptor }, data() { return { - showSettingDialog: false + showSettingDialog: false, + } }, methods: { @@ -81,7 +98,7 @@ } else if (cmd == 4) { // 插入群聊消息 this.handleGroupMessage(msgInfo); - } + } }) }, pullUnreadMessage() { @@ -113,6 +130,21 @@ }) }, insertPrivateMessage(friend, msg) { + // webrtc 信令 + if(msg.type >= this.$enums.MESSAGE_TYPE.RTC_CALL + && msg.type <= this.$enums.MESSAGE_TYPE.RTC_CANDIDATE){ + // 呼叫 + console.log(msg) + if(msg.type == this.$enums.MESSAGE_TYPE.RTC_CALL + || msg.type == this.$enums.MESSAGE_TYPE.RTC_CANCEL){ + this.$store.commit("showVideoAcceptorBox",friend); + this.$refs.videoAcceptor.handleMessage(msg) + }else { + this.$refs.privateVideo.handleMessage(msg) + } + return ; + } + let chatInfo = { type: 'PRIVATE', targetId: friend.id,