From 641babd2bea567e631746c82023e05404f6d0f80 Mon Sep 17 00:00:00 2001 From: xsx <825657193@qq.com> Date: Wed, 26 Mar 2025 18:19:46 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=92=A4=E5=9B=9E?= =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=8C=E6=94=AF=E6=8C=81=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E6=92=A4=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/GroupMessageController.java | 5 +- .../controller/PrivateMessageController.java | 5 +- .../service/GroupMessageService.java | 2 +- .../service/PrivateMessageService.java | 2 +- .../service/impl/GroupMessageServiceImpl.java | 34 +- .../impl/PrivateMessageServiceImpl.java | 30 +- im-uniapp/App.vue | 47 +- .../chat-message-item/chat-message-item.vue | 7 +- im-uniapp/main.js | 16 +- im-uniapp/pages/chat/chat-box.vue | 9 +- im-uniapp/store/chatStore.js | 38 +- im-web/src/components/chat/ChatBox.vue | 10 +- .../src/components/chat/ChatMessageItem.vue | 6 +- im-web/src/store/chatStore.js | 44 +- im-web/src/view/Chat.vue | 2 +- im-web/src/view/Home.vue | 946 +++++++++--------- 16 files changed, 629 insertions(+), 574 deletions(-) diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java index 007a3de..a1c5845 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java @@ -30,9 +30,8 @@ public class GroupMessageController { @DeleteMapping("/recall/{id}") @Operation(summary = "撤回消息", description = "撤回群聊消息") - public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { - groupMessageService.recallMessage(id); - return ResultUtils.success(); + public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { + return ResultUtils.success(groupMessageService.recallMessage(id)); } @GetMapping("/pullOfflineMessage") diff --git a/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java index 622c5dc..19536a7 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java @@ -30,9 +30,8 @@ public class PrivateMessageController { @DeleteMapping("/recall/{id}") @Operation(summary = "撤回消息", description = "撤回私聊消息") - public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { - privateMessageService.recallMessage(id); - return ResultUtils.success(); + public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { + return ResultUtils.success( privateMessageService.recallMessage(id)); } @GetMapping("/pullOfflineMessage") diff --git a/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java index dc4efda..1a3ed38 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java @@ -22,7 +22,7 @@ public interface GroupMessageService extends IService { * * @param id 消息id */ - void recallMessage(Long id); + GroupMessageVO recallMessage(Long id); /** * 拉取离线消息,只能拉取最近1个月的消息,最多拉取1000条 diff --git a/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java index 261a5e0..8ebed75 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java @@ -23,7 +23,7 @@ public interface PrivateMessageService extends IService { * * @param id 消息id */ - void recallMessage(Long id); + PrivateMessageVO recallMessage(Long id); /** * 拉取历史聊天记录 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 66c0c88..244c062 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 @@ -38,6 +38,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @@ -92,8 +93,9 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId()); - // 不用发给自己 - userIds = userIds.stream().filter(uid -> !session.getUserId().equals(uid)).collect(Collectors.toList()); - GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); - msgInfo.setType(MessageType.RECALL.code()); - String content = String.format("'%s'撤回了一条消息", member.getShowNickName()); - msgInfo.setContent(content); - msgInfo.setSendTime(new Date()); - + GroupMessageVO msgInfo = BeanUtils.copyProperties(recallMsg, GroupMessageVO.class); IMGroupMessage sendMessage = new IMGroupMessage<>(); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvIds(userIds); sendMessage.setData(msgInfo); - sendMessage.setSendResult(false); - sendMessage.setSendToSelf(false); - imClient.sendGroupMessage(sendMessage); - - // 推给自己其他终端 - msgInfo.setContent("你撤回了一条消息"); - sendMessage.setSendToSelf(true); - sendMessage.setRecvIds(Collections.emptyList()); - sendMessage.setRecvTerminals(Collections.emptyList()); imClient.sendGroupMessage(sendMessage); log.info("撤回群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), msg.getGroupId(), msg.getContent()); + return msgInfo; } @@ -165,7 +162,6 @@ public class GroupMessageServiceImpl extends ServiceImpl messages = this.list(wrapper); // 通过群聊对消息进行分组 diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java index 8380184..7e357c2 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java @@ -74,8 +74,9 @@ public class PrivateMessageServiceImpl extends ServiceImpl sendMessage = new IMPrivateMessage<>(); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvId(msgInfo.getRecvId()); - sendMessage.setSendToSelf(false); sendMessage.setData(msgInfo); - sendMessage.setSendResult(false); - imClient.sendPrivateMessage(sendMessage); - - // 推给自己其他终端 - msgInfo.setContent("你撤回了一条消息"); - sendMessage.setSendToSelf(true); - sendMessage.setRecvTerminals(Collections.emptyList()); imClient.sendPrivateMessage(sendMessage); log.info("撤回私聊消息,发送id:{},接收id:{},内容:{}", msg.getSendId(), msg.getRecvId(), msg.getContent()); + return msgInfo; } @Override @@ -154,8 +153,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl wrap.and( + queryWrapper.gt(PrivateMessage::getId, minId).ge(PrivateMessage::getSendTime, minDate).and(wrap -> wrap.and( wp -> wp.eq(PrivateMessage::getSendId, session.getUserId()).in(PrivateMessage::getRecvId, friendIds)) .or(wp -> wp.eq(PrivateMessage::getRecvId, session.getUserId()).in(PrivateMessage::getSendId, friendIds))) .orderByAsc(PrivateMessage::getId); diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue index 0f62016..649eaef 100644 --- a/im-uniapp/App.vue +++ b/im-uniapp/App.vue @@ -107,6 +107,15 @@ export default { }) }, handlePrivateMessage(msg) { + // 标记这条消息是不是自己发的 + msg.selfSend = msg.sendId == this.userStore.userInfo.id; + // 好友id + let friendId = msg.selfSend ? msg.recvId : msg.sendId; + // 会话信息 + let chatInfo = { + type: 'PRIVATE', + targetId: friendId + } // 消息加载标志 if (msg.type == enums.MESSAGE_TYPE.LOADING) { this.chatStore.setLoadingPrivateMsg(JSON.parse(msg.content)) @@ -114,10 +123,7 @@ export default { } // 消息已读处理,清空已读数量 if (msg.type == enums.MESSAGE_TYPE.READED) { - this.chatStore.resetUnreadCount({ - type: 'PRIVATE', - targetId: msg.recvId - }) + this.chatStore.resetUnreadCount(chatInfo); return; } // 消息回执处理,改消息状态为已读 @@ -127,10 +133,12 @@ export default { }) return; } - // 标记这条消息是不是自己发的 - msg.selfSend = msg.sendId == this.userStore.userInfo.id; - // 好友id - let friendId = msg.selfSend ? msg.recvId : msg.sendId; + // 消息撤回 + if (msg.type == enums.MESSAGE_TYPE.RECALL) { + this.chatStore.recallMessage(msg, chatInfo); + return; + } + // 消息插入 this.loadFriendInfo(friendId, (friend) => { this.insertPrivateMessage(friend, msg); }) @@ -177,6 +185,12 @@ export default { }, handleGroupMessage(msg) { + // 标记这条消息是不是自己发的 + msg.selfSend = msg.sendId == this.userStore.userInfo.id; + let chatInfo = { + type: 'GROUP', + targetId: msg.groupId + } // 消息加载标志 if (msg.type == enums.MESSAGE_TYPE.LOADING) { this.chatStore.setLoadingGroupMsg(JSON.parse(msg.content)) @@ -185,19 +199,11 @@ export default { // 消息已读处理 if (msg.type == enums.MESSAGE_TYPE.READED) { // 我已读对方的消息,清空已读数量 - let chatInfo = { - type: 'GROUP', - targetId: msg.groupId - } this.chatStore.resetUnreadCount(chatInfo) return; } // 消息回执处理 if (msg.type == enums.MESSAGE_TYPE.RECEIPT) { - let chatInfo = { - type: 'GROUP', - targetId: msg.groupId - } // 更新消息已读人数 let msgInfo = { id: msg.id, @@ -205,11 +211,14 @@ export default { readedCount: msg.readedCount, receiptOk: msg.receiptOk }; - this.chatStore.updateMessage(msgInfo,chatInfo) + this.chatStore.updateMessage(msgInfo, chatInfo) + return; + } + // 消息撤回 + if (msg.type == this.$enums.MESSAGE_TYPE.RECALL) { + this.chatStore.recallMessage(msg, chatInfo) return; } - // 标记这条消息是不是自己发的 - msg.selfSend = msg.sendId == this.userStore.userInfo.id; this.loadGroupInfo(msg.groupId, (group) => { // 插入群聊消息 this.insertGroupMessage(group, msg); diff --git a/im-uniapp/components/chat-message-item/chat-message-item.vue b/im-uniapp/components/chat-message-item/chat-message-item.vue index ae6f0c2..0904bad 100644 --- a/im-uniapp/components/chat-message-item/chat-message-item.vue +++ b/im-uniapp/components/chat-message-item/chat-message-item.vue @@ -1,13 +1,12 @@ @@ -37,7 +35,7 @@ export default { isModify: false, searchText: "", group: {}, - groupMembers: [] + members: [] } }, methods: { @@ -46,7 +44,7 @@ export default { url: "/pages/common/user-info?id=" + userId }) }, - onKickOut(member, idx) { + onKickOut(member) { uni.showModal({ title: '确认移出?', content: `确定将成员'${member.showNickName}'移出群聊吗?`, @@ -61,7 +59,7 @@ export default { title: `已将${member.showNickName}移出群聊`, icon: 'none' }) - this.groupMembers.splice(idx, 1); + member.quit = true; this.isModify = true; }); } @@ -80,7 +78,7 @@ export default { url: `/group/members/${id}`, method: "GET" }).then((members) => { - this.groupMembers = members.filter(m => !m.quit); + this.members = members; }) }, isSelf(userId) { @@ -90,6 +88,9 @@ export default { computed: { isOwner() { return this.userStore.userInfo.id == this.group.ownerId; + }, + showMembers() { + return this.members.filter(m => !m.quit && m.showNickName.includes(this.searchText)) } }, onLoad(options) { diff --git a/im-uniapp/store/chatStore.js b/im-uniapp/store/chatStore.js index 9db36b3..284b8d2 100644 --- a/im-uniapp/store/chatStore.js +++ b/im-uniapp/store/chatStore.js @@ -1,6 +1,7 @@ import { defineStore } from 'pinia'; import { MESSAGE_TYPE, MESSAGE_STATUS } from '@/common/enums.js'; import useUserStore from './userStore'; +import UNI_APP from '../.env'; let cacheChats = []; export default defineStore('chatStore', { diff --git a/im-web/src/components/chat/ChatAtBox.vue b/im-web/src/components/chat/ChatAtBox.vue index d90fc85..cbd0355 100644 --- a/im-web/src/components/chat/ChatAtBox.vue +++ b/im-web/src/components/chat/ChatAtBox.vue @@ -51,6 +51,10 @@ export default { }) } this.members.forEach((m) => { + // 只显示100条 + if (this.showMembers.length > 100) { + return; + } if (m.userId != userId && !m.quit && m.showNickName.startsWith(this.searchText)) { this.showMembers.push(m); } @@ -128,4 +132,4 @@ export default { background-color: #fff; box-shadow: var(--im-box-shadow); } - + \ No newline at end of file diff --git a/im-web/src/components/chat/ChatBox.vue b/im-web/src/components/chat/ChatBox.vue index 28afab4..654ffa6 100644 --- a/im-web/src/components/chat/ChatBox.vue +++ b/im-web/src/components/chat/ChatBox.vue @@ -41,8 +41,9 @@ -
+
@@ -67,7 +68,7 @@
- + @@ -510,7 +511,7 @@ export default { this.$http({ url: url, method: 'put' - }).then(() => {}) + }).then(() => { }) }, loadReaded(fId) { this.$http({ @@ -549,7 +550,7 @@ export default { friend.showNickName = friend.remarkNickName ? friend.remarkNickName : friend.nickName; this.$store.commit("updateChatFromFriend", friend); this.$store.commit("updateFriend", friend); - }else { + } else { this.$store.commit("updateChatFromUser", this.userInfo); } }, @@ -656,7 +657,7 @@ export default { return this.$store.getters.isFriend(this.userInfo.id); }, friend() { - return this.$store.getters.findFriend(this.userInfo.id) + return this.$store.getters.findFriend(this.userInfo.id) }, title() { let title = this.chat.showName; @@ -681,13 +682,16 @@ export default { isBanned() { return (this.chat.type == "PRIVATE" && this.userInfo.isBanned) || (this.chat.type == "GROUP" && this.group.isBanned) + }, + memberSize() { + return this.groupMembers.filter(m => !m.quit).length; } }, watch: { chat: { handler(newChat, oldChat) { if (newChat.targetId > 0 && (!oldChat || newChat.type != oldChat.type || - newChat.targetId != oldChat.targetId)) { + newChat.targetId != oldChat.targetId)) { if (this.chat.type == "GROUP") { this.loadGroup(this.chat.targetId); } else { diff --git a/im-web/src/components/chat/ChatGroupReaded.vue b/im-web/src/components/chat/ChatGroupReaded.vue index 0759f4d..a90e683 100644 --- a/im-web/src/components/chat/ChatGroupReaded.vue +++ b/im-web/src/components/chat/ChatGroupReaded.vue @@ -1,183 +1,189 @@ + \ No newline at end of file diff --git a/im-web/src/components/chat/ChatGroupSide.vue b/im-web/src/components/chat/ChatGroupSide.vue index 7c3fe36..09a4594 100644 --- a/im-web/src/components/chat/ChatGroupSide.vue +++ b/im-web/src/components/chat/ChatGroupSide.vue @@ -6,20 +6,22 @@
-
-
-
- + +
+
+
+ +
+
邀请
+ +
+
+
-
邀请
- -
-
-
-
+ @@ -62,7 +64,8 @@ export default { return { searchText: "", editing: false, - showAddGroupMember: false + showAddGroupMember: false, + showMaxIdx: 50 } }, props: { @@ -113,7 +116,15 @@ export default { }); }) }, - + onScroll(e) { + const scrollbar = e.target; + // 滚到底部 + if (scrollbar.scrollTop + scrollbar.clientHeight >= scrollbar.scrollHeight - 30) { + if (this.showMaxIdx < this.showMembers.length) { + this.showMaxIdx += 30; + } + } + } }, computed: { ownerName() { @@ -122,8 +133,17 @@ export default { }, isOwner() { return this.group.ownerId == this.$store.state.userStore.userInfo.id; + }, + showMembers() { + return this.groupMembers.filter((m) => !m.quit && m.showNickName.includes(this.searchText)) + }, + scrollHeight() { + return Math.min(400, 80 + this.showMembers.length / 5 * 80); } - + }, + mounted() { + let scrollWrap = this.$refs.scrollbar.$el.querySelector('.el-scrollbar__wrap'); + scrollWrap.addEventListener('scroll', this.onScroll); } } diff --git a/im-web/src/components/common/VirtualScroller.vue b/im-web/src/components/common/VirtualScroller.vue new file mode 100644 index 0000000..2fac4cd --- /dev/null +++ b/im-web/src/components/common/VirtualScroller.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/im-web/src/components/group/GroupMemberSelector.vue b/im-web/src/components/group/GroupMemberSelector.vue index a31571a..dc2f941 100644 --- a/im-web/src/components/group/GroupMemberSelector.vue +++ b/im-web/src/components/group/GroupMemberSelector.vue @@ -5,15 +5,14 @@ - -
- - + +
@@ -33,6 +32,7 @@ From a6b0dbb24f29aca34c8e94fc9a794ac455e497d9 Mon Sep 17 00:00:00 2001 From: xsx <825657193@qq.com> Date: Sun, 6 Apr 2025 19:10:13 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E5=BC=BA=E5=8C=96=E6=98=B5=E7=A7=B0?= =?UTF-8?q?=E3=80=81=E5=BC=B1=E5=8C=96=E7=94=A8=E6=88=B7=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- im-uniapp/pages/common/user-info.vue | 6 +++--- im-uniapp/pages/friend/friend-add.vue | 10 +++++----- im-web/src/components/common/UserInfo.vue | 4 ++-- im-web/src/components/friend/AddFriend.vue | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/im-uniapp/pages/common/user-info.vue b/im-uniapp/pages/common/user-info.vue index cb34f8f..0db26a7 100644 --- a/im-uniapp/pages/common/user-info.vue +++ b/im-uniapp/pages/common/user-info.vue @@ -9,17 +9,17 @@ - {{ userInfo.userName }} + {{ userInfo.nickName }} - 昵称: + 用户名: - {{ userInfo.nickName }} + {{ userInfo.userName }} diff --git a/im-uniapp/pages/friend/friend-add.vue b/im-uniapp/pages/friend/friend-add.vue index 72b45e0..8956e21 100644 --- a/im-uniapp/pages/friend/friend-add.vue +++ b/im-uniapp/pages/friend/friend-add.vue @@ -14,11 +14,11 @@
-
-
-
{{ user.userName }}
+
+
{{ user.nickName }}
{{ user.online ? "[在线]" :"[离线]"}}
-
-
昵称:{{ user.nickName }}
+
+
用户名:{{ user.userName }}
Date: Wed, 9 Apr 2025 17:08:09 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E8=8B=A5=E5=B9=B2=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/bx/implatform/contant/Constant.java | 8 +- .../service/impl/GroupMessageServiceImpl.java | 4 +- .../service/impl/GroupServiceImpl.java | 17 +- .../com/bx/implatform/vo/GroupInviteVO.java | 2 + im-uniapp/App.vue | 44 +- im-uniapp/common/wssocket.js | 100 +- im-uniapp/manifest.json | 4 +- im-uniapp/pages/chat/chat.vue | 7 +- im-uniapp/pages/login/login.vue | 26 +- im-uniapp/pages/register/register.vue | 38 +- im-web/src/api/emotion.js | 8 +- im-web/src/assets/style/im.scss | 18 + im-web/src/components/chat/ChatBox.vue | 66 -- im-web/src/components/chat/ChatInput.vue | 11 +- im-web/src/components/chat/ChatItem.vue | 7 +- .../src/components/chat/ChatMessageItem.vue | 2 +- im-web/src/components/common/Emotion.vue | 2 +- im-web/src/view/Friend.vue | 16 +- im-web/src/view/Group.vue | 893 +++++++++--------- im-web/src/view/Home.vue | 48 +- 20 files changed, 657 insertions(+), 664 deletions(-) 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 @@