260 lines
7.5 KiB
Markdown
260 lines
7.5 KiB
Markdown
# 消息点赞功能实现
|
||
|
||
## 概述
|
||
|
||
本功能基于网易云信 NIM Web SDK V2 实现了聊天消息的点赞和踩功能,通过更新消息的 `serverExtension` 字段来持久化存储用户的点赞状态,使用 NIM SDK 的 `modifyMessage` API 实现服务端同步。
|
||
|
||
## 功能特性
|
||
|
||
- ✅ 支持对AI回复消息进行点赞/踩
|
||
- ✅ 简洁的状态标记:只记录用户的点赞/踩状态,不统计数量
|
||
- ✅ 视觉反馈:点赞后按钮高亮显示
|
||
- ✅ 防重复点赞:再次点击取消点赞
|
||
- ✅ 状态持久化:通过 NIM SDK 的 `serverExtension` 字段保存到云端
|
||
- ✅ 多用户支持:支持多个用户对同一消息进行独立的点赞
|
||
- ✅ 自动同步:点赞状态自动同步到所有客户端
|
||
- ✅ 轻量级:简化数据结构,减少存储空间
|
||
|
||
## 技术实现
|
||
|
||
### 1. 数据结构
|
||
|
||
#### MessageLikeStatus 枚举
|
||
|
||
```typescript
|
||
export enum MessageLikeStatus {
|
||
None = 'none', // 未点赞/踩
|
||
Liked = 'liked', // 已点赞
|
||
Disliked = 'disliked', // 已踩
|
||
}
|
||
```
|
||
|
||
#### MessageServerExtension 接口(简化版)
|
||
|
||
```typescript
|
||
export interface MessageServerExtension {
|
||
[userId: string]: MessageLikeStatus // 用户ID -> 点赞状态的直接映射
|
||
}
|
||
```
|
||
|
||
#### 工具函数
|
||
|
||
```typescript
|
||
// 解析消息的serverExtension字段
|
||
export const parseMessageServerExtension = (serverExtension?: string): MessageServerExtension
|
||
|
||
// 序列化MessageServerExtension对象
|
||
export const stringifyMessageServerExtension = (extension: MessageServerExtension): string
|
||
|
||
// 获取用户对消息的点赞状态
|
||
export const getUserLikeStatus = (message: ExtendedMessage, userId: string): MessageLikeStatus
|
||
```
|
||
|
||
### 2. 核心功能
|
||
|
||
#### NimMsgContext 扩展
|
||
|
||
在 `NimMsgContext` 中添加了 `updateMessageLikeStatus` 方法,使用 NIM SDK 的 `modifyMessage` API:
|
||
|
||
```typescript
|
||
const updateMessageLikeStatus = useCallback(
|
||
async (conversationId: string, messageClientId: string, likeStatus: MessageLikeStatus) => {
|
||
// 1. 获取当前登录用户ID
|
||
const currentUserId = nim.V2NIMLoginService.getLoginUser()
|
||
|
||
// 2. 解析当前消息的serverExtension
|
||
const currentServerExt = parseMessageServerExtension(targetMessage.serverExtension)
|
||
|
||
// 3. 更新用户的点赞状态(简化版)
|
||
const newServerExt = { ...currentServerExt }
|
||
if (likeStatus === MessageLikeStatus.None) {
|
||
delete newServerExt[currentUserId] // 移除点赞状态
|
||
} else {
|
||
newServerExt[currentUserId] = likeStatus // 设置新状态
|
||
}
|
||
|
||
// 4. 调用NIM SDK更新消息
|
||
const modifyResult = await nim.V2NIMMessageService.modifyMessage(targetMessage, {
|
||
serverExtension: stringifyMessageServerExtension(newServerExt),
|
||
})
|
||
|
||
// 5. 更新本地状态
|
||
addMsg(conversationId, [modifyResult.message], false)
|
||
},
|
||
[addMsg]
|
||
)
|
||
```
|
||
|
||
#### useMessageLike Hook
|
||
|
||
提供便捷的点赞操作方法:
|
||
|
||
```typescript
|
||
const {
|
||
likeMessage, // 点赞消息
|
||
dislikeMessage, // 踩消息
|
||
cancelLikeMessage, // 取消点赞/踩
|
||
toggleLike, // 切换点赞状态
|
||
toggleDislike, // 切换踩状态
|
||
} = useMessageLike()
|
||
```
|
||
|
||
### 3. UI组件
|
||
|
||
#### ChatOtherTextContainer
|
||
|
||
AI消息容器组件已集成点赞功能:
|
||
|
||
- 鼠标悬停显示操作按钮
|
||
- 点赞后按钮高亮(红色)
|
||
- 踩后按钮高亮(灰色)
|
||
- 显示点赞/踩数量
|
||
|
||
## 使用方法
|
||
|
||
### 基本用法
|
||
|
||
```typescript
|
||
import { useMessageLike } from '@/hooks/useMessageLike';
|
||
import { getUserLikeStatus, MessageLikeStatus } from '@/atoms/im';
|
||
import { useNimChat } from '@/context/NimChat/useNimChat';
|
||
|
||
const MyComponent = ({ message }: { message: ExtendedMessage }) => {
|
||
const { toggleLike, toggleDislike } = useMessageLike();
|
||
const { nim } = useNimChat();
|
||
|
||
// 获取当前用户的点赞状态
|
||
const currentUserId = nim.V2NIMLoginService.getLoginUser();
|
||
const currentStatus = getUserLikeStatus(message, currentUserId || '');
|
||
|
||
const handleLike = async () => {
|
||
await toggleLike(message.conversationId, message.messageClientId, currentStatus);
|
||
};
|
||
|
||
const handleDislike = async () => {
|
||
await toggleDislike(message.conversationId, message.messageClientId, currentStatus);
|
||
};
|
||
|
||
return (
|
||
<div>
|
||
<button
|
||
onClick={handleLike}
|
||
className={currentStatus === MessageLikeStatus.Liked ? 'active' : ''}
|
||
>
|
||
👍
|
||
</button>
|
||
<button
|
||
onClick={handleDislike}
|
||
className={currentStatus === MessageLikeStatus.Disliked ? 'active' : ''}
|
||
>
|
||
👎
|
||
</button>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 高级用法
|
||
|
||
```typescript
|
||
// 直接设置点赞状态
|
||
await likeMessage(conversationId, messageClientId)
|
||
|
||
// 直接设置踩状态
|
||
await dislikeMessage(conversationId, messageClientId)
|
||
|
||
// 取消所有状态
|
||
await cancelLikeMessage(conversationId, messageClientId)
|
||
```
|
||
|
||
## 状态管理
|
||
|
||
点赞状态通过以下方式管理:
|
||
|
||
1. **服务端状态**: 存储在 `message.serverExtension` 字段中,通过 NIM SDK 同步到云端
|
||
2. **多用户支持**: 每个用户的点赞状态独立存储,使用用户ID作为键
|
||
3. **简化存储**: 仅存储用户的点赞状态,不计算总数,节省存储空间
|
||
4. **状态同步**: 通过 `msgListAtom` 全局状态管理,并通过 NIM SDK 自动同步到所有客户端
|
||
5. **持久化**: 点赞状态持久化存储在 NIM 服务器,不会丢失
|
||
|
||
## 扩展建议
|
||
|
||
### 1. 消息更新监听
|
||
|
||
由于使用了 NIM SDK 的 `modifyMessage` API,建议监听消息更新事件:
|
||
|
||
```typescript
|
||
// 监听消息修改事件
|
||
nim.V2NIMMessageService.on('onMessageUpdated', (messages: V2NIMMessage[]) => {
|
||
messages.forEach((message) => {
|
||
// 处理点赞状态更新
|
||
const serverExt = parseMessageServerExtension(message.serverExtension)
|
||
if (serverExt.likes) {
|
||
console.log('消息点赞状态已更新:', message.messageClientId, serverExt)
|
||
}
|
||
})
|
||
})
|
||
```
|
||
|
||
### 2. 错误处理
|
||
|
||
为点赞操作添加错误处理和重试机制:
|
||
|
||
```typescript
|
||
const updateMessageLikeStatusWithRetry = async (
|
||
conversationId: string,
|
||
messageClientId: string,
|
||
likeStatus: MessageLikeStatus,
|
||
retryCount = 3
|
||
) => {
|
||
try {
|
||
await updateMessageLikeStatus(conversationId, messageClientId, likeStatus)
|
||
} catch (error) {
|
||
if (retryCount > 0) {
|
||
console.log(`点赞失败,剩余重试次数: ${retryCount}`, error)
|
||
await new Promise((resolve) => setTimeout(resolve, 1000)) // 等待1秒
|
||
return updateMessageLikeStatusWithRetry(
|
||
conversationId,
|
||
messageClientId,
|
||
likeStatus,
|
||
retryCount - 1
|
||
)
|
||
} else {
|
||
throw error
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 批量操作
|
||
|
||
对于大量消息的点赞状态批量更新:
|
||
|
||
```typescript
|
||
const batchUpdateLikes = (
|
||
updates: Array<{
|
||
conversationId: string
|
||
messageClientId: string
|
||
likeStatus: MessageLikeStatus
|
||
}>
|
||
) => {
|
||
// 批量更新逻辑
|
||
}
|
||
```
|
||
|
||
## 注意事项
|
||
|
||
1. **性能考虑**: 点赞状态更新会触发组件重渲染,建议使用 React.memo 优化
|
||
2. **网络请求**: 每次点赞都会调用 NIM SDK 的 `modifyMessage` API,请考虑网络状况
|
||
3. **并发控制**: 快速连续点击可能导致并发请求,建议添加防抖或节流
|
||
4. **权限验证**: NIM SDK 会自动验证用户权限,无需额外处理
|
||
5. **消息类型限制**: `modifyMessage` API 仅支持特定类型的消息,请参考 NIM 文档
|
||
6. **扩展字段大小**: `serverExtension` 字段有大小限制,请合理设计数据结构
|
||
|
||
## 相关文件
|
||
|
||
- `src/atoms/im.ts` - 数据类型定义
|
||
- `src/context/NimChat/NimMsgContext.tsx` - 核心逻辑
|
||
- `src/hooks/useMessageLike.ts` - 便捷Hook
|
||
- `src/app/(main)/chat/[aiId]/components/ChatMessageItems/ChatOtherTextContainer.tsx` - UI实现
|