From 5c10825622ac1c9e370541906a4a7c4b7e6662b5 Mon Sep 17 00:00:00 2001 From: "xie.bx" Date: Wed, 1 Nov 2023 22:03:43 +0800 Subject: [PATCH 01/15] =?UTF-8?q?=E7=99=BB=E5=BD=95=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- im-ui/src/view/Login.vue | 167 ++++++++++++++++++++++++------------ im-ui/src/view/Register.vue | 17 ++-- 2 files changed, 117 insertions(+), 67 deletions(-) diff --git a/im-ui/src/view/Login.vue b/im-ui/src/view/Login.vue index 149bdeb..e4bb06e 100644 --- a/im-ui/src/view/Login.vue +++ b/im-ui/src/view/Login.vue @@ -1,27 +1,56 @@ @@ -71,11 +100,11 @@ }) .then((data) => { // 保存密码到cookie(不安全) - this.setCookie('username',this.loginForm.userName); - this.setCookie('password',this.loginForm.password); + this.setCookie('username', this.loginForm.userName); + this.setCookie('password', this.loginForm.password); // 保存token - sessionStorage.setItem("accessToken",data.accessToken); - sessionStorage.setItem("refreshToken",data.refreshToken); + sessionStorage.setItem("accessToken", data.accessToken); + sessionStorage.setItem("refreshToken", data.refreshToken); this.$message.success("登陆成功"); this.$router.push("/home/chat"); }) @@ -90,26 +119,27 @@ getCookie(name) { let reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); let arr = document.cookie.match(reg) - if (arr){ - return unescape(arr[2]); + if (arr) { + return unescape(arr[2]); } - return ''; - }, - // 设置cookie,增加到vue实例方便全局调用 - setCookie (name, value, expiredays) { - var exdate = new Date(); - exdate.setDate(exdate.getDate() + expiredays); - document.cookie = name + "=" + escape(value) + ((expiredays == null) ? "" : ";expires=" + exdate.toGMTString()); - }, - // 删除cookie - delCookie (name) { - var exp = new Date(); - exp.setTime(exp.getTime() - 1); - var cval = this.getCookie(name); - if (cval != null){ + return ''; + }, + // 设置cookie,增加到vue实例方便全局调用 + setCookie(name, value, expiredays) { + var exdate = new Date(); + exdate.setDate(exdate.getDate() + expiredays); + document.cookie = name + "=" + escape(value) + ((expiredays == null) ? "" : ";expires=" + exdate + .toGMTString()); + }, + // 删除cookie + delCookie(name) { + var exp = new Date(); + exp.setTime(exp.getTime() - 1); + var cval = this.getCookie(name); + if (cval != null) { document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString(); } - } + } }, mounted() { this.loginForm.userName = this.getCookie("username"); @@ -124,21 +154,46 @@ position: relative; display: flex; justify-content: space-around; + align-items: center; width: 100%; height: 100%; - background: linear-gradient(#65807a, #182e3c); + background: rgb(232, 242, 255); background-size: cover; - - .web-ruleForm { + box-sizing: border-box; + padding: 10%; + + .login-intro { + flex: 1; + height: 300px; + padding: 40px; + max-width: 600px; + .login-title { + text-align: center; + font-weight: 600; + font-size: 30px; + } + + .login-icons { + display: flex; + align-items: center; + + .login-icon { + padding-left: 5px; + } + } + } + + .login-form { height: 340px; - padding: 20px; - margin-top: 150px ; - background: rgba(255,255,255,.75); - box-shadow: 0px 0px 1px #ccc; - border-radius: 5px; + width: 400px; + padding: 30px; + background: white; + opacity: 0.9; + box-shadow: 0px 0px 1px #ccc; + border-radius: 3%; overflow: hidden; - - + border: 1px solid #ccc; + .login-brand { line-height: 50px; margin: 30px 0 40px 0; @@ -148,7 +203,7 @@ text-transform: uppercase; text-align: center; } - + .register { display: flex; flex-direction: row-reverse; @@ -158,6 +213,4 @@ } } } - - - + \ No newline at end of file diff --git a/im-ui/src/view/Register.vue b/im-ui/src/view/Register.vue index d1c4907..625da63 100644 --- a/im-ui/src/view/Register.vue +++ b/im-ui/src/view/Register.vue @@ -3,7 +3,7 @@
-
欢迎注册
+
欢迎成为盒子IM的用户
@@ -117,24 +117,21 @@ position: fixed; display: flex; justify-content: space-around; + align-items: center; width: 100%; height: 100%; - background: #466368; - background: linear-gradient(#65807a, #182e3c); - background-size: cover; - -webkit-user-select: none; - background-size: cover; - + background: rgb(232, 242, 255); .web-ruleForm { width: 500px; - height: 430px; + height: 450px; padding: 20px; - margin-top: 100px ; - background: rgba(255,255,255,.75); + background: white; + opacity: 0.9; box-shadow: 0px 0px 1px #ccc; border-radius: 3px; overflow: hidden; + border-radius: 3%; .register-brand { line-height: 50px; From a14674d240cfe77143a83475b6c4c3ae64cc2986 Mon Sep 17 00:00:00 2001 From: "xie.bx" Date: Wed, 1 Nov 2023 22:30:23 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E5=A4=A7=E5=9B=BE=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- im-ui/src/components/common/FullImage.vue | 60 ++++++++++++++++++----- im-ui/src/view/Login.vue | 2 +- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/im-ui/src/components/common/FullImage.vue b/im-ui/src/components/common/FullImage.vue index cd720c2..8bfd15e 100644 --- a/im-ui/src/components/common/FullImage.vue +++ b/im-ui/src/components/common/FullImage.vue @@ -1,10 +1,11 @@ - \ No newline at end of file diff --git a/im-ui/src/view/Login.vue b/im-ui/src/view/Login.vue index e4bb06e..9284f3e 100644 --- a/im-ui/src/view/Login.vue +++ b/im-ui/src/view/Login.vue @@ -8,7 +8,7 @@
  • 发布uniapp移动版本,支持移动端和web端同时在线,多端消息同步
  • 目前移动端仅兼容h5和微信小程序,后续会继续兼容更多终端类型
  • -
  • 页面风格优化:表情包更新、自动生成文字头像等
  • +
  • 页面风格升级:表情包更新、自动生成文字头像等
From 8d6d6c2b0f069ab0790942d4a3741624f929f5ea Mon Sep 17 00:00:00 2001 From: "xie.bx" Date: Wed, 1 Nov 2023 23:16:41 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/thirdparty/FileService.java | 16 ++++++++++------ im-ui/src/view/Home.vue | 6 ------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java b/im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java index 7fdb640..c3541a7 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java @@ -19,10 +19,12 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.PostConstruct; import java.io.IOException; -/* +/** + * todo 通过校验文件MD5实现重复文件秒传 * 文件上传服务 * @Author Blue * @Date 2022/10/28 + * */ @Slf4j @Service @@ -87,11 +89,13 @@ public class FileService { throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片上传失败"); } vo.setOriginUrl(generUrl(FileType.IMAGE,fileName)); - // 上传缩略图 - byte[] imageByte = ImageUtil.compressForScale(file.getBytes(),100); - fileName = minioUtil.upload(bucketName,imagePath,file.getOriginalFilename(),imageByte,file.getContentType()); - if(StringUtils.isEmpty(fileName)){ - throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片上传失败"); + // 大于30K的文件需上传缩略图 + if(file.getSize() > 30 * 1024){ + byte[] imageByte = ImageUtil.compressForScale(file.getBytes(),30); + fileName = minioUtil.upload(bucketName,imagePath,file.getOriginalFilename(),imageByte,file.getContentType()); + if(StringUtils.isEmpty(fileName)){ + throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片上传失败"); + } } vo.setThumbUrl(generUrl(FileType.IMAGE,fileName)); log.info("文件图片成功,用户id:{},url:{}",userId,vo.getOriginUrl()); diff --git a/im-ui/src/view/Home.vue b/im-ui/src/view/Home.vue index f6c0bf4..3cddad3 100644 --- a/im-ui/src/view/Home.vue +++ b/im-ui/src/view/Home.vue @@ -29,12 +29,6 @@ - - - - - -
From 30c69b81dacfcbd4eb12085f0e8b41060911184c Mon Sep 17 00:00:00 2001 From: "xie.bx" Date: Wed, 1 Nov 2023 23:29:17 +0800 Subject: [PATCH 04/15] =?UTF-8?q?=E7=99=BB=E5=BD=95=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- im-ui/src/view/Login.vue | 7 +++---- im-ui/src/view/Register.vue | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/im-ui/src/view/Login.vue b/im-ui/src/view/Login.vue index 9284f3e..faafa1a 100644 --- a/im-ui/src/view/Login.vue +++ b/im-ui/src/view/Login.vue @@ -32,15 +32,14 @@ @keyup.enter.native="submitForm('loginForm')"> - - + - + - + 登陆 diff --git a/im-ui/src/view/Register.vue b/im-ui/src/view/Register.vue index 625da63..f76e91f 100644 --- a/im-ui/src/view/Register.vue +++ b/im-ui/src/view/Register.vue @@ -5,16 +5,16 @@
欢迎成为盒子IM的用户
- + - + - + - + 注册 From 2c59c6a86ceb58b1bdcf2fb9a4462c17e189c487 Mon Sep 17 00:00:00 2001 From: "xie.bx" Date: Fri, 3 Nov 2023 22:59:21 +0800 Subject: [PATCH 05/15] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E6=A1=86=E7=B2=98=E8=B4=B4=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/bx/implatform/util/MinioUtil.java | 5 +- im-ui/src/components/chat/ChatBox.vue | 180 ++++++++++++++---- im-ui/src/components/chat/ChatMessageItem.vue | 17 +- im-ui/src/components/common/FileUpload.vue | 2 +- 4 files changed, 158 insertions(+), 46 deletions(-) diff --git a/im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java b/im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java index f10cfee..911d940 100644 --- a/im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java +++ b/im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java @@ -106,7 +106,10 @@ public class MinioUtil { if (StringUtils.isBlank(originalFilename)){ throw new RuntimeException(); } - String fileName = System.currentTimeMillis() + originalFilename.substring(originalFilename.lastIndexOf(".")); + String fileName = System.currentTimeMillis()+""; + if(originalFilename.lastIndexOf(".") >= 0){ + fileName +=originalFilename.substring(originalFilename.lastIndexOf(".")); + } String objectName = DateTimeUtils.getFormatDate(new Date(),DateTimeUtils.PARTDATEFORMAT)+ "/" + fileName; try { PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(path+"/" +objectName) diff --git a/im-ui/src/components/chat/ChatBox.vue b/im-ui/src/components/chat/ChatBox.vue index fe5b806..c49de90 100644 --- a/im-ui/src/components/chat/ChatBox.vue +++ b/im-ui/src/components/chat/ChatBox.vue @@ -2,7 +2,8 @@ {{title}} - + @@ -11,8 +12,9 @@
  • - +
@@ -20,31 +22,44 @@
-
+
- +
- +
-
+
- -
- 发送 +
+ + +
+
+ + +
+
+
+ 发送 +
@@ -56,7 +71,8 @@ - + @@ -89,6 +105,8 @@ group: {}, groupMembers: [], sendText: "", + sendImageUrl: "", + sendImageFile: "", showVoice: false, // 是否显示语音录制弹窗 showSide: false, // 是否显示群聊信息栏 showEmotion: false, // 是否显示emoji表情 @@ -101,9 +119,29 @@ } }, methods: { - handleImageSuccess(res, file) { - let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo)); - msgInfo.content = JSON.stringify(res.data); + handlePaste(e) { + let txt = event.clipboardData.getData('Text') + if (typeof(txt) == 'string') { + this.sendText += txt + } + const items = (event.clipboardData || window.clipboardData).items + if (items.length) { + for (let i = 0; i < items.length; i++) { + if (items[i].type.indexOf('image') !== -1) { + let file = items[i].getAsFile(); + this.sendImageFile = file; + this.sendImageUrl = URL.createObjectURL(file); + } + } + } + }, + removeSendImage() { + this.sendImageUrl = ""; + this.sendImageFile = null; + }, + handleImageSuccess(data, file) { + let msgInfo = JSON.parse(JSON.stringify(file.msgInfo || file.raw.msgInfo)); + msgInfo.content = JSON.stringify(data); this.$http({ url: this.messageAction, method: 'post', @@ -114,8 +152,8 @@ this.$store.commit("insertMessage", msgInfo); }) }, - handleImageFail(res, file) { - let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo)); + handleImageFail(e, file) { + let msgInfo = JSON.parse(JSON.stringify(file.msgInfo || file.raw.msgInfo)); msgInfo.loadStatus = 'fail'; this.$store.commit("insertMessage", msgInfo); }, @@ -144,11 +182,11 @@ // 借助file对象保存 file.msgInfo = msgInfo; }, - handleFileSuccess(res, file) { + handleFileSuccess(url, file) { let data = { name: file.name, size: file.size, - url: res.data + url: url } let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo)); msgInfo.content = JSON.stringify(data); @@ -162,7 +200,8 @@ this.$store.commit("insertMessage", msgInfo); }) }, - handleFileFail(res, file) { + handleFileFail(e, file) { + let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo)); msgInfo.loadStatus = 'fail'; this.$store.commit("insertMessage", msgInfo); @@ -261,6 +300,30 @@ msgInfo.recvId = targetId; } }, + handleSendMessage() { + if (this.sendImageFile) { + this.sendImageMessage(); + } else { + this.sendTextMessage(); + } + }, + sendImageMessage() { + this.handleImageBefore(this.sendImageFile); + let formData = new FormData() + formData.append('file', this.sendImageFile.raw || this.sendImageFile) + this.$http.post("/image/upload", formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }).then((data) => { + this.handleImageSuccess(data, this.sendImageFile); + }).catch((res) => { + this.handleImageSuccess(res, this.sendImageFile); + }).finally(() => { + this.sendImageFile = null; + this.sendImageUrl= "" + }); + }, sendTextMessage() { if (!this.sendText.trim()) { this.$message.error("不能发送空白信息"); @@ -498,22 +561,67 @@ } } - .send-text-area { - box-sizing: border-box; - padding: 5px; - width: 100%; - flex: 1; - resize: none; - font-size: 16px; - color: black; + .send-content-area { + display: flex; + flex-direction: column; + height: 100%; background-color: #f8f8f8 !important; outline-color: rgba(83, 160, 231, 0.61); - } - .im-chat-send { - text-align: right; - padding: 7px; + .send-text-area { + box-sizing: border-box; + padding: 5px; + width: 100%; + flex: 1; + resize: none; + font-size: 16px; + color: black; + background-color: #f8f8f8 !important; + outline-color: rgba(83, 160, 231, 0.61); + text-align: left; + border: 0; + } + + .send-image-area { + text-align: left; + + .send-image-box { + position: relative; + display: inline-block; + + .send-image { + max-height: 190px; + border: 1px solid #ccc; + border-radius: 2%; + margin: 2px; + } + + .send-image-close { + position: absolute; + padding: 3px; + right: 7px; + top: 7px; + color: white; + cursor: pointer; + font-size: 15px; + font-weight: 600; + background-color: #aaa; + border-radius: 50%; + border: 1px solid #ccc; + } + } + + } + + .send-btn-area { + + padding: 10px; + position: absolute; + bottom: 0; + right: 0; + } } + } .chat-group-side-box { @@ -521,4 +629,4 @@ animation: rtl-drawer-in .3s 1ms; } } - + \ No newline at end of file diff --git a/im-ui/src/components/chat/ChatMessageItem.vue b/im-ui/src/components/chat/ChatMessageItem.vue index 58a0f6e..45d89b3 100644 --- a/im-ui/src/components/chat/ChatMessageItem.vue +++ b/im-ui/src/components/chat/ChatMessageItem.vue @@ -21,7 +21,7 @@
-
Date: Fri, 3 Nov 2023 23:15:33 +0800 Subject: [PATCH 06/15] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E6=A1=86=E7=B2=98=E8=B4=B4=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- im-ui/src/components/chat/ChatBox.vue | 19 +++++++++++-------- im-ui/src/store/chatStore.js | 9 ++++++++- .../chat-message-item/chat-message-item.vue | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/im-ui/src/components/chat/ChatBox.vue b/im-ui/src/components/chat/ChatBox.vue index c49de90..6a7e33c 100644 --- a/im-ui/src/components/chat/ChatBox.vue +++ b/im-ui/src/components/chat/ChatBox.vue @@ -48,7 +48,7 @@
+ placeholder="温馨提示:可以粘贴截图了哦~">
@@ -120,6 +120,7 @@ }, methods: { handlePaste(e) { + console.log(e); let txt = event.clipboardData.getData('Text') if (typeof(txt) == 'string') { this.sendText += txt @@ -308,21 +309,23 @@ } }, sendImageMessage() { + let file = this.sendImageFile; this.handleImageBefore(this.sendImageFile); let formData = new FormData() - formData.append('file', this.sendImageFile.raw || this.sendImageFile) + formData.append('file', file.raw || file) this.$http.post("/image/upload", formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then((data) => { - this.handleImageSuccess(data, this.sendImageFile); + this.handleImageSuccess(data, file); }).catch((res) => { - this.handleImageSuccess(res, this.sendImageFile); - }).finally(() => { - this.sendImageFile = null; - this.sendImageUrl= "" - }); + this.handleImageSuccess(res, file); + }) + this.sendImageFile = null; + this.sendImageUrl= ""; + this.$nextTick(() => this.$refs.sendBox.focus()); + this.scrollToBottom(); }, sendTextMessage() { if (!this.sendText.trim()) { diff --git a/im-ui/src/store/chatStore.js b/im-ui/src/store/chatStore.js index be37c70..2c8d764 100644 --- a/im-ui/src/store/chatStore.js +++ b/im-ui/src/store/chatStore.js @@ -8,7 +8,14 @@ export default { mutations: { initChatStore(state) { - + // 防止图片一直处在加载中状态 + state.chats.forEach((chat)=>{ + chat.messages.forEach((msg)=>{ + if(msg.loadStatus == "loading"){ + msg.loadStatus = "fail" + } + }) + }) }, openChat(state, chatInfo) { let chat = null; 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 fcc805a..7a4893b 100644 --- a/im-uniapp/components/chat-message-item/chat-message-item.vue +++ b/im-uniapp/components/chat-message-item/chat-message-item.vue @@ -137,7 +137,7 @@ return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "loading"; }, loadFail() { - return this.msgInfo.loadStatus && (this.msgInfo.loadStatus === "fail"); + return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "fail"; }, data() { return JSON.parse(this.msgInfo.content) From 77da9edb61a1f21bf79d59be00cb5cffd8e37302 Mon Sep 17 00:00:00 2001 From: "xie.bx" Date: Sun, 5 Nov 2023 00:22:00 +0800 Subject: [PATCH 07/15] =?UTF-8?q?=E5=B7=B2=E8=AF=BB=E6=9C=AA=E8=AF=BB?= =?UTF-8?q?=E6=98=BE=E7=A4=BA(=E5=BC=80=E5=8F=91=E4=B8=AD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/bx/imcommon/enums/IMCmdType.java | 11 +- .../com/bx/imcommon/enums/IMListenerType.java | 12 +- .../com/bx/imcommon/enums/IMSendCode.java | 10 +- .../com/bx/imcommon/enums/IMTerminalType.java | 11 +- .../com/bx/imcommon/model/IMGroupMessage.java | 6 +- .../controller/GroupMessageController.java | 18 ++ .../controller/PrivateMessageController.java | 15 +- .../implatform/controller/UserController.java | 2 +- .../com/bx/implatform/enums/FileType.java | 12 +- .../bx/implatform/enums/MessageStatus.java | 17 +- .../com/bx/implatform/enums/MessageType.java | 11 +- .../com/bx/implatform/enums/ResultCode.java | 23 +- .../listener/PrivateMessageListener.java | 4 +- .../service/IGroupMessageService.java | 4 + .../service/IPrivateMessageService.java | 3 + .../service/impl/GroupMessageServiceImpl.java | 167 +++++++++---- .../impl/PrivateMessageServiceImpl.java | 99 +++++++- .../com/bx/implatform/vo/GroupMessageVO.java | 28 +-- .../bx/implatform/vo/PrivateMessageVO.java | 30 +-- im-ui/src/api/emotion.js | 3 + im-ui/src/api/enums.js | 12 +- im-ui/src/components/chat/ChatBox.vue | 54 ++-- im-ui/src/components/chat/ChatMessageItem.vue | 51 ++-- im-ui/src/store/chatStore.js | 157 ++++++++---- im-ui/src/store/friendStore.js | 41 ++-- im-ui/src/store/groupStore.js | 56 +++-- im-ui/src/store/index.js | 27 +- im-ui/src/store/uiStore.js | 1 - im-ui/src/store/userStore.js | 28 ++- im-ui/src/view/Chat.vue | 30 ++- im-ui/src/view/Home.vue | 232 +++++++++++------- 31 files changed, 765 insertions(+), 410 deletions(-) diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java index 6ae35de..2311468 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java @@ -1,7 +1,9 @@ package com.bx.imcommon.enums; +import lombok.AllArgsConstructor; +@AllArgsConstructor public enum IMCmdType { LOGIN(0,"登陆"), @@ -11,15 +13,10 @@ public enum IMCmdType { GROUP_MESSAGE(4,"群发消息"); - private Integer code; private String desc; - IMCmdType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } public static IMCmdType fromCode(Integer code){ for (IMCmdType typeEnum:values()) { @@ -31,10 +28,6 @@ public enum IMCmdType { } - public String description() { - return desc; - } - public Integer code(){ return this.code; } diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java index 6ec1828..c0de103 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java @@ -1,5 +1,8 @@ package com.bx.imcommon.enums; +import lombok.AllArgsConstructor; + +@AllArgsConstructor public enum IMListenerType{ ALL(0,"全部消息"), PRIVATE_MESSAGE(1,"私聊消息"), @@ -9,15 +12,6 @@ public enum IMListenerType{ private String desc; - IMListenerType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } - - - public String description() { - return desc; - } public Integer code(){ diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java index 37de1d7..5189a2f 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java @@ -1,6 +1,8 @@ package com.bx.imcommon.enums; +import lombok.AllArgsConstructor; +@AllArgsConstructor public enum IMSendCode { SUCCESS(0,"发送成功"), @@ -11,11 +13,6 @@ public enum IMSendCode { private Integer code; private String desc; - // 构造方法 - IMSendCode(int code, String desc) { - this.code = code; - this.desc = desc; - } public static IMSendCode fromCode(Integer code){ for (IMSendCode typeEnum:values()) { @@ -27,9 +24,6 @@ public enum IMSendCode { } - public String description() { - return desc; - } public Integer code(){ diff --git a/im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java b/im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java index 7ca76f1..d0f4b81 100644 --- a/im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java +++ b/im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java @@ -1,9 +1,12 @@ package com.bx.imcommon.enums; +import lombok.AllArgsConstructor; + import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +@AllArgsConstructor public enum IMTerminalType { WEB(0,"web"), @@ -13,10 +16,6 @@ public enum IMTerminalType { private String desc; - IMTerminalType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } public static IMTerminalType fromCode(Integer code){ for (IMTerminalType typeEnum:values()) { @@ -31,10 +30,6 @@ public enum IMTerminalType { return Arrays.stream(values()).map(IMTerminalType::code).collect(Collectors.toList()); } - public String description() { - return desc; - } - public Integer code(){ return this.code; } diff --git a/im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java b/im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java index 9bbc905..2c298ca 100644 --- a/im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java +++ b/im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java @@ -3,6 +3,8 @@ package com.bx.imcommon.model; import com.bx.imcommon.enums.IMTerminalType; import lombok.Data; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; @Data @@ -14,9 +16,9 @@ public class IMGroupMessage { private IMUserInfo sender; /** - * 接收者id列表(群成员列表) + * 接收者id列表(群成员列表,为空则不会推送) */ - private List recvIds; + private List recvIds = Collections.EMPTY_LIST; /** 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 8d57a81..73abf24 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 @@ -6,6 +6,7 @@ import com.bx.implatform.result.Result; import com.bx.implatform.result.ResultUtils; import com.bx.implatform.service.IGroupMessageService; import com.bx.implatform.dto.GroupMessageDTO; +import com.bx.implatform.vo.PrivateMessageVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +39,7 @@ public class GroupMessageController { return ResultUtils.success(); } + // todo 删除 @PostMapping("/pullUnreadMessage") @ApiOperation(value = "拉取未读消息",notes="拉取未读消息") public Result pullUnreadMessage(){ @@ -45,6 +47,22 @@ public class GroupMessageController { return ResultUtils.success(); } + @GetMapping("/loadMessage") + @ApiOperation(value = "拉取消息",notes="拉取消息,一次最多拉取100条") + public Result> loadMessage(@RequestParam Long minId){ + return ResultUtils.success(groupMessageService.loadMessage(minId)); + } + + + @PutMapping("/readed") + @ApiOperation(value = "消息已读",notes="将群聊中的消息状态置为已读") + public Result readedMessage(@RequestParam Long groupId){ + groupMessageService.readedMessage(groupId); + return ResultUtils.success(); + } + + + @GetMapping("/history") @ApiOperation(value = "查询聊天记录",notes="查询聊天记录") public Result> recallMessage(@NotNull(message = "群聊id不能为空") @RequestParam Long groupId, 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 cde3c38..ea55123 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 @@ -37,7 +37,7 @@ public class PrivateMessageController { return ResultUtils.success(); } - + // todo 删除 @PostMapping("/pullUnreadMessage") @ApiOperation(value = "拉取未读消息",notes="拉取未读消息") public Result pullUnreadMessage(){ @@ -46,6 +46,19 @@ public class PrivateMessageController { } + @GetMapping("/loadMessage") + @ApiOperation(value = "拉取消息",notes="拉取消息,一次最多拉取100条") + public Result> loadMessage(@RequestParam Long minId){ + return ResultUtils.success(privateMessageService.loadMessage(minId)); + } + + @PutMapping("/readed") + @ApiOperation(value = "消息已读",notes="将会话中接收的消息状态置为已读") + public Result readedMessage(@RequestParam Long friendId){ + privateMessageService.readedMessage(friendId); + return ResultUtils.success(); + } + @GetMapping("/history") @ApiOperation(value = "查询聊天记录",notes="查询聊天记录") public Result> recallMessage(@NotNull(message = "好友id不能为空") @RequestParam Long friendId, diff --git a/im-platform/src/main/java/com/bx/implatform/controller/UserController.java b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java index 22222c0..2165cde 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/UserController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/UserController.java @@ -54,7 +54,7 @@ public class UserController { @GetMapping("/find/{id}") @ApiOperation(value = "查找用户",notes="根据id查找用户") - public Result findById(@NotEmpty @PathVariable("id") Long id){ + public Result findById(@NotEmpty @PathVariable("id") Long id){ return ResultUtils.success(userService.findUserById(id)); } diff --git a/im-platform/src/main/java/com/bx/implatform/enums/FileType.java b/im-platform/src/main/java/com/bx/implatform/enums/FileType.java index dc2071f..912029b 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/FileType.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/FileType.java @@ -1,5 +1,8 @@ package com.bx.implatform.enums; +import lombok.AllArgsConstructor; + +@AllArgsConstructor public enum FileType { FILE(0,"文件"), @@ -13,15 +16,6 @@ public enum FileType { private final String desc; - FileType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } - - - public String description() { - return desc; - } public Integer code(){ return this.code; diff --git a/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java b/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java index 8a7968e..3707a7f 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java @@ -1,24 +1,19 @@ package com.bx.implatform.enums; +import lombok.AllArgsConstructor; +@AllArgsConstructor public enum MessageStatus { - UNREAD(0,"未读"), - ALREADY_READ(1,"已读"), - RECALL(2,"已撤回"); + UNSEND(0,"未送达"), + SENDED(1,"送达"), + RECALL(2,"撤回"), + READED(3,"已读"); private final Integer code; private final String desc; - MessageStatus(Integer index, String desc) { - this.code =index; - this.desc=desc; - } - - public String description() { - return desc; - } public Integer code(){ return this.code; 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 5e6d2e8..559531c 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 @@ -1,6 +1,8 @@ package com.bx.implatform.enums; +import lombok.AllArgsConstructor; +@AllArgsConstructor public enum MessageType { TEXT(0,"文字"), @@ -9,6 +11,7 @@ public enum MessageType { AUDIO(3,"音频"), VIDEO(4,"视频"), RECALL(10,"撤回"), + READED(11, "已读"), RTC_CALL(101,"呼叫"), RTC_ACCEPT(102,"接受"), @@ -22,14 +25,6 @@ public enum MessageType { private final String desc; - MessageType(Integer index, String desc) { - this.code =index; - this.desc=desc; - } - - public String description() { - return desc; - } public Integer code(){ return this.code; diff --git a/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java b/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java index 8b8a4b0..109ac59 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java @@ -1,5 +1,8 @@ package com.bx.implatform.enums; +import lombok.AllArgsConstructor; +import lombok.Getter; + /** * 响应码枚举 * @@ -7,6 +10,8 @@ package com.bx.implatform.enums; * @date 2020/10/19 * **/ +@Getter +@AllArgsConstructor public enum ResultCode { SUCCESS(200,"成功"), NO_LOGIN(400,"未登录"), @@ -21,24 +26,6 @@ public enum ResultCode { private int code; private String msg; - ResultCode(int code, String msg) { - this.code = code; - this.msg = msg; - } - public int getCode() { - return code; - } - - public void setCode(int code) { - this.code = code; - } - - public String getMsg() { - return msg; - } - public void setMsg(String msg) { - this.msg = msg; - } } 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 68a6f2b..fe82817 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 @@ -29,8 +29,8 @@ public class PrivateMessageListener implements MessageListener if(result.getCode().equals(IMSendCode.SUCCESS.code())){ UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.lambda().eq(PrivateMessage::getId,messageInfo.getId()) - .eq(PrivateMessage::getStatus, MessageStatus.UNREAD.code()) - .set(PrivateMessage::getStatus, MessageStatus.ALREADY_READ.code()); + .eq(PrivateMessage::getStatus, MessageStatus.UNSEND.code()) + .set(PrivateMessage::getStatus, MessageStatus.SENDED.code()); privateMessageService.update(updateWrapper); log.info("消息已读,消息id:{},发送者:{},接收者:{},终端:{}",messageInfo.getId(),result.getSender().getId(),result.getReceiver().getId(),result.getReceiver().getTerminal()); } diff --git a/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java index a003afe..304a00c 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java @@ -17,5 +17,9 @@ public interface IGroupMessageService extends IService { void pullUnreadMessage(); + List loadMessage(Long minId); + + void readedMessage(Long groupId); + List findHistoryMessage(Long groupId, Long page, Long size); } diff --git a/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java index 0b42d21..f0036e0 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java @@ -18,4 +18,7 @@ public interface IPrivateMessageService extends IService { void pullUnreadMessage(); + List loadMessage(Long minId); + + void readedMessage(Long friendId); } 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 c2f992a..8a90f4e 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 @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bx.imclient.IMClient; import com.bx.imcommon.contant.IMConstant; +import com.bx.implatform.util.DateTimeUtils; import com.bx.implatform.vo.GroupMessageVO; import com.bx.imcommon.model.IMGroupMessage; import com.bx.imcommon.model.IMUserInfo; @@ -33,6 +34,7 @@ import org.springframework.stereotype.Service; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; @Slf4j @@ -43,7 +45,7 @@ public class GroupMessageServiceImpl extends ServiceImpl redisTemplate; + private RedisTemplate redisTemplate; @Autowired private IMClient imClient; @@ -57,16 +59,16 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds = groupMemberService.findUserIdsByGroupId(group.getId()); - if(!userIds.contains(session.getUserId())){ - throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法发送消息"); + if (!userIds.contains(session.getUserId())) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法发送消息"); } // 保存消息 GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class); @@ -74,21 +76,19 @@ public class GroupMessageServiceImpl extends ServiceImpl!session.getUserId().equals(id)).collect(Collectors.toList()); + userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList()); // 群发 GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); IMGroupMessage sendMessage = new IMGroupMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvIds(userIds); sendMessage.setData(msgInfo); imClient.sendGroupMessage(sendMessage); - log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}",session.getUserId(),dto.getGroupId(),dto.getContent()); + log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), dto.getGroupId(), dto.getContent()); return msg.getId(); } - - /** * 撤回消息 * @@ -98,19 +98,19 @@ public class GroupMessageServiceImpl extends ServiceImpl IMConstant.ALLOW_RECALL_SECOND * 1000){ - throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息已发送超过5分钟,无法撤回"); + if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息已发送超过5分钟,无法撤回"); } // 判断是否在群里 - GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(),session.getUserId()); - if(member == null){ - throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法撤回消息"); + GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(), session.getUserId()); + if (member == null) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法撤回消息"); } // 修改数据库 msg.setStatus(MessageStatus.RECALL.code()); @@ -118,15 +118,15 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId()); // 不用发给自己 - userIds = userIds.stream().filter(uid->!session.getUserId().equals(uid)).collect(Collectors.toList()); + 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.getAliasName()); + String content = String.format("'%s'撤回了一条消息", member.getAliasName()); msgInfo.setContent(content); msgInfo.setSendTime(new Date()); IMGroupMessage sendMessage = new IMGroupMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvIds(userIds); sendMessage.setData(msgInfo); sendMessage.setSendResult(false); @@ -139,40 +139,39 @@ public class GroupMessageServiceImpl extends ServiceImpl members = groupMemberService.findByUserId(session.getUserId()); - for(GroupMember member:members){ + for (GroupMember member : members) { // 获取群聊已读的最大消息id,只推送未读消息 - String key = String.join(":",RedisKey.IM_GROUP_READED_POSITION,member.getGroupId().toString(),session.getUserId().toString()); - Integer maxReadedId = (Integer)redisTemplate.opsForValue().get(key); + String key = String.join(":", RedisKey.IM_GROUP_READED_POSITION, member.getGroupId().toString(), session.getUserId().toString()); + Integer maxReadedId = (Integer) redisTemplate.opsForValue().get(key); LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.eq(GroupMessage::getGroupId,member.getGroupId()) - .gt(GroupMessage::getSendTime,member.getCreatedTime()) + wrapper.eq(GroupMessage::getGroupId, member.getGroupId()) + .gt(GroupMessage::getSendTime, member.getCreatedTime()) .ne(GroupMessage::getSendId, session.getUserId()) .ne(GroupMessage::getStatus, MessageStatus.RECALL.code()); - if(maxReadedId!=null){ - wrapper.gt(GroupMessage::getId,maxReadedId); + if (maxReadedId != null) { + wrapper.gt(GroupMessage::getId, maxReadedId); } wrapper.last("limit 100"); List messages = this.list(wrapper); - if(messages.isEmpty()){ + if (messages.isEmpty()) { continue; } // 推送 - for (GroupMessage message:messages ){ + for (GroupMessage message : messages) { GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class); IMGroupMessage sendMessage = new IMGroupMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); // 只推给自己当前终端 sendMessage.setRecvIds(Collections.singletonList(session.getUserId())); sendMessage.setRecvTerminals(Collections.singletonList(session.getTerminal())); @@ -180,41 +179,109 @@ public class GroupMessageServiceImpl extends ServiceImpl loadMessage(Long minId) { + UserSession session = SessionContext.getSession(); + List members = groupMemberService.findByUserId(session.getUserId()); + List ids = members.stream().map(GroupMember::getGroupId).collect(Collectors.toList()); + // 只能拉取最近3个月的 + Date minDate = DateTimeUtils.addMonths(new Date(), -1); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.gt(GroupMessage::getId, minId) + .gt(GroupMessage::getSendTime, minDate) + .in(GroupMessage::getGroupId, ids) + .ne(GroupMessage::getStatus, MessageStatus.RECALL.code()) + .last("limit 100"); + + List messages = this.list(wrapper); + // 转成vo + List vos = messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList()); + // 消息状态,数据库没有存群聊的消息状态,需要从redis取 + List keys = ids.stream() + .map(id -> String.join(":", RedisKey.IM_GROUP_READED_POSITION, id.toString(), session.getUserId().toString())) + .collect(Collectors.toList()); + List sendPos = redisTemplate.opsForValue().multiGet(keys); + int idx = 0; + for (Long id : ids) { + Object o = sendPos.get(idx); + Integer sendMaxId = Objects.isNull(o) ? -1 : (Integer) o; + vos.stream().filter(vo -> vo.getGroupId().equals(id)).forEach(vo -> { + if (vo.getId() <= sendMaxId) { + // 已推送过 + vo.setStatus(MessageStatus.SENDED.code()); + } else { + // 未推送 + vo.setStatus(MessageStatus.UNSEND.code()); + } + }); + idx++; } + return vos; + } + /** + * 消息已读,同步其他终端,清空未读数量 + * + * @param groupId 群聊 + */ + @Override + public void readedMessage(Long groupId) { + UserSession session = SessionContext.getSession(); + // 推送消息给自己的其他终端 + GroupMessageVO msgInfo = new GroupMessageVO(); + msgInfo.setType(MessageType.READED.code()); + msgInfo.setSendTime(new Date()); + msgInfo.setSendId(session.getUserId()); + msgInfo.setGroupId(groupId); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setSendToSelf(true); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(false); + imClient.sendGroupMessage(sendMessage); } /** * 拉取历史聊天记录 * * @param groupId 群聊id - * @param page 页码 - * @param size 页码大小 + * @param page 页码 + * @param size 页码大小 * @return 聊天记录列表 */ @Override public List findHistoryMessage(Long groupId, Long page, Long size) { - page = page > 0 ? page:1; - size = size > 0 ? size:10; + page = page > 0 ? page : 1; + size = size > 0 ? size : 10; Long userId = SessionContext.getSession().getUserId(); - long stIdx = (page-1)* size; + long stIdx = (page - 1) * size; // 群聊成员信息 - GroupMember member = groupMemberService.findByGroupAndUserId(groupId,userId); - if(member == null || member.getQuit()){ - throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊中"); + GroupMember member = groupMemberService.findByGroupAndUserId(groupId, userId); + if (member == null || member.getQuit()) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊中"); } // 查询聊天记录,只查询加入群聊时间之后的消息 QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.lambda().eq(GroupMessage::getGroupId,groupId) - .gt(GroupMessage::getSendTime,member.getCreatedTime()) + wrapper.lambda().eq(GroupMessage::getGroupId, groupId) + .gt(GroupMessage::getSendTime, member.getCreatedTime()) .ne(GroupMessage::getStatus, MessageStatus.RECALL.code()) .orderByDesc(GroupMessage::getId) - .last("limit "+stIdx + ","+size); + .last("limit " + stIdx + "," + size); List messages = this.list(wrapper); - List messageInfos = messages.stream().map(m->BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList()); - log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}",userId,groupId,messageInfos.size()); + List messageInfos = messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList()); + log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}", userId, groupId, messageInfos.size()); return messageInfos; } 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 0093db6..7de2679 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 @@ -2,6 +2,7 @@ package com.bx.implatform.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bx.imclient.IMClient; @@ -9,6 +10,7 @@ import com.bx.imcommon.contant.IMConstant; import com.bx.imcommon.model.IMPrivateMessage; import com.bx.imcommon.model.IMUserInfo; import com.bx.implatform.entity.Friend; +import com.bx.implatform.util.DateTimeUtils; import com.bx.implatform.vo.PrivateMessageVO; import com.bx.implatform.entity.PrivateMessage; import com.bx.implatform.enums.MessageStatus; @@ -25,6 +27,7 @@ import com.bx.implatform.dto.PrivateMessageDTO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.Collections; import java.util.Date; @@ -40,6 +43,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl sendMessage = new IMPrivateMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvId(msgInfo.getRecvId()); sendMessage.setSendToSelf(true); sendMessage.setData(msgInfo); @@ -99,7 +103,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl sendMessage = new IMPrivateMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvId(msgInfo.getRecvId()); sendMessage.setSendToSelf(false); sendMessage.setData(msgInfo); @@ -147,7 +151,6 @@ public class PrivateMessageServiceImpl extends ServiceImpl friends = friendService.findFriendByUserId(session.getUserId()); - if(friends.isEmpty()){ + if (friends.isEmpty()) { return; } List friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList()); // 获取当前用户所有未读消息 LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(PrivateMessage::getRecvId, session.getUserId()) - .eq(PrivateMessage::getStatus, MessageStatus.UNREAD) - .in(PrivateMessage::getSendId,friendIds); + .eq(PrivateMessage::getStatus, MessageStatus.UNSEND) + .in(PrivateMessage::getSendId, friendIds); List messages = this.list(queryWrapper); // 上传至redis,等待推送 - for(PrivateMessage message:messages){ + for (PrivateMessage message : messages) { PrivateMessageVO msgInfo = BeanUtils.copyProperties(message, PrivateMessageVO.class); // 推送消息 IMPrivateMessage sendMessage = new IMPrivateMessage<>(); - sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal())); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvId(session.getUserId()); sendMessage.setRecvTerminals(Collections.singletonList(session.getTerminal())); sendMessage.setSendToSelf(false); @@ -183,4 +186,82 @@ public class PrivateMessageServiceImpl extends ServiceImpl loadMessage(Long minId) { + UserSession session = SessionContext.getSession(); + List friends = friendService.findFriendByUserId(session.getUserId()); + if (friends.isEmpty()) { + return Collections.EMPTY_LIST; + } + List friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList()); + // 获取当前用户的消息 + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + // 只能拉取最近6个月的 + Date minDate = DateTimeUtils.addMonths(new Date(), -1); + queryWrapper.gt(PrivateMessage::getId, minId) + .ge(PrivateMessage::getSendTime, minDate) + .ne(PrivateMessage::getStatus, MessageStatus.RECALL.code()) + .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))) + .last("limit 100"); + + List messages = this.list(queryWrapper); + // 更新发送状态 + List ids = messages.stream() + .filter(m -> !m.getSendId().equals(session.getUserId()) && m.getStatus().equals(MessageStatus.UNSEND.code())) + .map(PrivateMessage::getId) + .collect(Collectors.toList()); + if (!ids.isEmpty()) { + LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); + updateWrapper.in(PrivateMessage::getId, ids) + .set(PrivateMessage::getStatus, MessageStatus.SENDED.code()); + this.update(updateWrapper); + } + log.info("拉取消息,用户id:{},数量:{}", session.getUserId(), messages.size()); + return messages.stream().map(m -> BeanUtils.copyProperties(m, PrivateMessageVO.class)).collect(Collectors.toList()); + } + + + /** + * 消息已读,将整个会话的消息都置为已读状态 + * + * @param friendId 好友id + */ + @Transactional + @Override + public void readedMessage(Long friendId) { + UserSession session = SessionContext.getSession(); + // 推送消息 + PrivateMessageVO msgInfo = new PrivateMessageVO(); + msgInfo.setType(MessageType.READED.code()); + msgInfo.setSendTime(new Date()); + msgInfo.setSendId(session.getUserId()); + msgInfo.setRecvId(friendId); + IMPrivateMessage sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvId(friendId); + sendMessage.setSendToSelf(true); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(false); + imClient.sendPrivateMessage(sendMessage); + // 修改消息状态为已读 + LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); + updateWrapper.eq(PrivateMessage::getSendId,friendId) + .eq(PrivateMessage::getRecvId,session.getUserId()) + .eq(PrivateMessage::getStatus,MessageStatus.SENDED.code()) + .set(PrivateMessage::getStatus,MessageStatus.READED.code()); + this.update(updateWrapper); + log.info("消息已读,接收方id:{},发送方id:{}", session.getUserId(),friendId); + } } diff --git a/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java b/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java index 72480e8..f00cccc 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java +++ b/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java @@ -2,6 +2,7 @@ package com.bx.implatform.vo; import com.bx.imcommon.serializer.DateToLongSerializer; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; @@ -9,34 +10,25 @@ import java.util.Date; @Data public class GroupMessageVO { - /* - * 消息id - */ + @ApiModelProperty(value = "消息id") private Long id; - /* - * 群聊id - */ + @ApiModelProperty(value = "群聊id") private Long groupId; - /* - * 发送者id - */ + @ApiModelProperty(value = " 发送者id") private Long sendId; - /* - * 消息内容 - */ + @ApiModelProperty(value = "消息内容") private String content; - /* - * 消息内容类型 具体枚举值由应用层定义 - */ + @ApiModelProperty(value = "消息内容类型 具体枚举值由应用层定义") private Integer type; - /** - * 发送时间 - */ + @ApiModelProperty(value = " 状态") + private Integer status; + + @ApiModelProperty(value = "发送时间") @JsonSerialize(using = DateToLongSerializer.class) private Date sendTime; } diff --git a/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java b/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java index 1b7c41a..431dc9a 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java +++ b/im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java @@ -2,41 +2,35 @@ package com.bx.implatform.vo; import com.bx.imcommon.serializer.DateToLongSerializer; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; @Data +@ApiModel("私聊消息VO") public class PrivateMessageVO { - /* - * 消息id - */ + @ApiModelProperty(value = " 消息id") private long id; - /* - * 发送者id - */ + @ApiModelProperty(value = " 发送者id") private Long sendId; - /* - * 接收者id - */ + @ApiModelProperty(value = " 接收者id") private Long recvId; - /* - * 发送内容 - */ + @ApiModelProperty(value = " 发送内容") private String content; - /* - * 消息内容类型 IMCmdType - */ + @ApiModelProperty(value = "消息内容类型 IMCmdType") private Integer type; - /** - * 发送时间 - */ + @ApiModelProperty(value = " 状态") + private Integer status; + + @ApiModelProperty(value = " 发送时间") @JsonSerialize(using = DateToLongSerializer.class) private Date sendTime; } diff --git a/im-ui/src/api/emotion.js b/im-ui/src/api/emotion.js index 89fc18c..86af031 100644 --- a/im-ui/src/api/emotion.js +++ b/im-ui/src/api/emotion.js @@ -15,6 +15,9 @@ let transform = (content) => { let textToImg = (emoText) => { let word = emoText.replace(/\#|\;/gi, ''); let idx = emoTextList.indexOf(word); + if(idx==-1){ + return ""; + } let url = require(`@/assets/emoji/${idx}.gif`); return `` } diff --git a/im-ui/src/api/enums.js b/im-ui/src/api/enums.js index 27bfb78..d5f2390 100644 --- a/im-ui/src/api/enums.js +++ b/im-ui/src/api/enums.js @@ -6,6 +6,7 @@ const MESSAGE_TYPE = { AUDIO:3, VIDEO:4, RECALL:10, + READED:11, TIP_TIME:20, RTC_CALL: 101, RTC_ACCEPT: 102, @@ -27,8 +28,17 @@ const TERMINAL_TYPE = { APP: 1 } +const MESSAGE_STATUS = { + UNSEND: 0, + SENDED: 1, + RECALL:2, + READED:3 +} + + export { MESSAGE_TYPE, USER_STATE, - TERMINAL_TYPE + TERMINAL_TYPE, + MESSAGE_STATUS } diff --git a/im-ui/src/components/chat/ChatBox.vue b/im-ui/src/components/chat/ChatBox.vue index 6a7e33c..f8a6261 100644 --- a/im-ui/src/components/chat/ChatBox.vue +++ b/im-ui/src/components/chat/ChatBox.vue @@ -48,10 +48,10 @@
+ placeholder="温馨提示:可以粘贴截图到这里了哦~"> -
-
+
+
@@ -120,7 +120,6 @@ }, methods: { handlePaste(e) { - console.log(e); let txt = event.clipboardData.getData('Text') if (typeof(txt) == 'string') { this.sendText += txt @@ -172,7 +171,8 @@ sendTime: new Date().getTime(), selfSend: true, type: 1, - loadStatus: "loading" + loadStatus: "loading", + status: this.$enums.MESSAGE_STATUS.UNSEND } // 填充对方id this.fillTargetId(msgInfo, this.chat.targetId); @@ -202,7 +202,7 @@ }) }, handleFileFail(e, file) { - + let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo)); msgInfo.loadStatus = 'fail'; this.$store.commit("insertMessage", msgInfo); @@ -221,7 +221,8 @@ sendTime: new Date().getTime(), selfSend: true, type: 2, - loadStatus: "loading" + loadStatus: "loading", + status: this.$enums.MESSAGE_STATUS.UNSEND } // 填充对方id this.fillTargetId(msgInfo, this.chat.targetId); @@ -256,7 +257,6 @@ this.showVoice = false; }, showVideoBox() { - console.log(this.friend) this.$store.commit("showChatPrivateVideoBox", { friend: this.friend, master: true @@ -280,11 +280,11 @@ method: 'post', data: msgInfo }).then((id) => { - this.$message.success("发送成功"); msgInfo.id = id; msgInfo.sendTime = new Date().getTime(); msgInfo.sendId = this.$store.state.userStore.userInfo.id; msgInfo.selfSend = true; + msgInfo.status = this.$enums.MESSAGE_STATUS.UNSEND; this.$store.commit("insertMessage", msgInfo); // 保持输入框焦点 this.$refs.sendBox.focus(); @@ -323,7 +323,7 @@ this.handleImageSuccess(res, file); }) this.sendImageFile = null; - this.sendImageUrl= ""; + this.sendImageUrl = ""; this.$nextTick(() => this.$refs.sendBox.focus()); this.scrollToBottom(); }, @@ -344,12 +344,12 @@ method: 'post', data: msgInfo }).then((id) => { - this.$message.success("发送成功"); this.sendText = ""; msgInfo.id = id; msgInfo.sendTime = new Date().getTime(); msgInfo.sendId = this.$store.state.userStore.userInfo.id; msgInfo.selfSend = true; + msgInfo.status = this.$enums.MESSAGE_STATUS.UNSEND; this.$store.commit("insertMessage", msgInfo); }).finally(() => { // 解除锁定 @@ -390,10 +390,24 @@ msgInfo = JSON.parse(JSON.stringify(msgInfo)); msgInfo.type = 10; msgInfo.content = '你撤回了一条消息'; + msgInfo.status = this.$enums.MESSAGE_STATUS.RECALL; this.$store.commit("insertMessage", msgInfo); }) }); - + }, + readedMessage() { + if(this.chat.type == "GROUP"){ + var url = `/message/group/readed?groupId=${this.chat.targetId}` + }else{ + url = `/message/private/readed?friendId=${this.chat.targetId}` + } + this.$http({ + url: url, + method: 'put' + }).then(() => { + this.$store.commit("resetUnreadCount",this.chat) + this.scrollToBottom(); + }) }, loadGroup(groupId) { this.$http({ @@ -420,7 +434,6 @@ method: 'get' }).then((friend) => { this.friend = friend; - console.log(this.friend) this.$store.commit("updateChatFromFriend", friend); this.$store.commit("updateFriend", friend); }) @@ -469,12 +482,16 @@ }, messageAction() { return `/message/${this.chat.type.toLowerCase()}/send`; + }, + unreadCount() { + return this.chat.unreadCount; } }, watch: { chat: { handler(newChat, oldChat) { - if (newChat.targetId > 0 && (!oldChat || newChat.type != oldChat.type || newChat.targetId != oldChat.targetId)) { + if (newChat.targetId > 0 && (!oldChat || newChat.type != oldChat.type || newChat.targetId != oldChat + .targetId)) { if (this.chat.type == "GROUP") { this.loadGroup(this.chat.targetId); } else { @@ -489,6 +506,14 @@ } }, immediate: true + }, + unreadCount: { + handler(newCount, oldCount) { + if(newCount > 0){ + // 消息已读 + this.readedMessage() + } + } } } } @@ -617,7 +642,6 @@ } .send-btn-area { - padding: 10px; position: absolute; bottom: 0; diff --git a/im-ui/src/components/chat/ChatMessageItem.vue b/im-ui/src/components/chat/ChatMessageItem.vue index 45d89b3..4fde6eb 100644 --- a/im-ui/src/components/chat/ChatMessageItem.vue +++ b/im-ui/src/components/chat/ChatMessageItem.vue @@ -1,11 +1,13 @@