diff --git a/README.md b/README.md index d394995..5fa4822 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ - 后台管理端上线,后台管理代码仓库地址:https://gitee.com/bluexsx/box-im-admin - 框架和组件版本全面升级: jdk17、springboot3.3、node18等 -- 部分界面,功能、性能优化1 +- 部分界面,功能、性能优化 #### 在线体验 @@ -39,10 +39,12 @@ ![输入图片说明](%E6%88%AA%E5%9B%BE/h5%E4%BA%8C%E7%BB%B4%E7%A0%81.png) 说明: -1.由于微信小程序每次发布审核过于严苛和繁琐,暂时不再提供体验环境,但uniapp端依然会继续兼容小程序 -2.体验环境部署的是商业版本,与开源版本功能存在一定差异,具体请参考: +1.**请勿利用公开账号辱骂他人、发布低俗内容,否则将直接对您的IP进行封禁** +2.由于微信小程序每次发布审核过于严苛和繁琐,暂时不再提供体验环境,但uniapp端依然会继续兼容小程序 +3.体验环境部署的是商业版本,与开源版本功能存在一定差异,具体请参考: https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29#SbvXq + #### 付费服务 商业版源码: https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29 远程协助: https://www.yuque.com/u1475064/imk5n2/fettd57rvzc29s5r 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 1495851..3884cd7 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 @@ -18,7 +18,7 @@ public final class Constant { /** * 大群人数上限 */ - public static final Long MAX_LARGE_GROUP_MEMBER = 10000L; + public static final Long MAX_LARGE_GROUP_MEMBER = 3000L; /** * 普通群人数上限 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 6091683..81c91d5 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 @@ -67,10 +67,10 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds = groupMemberService.findUserIdsByGroupId(group.getId()); - if (dto.getReceipt() && userIds.size() > Constant.MAX_LARGE_GROUP_MEMBER) { + if (dto.getReceipt() && userIds.size() > Constant.MAX_NORMAL_GROUP_MEMBER) { // 大群的回执消息过于消耗资源,不允许发送 throw new GlobalException( - String.format("当前群聊大于%s人,不支持发送回执消息", Constant.MAX_LARGE_GROUP_MEMBER)); + String.format("当前群聊大于%s人,不支持发送回执消息", Constant.MAX_NORMAL_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/thirdparty/MinioService.java b/im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java index 254a8e2..d8184ea 100644 --- a/im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java +++ b/im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java @@ -1,6 +1,8 @@ package com.bx.implatform.thirdparty; +import cn.hutool.core.util.RandomUtil; import com.bx.implatform.util.DateTimeUtils; +import com.bx.implatform.util.FileUtil; import io.minio.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -38,9 +40,7 @@ public class MinioService { */ public void makeBucket(String bucketName) { try { - minioClient.makeBucket(MakeBucketArgs.builder() - .bucket(bucketName) - .build()); + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } catch (Exception e) { log.error("创建bucket失败,", e); } @@ -52,18 +52,9 @@ public class MinioService { public void setBucketPublic(String bucketName) { try { // 设置公开 - String sb = "{\"Version\":\"2012-10-17\"," + - "\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" + - "{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," + - "\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + - "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" + - bucketName + - "/*\"]}]}"; - minioClient.setBucketPolicy( - SetBucketPolicyArgs.builder() - .bucket(bucketName) - .config(sb) - .build()); + String sb = + "{\"Version\":\"2012-10-17\"," + "\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" + "{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," + "\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}"; + minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(sb).build()); } catch (Exception e) { log.error("创建bucket失败,", e); } @@ -82,10 +73,10 @@ public class MinioService { if (StringUtils.isBlank(originalFilename)) { throw new RuntimeException(); } - String fileName = System.currentTimeMillis() + ""; - if (originalFilename.lastIndexOf(".") >= 0) { - fileName += originalFilename.substring(originalFilename.lastIndexOf(".")); - } + // 加入随机数,防止文件重名 + String fileName = FileUtil.excludeExtension(originalFilename); + fileName += "_" + RandomUtil.randomString(4); + fileName += "." + FileUtil.getFileExtension(originalFilename); String objectName = DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.PARTDATEFORMAT) + "/" + fileName; try { InputStream stream = new ByteArrayInputStream(file.getBytes()); @@ -111,13 +102,15 @@ public class MinioService { * @return objectName */ public String upload(String bucketName, String path, String name, byte[] fileByte, String contentType) { - - String fileName = System.currentTimeMillis() + name.substring(name.lastIndexOf(".")); + // 加入随机数,防止文件重名 + String fileName = FileUtil.excludeExtension(name); + fileName += "_" + RandomUtil.randomString(4); + fileName += "." + FileUtil.getFileExtension(name); String objectName = DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.PARTDATEFORMAT) + "/" + fileName; try { InputStream stream = new ByteArrayInputStream(fileByte); PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(path + "/" + objectName) - .stream(stream, fileByte.length, -1).contentType(contentType).build(); + .stream(stream, fileByte.length, -1).contentType(contentType).build(); //文件名称相同会覆盖 minioClient.putObject(objectArgs); } catch (Exception e) { @@ -127,7 +120,6 @@ public class MinioService { return objectName; } - /** * 删除 * @@ -138,7 +130,8 @@ public class MinioService { */ public boolean remove(String bucketName, String path, String fileName) { try { - minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build()); + minioClient.removeObject( + RemoveObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build()); } catch (Exception e) { log.error("删除文件失败,", e); return false; @@ -156,7 +149,7 @@ public class MinioService { */ public Boolean isExist(String bucketName, String path, String fileName) { try { - minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build()); + minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build()); } catch (Exception e) { return false; } diff --git a/im-platform/src/main/java/com/bx/implatform/util/FileUtil.java b/im-platform/src/main/java/com/bx/implatform/util/FileUtil.java index 78d2ab6..89aeddd 100644 --- a/im-platform/src/main/java/com/bx/implatform/util/FileUtil.java +++ b/im-platform/src/main/java/com/bx/implatform/util/FileUtil.java @@ -13,6 +13,16 @@ public final class FileUtil { return fileName.substring(fileName.lastIndexOf(".") + 1); } + /** + * 去除文件扩展名 + * + * @param fileName 文件名 + * @return + */ + public static String excludeExtension(String fileName) { + return fileName.substring(0,fileName.lastIndexOf(".")); + } + /** * 判断文件是否图片类型 * diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue index de5c4bc..6be74ca 100644 --- a/im-uniapp/App.vue +++ b/im-uniapp/App.vue @@ -116,8 +116,11 @@ export default { this.chatStore.refreshChats(); }).catch((e) => { console.log(e) - this.$message.error("拉取离线消息失败"); - this.onExit(); + uni.showToast({ + title: "拉取离线消息失败", + icon: "none" + }) + this.exit(); }) }, pullPrivateOfflineMessage(minId) { diff --git a/im-uniapp/store/chatStore.js b/im-uniapp/store/chatStore.js index 462dca5..16d3419 100644 --- a/im-uniapp/store/chatStore.js +++ b/im-uniapp/store/chatStore.js @@ -335,11 +335,11 @@ export default defineStore('chatStore', { } }, refreshChats() { - if (!cacheChats) return; + let chats = cacheChats || this.chats; // 更新会话免打扰状态 const friendStore = useFriendStore(); const groupStore = useGroupStore(); - cacheChats.forEach(chat => { + chats.forEach(chat => { if (chat.type == 'PRIVATE') { let friend = friendStore.findFriend(chat.targetId); if (friend) { @@ -353,28 +353,37 @@ export default defineStore('chatStore', { } }) // 排序 - cacheChats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); + chats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); // #ifndef APP-PLUS /** - * 由于h5和小程序的stroge只有5m,大约只能存储2w条消息, - * 所以这里每个会话只保留1000条消息,防止溢出 + * 由于h5和小程序的stroge只有5m,大约只能存储2w条消息,所以可能需要清理部分历史消息 */ - cacheChats.forEach(chat => { - if (chat.messages.length > 1000) { - let idx = chat.messages.length - 1000; - chat.messages = chat.messages.slice(idx); - } - }) + this.fliterMessage(chats, 5000, 1000); // #endif // 记录热数据索引位置 - cacheChats.forEach(chat => chat.hotMinIdx = chat.messages.length); + chats.forEach(chat => chat.hotMinIdx = chat.messages.length); // 将消息一次性装载回来 - this.chats = cacheChats; + this.chats = chats; // 清空缓存,不再使用 cacheChats = null; // 消息持久化 this.saveToStorage(true); }, + fliterMessage(chats, maxTotalSize, maxPerChatSize) { + // 每个会话只保留maxPerChatSize条消息 + let remainTotalSize = 0; + chats.forEach(chat => { + if (chat.messages.length > maxPerChatSize) { + let idx = chat.messages.length - maxPerChatSize; + chat.messages = chat.messages.slice(idx); + } + remainTotalSize += chat.messages.length; + }) + // 保证消息总数不超过maxTotalSize条,否则继续清理 + if (remainTotalSize > maxTotalSize) { + this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2); + } + }, saveToStorage(withColdMessage) { // 加载中不保存,防止卡顿 if (this.loading) { diff --git a/im-web/src/components/common/RightMenu.vue b/im-web/src/components/common/RightMenu.vue index 7e90484..9c22341 100644 --- a/im-web/src/components/common/RightMenu.vue +++ b/im-web/src/components/common/RightMenu.vue @@ -26,9 +26,11 @@ export default { }, methods: { open(pos, items) { - this.pos = pos; + this.pos.x = pos.x; + this.pos.y = pos.y; this.items = items; this.show = true; + this.rejustPos(); }, close() { this.show = false; @@ -36,6 +38,16 @@ export default { onSelectMenu(item) { this.$emit("select", item); this.close(); + }, + rejustPos() { + let menuH = this.items.length * 36; + let menuW = 100; + if (this.pos.y > window.innerHeight - menuH) { + this.pos.y = window.innerHeight - menuH; + } + if (this.pos.x > window.innerWidth - menuW) { + this.pos.x = window.innerWidth - menuW; + } } } } @@ -75,4 +87,4 @@ export default { } } } - + \ No newline at end of file diff --git a/im-web/src/store/chatStore.js b/im-web/src/store/chatStore.js index 238e8cd..046d842 100644 --- a/im-web/src/store/chatStore.js +++ b/im-web/src/store/chatStore.js @@ -333,11 +333,11 @@ export default defineStore('chatStore', { } }, refreshChats() { - if (!cacheChats) return; + let chats = cacheChats || this.chats; // 刷新免打扰状态 const friendStore = useFriendStore(); const groupStore = useGroupStore(); - cacheChats.forEach(chat => { + chats.forEach(chat => { if (chat.type == 'PRIVATE') { let friend = friendStore.findFriend(chat.targetId); if (friend) { @@ -351,26 +351,38 @@ export default defineStore('chatStore', { } }) // 排序 - cacheChats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); + chats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); /** * 由于部分浏览器不支持websql或indexdb,只能使用localstorage,而localstorage大小只有10m,可能会导致缓存空间溢出 - * 解决办法:如果是使用localstorage的浏览器,每个会话只保留1000条消息,防止溢出 + * 解决办法:针对只能使用localstorage的浏览器,最多保留1w条消息,每个会话最多保留1000条消息 */ - cacheChats.forEach(chat => { - if (localForage.driver().includes("localStorage") && chat.messages.length > 1000) { - let idx = chat.messages.length - 1000; - chat.messages = chat.messages.slice(idx); - } - }) + if (localForage.driver().includes("localStorage")) { + this.fliterMessage(chats, 10000, 1000) + } // 记录热数据索引位置 - cacheChats.forEach(chat => chat.hotMinIdx = chat.messages.length); + chats.forEach(chat => chat.hotMinIdx = chat.messages.length); // 将消息一次性装载回来 - this.chats = cacheChats; + this.chats = chats; // 清空缓存 cacheChats = null; // 持久化消息 this.saveToStorage(true); }, + fliterMessage(chats, maxTotalSize, maxPerChatSize) { + // 每个会话只保留maxPerChatSize条消息 + let remainTotalSize = 0; + chats.forEach(chat => { + if (chat.messages.length > maxPerChatSize) { + let idx = chat.messages.length - maxPerChatSize; + chat.messages = chat.messages.slice(idx); + } + remainTotalSize += chat.messages.length; + }) + // 保证消息总数不超过maxTotalSize条,否则继续清理 + if (remainTotalSize > maxTotalSize) { + this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2); + } + }, saveToStorage(withColdMessage) { // 加载中不保存,防止卡顿 if (this.loading) { diff --git a/im-web/src/view/Login.vue b/im-web/src/view/Login.vue index f3953a8..1bc8d3a 100644 --- a/im-web/src/view/Login.vue +++ b/im-web/src/view/Login.vue @@ -1,5 +1,8 @@