crush-level-web/docs/MessageLikeFeature.md

7.5 KiB
Raw Permalink Blame History

消息点赞功能实现

概述

本功能基于网易云信 NIM Web SDK V2 实现了聊天消息的点赞和踩功能,通过更新消息的 serverExtension 字段来持久化存储用户的点赞状态,使用 NIM SDK 的 modifyMessage API 实现服务端同步。

功能特性

  • 支持对AI回复消息进行点赞/踩
  • 简洁的状态标记:只记录用户的点赞/踩状态,不统计数量
  • 视觉反馈:点赞后按钮高亮显示
  • 防重复点赞:再次点击取消点赞
  • 状态持久化:通过 NIM SDK 的 serverExtension 字段保存到云端
  • 多用户支持:支持多个用户对同一消息进行独立的点赞
  • 自动同步:点赞状态自动同步到所有客户端
  • 轻量级:简化数据结构,减少存储空间

技术实现

1. 数据结构

MessageLikeStatus 枚举

export enum MessageLikeStatus {
  None = 'none',      // 未点赞/踩
  Liked = 'liked',    // 已点赞
  Disliked = 'disliked' // 已踩
}

MessageServerExtension 接口(简化版)

export interface MessageServerExtension {
  [userId: string]: MessageLikeStatus; // 用户ID -> 点赞状态的直接映射
}

工具函数

// 解析消息的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

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

提供便捷的点赞操作方法:

const {
  likeMessage,      // 点赞消息
  dislikeMessage,   // 踩消息
  cancelLikeMessage, // 取消点赞/踩
  toggleLike,       // 切换点赞状态
  toggleDislike,    // 切换踩状态
} = useMessageLike();

3. UI组件

ChatOtherTextContainer

AI消息容器组件已集成点赞功能

  • 鼠标悬停显示操作按钮
  • 点赞后按钮高亮(红色)
  • 踩后按钮高亮(灰色)
  • 显示点赞/踩数量

使用方法

基本用法

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>
  );
};

高级用法

// 直接设置点赞状态
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建议监听消息更新事件

// 监听消息修改事件
nim.V2NIMMessageService.on('onMessageUpdated', (messages: V2NIMMessage[]) => {
  messages.forEach(message => {
    // 处理点赞状态更新
    const serverExt = parseMessageServerExtension(message.serverExtension);
    if (serverExt.likes) {
      console.log('消息点赞状态已更新:', message.messageClientId, serverExt);
    }
  });
});

2. 错误处理

为点赞操作添加错误处理和重试机制:

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. 批量操作

对于大量消息的点赞状态批量更新:

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实现