feat(im): 增加频道消息的已读状态

im
YunaiV 2026-05-20 01:00:46 +08:00
parent 30b963149a
commit fc812aef26
5 changed files with 54 additions and 11 deletions

View File

@ -6,6 +6,7 @@ export interface ImManagerChannelMessageVO {
channelName?: string channelName?: string
materialId: number materialId: number
materialTitle?: string materialTitle?: string
materialCoverUrl?: string
type: number type: number
content?: string content?: string
receiverUserIds?: number[] receiverUserIds?: number[]

View File

@ -6,6 +6,8 @@ export interface ImChannelMessageRespVO {
materialId: number materialId: number
type: number type: number
content: string content: string
/** 当前用户已读态pull 时按 Redis 游标计算填充,多端同步使用 */
status?: number
sendTime: string sendTime: string
} }
@ -16,3 +18,11 @@ export const pullChannelMessages = (params: { minId: number; size?: number }) =>
params params
}) })
} }
// 上报频道消息已读位置;切到频道会话或拉到新消息后调
export const readChannelMessages = (channelId: number, messageId: number) => {
return request.put({
url: '/im/channel/message/read',
params: { channelId, messageId }
})
}

View File

@ -19,6 +19,7 @@ import {
} from '@/api/im/message/channel' } from '@/api/im/message/channel'
import { import {
ImConversationType, ImConversationType,
ImMessageStatus,
ImMessageType, ImMessageType,
isFriendChatTip, isFriendChatTip,
isFriendNotification isFriendNotification
@ -96,7 +97,7 @@ export const useMessagePuller = () => {
clientMessageId: '', clientMessageId: '',
type: message.type, type: message.type,
content: message.content, content: message.content,
status: 0, // 频道消息无状态机;占位 UNREAD status: message.status ?? ImMessageStatus.UNREAD,
sendTime: new Date(message.sendTime).getTime(), sendTime: new Date(message.sendTime).getTime(),
senderId: 0, // 系统下发,无发送人 senderId: 0, // 系统下发,无发送人
targetId: message.channelId, // 会话归属到频道编号 targetId: message.channelId, // 会话归属到频道编号

View File

@ -10,6 +10,7 @@ import {
readGroupMessages as apiReadGroupMessages, readGroupMessages as apiReadGroupMessages,
recallGroupMessage as apiRecallGroupMessage recallGroupMessage as apiRecallGroupMessage
} from '@/api/im/message/group' } from '@/api/im/message/group'
import { readChannelMessages as apiReadChannelMessages } from '@/api/im/message/channel'
import { import {
generateClientMessageId, generateClientMessageId,
serializeMessage, serializeMessage,
@ -233,19 +234,24 @@ export const useMessageSender = () => {
// 接口调用:按会话类型分发,并按对应已读开关控制;失败仅记录日志,不回退本地已读状态 // 接口调用:按会话类型分发,并按对应已读开关控制;失败仅记录日志,不回退本地已读状态
const isPrivate = conversation.type === ImConversationType.PRIVATE const isPrivate = conversation.type === ImConversationType.PRIVATE
const isGroup = conversation.type === ImConversationType.GROUP const isGroup = conversation.type === ImConversationType.GROUP
// 频道目前不上报已读 const isChannel = conversation.type === ImConversationType.CHANNEL
// TODO @AI频道已读应该还是要上报的同步到别的端。但是不用记录 status 字段。 if (!isPrivate && !isGroup && !isChannel) {
if (!isPrivate && !isGroup) {
return return
} }
const readEnabled = isPrivate ? MESSAGE_PRIVATE_READ_ENABLED : MESSAGE_GROUP_READ_ENABLED if (isPrivate && !MESSAGE_PRIVATE_READ_ENABLED) {
if (!readEnabled) { return
}
if (isGroup && !MESSAGE_GROUP_READ_ENABLED) {
return return
} }
try { try {
await (isPrivate if (isPrivate) {
? apiReadPrivateMessages(conversation.targetId, maxMessageId) await apiReadPrivateMessages(conversation.targetId, maxMessageId)
: apiReadGroupMessages(conversation.targetId, maxMessageId)) } else if (isGroup) {
await apiReadGroupMessages(conversation.targetId, maxMessageId)
} else {
await apiReadChannelMessages(conversation.targetId, maxMessageId)
}
} catch (e) { } catch (e) {
console.error( console.error(
'[IM] 标记已读失败', '[IM] 标记已读失败',

View File

@ -5,6 +5,7 @@ import { useUserStore } from '@/store/modules/user'
import { import {
ImWebSocketMessageType, ImWebSocketMessageType,
ImMessageStatus,
ImMessageType, ImMessageType,
ImConversationType, ImConversationType,
ImRtcParticipantStatus, ImRtcParticipantStatus,
@ -213,13 +214,37 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
this.dispatchGroupFrame(content as ImGroupMessageDTO) this.dispatchGroupFrame(content as ImGroupMessageDTO)
break break
case ImWebSocketMessageType.CHANNEL_MESSAGE: case ImWebSocketMessageType.CHANNEL_MESSAGE:
this.handleChannelMessage(content as ImChannelMessageRespVO) this.dispatchChannelFrame(content as ImChannelMessageRespVO)
break break
default: default:
console.debug('[IM WS] 未识别事件', frame) console.debug('[IM WS] 未识别事件', frame)
} }
}, },
/**
* payload.type READ
*/
dispatchChannelFrame(websocketMessage: ImChannelMessageRespVO) {
if (websocketMessage.type === ImMessageType.READ) {
this.handleChannelRead(websocketMessage)
return
}
this.handleChannelMessage(websocketMessage)
},
/** 频道 READ自己其它终端在某频道里标为已读本端同步清零该频道未读 */
handleChannelRead(websocketMessage: ImChannelMessageRespVO) {
const conversationStore = useConversationStore()
const conversation = conversationStore.getConversation(
ImConversationType.CHANNEL,
websocketMessage.channelId
)
if (conversation) {
conversation.unreadCount = 0
}
conversationStore.saveConversations()
},
/** /**
* + insertMessage * + insertMessage
* pull WS id conversationStore.insertMessage id * pull WS id conversationStore.insertMessage id
@ -245,7 +270,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
clientMessageId: '', clientMessageId: '',
type: websocketMessage.type, type: websocketMessage.type,
content: websocketMessage.content, content: websocketMessage.content,
status: 0, status: ImMessageStatus.UNREAD,
sendTime: sendTimeMs, sendTime: sendTimeMs,
senderId: 0, senderId: 0,
targetId: websocketMessage.channelId, targetId: websocketMessage.channelId,