# 红包消息结构文档

## 客户端收到的推送消息结构

### 完整消息结构 (MsgData)

客户端通过WebSocket或HTTP拉取收到的红包消息，消息结构如下：

```json
{
    "clientMsgID": "client_1234567890abcdef",      // 客户端消息ID（string）
    "serverMsgID": "msg_1234567890abcdef",         // 服务器消息ID（string）
    "sendID": "user_owner123",                     // 发送者ID（群主ID）（string）
    "recvID": "",                                  // 接收者ID（群消息为空）（string）
    "groupID": "group123",                         // 群ID（string）
    "senderPlatformID": 0,                         // 发送者平台ID（int32，0表示系统）
    "senderNickname": "群主昵称",                   // 发送者昵称（string）
    "senderFaceURL": "https://...",                // 发送者头像URL（string）
    "sessionType": 3,                              // 会话类型（int32）：3-群聊
    "msgFrom": 200,                                // 消息来源（int32）：200-系统消息
    "contentType": 110,                            // 消息类型（int32）：110-自定义消息
    "content": "{\"data\":\"{\\\"redPacketID\\\":\\\"rp_123\\\",\\\"redPacketType\\\":1,\\\"blessing\\\":\\\"恭喜发财\\\"}\",\"description\":\"redpacket\",\"extension\":\"\"}",  // 消息内容（JSON字符串，自定义消息格式）
    "seq": 123456,                                 // 消息序号（int64）
    "sendTime": 1704067200000,                     // 发送时间戳（int64，毫秒）
    "createTime": 1704067200000,                   // 创建时间戳（int64，毫秒）
    "status": 2,                                   // 消息状态（int32）：2-发送成功
    "isRead": false,                               // 是否已读（bool）
    "options": {                                   // 消息选项（map[string]bool）
        "history": true,                           // 是否保存历史
        "persistent": true,                        // 是否持久化
        "offlinePush": true,                       // 是否离线推送
        "unreadCount": true,                       // 是否计入未读数
        "conversationUpdate": true,                // 是否更新会话
        "senderSync": true                         // 是否同步给发送者
    },
    "offlinePushInfo": {                          // 离线推送信息（可选）
        "title": "[HONGBAO]",                      // 推送标题
        "desc": "[HONGBAO]",                       // 推送描述
        "ex": "",                                  // 扩展字段
        "iosPushSound": "",                        // iOS推送声音
        "iosBadgeCount": false                     // iOS角标计数
    },
    "atUserIDList": [],                           // @用户列表（string[]）
    "attachedInfo": "",                           // 附加信息（string）
    "ex": ""                                      // 扩展字段（string）
}
```

### Content字段解析

`content` 字段是一个JSON字符串，需要先解析为 `CustomElem` 结构（自定义消息格式），然后从 `data` 字段中解析出 `RedPacketElem` 结构：

#### CustomElem 结构（自定义消息外层结构）

```json
{
    "data": "{\"redPacketID\":\"rp_123\",\"redPacketType\":1,\"blessing\":\"恭喜发财\",\"isReceived\":false,\"receiveInfo\":null}",  // 红包数据的JSON字符串（string，必填）
    "description": "redpacket",  // 二级类型标识（string）："redpacket"表示红包消息
    "extension": ""              // 扩展字段（string，可选）
}
```

#### RedPacketElem 结构（从data字段中解析）

```json
{
    "redPacketID": "rp_1234567890abcdef",          // 红包ID（string，必填）
    "redPacketType": 1,                            // 红包类型（int32，必填）：1-普通红包，2-拼手气红包
    "blessing": "恭喜发财",                         // 祝福语（string，可选）
    "isReceived": false,                           // 当前用户是否已领取（bool，服务器填充）
    "receiveInfo": null                            // 领取信息（RedPacketReceiveInfo，仅拼手气红包且已领取时返回）
}
```

#### RedPacketReceiveInfo 结构（领取信息）

```json
{
    "amount": 1000,                                 // 领取金额（int64，单位：分）
    "receiveTime": 1704067200000,                  // 领取时间戳（int64，毫秒）
    "isLucky": false                               // 是否为手气最佳（bool，仅拼手气红包有效）
}
```

#### 字段说明

| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `redPacketID` | string | 是 | 红包唯一标识，用于后续领取、查询等操作（总金额、总个数等信息可通过红包ID查询获取） |
| `redPacketType` | int32 | 是 | 红包类型：1-普通红包（平均分配），2-拼手气红包（随机分配） |
| `blessing` | string | 否 | 祝福语 |
| `isReceived` | bool | 是 | 当前用户是否已领取（服务器根据用户ID自动填充） |
| `receiveInfo` | RedPacketReceiveInfo | 否 | 领取信息，仅当 `isReceived=true` 且 `redPacketType=2`（拼手气红包）时返回 |

#### receiveInfo 字段说明

| 字段名 | 类型 | 说明 |
|--------|------|------|
| `amount` | int64 | 领取金额，单位：分 |
| `receiveTime` | int64 | 领取时间戳，毫秒 |
| `isLucky` | bool | 是否为手气最佳（仅拼手气红包有效） |

### 消息类型常量

- **ContentType**: `110` (`constant.Custom` - 自定义消息)
- **二级类型标识**: `"redpacket"` (存储在 `CustomElem.description` 字段中)
- **SessionType**: `3` (`constant.ReadGroupChatType` - 群聊)
- **MsgFrom**: `200` (`constant.SysMsgType` - 系统消息)

### 客户端解析示例

#### JavaScript/TypeScript

```typescript
interface RedPacketReceiveInfo {
    amount: number;         // 领取金额（分）
    receiveTime: number;     // 领取时间戳（毫秒）
    isLucky: boolean;        // 是否为手气最佳
}

interface RedPacketElem {
    redPacketID: string;
    redPacketType: number;   // 1-普通红包，2-拼手气红包
    blessing?: string;
    isReceived: boolean;     // 当前用户是否已领取
    receiveInfo?: RedPacketReceiveInfo;  // 领取信息（仅拼手气红包且已领取时返回）
}

interface CustomElem {
    data: string;        // 红包数据的JSON字符串
    description: string; // 二级类型标识："redpacket"
    extension?: string;  // 扩展字段
}

interface RedPacketMessage {
    clientMsgID: string;
    serverMsgID: string;
    sendID: string;
    groupID: string;
    contentType: number;   // 110 (自定义消息)
    content: string;       // CustomElem的JSON字符串
    sendTime: number;
    // ... 其他字段
}

// 解析消息
function parseRedPacketMessage(msg: RedPacketMessage): RedPacketElem | null {
    // 检查是否为自定义消息类型
    if (msg.contentType !== 110) {
        return null;
    }
    
    try {
        // 先解析自定义消息结构
        const customElem: CustomElem = JSON.parse(msg.content);
        
        // 检查二级类型是否为红包
        if (customElem.description !== "redpacket") {
            return null;
        }
        
        // 从data字段中解析红包数据
        const redPacketElem: RedPacketElem = JSON.parse(customElem.data);
        return redPacketElem;
    } catch (e) {
        console.error('Failed to parse red packet content:', e);
        return null;
    }
}
```

#### Go

```go
type RedPacketReceiveInfo struct {
    Amount      int64 `json:"amount"`      // 领取金额（分）
    ReceiveTime int64 `json:"receiveTime"` // 领取时间戳（毫秒）
    IsLucky     bool  `json:"isLucky"`     // 是否为手气最佳
}

type RedPacketElem struct {
    RedPacketID   string                 `json:"redPacketID"`
    RedPacketType int32                  `json:"redPacketType"`
    TotalAmount   int64                  `json:"totalAmount"`
    TotalCount    int32                  `json:"totalCount"`
    Blessing      string                 `json:"blessing"`
    IsReceived    bool                   `json:"isReceived"`    // 当前用户是否已领取
    ReceiveInfo   *RedPacketReceiveInfo  `json:"receiveInfo,omitempty"` // 领取信息（仅拼手气红包且已领取时返回）
}

type CustomElem struct {
    Data        string `json:"data"`        // 红包数据的JSON字符串
    Description string `json:"description"` // 二级类型标识："redpacket"
    Extension   string `json:"extension"`   // 扩展字段
}

func ParseRedPacketContent(content []byte) (*RedPacketElem, error) {
    // 先解析自定义消息结构
    var customElem CustomElem
    if err := json.Unmarshal(content, &customElem); err != nil {
        return nil, err
    }
    
    // 检查二级类型是否为红包
    if customElem.Description != "redpacket" {
        return nil, fmt.Errorf("not a red packet message")
    }
    
    // 从data字段中解析红包数据
    var redPacketElem RedPacketElem
    if err := json.Unmarshal([]byte(customElem.Data), &redPacketElem); err != nil {
        return nil, err
    }
    
    return &redPacketElem, nil
}
```

### 完整消息示例

```json
{
    "clientMsgID": "client_1704067200000_abc123",
    "serverMsgID": "msg_1704067200000_def456",
    "sendID": "user_owner123",
    "recvID": "",
    "groupID": "group123",
    "senderPlatformID": 0,
    "senderNickname": "群主",
    "senderFaceURL": "https://example.com/avatar.jpg",
    "sessionType": 3,
    "msgFrom": 200,
    "contentType": 110,
    "content": "{\"data\":\"{\\\"redPacketID\\\":\\\"rp_1234567890abcdef\\\",\\\"redPacketType\\\":1,\\\"blessing\\\":\\\"恭喜发财\\\",\\\"isReceived\\\":false,\\\"receiveInfo\\\":null}\",\"description\":\"redpacket\",\"extension\":\"\"}",
    "seq": 123456,
    "sendTime": 1704067200000,
    "createTime": 1704067200000,
    "status": 2,
    "isRead": false,
    "options": {
        "history": true,
        "persistent": true,
        "offlinePush": true,
        "unreadCount": true,
        "conversationUpdate": true,
        "senderSync": true
    },
    "offlinePushInfo": {
        "title": "[HONGBAO]",
        "desc": "[HONGBAO]"
    },
    "atUserIDList": [],
    "attachedInfo": "",
    "ex": ""
}
```

### 消息选项说明

| 选项名 | 说明 |
|--------|------|
| `history` | 是否保存到历史记录 |
| `persistent` | 是否持久化存储 |
| `offlinePush` | 是否离线推送 |
| `unreadCount` | 是否计入未读数 |
| `conversationUpdate` | 是否更新会话 |
| `senderSync` | 是否同步给发送者 |

### 注意事项

1. **Content字段**: 是JSON字符串，需要先解析为 `CustomElem` 结构，然后从 `data` 字段中解析 `RedPacketElem`
2. **消息类型**: 使用自定义消息类型（`contentType = 110`），通过 `description` 字段标识二级类型为 `"redpacket"`
3. **二级类型扩展**: 未来可以扩展其他自定义消息类型，只需在 `description` 字段中使用不同的标识（如 `"wallet"`、`"coupon"` 等）
4. **金额单位**: `receiveInfo.amount` 单位是"分"，不是"元"
5. **总金额和总个数**: 不在消息中传递，客户端可通过 `redPacketID` 调用查询接口获取详细信息
5. **红包类型**: 
   - `1` = 普通红包（平均分配）
   - `2` = 拼手气红包（随机分配）
6. **发送者**: 固定为群主，`sendID` 为群主ID
7. **消息来源**: `msgFrom = 200` 表示系统消息
8. **会话类型**: `sessionType = 3` 表示群聊
9. **领取状态**: `isReceived` 字段由服务器根据当前用户ID自动填充，客户端无需设置
10. **领取信息**: `receiveInfo` 仅在以下情况返回：
    - `isReceived = true`（用户已领取）
    - `redPacketType = 2`（拼手气红包）
    - 普通红包（`redPacketType = 1`）即使已领取也不会返回 `receiveInfo`
11. **客户端存储**: 客户端收到消息后，应将 `isReceived` 和 `receiveInfo` 存储到本地，用于UI展示
12. **兼容性**: 使用标准自定义消息类型，无需修改客户端SDK，只需在客户端添加红包消息的解析逻辑

