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

# Telegram4J 常见错误处理指南

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


[[toc]]
在使用 Telegram4J 库与 Telegram 服务器进行交互时，开发者可能会遇到一些常见错误和警告。本文将详细分析这些问题的原因，并提供相应的解决方案，帮助您优化应用的稳定性和性能。

---

## 1. 处理 `telegram4j.mtproto.RpcException: channels.getFullChannel returned code: 420, message: FLOOD_WAIT_6` 错误

### 错误原因

- **错误代码 420 和消息 `FLOOD_WAIT_6`**：这是 Telegram 服务器返回的标准错误，表示客户端在短时间内发送了过多请求，触发了速率限制（Rate Limiting）。具体来说，`FLOOD_WAIT_6` 意味着客户端需要等待 **6 秒** 后才能重新发送相关请求。

- **与 Telegram 服务器有关**：此错误直接来自 Telegram 服务器，表明在调用 `channels.getFullChannel` 方法时，客户端超过了服务器设定的请求频率限制。服务器通过速率限制机制保护自身资源，确保服务的稳定性。

### 解决办法

1. **降低请求频率**：

   - 确保应用在一定时间内不发送过多的 `channels.getFullChannel` 请求。可以通过引入请求节流机制（如限制每分钟的请求次数）来实现。

2. **处理 `FLOOD_WAIT` 错误**：

   - 在代码中捕获此类错误，并根据服务器指示的等待时间（例如 6 秒）进行适当的延迟再重试请求。

   ```java
   try {
       // 调用 channels.getFullChannel 方法
   } catch (RpcException e) {
       if (e.getMessage().startsWith("FLOOD_WAIT_")) {
           int waitTime = Integer.parseInt(e.getMessage().split("_")[2]);
           Thread.sleep(waitTime * 1000L);
           // 重新尝试请求
       } else {
           throw e;
       }
   }
   ```

3. **优化请求逻辑**：
   - 检查是否存在不必要的重复请求，或者是否可以合并多个请求以减少总的请求次数。例如，批量获取多个频道的信息，而不是逐个请求。

---

## 2. 解决 `WARN t.c.e.DefaultUpdatesManager.warn:299 - Incorrect get difference parameters, pts: -1, qts: -1, date: 1734221299` 警告

### 问题分析

此警告信息指出在调用 `getDifference` 方法时，传入的参数 `pts` 和 `qts` 均为 `-1`，而 `date` 为异常值 `1734221299`。这些参数的初始值为 `-1`，表示状态尚未正确初始化。导致此警告的可能原因包括：

1. **状态未初始化或初始化失败**：

   - `DefaultUpdatesManager` 在启动时通过 `fillGap()` 方法尝试从本地存储或服务器获取当前状态。如果此过程失败，`pts` 和 `qts` 将保持为 `-1`。

2. **本地存储中缺少有效状态**：

   - 如果本地存储（如默认的 `t4j-bot.bin` 文件）不存在或损坏，客户端无法加载之前保存的状态，导致参数未被正确设置。

3. **并发或时序问题**：

   - 在状态初始化完成之前，某些更新处理逻辑已经尝试调用 `getDifference` 方法，使用了默认的无效参数。

4. **错误的状态应用逻辑**：
   - 即使成功获取到状态，`applyStateLocal` 方法可能未能正确应用这些状态，导致参数依旧无效。

### 解决方案

1. **确保状态初始化成功**：

   - 检查 `fillGap()` 方法是否成功从本地存储或服务器获取并应用状态。可以通过增加日志来确认初始化过程是否顺利完成。

2. **验证本地存储机制**：

   - 确保本地存储（如 `t4j-bot.bin` 文件）能够正确保存和加载 `State` 对象。如果存储文件损坏，可以尝试删除文件，让客户端重新从服务器获取状态。

3. **处理并发问题**：

   - 确保在状态初始化完成之前，不会有其他更新处理逻辑试图调用 `getDifference`。可以通过同步机制或调整初始化顺序来避免此类问题。

4. **优化状态存储**：
   - 考虑使用数据库来存储状态信息，替代默认的本地文件存储，提升数据的持久性和可靠性。

### 详细解释 `pts`、`qts` 和 `date`

- **`pts`（Persistent Timestamp）**：

  - 用于跟踪普通聊天（包括私聊和群聊）中的更新状态，确保客户端能够同步所有未处理的更新。

- **`qts`（Queue Timestamp）**：

  - 用于跟踪加密聊天（如秘密聊天）和某些特定类型的更新，确保这些更新能够独立且准确地同步。

- **`date`**：
  - 表示服务器当前的时间戳，用于标识更新的时间点，帮助客户端理解更新的顺序和时效性。

---

## 3. 优化状态存储：使用数据库替代本地文件

默认情况下，Telegram4J 会将频道信息缓存到内存中，并通过本地文件（如 `t4j-bot.bin`）进行持久化存储。然而，使用本地文件存储可能存在数据损坏或并发访问问题。为了提高数据的可靠性和管理的便捷性，建议使用数据库来存储状态信息。

### 解决办法

1. **删除默认存储文件**：

   - 删除 `t4j-bot.bin` 文件后，客户端会再次从服务器获取最新的状态信息。这适用于初始设置或恢复状态的场景。

2. **使用数据库存储 `State`**：
   - 通过数据库存储 `pts`、`qts` 和 `date`，确保状态信息的持久性和一致性。

### 数据库表结构

使用以下 SQL 语句创建用于存储 Telegram 状态的表：

```sql
DROP TABLE IF EXISTS telegram_state;
CREATE TABLE telegram_state (
  id BIGINT PRIMARY KEY,
  pts INT,
  qts INT,
  date INT,
  seq INT,
  unread_count INT,
  creator VARCHAR(64) DEFAULT '',
  create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updater VARCHAR(64) DEFAULT '',
  update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  deleted SMALLINT NOT NULL DEFAULT 0,
  tenant_id BIGINT NOT NULL DEFAULT 0
);
```

### Java 实现

创建一个实现 `DbStateStoreService` 接口的类，用于与数据库交互：

```java
package nexus.io.telegram.bots.service;

import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.telegram.store.service.DbStateStoreService;
import telegram4j.tl.updates.ImmutableState;

public class DbStateStoreServiceImpl implements DbStateStoreService {

  public static final String tableName = "telegram_state";
  protected volatile long selfId;

  @Override
  public void setSelfId(long selfId) {
    if (!Db.exists(tableName, "id", selfId)) {
      Db.save(tableName, Row.by("id", selfId));
    }
    this.selfId = selfId;
  }

  @Override
  public long getSelfId() {
    return this.selfId;
  }

  @Override
  public ImmutableState getCurrentState() {
    Row row = Db.findById(tableName, selfId);
    if (row != null) {
      Integer pts = row.getInt("pts");
      Integer qts = row.getInt("qts");
      Integer date = row.getInt("date");
      Integer seq = row.getInt("seq");
      Integer unread_count = row.getInt("unread_count");
      return ImmutableState.of(pts, qts, date, seq, unread_count);
    } else {
      return null;
    }
  }

  @Override
  public void updateState(ImmutableState state) {
    int pts = state.pts();
    int qts = state.qts();
    int date = state.date();
    int seq = state.seq();
    int unreadCount = state.unreadCount();
    Row row = Row.by("id", selfId)
                 .set("pts", pts)
                 .set("qts", qts)
                 .set("date", date)
                 .set("seq", seq)
                 .set("unread_count", unreadCount);
    Db.update(tableName, row);
  }
}
```

### 配置使用 `DbStateStoreService`

在初始化 Telegram 客户端时，配置使用数据库存储服务：

```java
import nexus.io.telegram.bots.service.DbStateStoreServiceImpl;
import nexus.io.telegram.store.service.DbChannelStoreService;
import nexus.io.telegram.store.service.DbStateStoreService;
import telegram4j.MTProtoBootstrap;
import telegram4j.MTProtoTelegramClient;

// 创建并连接 MTProto Telegram 客户端
MTProtoBootstrap bootstrap = MTProtoTelegramClient.create(apiId, apiHash, botAuthToken);

// 实例化数据库存储服务
DbChannelStoreService dbChannelStoreService = new DbChannelStoreServiceImpl();
DbStateStoreService dbStateStoreService = new DbStateStoreServiceImpl();

// 配置 StoreLayout
DbStoreLayoutImpl storeLayoutImpl = new DbStoreLayoutImpl(Function.identity());
storeLayoutImpl.setDbChannelStoreService(dbChannelStoreService);
storeLayoutImpl.setDbStateStoreService(dbStateStoreService);

// 使用文件存储布局，同时结合数据库存储
FileStoreLayout storeLayout = new FileStoreLayout(storeLayoutImpl, Path.of("t4j-bot_" + botId + ".bin"));
bootstrap.setStoreLayout(storeLayout);

// 启动客户端
bootstrap.start();
```

**说明**：

- **`DbStateStoreServiceImpl`**：实现了 `DbStateStoreService` 接口，负责从数据库中读取和更新状态信息。
- **`DbStoreLayoutImpl`**：自定义的 `StoreLayout` 实现，整合了数据库存储服务。
- **`FileStoreLayout`**：尽管使用了数据库存储，但仍保留文件存储布局以备不时之需。

### 优势

- **数据持久性**：数据库能够更可靠地保存状态信息，避免因文件损坏导致的数据丢失。
- **并发处理**：数据库天然支持并发访问，减少了多线程环境下的数据一致性问题。
- **可扩展性**：随着应用规模的扩大，数据库能够更高效地管理大量的状态数据。

---

## 4. 总结与建议

在开发基于 Telegram4J 的应用时，合理处理错误和优化状态存储至关重要。以下是关键要点：

1. **理解 Telegram 服务器的速率限制**：

   - 避免频繁请求，合理安排请求节奏，处理 `FLOOD_WAIT` 错误，确保应用的稳定性。

2. **确保状态信息的正确初始化**：

   - 通过本地存储或数据库持久化状态，避免因状态未初始化导致的同步问题。

3. **优化存储机制**：

   - 优先考虑使用数据库存储状态信息，以提升数据的可靠性和管理的便捷性。

4. **增加日志和监控**：

   - 通过详细的日志记录，及时发现并解决潜在的问题，提升应用的可维护性。

5. **处理并发和时序问题**：
   - 确保状态初始化完成后再进行更新处理，避免因并发导致的状态不一致。

通过以上方法，您可以有效地应对 Telegram4J 开发过程中常见的错误和警告，提升应用的稳定性和用户体验。
