diff --git a/im-platform/src/main/java/com/bx/implatform/contant/Constant.java b/im-platform/src/main/java/com/bx/implatform/contant/Constant.java index e5e57ba..1495851 100644 --- a/im-platform/src/main/java/com/bx/implatform/contant/Constant.java +++ b/im-platform/src/main/java/com/bx/implatform/contant/Constant.java @@ -16,13 +16,13 @@ public final class Constant { public static final Long MAX_FILE_SIZE = 20 * 1024 * 1024L; /** - * 群聊最大人数 + * 大群人数上限 */ - public static final Long MAX_GROUP_MEMBER = 10000L; + public static final Long MAX_LARGE_GROUP_MEMBER = 10000L; /** - * 回执消息限制最大人数 + * 普通群人数上限 */ - public static final Long LARGE_GROUP_MEMBER = 500L; + public static final Long MAX_NORMAL_GROUP_MEMBER = 500L; } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java index 805de09..5aa5857 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java @@ -65,9 +65,9 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds = groupMemberService.findUserIdsByGroupId(group.getId()); - if (dto.getReceipt() && userIds.size() > Constant.LARGE_GROUP_MEMBER) { + if (dto.getReceipt() && userIds.size() > Constant.MAX_LARGE_GROUP_MEMBER) { // 大群的回执消息过于消耗资源,不允许发送 - throw new GlobalException(String.format("当前群聊大于%s人,不支持发送回执消息", Constant.LARGE_GROUP_MEMBER)); + throw new GlobalException(String.format("当前群聊大于%s人,不支持发送回执消息", Constant.MAX_LARGE_GROUP_MEMBER)); } // 不用发给自己 userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList()); diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java index a55b0de..809b9ac 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java @@ -123,7 +123,8 @@ public class GroupServiceImpl extends ServiceImpl implements String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); redisTemplate.delete(key); // 推送解散群聊提示 - this.sendTipMessage(groupId, userIds, String.format("'%s'解散了群聊", session.getNickName())); + String content = String.format("'%s'解散了群聊", session.getNickName()); + this.sendTipMessage(groupId, userIds, content, true); // 推送同步消息 this.sendDelGroupMessage(groupId, userIds, false); log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); @@ -142,7 +143,7 @@ public class GroupServiceImpl extends ServiceImpl implements String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); redisTemplate.opsForHash().delete(key, userId.toString()); // 推送退出群聊提示 - this.sendTipMessage(groupId, List.of(userId), "您已退出群聊"); + this.sendTipMessage(groupId, List.of(userId), "您已退出群聊", false); // 推送同步消息 this.sendDelGroupMessage(groupId, Lists.newArrayList(), true); log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); @@ -164,7 +165,7 @@ public class GroupServiceImpl extends ServiceImpl implements String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); redisTemplate.opsForHash().delete(key, userId.toString()); // 推送踢出群聊提示 - this.sendTipMessage(groupId, List.of(userId), "您已被移出群聊"); + this.sendTipMessage(groupId, List.of(userId), "您已被移出群聊", false); // 推送同步消息 this.sendDelGroupMessage(groupId, List.of(userId), false); log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); @@ -234,8 +235,8 @@ public class GroupServiceImpl extends ServiceImpl implements // 群聊人数校验 List members = groupMemberService.findByGroupId(vo.getGroupId()); long size = members.stream().filter(m -> !m.getQuit()).count(); - if (vo.getFriendIds().size() + size > Constant.MAX_GROUP_MEMBER) { - throw new GlobalException("群聊人数不能大于" + Constant.MAX_GROUP_MEMBER + "人"); + if (vo.getFriendIds().size() + size > Constant.MAX_LARGE_GROUP_MEMBER) { + throw new GlobalException("群聊人数不能大于" + Constant.MAX_LARGE_GROUP_MEMBER + "人"); } // 找出好友信息 List friends = friendsService.findByFriendIds(vo.getFriendIds()); @@ -267,7 +268,7 @@ public class GroupServiceImpl extends ServiceImpl implements List userIds = groupMemberService.findUserIdsByGroupId(vo.getGroupId()); String memberNames = groupMembers.stream().map(GroupMember::getShowNickName).collect(Collectors.joining(",")); String content = String.format("'%s'邀请'%s'加入了群聊", session.getNickName(), memberNames); - this.sendTipMessage(vo.getGroupId(), userIds, content); + this.sendTipMessage(vo.getGroupId(), userIds, content, true); log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(), vo.getFriendIds()); } @@ -287,7 +288,7 @@ public class GroupServiceImpl extends ServiceImpl implements }).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList()); } - private void sendTipMessage(Long groupId, List recvIds, String content) { + private void sendTipMessage(Long groupId, List recvIds, String content, Boolean sendToAll) { UserSession session = SessionContext.getSession(); // 消息入库 GroupMessage message = new GroupMessage(); @@ -298,7 +299,7 @@ public class GroupServiceImpl extends ServiceImpl implements message.setSendNickName(session.getNickName()); message.setGroupId(groupId); message.setSendId(session.getUserId()); - message.setRecvIds(CommaTextUtils.asText(recvIds)); + message.setRecvIds(sendToAll ? "" : CommaTextUtils.asText(recvIds)); groupMessageMapper.insert(message); // 推送 GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class); diff --git a/im-platform/src/main/java/com/bx/implatform/vo/GroupInviteVO.java b/im-platform/src/main/java/com/bx/implatform/vo/GroupInviteVO.java index 7c86962..b78f43f 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/GroupInviteVO.java +++ b/im-platform/src/main/java/com/bx/implatform/vo/GroupInviteVO.java @@ -3,6 +3,7 @@ package com.bx.implatform.vo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.Data; import java.util.List; @@ -15,6 +16,7 @@ public class GroupInviteVO { @Schema(description = "群id") private Long groupId; + @Size(max = 50, message = "一次最多只能邀请50位用户") @NotEmpty(message = "群id不可为空") @Schema(description = "好友id列表不可为空") private List friendIds; diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue index 991d37c..c5dec44 100644 --- a/im-uniapp/App.vue +++ b/im-uniapp/App.vue @@ -17,6 +17,7 @@ export default { }, methods: { init() { + this.reconnecting = false; this.isExit = false; // 加载数据 this.loadStore().then(() => { @@ -30,20 +31,17 @@ export default { }, initWebSocket() { let loginInfo = uni.getStorageSync("loginInfo") - wsApi.init(); wsApi.connect(UNI_APP.WS_URL, loginInfo.accessToken); wsApi.onConnect(() => { - // 重连成功提示 if (this.reconnecting) { - this.reconnecting = false; - uni.showToast({ - title: "已重新连接", - icon: 'none' - }) + // 重连成功 + this.onReconnectWs(); + } else { + // 加载离线消息 + this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId); + this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId); + } - // 加载离线消息 - this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId); - this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId); }); wsApi.onMessage((cmd, msgInfo) => { if (cmd == 2) { @@ -371,12 +369,11 @@ export default { // 记录标志 this.reconnecting = true; // 重新加载一次个人信息,目的是为了保证网络已经正常且token有效 - this.reloadUserInfo().then((userInfo) => { + this.userStore.loadUser().then((userInfo) => { uni.showToast({ title: '连接已断开,尝试重新连接...', - icon: 'none', + icon: 'none' }) - this.userStore.setUserInfo(userInfo); // 重新连接 let loginInfo = uni.getStorageSync("loginInfo") wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken); @@ -387,10 +384,23 @@ export default { }, 5000) }) }, - reloadUserInfo() { - return http({ - url: '/user/self', - method: 'GET' + onReconnectWs() { + this.reconnecting = false; + // 重新加载好友和群聊 + const promises = []; + promises.push(this.friendStore.loadFriend()); + promises.push(this.groupStore.loadGroup()); + Promise.all(promises).then(() => { + uni.showToast({ + title: "已重新连接", + icon: 'none' + }) + // 加载离线消息 + this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId); + this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId); + }).catch((e) => { + console.log(e); + this.exit(); }) }, closeSplashscreen(delay) { diff --git a/im-uniapp/common/wssocket.js b/im-uniapp/common/wssocket.js index 144c746..f0aaafe 100644 --- a/im-uniapp/common/wssocket.js +++ b/im-uniapp/common/wssocket.js @@ -1,20 +1,33 @@ -let wsurl = ""; let accessToken = ""; let messageCallBack = null; let closeCallBack = null; let connectCallBack = null; let isConnect = false; //连接标识 避免重复连接 let rec = null; -let isInit = false; let lastConnectTime = new Date(); // 最后一次连接时间 +let socketTask = null; -let init = () => { - // 防止重复初始化 - if (isInit) { +let connect = (wsurl, token) => { + accessToken = token; + if (isConnect) { return; } - isInit = true; - uni.onSocketOpen((res) => { + lastConnectTime = new Date(); + socketTask = uni.connectSocket({ + url: wsurl, + success: (res) => { + console.log("websocket连接成功"); + }, + fail: (e) => { + console.log(e); + console.log("websocket连接失败,10s后重连"); + setTimeout(() => { + connect(); + }, 10000) + } + }); + + socketTask.onOpen((res) => { console.log("WebSocket连接已打开"); isConnect = true; // 发送登录命令 @@ -24,12 +37,12 @@ let init = () => { accessToken: accessToken } }; - uni.sendSocketMessage({ + socketTask.send({ data: JSON.stringify(loginInfo) }); }) - uni.onSocketMessage((res) => { + socketTask.onMessage((res) => { let sendInfo = JSON.parse(res.data) if (sendInfo.cmd == 0) { heartCheck.start() @@ -45,54 +58,31 @@ let init = () => { } }) - uni.onSocketClose((res) => { + socketTask.onClose((res) => { console.log('WebSocket连接关闭') isConnect = false; closeCallBack && closeCallBack(res); }) - uni.onSocketError((e) => { + socketTask.onError((e) => { console.log(e) isConnect = false; // APP 应用切出超过一定时间(约1分钟)会触发报错,此处回调给应用进行重连 closeCallBack && closeCallBack({ code: 1006 }); }) -}; - -let connect = (url, token) => { - wsurl = url; - accessToken = token; - if (isConnect) { - return; - } - lastConnectTime = new Date(); - uni.connectSocket({ - url: wsurl, - success: (res) => { - console.log("websocket连接成功"); - }, - fail: (e) => { - console.log(e); - console.log("websocket连接失败,10s后重连"); - setTimeout(() => { - connect(); - }, 10000) - } - }); } //定义重连函数 let reconnect = (wsurl, accessToken) => { console.log("尝试重新连接"); if (isConnect) { - //如果已经连上就不在重连了 return; } // 延迟10秒重连 避免过多次过频繁请求重连 let timeDiff = new Date().getTime() - lastConnectTime.getTime() let delay = timeDiff < 10000 ? 10000 - timeDiff : 0; rec && clearTimeout(rec); - rec = setTimeout(function () { + rec = setTimeout(function() { connect(wsurl, accessToken); }, delay); }; @@ -102,7 +92,7 @@ let close = (code) => { if (!isConnect) { return; } - uni.closeSocket({ + socketTask.close({ code: code, complete: (res) => { console.log("关闭websocket连接"); @@ -115,39 +105,28 @@ let close = (code) => { }; -//心跳设置 -var heartCheck = { - timeout: 10000, //每段时间发送一次心跳包 这里设置为30s - timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) - start: function () { +// 心跳设置 +let heartCheck = { + timeout: 20000, // 每段时间发送一次心跳包 这里设置为20s + timeoutObj: null, // 延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) + start: function() { if (isConnect) { console.log('发送WebSocket心跳') let heartBeat = { cmd: 1, data: {} }; - uni.sendSocketMessage({ - data: JSON.stringify(heartBeat), - fail(res) { - console.log(res); - } - }) + sendMessage(JSON.stringify(heartBeat)) } }, - reset: function () { + reset: function() { clearTimeout(this.timeoutObj); - this.timeoutObj = setTimeout(function () { - heartCheck.start(); - }, this.timeout); + this.timeoutObj = setTimeout(() => heartCheck.start(), this.timeout); } +}; -} - -// 实际调用的方法 -function sendMessage(agentData) { - uni.sendSocketMessage({ - data: agentData - }) +let sendMessage = (message) => { + socketTask.send({ data: message }) } let onConnect = (callback) => { @@ -155,19 +134,18 @@ let onConnect = (callback) => { } -function onMessage(callback) { +let onMessage = (callback) => { messageCallBack = callback; } -function onClose(callback) { +let onClose = (callback) => { closeCallBack = callback; } // 将方法暴露出去 export { - init, connect, reconnect, close, diff --git a/im-uniapp/manifest.json b/im-uniapp/manifest.json index 04d0f26..ff617c0 100644 --- a/im-uniapp/manifest.json +++ b/im-uniapp/manifest.json @@ -2,8 +2,8 @@ "name" : "盒子IM", "appid" : "__UNI__69DD57A", "description" : "", - "versionName" : "3.1.0", - "versionCode" : 3100, + "versionName" : "3.4.0", + "versionCode" : 3400, "transformPx" : false, /* 5+App特有相关 */ "app-plus" : { diff --git a/im-uniapp/pages/chat/chat.vue b/im-uniapp/pages/chat/chat.vue index b798427..bd74f8a 100644 --- a/im-uniapp/pages/chat/chat.vue +++ b/im-uniapp/pages/chat/chat.vue @@ -76,6 +76,12 @@ export default { moveToTop(chatIdx) { this.chatStore.moveTop(chatIdx); }, + isShowChat(chat) { + if (chat.delete) { + return false; + } + return !this.searchText || chat.showName.includes(this.searchText) + }, onSearch() { this.showSearch = !this.showSearch; this.searchText = ""; @@ -146,7 +152,6 @@ export default { width: 100%; height: 120rpx; background: white; - color: $im-text-color-lighter; .loading-box { diff --git a/im-uniapp/pages/login/login.vue b/im-uniapp/pages/login/login.vue index a6f65c9..fc99aa7 100644 --- a/im-uniapp/pages/login/login.vue +++ b/im-uniapp/pages/login/login.vue @@ -1,15 +1,17 @@