<!-- Source: https://telegram-bot.io.nexus/telegram4j/05 -->

# 保存机器人消息

::: danger 不推荐使用
笔者不推荐使用 Telegram4J：它有并发问题，服务总是挂掉。新项目请使用 [telegram-bot-base](<https://telegram-bot.io.nexus/telegram-bot-base/>)。本章仅作为历史记录保留。
:::


## 引言

在开发 Telegram 机器人时，经常需要对接收到的消息进行存储和处理，以便于后续的分析和响应。为了高效地实现这一需求，我们需要深入理解消息的数据结构，提取关键信息，并设计合理的数据库表结构。本文将详细介绍如何从 `SendMessageEvent` 中提取必要的数据，设计数据库表，并实现消息的存储。

## 1. SendMessageEvent 数据分析

`SendMessageEvent` 是 Telegram 机器人接收到消息时触发的事件，其中包含了丰富的消息和用户信息。为了有效地存储这些信息，我们需要明确需要提取和保存的关键字段。

### 1.1 关键字段

在 `SendMessageEvent` 中，以下字段对于消息处理和存储至关重要：

- **`message.Message.data.Variant2.t1.BaseMessage`**:

  - `id`: 消息的唯一标识符。
  - `fromId.PeerUser.userId`: 发送者的用户 ID。
  - `peerId.PeerUser.userId`: 接收者的用户 ID（通常是机器人自身）。
  - `date`: 消息发送的时间戳。
  - `message`: 消息的文本内容。
  - `entities`: 消息中的实体（如命令、链接等）。

- **`chat.PrivateChat.user.User.minData.BaseUser`**:

  - `id`: 聊天中的另一位用户的 ID。
  - `firstName`: 用户的名字。
  - `lastName`: 用户的姓氏。
  - `username`: 用户的用户名。

- **`chat.PrivateChat.selfUser.User.minData.BaseUser`**:

  - `id`: 机器人的用户 ID。
  - `firstName`: 机器人的名字。
  - `username`: 机器人的用户名。

- **`author.User.minData.BaseUser`**:

  - `id`: 消息作者的 ID。
  - `firstName`: 消息作者的名字。
  - `username`: 消息作者的用户名。

### 1.2 数据提取

基于以上字段，我们可以提取以下关键信息：

- **消息基本信息**:

  - 消息 ID (`id`)
  - 发送者 ID (`fromId`)
  - 接收者 ID (`toId`)
  - 消息内容 (`message`)
  - 消息发送时间 (`date`)
  - 消息类型（可根据 `entities` 或其他标志位判断）

- **用户信息**:

  - 发送者的用户名、名字和姓氏
  - 接收者（通常是机器人）的用户名、名字和姓氏

## 2. 数据库表设计

为了存储提取的信息，我们需要设计一个合适的数据库表。

### 2.1 表结构设计

创建一个名为 `telegram_bot_messages` 的表，包含以下字段：

| 字段名            | 数据类型     | 描述                          | 约束                      |
| ----------------- | ------------ | ----------------------------- | ------------------------- |
| `id`              | BIGINT       | 消息 ID                       | PRIMARY KEY               |
| `from_id`         | BIGINT       | 发送者的用户 ID               | NOT NULL                  |
| `to_id`           | BIGINT       | 接收者的用户 ID               | NOT NULL                  |
| `sequence`        | INT          | 消息序列 ID                   | NOT NULL                  |
| `message_text`    | TEXT         | 消息内容                      | NOT NULL                  |
| `message_date`    | TIMESTAMP    | 消息发送时间                  | NOT NULL                  |
| `message_type`    | VARCHAR(50)  | 消息类型（如文本、命令等）    | NOT NULL                  |
| `entities`        | TEXT         | 消息中的实体（JSON 格式存储） | NULLABLE                  |
| `from_username`   | VARCHAR(100) | 发送者的用户名                | NULLABLE                  |
| `from_first_name` | VARCHAR(100) | 发送者的名字                  | NULLABLE                  |
| `from_last_name`  | VARCHAR(100) | 发送者的姓氏                  | NULLABLE                  |
| `to_username`     | VARCHAR(100) | 接收者的用户名                | NULLABLE                  |
| `to_first_name`   | VARCHAR(100) | 接收者的名字                  | NULLABLE                  |
| `to_last_name`    | VARCHAR(100) | 接收者的姓氏                  | NULLABLE                  |
| `create_time`     | TIMESTAMP    | 记录创建时间                  | DEFAULT CURRENT_TIMESTAMP |
| `update_time`     | TIMESTAMP    | 记录更新时间                  | DEFAULT CURRENT_TIMESTAMP |
| `tenant_id`       | BIGINT       | 租户 ID（默认为 0）           | NOT NULL DEFAULT 0        |

### 2.2 创建表的 SQL 语句

```sql
DROP TABLE IF EXISTS telegram_bot_messages;
CREATE TABLE telegram_bot_messages (
    id BIGINT PRIMARY KEY,
    from_id BIGINT NOT NULL,
    to_id BIGINT NOT NULL,
    sequence INT NOT NULL,
    message_text TEXT NOT NULL,
    message_date TIMESTAMP NOT NULL,
    message_type VARCHAR(50) NOT NULL,
    entities TEXT,
    from_username VARCHAR(100),
    from_first_name VARCHAR(100),
    from_last_name VARCHAR(100),
    to_username VARCHAR(100),
    to_first_name VARCHAR(100),
    to_last_name VARCHAR(100),
    create_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
    tenant_id BIGINT NOT NULL DEFAULT 0
);
```

### 2.3 字段说明

- **`id`**: 消息的唯一标识符，用于唯一标识每条消息。
- **`from_id`**: 发送者的用户 ID，用于关联用户信息。
- **`to_id`**: 接收者的用户 ID，可以是另一个用户或机器人自身。
- **`sequence`**: 消息的序列 ID，用于表示消息的顺序。
- **`message_text`**: 存储消息的具体内容。
- **`message_date`**: 消息发送的时间，便于按时间排序和查询。
- **`message_type`**: 消息的类型，如 Private,GROUP,SUPERGROUP,CHANNEL。
- **`entities`**: 以 JSON 字符串格式存储消息中的实体信息，如命令、链接等，便于后续解析和处理。
- **`from_username`、`from_first_name`、`from_last_name`**: 发送者的基本信息，便于快速查看和展示。
- **`to_username`、`to_first_name`、`to_last_name`**: 接收者的基本信息，便于快速查看和展示。
- **`create_time`**: 记录数据插入的时间，便于审计和数据管理。
- **`update_time`**: 记录数据更新时间。
- **`tenant_id`**: 租户 ID，便于多租户系统的数据隔离。

## 3. 数据存储实现

完成数据库表设计后，需要在代码中实现将接收到的 `SendMessageEvent` 数据存储到数据库的逻辑。

### 3.1 数据模型定义

首先，定义一个与数据库表对应的实体类 `TelegramMessage`。

```java
package nexus.io.gpt.translator.vo;

import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TelegramMessage {
  private Long id;
  private Long fromId;
  private Long toId;
  private Integer sequence;
  private String messageText;
  private LocalDateTime messageDate;
  private String messageType;
  private String entities;
  private String fromUsername;
  private String fromFirstName;
  private String fromLastName;
  private String toUsername;
  private String toFirstName;
  private String toLastName;
  private LocalDateTime createTime;
  private LocalDateTime updateTime;
  private Long tenantId;
}
```

### 3.2 数据存储逻辑实现

创建一个服务类 `SendMessageEventService`，用于处理 `SendMessageEvent` 并保存数据。

```java
package nexus.io.gpt.translator.services;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Optional;
import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.gpt.translator.vo.TelegramMessage;
import nexus.io.tio.utils.json.JsonUtils;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;
import telegram4j.core.event.domain.message.SendMessageEvent;
import telegram4j.core.object.Message;
import telegram4j.core.object.MessageEntity;
import telegram4j.core.object.User;
import telegram4j.core.object.chat.Chat;
import telegram4j.core.object.chat.PrivateChat;

public class SendMessageEventService {

  public void save(SendMessageEvent event) {
    TelegramMessage telegramMessage = extract(event);
    if (telegramMessage != null) {
      Db.save("telegram_bot_messages", Row.fromBean(telegramMessage));
    }
  }

  private TelegramMessage extract(SendMessageEvent event) {
    Optional<Chat> chatOptional = event.getChat();

    if (chatOptional.isEmpty()) {
      return null;
    }

    Chat chat = chatOptional.get();
    String fromFirstName = null;
    String fromLastName = null;
    String fromUsername = null;
    long toUserId = 0;
    String toFirstName = null;
    String toLastName = null;
    String toUsername = null;

    if (chat instanceof PrivateChat) {
      PrivateChat privateChat = (PrivateChat) chat;
      User user = privateChat.getUser();
      fromFirstName = user.getFirstName().orElse(null);
      fromLastName = user.getLastName().orElse(null);
      fromUsername = user.getUsername().orElse(null);

      Optional<User> selfUserOptional = privateChat.getSelfUser();
      if (selfUserOptional.isPresent()) {
        User selfUser = selfUserOptional.get();
        toUserId = selfUser.getId().asLong();
        toUsername = selfUser.getUsername().orElse(null);
        toFirstName = selfUser.getFirstName().orElse(null);
        toLastName = selfUser.getLastName().orElse(null);
      }
    }

    // 提取消息基本信息
    Message message = event.getMessage();

    int sequence = message.getId();
    // 提取实体信息
    List<MessageEntity> entities = message.getEntities();
    String entitiesJson = JsonUtils.toJson(entities);

    // 消息发送时间
    LocalDateTime messageDateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(message.getDate()), ZoneId.systemDefault());

    // 生成唯一 ID
    long id = SnowflakeIdUtils.id();

    TelegramMessage telegramMessage = new TelegramMessage();
    telegramMessage.setId(id);
    telegramMessage.setFromId(event.getAuthor().get().getId().asLong());
    telegramMessage.setFromUsername(fromUsername);
    telegramMessage.setFromFirstName(fromFirstName);
    telegramMessage.setFromLastName(fromLastName);

    telegramMessage.setToId(toUserId);
    telegramMessage.setToUsername(toUsername);
    telegramMessage.setToFirstName(toFirstName);
    telegramMessage.setToLastName(toLastName);

    telegramMessage.setSequence(sequence);
    telegramMessage.setMessageText(message.getContent());
    telegramMessage.setMessageDate(messageDateTime);
    telegramMessage.setMessageType(chat.getType().toString());
    telegramMessage.setEntities(entitiesJson);

    // 设置创建和更新时间
    telegramMessage.setCreateTime(LocalDateTime.now());
    telegramMessage.setUpdateTime(LocalDateTime.now());

    // 设置租户 ID（如有需要）
    telegramMessage.setTenantId(0L);

    return telegramMessage;
  }
}
```

#### 3.2.1 代码说明

- **消息提取**:

  - 从 `SendMessageEvent` 中获取 `Chat` 对象，判断是否为 `PrivateChat` 类型。
  - 提取发送者和接收者的用户信息，包括 ID、用户名、名字和姓氏。

- **实体信息处理**:

  - 获取消息中的实体列表，将其转换为 JSON 字符串，便于存储。

- **时间处理**:

  - 将消息的 Unix 时间戳转换为 `LocalDateTime`。

- **ID 生成**:

  - 使用雪花算法生成全局唯一的消息 ID，确保在分布式环境下的唯一性。

- **数据存储**:

  - 将 `TelegramMessage` 对象转换为数据库行，保存到 `telegram_bot_messages` 表中。

### 3.3 在应用中调用保存方法

在消息处理的逻辑中，调用 `SendMessageEventService` 的 `save` 方法：

```java
Aop.get(SendMessageEventService.class).save(event);
```

## 4. 测试与验证

在完成上述实现后，我们可以对系统进行测试，确保消息数据被正确地存储到了数据库中。

### 4.1 示例数据

假设接收到以下消息：

- **消息内容**: "what's your name"
- **发送者 ID**: 6276672963
- **接收者（机器人）ID**: 7847170133
- **消息序列**: 1589
- **消息日期**: 2024-12-03 19:14:58
- **消息类型**: PRIVATE
- **发送者用户名**: litonglinux
- **发送者名字**: 蕾娜斯
- **接收者用户名**: litongjava_bot
- **接收者名字**: my-translator

### 4.2 数据库中的存储结果

保存后的数据库记录如下：

| id                 | from_id    | to_id      | sequence | message_text     | message_date            | message_type | entities | from_username | from_first_name | from_last_name | to_username    | to_first_name | to_last_name | create_time                | update_time                | tenant_id |
| ------------------ | ---------- | ---------- | -------- | ---------------- | ----------------------- | ------------ | -------- | ------------- | --------------- | -------------- | -------------- | ------------- | ------------ | -------------------------- | -------------------------- | --------- |
| 453879471025647616 | 6276672963 | 7847170133 | 1589     | what's your name | 2024-12-03 19:14:58.000 | PRIVATE      | []       | litonglinux   | 蕾娜斯          | NULL           | litongjava_bot | my-translator | NULL         | 2024-12-03 19:15:18.928963 | 2024-12-03 19:15:18.928963 | 0         |

可以看到，消息的各项信息都被正确地存储到了数据库中，包括发送者和接收者的信息、消息内容、发送时间等。

## 5. 结论

通过以上步骤，我们成功地实现了对 Telegram 机器人接收到的消息的存储。详细分析了消息的数据结构，设计了合理的数据库表结构，并实现了数据的提取和存储逻辑。这为后续的消息处理、分析和响应奠定了坚实的基础。

## 附录：关键代码解释

### A.1 `SendMessageEventService` 类

该类的作用是处理 `SendMessageEvent`，提取必要的信息，并将其保存到数据库中。

- **`save` 方法**: 接收 `SendMessageEvent`，调用 `extract` 方法提取信息，并保存到数据库。
- **`extract` 方法**: 从事件中提取消息和用户信息，处理实体和时间数据。

### A.2 时间处理

将 Unix 时间戳转换为 `LocalDateTime`:

```java
LocalDateTime messageDateTime = LocalDateTime.ofInstant(
    Instant.ofEpochSecond(message.getDate()),
    ZoneId.systemDefault()
);
```

### A.3 实体信息处理

将消息中的实体列表转换为 JSON 字符串:

```java
List<MessageEntity> entities = message.getEntities();
String entitiesJson = JsonUtils.toJson(entities);
telegramMessage.setEntities(entitiesJson);
```

### A.4 ID 生成

使用雪花算法生成全局唯一的 ID:

```java
long id = SnowflakeIdUtils.id();
telegramMessage.setId(id);
```

---

**备注**: 在实际应用中，需要根据具体的业务需求和技术栈，选择合适的数据库、ORM 框架和 ID 生成策略。同时，要注意异常处理和日志记录，确保系统的稳定性和可维护性。
