You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Airgram(TDLib)实现Telegram全局聊天搜索耗时过长问题

问题描述

我正尝试复刻Telegram官方的全局聊天搜索功能,功能效果参考如下图:
全局聊天搜索效果示例

我当前使用getChats方法执行搜索,但该方法仅返回聊天ID列表,而我还需要获取对应聊天的名称、头像信息,预期返回字段参考如下图:
预期返回字段示例

目前我只能遍历返回的chatId列表,针对每个ID单独调用getChat方法拉取所需数据,但该方案存在严重的查询耗时问题,总耗时长达14秒,远高于Telegram官方搜索约2秒的响应速度。我已通读全部官方文档,未找到可传入聊天搜索关键词、同时返回聊天ID、标题、头像信息的接口,想确认是否存在更高效的实现方案。我的当前实现代码如下:

import BaseAction from "./BaseAction";
import airgram from "../airgram/airgram";
import { ChatsUnion, ChatUnion } from 'airgram';

class SearchChatsAction implements BaseAction
{
    async run(name: string): Promise<any>
    {
        const output = await airgram.api.searchPublicChats({
            query: name
        });

        const promises: Array<any> = [];
        const result: Array<any> = [];
        for (const chatId of (output.response as ChatsUnion).chatIds)
        {
            promises.push(
                airgram.api.getChat({
                    chatId: chatId
                }).then(output => {
                    result.push({
                        id: (output.response as ChatUnion).id,
                        title: (output.response as ChatUnion).title
                    });
                })
            );
        }

        await Promise.all(promises);
        return result;
    }
}

export default SearchChatsAction;
解决方案

耗时问题和接口缺失无关,是实现逻辑的问题,按以下顺序优化即可把响应速度压到和官方客户端一致的水平:

  • 先纠正接口选型错误:不要用searchPublicChats做全局搜索,这个接口只会搜索带公开用户名的频道/机器人,搜不到本地私人聊天、已加入的私有群,和官方全局搜索的范围不一致。做全局搜索应该用searchChats接口,传入搜索关键词、需要返回的结果数量即可,这个接口返回chatId列表的同时,TDLib会自动把对应聊天的基础信息预加载到本地数据库,不需要全量重复拉取。
  • 必须做本地聊天缓存:TDLib本身在本地持久化存储了所有已接触过的聊天详情,不需要每次搜索都调用getChat拉取数据。初始化Airgram的时候新建一个Map<number, ChatUnion>结构的缓存池,监听updateNewChat、updateChatTitle、updateChatPhoto、updateChatLastMessage这几个事件,事件触发时直接把返回的chat对象写入/更新缓存。后续搜索拿到chatId列表后,优先从缓存里取标题、头像信息,90%以上的场景根本不需要发起额外的getChat请求。
  • 控制接口请求并发数:如果遇到缓存未命中的chatId,不要一次性把所有请求丢进Promise.all,Airgram底层的TDLib默认对同时处理的请求数有限制,无节制并发会导致请求排队等待,这就是当前实现14秒耗时的核心原因。写个简单的异步并发池,把同时发起的getChat请求数控制在3-5个即可,避免触发限流。
  • 头像加载不要阻塞搜索结果返回:聊天头像是文件资源,不需要等所有头像都下载完再返回搜索结果。拿到chat对象里的头像文件ID后,优先返回标题、ID等文本信息,头像用懒加载的方式异步拉取,拉取时设置最高请求优先级,已经下载过的头像会直接返回本地路径,不会产生网络耗时。

优化后的参考实现代码:

import BaseAction from "./BaseAction";
import airgram from "../airgram/airgram";
import { ChatsUnion, ChatUnion } from 'airgram';

// 全局聊天缓存,初始化时通过update事件预填充
const chatCache = new Map<number, ChatUnion>();

// 初始化阶段注册缓存更新逻辑
airgram.on('updateNewChat', ({ response }) => {
  chatCache.set(response.chat.id, response.chat);
});
airgram.on('updateChatTitle', ({ response }) => {
  const chat = chatCache.get(response.chatId);
  if (chat) chat.title = response.title;
});
airgram.on('updateChatPhoto', ({ response }) => {
  const chat = chatCache.get(response.chatId);
  if (chat) chat.photo = response.photo;
});

// 并发控制工具,限制同时请求数为3
async function asyncPool<T, R>(concurrency: number, items: T[], fn: (item: T) => Promise<R>): Promise<R[]> {
  const results: R[] = [];
  const executing = new Set<Promise<R>>();
  for (const item of items) {
    const task = Promise.resolve().then(() => fn(item));
    results.push(task);
    executing.add(task);
    task.finally(() => executing.delete(task));
    if (executing.size >= concurrency) await Promise.race(executing);
  }
  return Promise.all(results);
}

class SearchChatsAction implements BaseAction
{
    async run(name: string): Promise<any>
    {
        // 用searchChats替代searchPublicChats,匹配全局搜索范围
        const output = await airgram.api.searchChats({
            query: name,
            limit: 20 // 按需求设置返回结果数
        });

        const chatIds = (output.response as ChatsUnion).chatIds;
        const result: Array<any> = [];
        const missChatIds: number[] = [];

        // 优先查缓存
        for (const chatId of chatIds) {
          const cacheChat = chatCache.get(chatId);
          if (cacheChat) {
            result.push({
              id: cacheChat.id,
              title: cacheChat.title,
              photo: cacheChat.photo
            });
          } else {
            missChatIds.push(chatId);
          }
        }

        // 缓存未命中的才发起请求,控制并发数为3
        if (missChatIds.length > 0) {
          const missChats = await asyncPool(3, missChatIds, async (chatId) => {
            const res = await airgram.api.getChat({ chatId });
            const chat = res.response as ChatUnion;
            chatCache.set(chatId, chat); // 写入缓存下次直接复用
            return {
              id: chat.id,
              title: chat.title,
              photo: chat.photo
            };
          });
          result.push(...missChats);
        }

        return result;
    }
}

export default SearchChatsAction;

内容的提问来源于stack exchange,提问作者pibocik125

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 17:01:03