diff --git a/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java b/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java index d985867..dd9e30e 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java +++ b/im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java @@ -74,7 +74,7 @@ public class IMChannelHandler extends SimpleChannelInboundHandler { RedisTemplate redisTemplate = SpringContextHolder.getBean("redisTemplate"); String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.toString()); redisTemplate.delete(key); - log.info("断开连接,userId:{},终端类型:{}", userId, terminal); + log.info("断开连接,userId:{},终端类型:{},{}", userId, terminal, ctx.channel().id().asLongText()); } } diff --git a/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java b/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java index bf9bf3c..2a0e997 100644 --- a/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java +++ b/im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java @@ -30,7 +30,7 @@ public class HeartbeatProcessor extends AbstractMessageProcessor heartBeatAttr = AttributeKey.valueOf(ChannelAttrKey.HEARTBEAT_TIMES); Long heartbeatTimes = ctx.channel().attr(heartBeatAttr).get(); @@ -44,6 +44,9 @@ public class HeartbeatProcessor extends AbstractMessageProcessor userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID); + Long userId = ctx.channel().attr(userIdAttr).get(); + log.info("心跳,userId:{},{}",userId,ctx.channel().id().asLongText()); } @Override diff --git a/im-ui/package.json b/im-ui/package.json index 7c85713..c10926b 100644 --- a/im-ui/package.json +++ b/im-ui/package.json @@ -13,7 +13,7 @@ "element-ui": "^2.15.10", "js-audio-recorder": "^1.0.7", "sass": "^1.47.0", - "sass-loader": "^7.3.1", + "sass-loader": "^10.1.1", "vue": "^2.6.11", "vue-axios": "^3.5.0", "vue-router": "^3.3.3", diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue index 72cb7d5..549bf4e 100644 --- a/im-uniapp/App.vue +++ b/im-uniapp/App.vue @@ -5,11 +5,12 @@ import * as enums from './common/enums'; import * as wsApi from './common/wssocket'; import UNI_APP from '@/.env.js' - + export default { data() { return { - audioTip: null + audioTip: null, + reconnecting: false // 正在重连标志 } }, methods: { @@ -28,6 +29,14 @@ wsApi.init(); wsApi.connect(UNI_APP.WS_URL, loginInfo.accessToken); wsApi.onConnect(() => { + // 重连成功提示 + if(this.reconnecting){ + this.reconnecting = false; + uni.showToast({ + title: "已重新连接", + icon: 'none' + }) + } // 加载离线消息 this.pullPrivateOfflineMessage(store.state.chatStore.privateMsgMaxId); this.pullGroupOfflineMessage(store.state.chatStore.groupMsgMaxId); @@ -49,35 +58,30 @@ } }); wsApi.onClose((res) => { - console.log("ws断开",res); - // 1000是客户端正常主动关闭 - if (res.code != 1000) { + console.log("ws断开", res); + // 3099是客户端正常主动关闭 + if (res.code != 3099) { // 重新连接 - uni.showToast({ - title: '连接已断开,尝试重新连接...', - icon: 'none', - }) - let loginInfo = uni.getStorageSync("loginInfo") - wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken); + this.reconnectWs(); } }) }, pullPrivateOfflineMessage(minId) { - store.commit("loadingPrivateMsg",true) + store.commit("loadingPrivateMsg", true) http({ url: "/message/private/pullOfflineMessage?minId=" + minId, method: 'GET' - }).catch(()=>{ - store.commit("loadingPrivateMsg",false) + }).catch(() => { + store.commit("loadingPrivateMsg", false) }) }, pullGroupOfflineMessage(minId) { - store.commit("loadingGroupMsg",true) + store.commit("loadingGroupMsg", true) http({ url: "/message/group/pullOfflineMessage?minId=" + minId, method: 'GET' - }).catch(()=>{ - store.commit("loadingGroupMsg",false) + }).catch(() => { + store.commit("loadingGroupMsg", false) }) }, handlePrivateMessage(msg) { @@ -96,7 +100,9 @@ } // 消息回执处理,改消息状态为已读 if (msg.type == enums.MESSAGE_TYPE.RECEIPT) { - store.commit("readedMessage", { friendId: msg.sendId }) + store.commit("readedMessage", { + friendId: msg.sendId + }) return; } // 标记这条消息是不是自己发的 @@ -112,17 +118,17 @@ // 单人视频信令 if (msgType.isRtcPrivate(msg.type)) { // #ifdef MP-WEIXIN - // 小程序不支持音视频 - return; + // 小程序不支持音视频 + return; // #endif // 被呼叫,弹出视频页面 let delayTime = 100; - if(msg.type == enums.MESSAGE_TYPE.RTC_CALL_VOICE - || msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO){ - let mode = msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO? "video":"voice"; + if (msg.type == enums.MESSAGE_TYPE.RTC_CALL_VOICE || + msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO) { + let mode = msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO ? "video" : "voice"; let pages = getCurrentPages(); - let curPage = pages[pages.length-1].route; - if(curPage != "pages/chat/chat-private-video"){ + let curPage = pages[pages.length - 1].route; + if (curPage != "pages/chat/chat-private-video") { const friendInfo = encodeURIComponent(JSON.stringify(friend)); uni.navigateTo({ url: `/pages/chat/chat-private-video?mode=${mode}&friend=${friendInfo}&isHost=false` @@ -131,8 +137,8 @@ } } setTimeout(() => { - uni.$emit('WS_RTC_PRIVATE',msg); - },delayTime) + uni.$emit('WS_RTC_PRIVATE', msg); + }, delayTime) return; } let chatInfo = { @@ -152,7 +158,7 @@ handleGroupMessage(msg) { // 消息加载标志 if (msg.type == enums.MESSAGE_TYPE.LOADING) { - store.commit("loadingGroupMsg",JSON.parse(msg.content)) + store.commit("loadingGroupMsg", JSON.parse(msg.content)) return; } // 消息已读处理 @@ -189,15 +195,15 @@ // 群视频信令 if (msgType.isRtcGroup(msg.type)) { // #ifdef MP-WEIXIN - // 小程序不支持音视频 - return; + // 小程序不支持音视频 + return; // #endif // 被呼叫,弹出视频页面 let delayTime = 100; - if(msg.type == enums.MESSAGE_TYPE.RTC_GROUP_SETUP){ + if (msg.type == enums.MESSAGE_TYPE.RTC_GROUP_SETUP) { let pages = getCurrentPages(); - let curPage = pages[pages.length-1].route; - if(curPage != "pages/chat/chat-group-video"){ + let curPage = pages[pages.length - 1].route; + if (curPage != "pages/chat/chat-group-video") { const userInfos = encodeURIComponent(msg.content); const inviterId = msg.sendId; const groupId = msg.groupId @@ -210,11 +216,11 @@ } // 消息转发到chat-group-video页面进行处理 setTimeout(() => { - uni.$emit('WS_RTC_GROUP',msg); - },delayTime) + uni.$emit('WS_RTC_GROUP', msg); + }, delayTime) return; } - + let chatInfo = { type: 'GROUP', targetId: group.id, @@ -275,11 +281,37 @@ // this.audioTip.src = "/static/audio/tip.wav"; // this.audioTip.play(); }, - isExpired(loginInfo){ - if(!loginInfo || !loginInfo.expireTime){ + isExpired(loginInfo) { + if (!loginInfo || !loginInfo.expireTime) { return true; } return loginInfo.expireTime < new Date().getTime(); + }, + reconnectWs() { + // 记录标志 + this.reconnecting = true; + // 重新加载一次个人信息,目的是为了保证网络已经正常且token有效 + this.reloadUserInfo().then((userInfo) => { + uni.showToast({ + title: '连接已断开,尝试重新连接...', + icon: 'none', + }) + store.commit("setUserInfo", userInfo); + // 重新连接 + let loginInfo = uni.getStorageSync("loginInfo") + wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken); + }).catch(() => { + // 5s后重试 + setTimeout(()=>{ + this.reconnectWs(); + },5000) + }) + }, + reloadUserInfo() { + return http({ + url: '/user/self', + method: 'GET' + }) } }, onLaunch() { @@ -293,12 +325,12 @@ uni.switchTab({ url: "/pages/chat/chat" }) - } else{ + } else { // 跳转到登录页 // #ifdef H5 - uni.navigateTo({ - url: "/pages/login/login" - }) + uni.navigateTo({ + url: "/pages/login/login" + }) // #endif } } diff --git a/im-uniapp/common/request.js b/im-uniapp/common/request.js index ea9ed94..481c6be 100644 --- a/im-uniapp/common/request.js +++ b/im-uniapp/common/request.js @@ -61,7 +61,8 @@ const request = (options) => { }, fail(error) { uni.showToast({ - title: "网络似乎有点不给力,请稍后重试", + title: "网络似乎有点不给力哟", + icon: "none", duration: 1500 }) return reject(error) diff --git a/im-uniapp/common/wssocket.js b/im-uniapp/common/wssocket.js index 7c6acff..d7cce1a 100644 --- a/im-uniapp/common/wssocket.js +++ b/im-uniapp/common/wssocket.js @@ -6,6 +6,7 @@ let connectCallBack = null; let isConnect = false; //连接标识 避免重复连接 let rec = null; let isInit = false; +let lastConnectTime = new Date(); // 最后一次连接时间 let init = () => { // 防止重复初始化 @@ -64,6 +65,7 @@ let connect = (url, token) => { if (isConnect) { return; } + lastConnectTime = new Date(); uni.connectSocket({ url: wsurl, success: (res) => { @@ -86,10 +88,13 @@ let reconnect = (wsurl, accessToken) => { //如果已经连上就不在重连了 return; } + // 延迟10秒重连 避免过多次过频繁请求重连 + let timeDiff = new Date().getTime() - lastConnectTime.getTime() + let delay = timeDiff < 10000 ? 10000 - timeDiff : 0; rec && clearTimeout(rec); - rec = setTimeout(function() { // 延迟15秒重连 避免过多次过频繁请求重连 + rec = setTimeout(function() { connect(wsurl, accessToken); - }, 15000); + }, delay); }; //设置关闭连接 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 cced730..1d3cb6a 100644 --- a/im-uniapp/components/group-member-selector/group-member-selector.vue +++ b/im-uniapp/components/group-member-selector/group-member-selector.vue @@ -4,7 +4,7 @@ 选择成员 - @@ -120,7 +120,7 @@ display: flex; align-items: center; height: 70rpx; - padding: 10rpx; + padding: 10rpx 30rpx; .top-tip { flex: 1; @@ -135,7 +135,7 @@ display: flex; align-items: center; height: 90rpx; - + padding: 0 30rpx; .user-item { padding: 3rpx; } diff --git a/im-uniapp/pages/group/group-info.vue b/im-uniapp/pages/group/group-info.vue index 60cff9b..fe4b4e1 100644 --- a/im-uniapp/pages/group/group-info.vue +++ b/im-uniapp/pages/group/group-info.vue @@ -112,6 +112,7 @@ url:"/pages/group/group" }); this.$store.commit("removeGroup", this.groupId); + this.$store.commit("removeGroupChat",this.groupId); },100) } }) @@ -141,6 +142,7 @@ url:"/pages/group/group" }); this.$store.commit("removeGroup", this.groupId); + this.$store.commit("removeGroupChat",this.groupId); },100) } }) diff --git a/im-uniapp/static/icon/iconfont.css b/im-uniapp/static/icon/iconfont.css index c3ed695..4adf106 100644 --- a/im-uniapp/static/icon/iconfont.css +++ b/im-uniapp/static/icon/iconfont.css @@ -1,6 +1,6 @@ @font-face { font-family: "iconfont"; /* Project id 4272106 */ - src: url('iconfont.ttf?t=1711870080646') format('truetype'); + src: url('iconfont.ttf?t=1719727774055') format('truetype'); } .iconfont { @@ -11,6 +11,10 @@ -moz-osx-font-smoothing: grayscale; } +.icon-receipt:before { + content: "\e601"; +} + .icon-pause:before { content: "\e669"; } @@ -35,10 +39,6 @@ content: "\e685"; } -.icon-receipt:before { - content: "\e61a"; -} - .icon-ok:before { content: "\e65a"; } diff --git a/im-uniapp/static/icon/iconfont.ttf b/im-uniapp/static/icon/iconfont.ttf index 060b3e5..926d0b1 100644 Binary files a/im-uniapp/static/icon/iconfont.ttf and b/im-uniapp/static/icon/iconfont.ttf differ diff --git a/im-uniapp/store/chatStore.js b/im-uniapp/store/chatStore.js index a89fae7..7e2e1a2 100644 --- a/im-uniapp/store/chatStore.js +++ b/im-uniapp/store/chatStore.js @@ -133,6 +133,15 @@ export default { } } }, + removeGroupChat(state, groupId) { + let chats = this.getters.findChats(); + for (let idx in chats) { + if (chats[idx].type == 'GROUP' && + chats[idx].targetId == groupId) { + this.commit("removeChat", idx); + } + } + }, moveTop(state, idx) { let chats = this.getters.findChats(); let chat = chats[idx]; @@ -294,6 +303,8 @@ export default { }); // 将消息一次性装载回来 state.chats = cacheChats; + // 断线重连后不能使用缓存模式,否则会导致聊天窗口的消息不刷新 + cacheChats = state.chats; this.commit("saveToStorage"); }, saveToStorage(state) {