Browse Source

优化: 消息支持显示发送状态

master
xsx 9 months ago
parent
commit
43078dfc90
  1. 4
      db/im-platform.sql
  2. 3
      im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java
  3. 4
      im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java
  4. 6
      im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java
  5. 6
      im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java
  6. 9
      im-platform/src/main/java/com/bx/implatform/service/impl/FileServiceImpl.java
  7. 3
      im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java
  8. 3
      im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java
  9. 2
      im-uniapp/components/chat-message-item/chat-message-item.vue
  10. 5
      im-uniapp/pages/chat/chat-box.vue
  11. 23
      im-uniapp/store/chatStore.js
  12. 1
      im-web/src/api/wssocket.js
  13. 5
      im-web/src/components/chat/ChatBox.vue
  14. 4
      im-web/src/components/chat/ChatMessageItem.vue
  15. 23
      im-web/src/store/chatStore.js

4
db/im-platform.sql

@ -31,6 +31,7 @@ create table `im_friend`(
create table `im_private_message`( create table `im_private_message`(
`id` bigint not null auto_increment primary key comment 'id', `id` bigint not null auto_increment primary key comment 'id',
`tmp_id` varchar(32) comment '临时id,由前端生成',
`send_id` bigint not null comment '发送用户id', `send_id` bigint not null comment '发送用户id',
`recv_id` bigint not null comment '接收用户id', `recv_id` bigint not null comment '接收用户id',
`content` text comment '发送内容', `content` text comment '发送内容',
@ -44,6 +45,7 @@ create table `im_private_message`(
create table `im_group`( create table `im_group`(
`id` bigint not null auto_increment primary key comment 'id', `id` bigint not null auto_increment primary key comment 'id',
`tmp_id` varchar(32) comment '临时id,由前端生成',
`name` varchar(255) not null comment '群名字', `name` varchar(255) not null comment '群名字',
`owner_id` bigint not null comment '群主id', `owner_id` bigint not null comment '群主id',
`head_image` varchar(255) default '' comment '群头像', `head_image` varchar(255) default '' comment '群头像',
@ -106,5 +108,5 @@ CREATE TABLE `im_file_info` (
`upload_time` datetime DEFAULT CURRENT_TIMESTAMP comment '上传时间', `upload_time` datetime DEFAULT CURRENT_TIMESTAMP comment '上传时间',
`is_permanent` tinyint DEFAULT 0 comment '是否永久文件', `is_permanent` tinyint DEFAULT 0 comment '是否永久文件',
`md5` VARCHAR(64) NOT NULL comment '文件md5', `md5` VARCHAR(64) NOT NULL comment '文件md5',
UNIQUE KEY `idx_md5` (md5) KEY `idx_md5` (md5)
) ENGINE = InnoDB CHARSET = utf8mb4 comment '文件'; ) ENGINE = InnoDB CHARSET = utf8mb4 comment '文件';

3
im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java

@ -13,6 +13,9 @@ import java.util.List;
@Schema(description = "群聊消息DTO") @Schema(description = "群聊消息DTO")
public class GroupMessageDTO { public class GroupMessageDTO {
@Schema(description = "临时id")
private String tmpId;
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@Schema(description = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;

4
im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java

@ -10,11 +10,13 @@ import org.hibernate.validator.constraints.Length;
@Schema(description = "私聊消息DTO") @Schema(description = "私聊消息DTO")
public class PrivateMessageDTO { public class PrivateMessageDTO {
@Schema(description = "临时id")
private String tmpId;
@NotNull(message = "接收用户id不可为空") @NotNull(message = "接收用户id不可为空")
@Schema(description = "接收用户id") @Schema(description = "接收用户id")
private Long recvId; private Long recvId;
@Length(max = 1024, message = "内容长度不得大于1024") @Length(max = 1024, message = "内容长度不得大于1024")
@NotEmpty(message = "发送内容不可为空") @NotEmpty(message = "发送内容不可为空")
@Schema(description = "发送内容") @Schema(description = "发送内容")

6
im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java

@ -25,6 +25,12 @@ public class GroupMessage {
@TableId @TableId
private Long id; private Long id;
/**
* 临时id,由前端生成
* 作用:如果用户正在发送消息时掉线了可以通过此字段获取该消息的实际发送状态
*/
private String tmpId;
/** /**
* 群id * 群id
*/ */

6
im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java

@ -22,6 +22,12 @@ public class PrivateMessage {
*/ */
private Long id; private Long id;
/**
* 临时id,由前端生成
* 作用:如果用户正在发送消息时掉线了可以通过此字段获取该消息的实际发送状态
*/
private String tmpId;
/** /**
* 发送用户id * 发送用户id
*/ */

9
im-platform/src/main/java/com/bx/implatform/service/impl/FileServiceImpl.java

@ -63,7 +63,7 @@ public class FileServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> imple
} }
// 如果文件已存在,直接复用 // 如果文件已存在,直接复用
String md5 = DigestUtils.md5DigestAsHex(file.getInputStream()); String md5 = DigestUtils.md5DigestAsHex(file.getInputStream());
FileInfo fileInfo = findByMd5(md5); FileInfo fileInfo = findByMd5(md5, FileType.FILE.code());
if (!Objects.isNull(fileInfo)) { if (!Objects.isNull(fileInfo)) {
// 更新上传时间 // 更新上传时间
fileInfo.setUploadTime(new Date()); fileInfo.setUploadTime(new Date());
@ -103,7 +103,7 @@ public class FileServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> imple
UploadImageVO vo = new UploadImageVO(); UploadImageVO vo = new UploadImageVO();
// 如果文件已存在,直接复用 // 如果文件已存在,直接复用
String md5 = DigestUtils.md5DigestAsHex(file.getInputStream()); String md5 = DigestUtils.md5DigestAsHex(file.getInputStream());
FileInfo fileInfo = findByMd5(md5); FileInfo fileInfo = findByMd5(md5, FileType.IMAGE.code());
if (!Objects.isNull(fileInfo)) { if (!Objects.isNull(fileInfo)) {
// 更新上传时间和持久化标记 // 更新上传时间和持久化标记
fileInfo.setIsPermanent(isPermanent || fileInfo.getIsPermanent()); fileInfo.setIsPermanent(isPermanent || fileInfo.getIsPermanent());
@ -131,7 +131,7 @@ public class FileServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> imple
vo.setThumbUrl(generUrl(FileType.IMAGE, thumbFileName)); vo.setThumbUrl(generUrl(FileType.IMAGE, thumbFileName));
// 保存文件信息 // 保存文件信息
saveImageFileInfo(file, md5, vo.getOriginUrl(), vo.getThumbUrl(), isPermanent); saveImageFileInfo(file, md5, vo.getOriginUrl(), vo.getThumbUrl(), isPermanent);
}else{ } else {
// 小于50k,用原图充当缩略图 // 小于50k,用原图充当缩略图
vo.setThumbUrl(generUrl(FileType.IMAGE, fileName)); vo.setThumbUrl(generUrl(FileType.IMAGE, fileName));
// 保存文件信息,由于缩略图不允许删除,此时原图也不允许删除 // 保存文件信息,由于缩略图不允许删除,此时原图也不允许删除
@ -157,9 +157,10 @@ public class FileServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> imple
}; };
} }
private FileInfo findByMd5(String md5) { private FileInfo findByMd5(String md5, Integer fileType) {
LambdaQueryWrapper<FileInfo> wrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<FileInfo> wrapper = Wrappers.lambdaQuery();
wrapper.eq(FileInfo::getMd5, md5); wrapper.eq(FileInfo::getMd5, md5);
wrapper.eq(FileInfo::getFileType, fileType);
return getOne(wrapper); return getOne(wrapper);
} }

3
im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java

@ -14,6 +14,9 @@ public class GroupMessageVO {
@Schema(description = "消息id") @Schema(description = "消息id")
private Long id; private Long id;
@Schema(description = "临时id")
private String tmpId;
@Schema(description = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;

3
im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java

@ -14,6 +14,9 @@ public class PrivateMessageVO {
@Schema(description = " 消息id") @Schema(description = " 消息id")
private Long id; private Long id;
@Schema(description = "临时id")
private String tmpId;
@Schema(description = " 发送者id") @Schema(description = " 发送者id")
private Long sendId; private Long sendId;

2
im-uniapp/components/chat-message-item/chat-message-item.vue

@ -58,7 +58,7 @@
</view> </view>
</long-press-menu> </long-press-menu>
<view v-if="sending&&isTextMessage" class="sending"> <view v-if="sending&&isTextMessage" class="sending">
<loading size="40" icon-color="#656adf" :mask="false"></loading> <loading :size="40" icon-color="#656adf" :mask="false"></loading>
</view> </view>
<view v-else-if="sendFail" @click="onSendFail" <view v-else-if="sendFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></view> class="send-fail iconfont icon-warning-circle-fill"></view>

5
im-uniapp/pages/chat/chat-box.vue

@ -167,6 +167,7 @@ export default {
return; return;
} }
let msgInfo = { let msgInfo = {
tmpId: this.generateId(),
content: JSON.stringify(data), content: JSON.stringify(data),
type: this.$enums.MESSAGE_TYPE.AUDIO, type: this.$enums.MESSAGE_TYPE.AUDIO,
receipt: this.isReceipt receipt: this.isReceipt
@ -307,6 +308,7 @@ export default {
let receiptText = this.isReceipt ? "【回执消息】" : ""; let receiptText = this.isReceipt ? "【回执消息】" : "";
let atText = this.createAtText(); let atText = this.createAtText();
let msgInfo = { let msgInfo = {
tmpId: this.generateId(),
content: receiptText + sendText + atText, content: receiptText + sendText + atText,
atUserIds: this.atUserIds, atUserIds: this.atUserIds,
receipt: this.isReceipt, receipt: this.isReceipt,
@ -893,7 +895,6 @@ export default {
}, },
buildTmpMessage(msgInfo) { buildTmpMessage(msgInfo) {
let message = JSON.parse(JSON.stringify(msgInfo)); let message = JSON.parse(JSON.stringify(msgInfo));
message.tmpId = this.generateId();
message.sendId = this.mine.id; message.sendId = this.mine.id;
message.sendTime = new Date().getTime(); message.sendTime = new Date().getTime();
message.status = this.$enums.MESSAGE_STATUS.SENDING; message.status = this.$enums.MESSAGE_STATUS.SENDING;
@ -902,7 +903,7 @@ export default {
message.readedCount = 0; message.readedCount = 0;
} }
return message; return message;
}, },
generateId() { generateId() {
// id // id
return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000)); return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000));

23
im-uniapp/store/chatStore.js

@ -30,14 +30,6 @@ export default defineStore('chatStore', {
} }
this.privateMsgMaxId = chatsData.privateMsgMaxId || 0; this.privateMsgMaxId = chatsData.privateMsgMaxId || 0;
this.groupMsgMaxId = chatsData.groupMsgMaxId || 0; this.groupMsgMaxId = chatsData.groupMsgMaxId || 0;
// 防止消息一直处在发送中状态
cacheChats.forEach(chat => {
chat.messages.forEach(msg => {
if (msg.status == MESSAGE_STATUS.SENDING) {
msg.status = MESSAGE_STATUS.FAILED
}
})
})
}, },
openChat(chatInfo) { openChat(chatInfo) {
let chats = this.curChats; let chats = this.curChats;
@ -63,6 +55,7 @@ export default defineStore('chatStore', {
lastSendTime: new Date().getTime(), lastSendTime: new Date().getTime(),
unreadCount: 0, unreadCount: 0,
hotMinIdx: 0, hotMinIdx: 0,
readedMessageIdx: 0,
messages: [], messages: [],
atMe: false, atMe: false,
atAll: false, atAll: false,
@ -95,15 +88,17 @@ export default defineStore('chatStore', {
readedMessage(pos) { readedMessage(pos) {
let chat = this.findChatByFriend(pos.friendId); let chat = this.findChatByFriend(pos.friendId);
if (!chat) return; if (!chat) return;
chat.messages.forEach((m) => { for (let idx = chat.readedMessageIdx; idx < chat.messages.length; idx++) {
let m = chat.messages[idx];
if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) { if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) {
// pos.maxId为空表示整个会话已读 // pos.maxId为空表示整个会话已读
if (!pos.maxId || m.id <= pos.maxId) { if (!pos.maxId || m.id <= pos.maxId) {
m.status = MESSAGE_STATUS.READED m.status = MESSAGE_STATUS.READED
chat.readedMessageIdx = idx;
chat.stored = false; chat.stored = false;
} }
} }
}) }
if (!chat.stored) { if (!chat.stored) {
this.saveToStorage(); this.saveToStorage();
} }
@ -463,11 +458,19 @@ export default defineStore('chatStore', {
if (!coldChat && !hotChat) { if (!coldChat && !hotChat) {
return; return;
} }
// 防止消息一直处在发送中状态
hotChat && hotChat.messages.forEach(msg => {
if (msg.status == MESSAGE_STATUS.SENDING) {
msg.status = MESSAGE_STATUS.FAILED
}
})
// 冷热消息合并 // 冷热消息合并
let chat = Object.assign({}, coldChat, hotChat); let chat = Object.assign({}, coldChat, hotChat);
if (hotChat && coldChat) { if (hotChat && coldChat) {
chat.messages = coldChat.messages.concat(hotChat.messages) chat.messages = coldChat.messages.concat(hotChat.messages)
} }
// 历史版本没有readedMessageIdx字段,做兼容一下
chat.readedMessageIdx = chat.readedMessageIdx || 0;
chatsData.chats.push(chat); chatsData.chats.push(chat);
}) })
} }

1
im-web/src/api/wssocket.js

@ -92,7 +92,6 @@ let heartCheck = {
websock.send(JSON.stringify(heartBeat)) websock.send(JSON.stringify(heartBeat))
} }
}, },
reset: function () { reset: function () {
clearTimeout(this.timeoutObj); clearTimeout(this.timeoutObj);
this.timeoutObj = setTimeout(function () { this.timeoutObj = setTimeout(function () {

5
im-web/src/components/chat/ChatBox.vue

@ -361,6 +361,7 @@ export default {
return; return;
} }
let msgInfo = { let msgInfo = {
tmpId: this.generateId(),
content: JSON.stringify(data), content: JSON.stringify(data),
type: this.$enums.MESSAGE_TYPE.AUDIO, type: this.$enums.MESSAGE_TYPE.AUDIO,
receipt: this.isReceipt receipt: this.isReceipt
@ -452,6 +453,7 @@ export default {
reject(); reject();
} }
let msgInfo = { let msgInfo = {
tmpId: this.generateId(),
content: sendText, content: sendText,
type: this.$enums.MESSAGE_TYPE.TEXT type: this.$enums.MESSAGE_TYPE.TEXT
} }
@ -670,7 +672,6 @@ export default {
}, },
buildTmpMessage(msgInfo) { buildTmpMessage(msgInfo) {
let message = JSON.parse(JSON.stringify(msgInfo)); let message = JSON.parse(JSON.stringify(msgInfo));
message.tmpId = this.generateId();
message.sendId = this.mine.id; message.sendId = this.mine.id;
message.sendTime = new Date().getTime(); message.sendTime = new Date().getTime();
message.status = this.$enums.MESSAGE_STATUS.SENDING; message.status = this.$enums.MESSAGE_STATUS.SENDING;
@ -679,7 +680,7 @@ export default {
message.readedCount = 0; message.readedCount = 0;
} }
return message; return message;
}, },
generateId() { generateId() {
// id // id
return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000)); return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000));

4
im-web/src/components/chat/ChatMessageItem.vue

@ -254,7 +254,7 @@ export default {
.message-content-wrapper { .message-content-wrapper {
position: relative; position: relative;
display: flex; display: flex;
align-items: end; align-items: center;
.sending { .sending {
width: 25px; width: 25px;
@ -270,7 +270,7 @@ export default {
color: #e45050; color: #e45050;
font-size: 30px; font-size: 30px;
cursor: pointer; cursor: pointer;
margin: 0 20px; margin: 0 3px;
} }
} }

23
im-web/src/store/chatStore.js

@ -40,14 +40,6 @@ export default defineStore('chatStore', {
this.privateMsgMaxId = chatsData.privateMsgMaxId || 0; this.privateMsgMaxId = chatsData.privateMsgMaxId || 0;
this.groupMsgMaxId = chatsData.groupMsgMaxId || 0; this.groupMsgMaxId = chatsData.groupMsgMaxId || 0;
cacheChats = chatsData.chats || []; cacheChats = chatsData.chats || [];
// 防止消息一直处在发送中状态
cacheChats.forEach(chat => {
chat.messages.forEach(msg => {
if (msg.status == MESSAGE_STATUS.SENDING) {
msg.status = MESSAGE_STATUS.FAILED
}
})
})
}, },
openChat(chatInfo) { openChat(chatInfo) {
let chats = this.findChats() let chats = this.findChats()
@ -73,6 +65,7 @@ export default defineStore('chatStore', {
lastSendTime: new Date().getTime(), lastSendTime: new Date().getTime(),
unreadCount: 0, unreadCount: 0,
hotMinIdx: 0, hotMinIdx: 0,
readedMessageIdx: 0,
messages: [], messages: [],
atMe: false, atMe: false,
atAll: false, atAll: false,
@ -103,15 +96,17 @@ export default defineStore('chatStore', {
readedMessage(pos) { readedMessage(pos) {
let chat = this.findChatByFriend(pos.friendId); let chat = this.findChatByFriend(pos.friendId);
if (!chat) return; if (!chat) return;
chat.messages.forEach((m) => { for (let idx = chat.readedMessageIdx; idx < chat.messages.length; idx++) {
let m = chat.messages[idx];
if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) { if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) {
// pos.maxId为空表示整个会话已读 // pos.maxId为空表示整个会话已读
if (!pos.maxId || m.id <= pos.maxId) { if (!pos.maxId || m.id <= pos.maxId) {
m.status = MESSAGE_STATUS.READED m.status = MESSAGE_STATUS.READED
chat.readedMessageIdx = idx;
chat.stored = false; chat.stored = false;
} }
} }
}) }
this.saveToStorage(); this.saveToStorage();
}, },
removeChat(idx) { removeChat(idx) {
@ -474,11 +469,19 @@ export default defineStore('chatStore', {
} }
let coldChat = chats[i]; let coldChat = chats[i];
let hotChat = chats[i + 1]; let hotChat = chats[i + 1];
// 防止消息一直处在发送中状态
hotChat && hotChat.messages.forEach(msg => {
if (msg.status == MESSAGE_STATUS.SENDING) {
msg.status = MESSAGE_STATUS.FAILED
}
})
// 冷热消息合并 // 冷热消息合并
let chat = Object.assign({}, coldChat, hotChat); let chat = Object.assign({}, coldChat, hotChat);
if (hotChat && coldChat) { if (hotChat && coldChat) {
chat.messages = coldChat.messages.concat(hotChat.messages) chat.messages = coldChat.messages.concat(hotChat.messages)
} }
// 历史版本没有readedMessageIdx字段,做兼容一下
chat.readedMessageIdx = chat.readedMessageIdx || 0;
chatsData.chats.push(chat); chatsData.chats.push(chat);
} }
this.initChats(chatsData); this.initChats(chatsData);

Loading…
Cancel
Save