# 消息点赞功能实现

## 概述

本功能基于网易云信 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实现
