基于 TDLib 的生产级 Telegram 用户账号网关,通过 REST API + Webhook 方式管理多租户多账号的 Telegram 操作
这是一个 生产级 Telegram 用户账号网关。它不是 Telegram Bot API 的封装,而是通过 TDLib(Telegram 的底层 C 语言库)以用户账号身份操作 Telegram。
对外暴露标准 REST API,让上层业务系统可以通过 HTTP 请求操控 Telegram 账号,支持多租户、多账号管理。
| 层次 | 技术选型 | 说明 |
|---|---|---|
| 运行时 | Node.js 20+ / TypeScript 5.3 | 高性能异步 I/O |
| Web 框架 | NestJS 10.x | 模块化架构 + 依赖注入 |
| 数据库 | PostgreSQL 16 / SQLite (sql.js) | 生产用 PG,开发用 SQLite |
| 队列 | Redis 7 + BullMQ | 可选,通过 .env 关闭 |
| TDLib 绑定 | prebuilt-tdlib + koffi | FFI 调用原生 DLL/SO |
| 线程模型 | worker_threads | 每账号一个独立线程 |
| 认证 | JWT + Passport | Bearer Token 认证 |
| API 文档 | Swagger | 自动生成 /api/docs |
| 日志 | Winston | 结构化日志 |
| 容器化 | Docker + Docker Compose | 多阶段构建 |
系统采用 三层架构:应用层(NestJS 主线程)、Worker 层(每账号独立线程)、TDLib 层(C 原生库)。
每个请求通过 UUID @extra 标记,存入 pendingRequests Map。Worker 处理完成后通过相同的 @extra 回传,主线程匹配后 resolve/reject 对应的 Promise。超时 30 秒自动 reject。
src/modules/tdlib/tdlib-client-manager.service.ts — 整个系统的心脏
| 属性 | 类型 | 作用 |
|---|---|---|
workers | Map<string, Worker> | accountId → Worker 线程映射 |
pendingRequests | Map<string, PendingRequest> | UUID → Promise 映射(请求-响应匹配) |
accountAuthState | Map<string, string> | 追踪每个账号的授权状态 |
clientIdToAccountId | Map<number, string> | clientId → accountId 反向查找 |
核心方法:
startClient(accountId) — 创建 Worker 线程,启动 TDLib 实例stopClient(accountId) — 发送 stop 消息,清理 WorkersendRequest(accountId, request) — 发送请求并返回 Promise(30s 超时)handleTdlibResponse(msg) — 处理 Worker 回传的响应,匹配 pending 请求handleUnsolicitedUpdate(msg) — 处理非请求触发的 TG 更新(如状态变化)src/workers/tdlib-client.worker.ts — 每个 TG 账号的独立线程
关键兼容性:TDLib 1.8.64+ 使用 FLAT 格式,setTdlibParameters 参数不再包裹在 parameters 对象里。
启动流程:
事件循环逻辑:
td_json_client_receive(timeout=0.1) — 非阻塞轮询响应processResponse() — 处理 TDLib 响应,自动处理授权状态机setImmediate(mainLoop) — 让出事件循环,继续下一轮src/modules/tg-auth/tg-auth.service.ts — 处理 Telegram 完整授权流程
| API 端点 | 方法 | TDLib 调用 | 状态转换 |
|---|---|---|---|
POST /auth/phone | setPhoneNumber() | setAuthenticationPhoneNumber | → WAITING_CODE |
POST /auth/code | submitCode() | checkAuthenticationCode | → READY / WAITING_PASSWORD |
POST /auth/password | submitPassword() | checkAuthenticationPassword | → READY |
POST /auth/logout | logout() | logOut | → CREATED |
POST /auth/resend | resendCode() | resendAuthenticationCode | — |
src/modules/tg-messages/tg-messages.service.ts — 双模式消息发送
支持的操作:
| 操作 | TDLib 方法 | 说明 |
|---|---|---|
| sendMessage | sendMessage | 发送文本/媒体消息 |
| editMessage | editMessageText | 编辑已发消息 |
| forwardMessage | forwardMessages | 转发消息 |
| deleteMessage | deleteMessages | 删除消息(可选 revoke) |
| searchMessages | searchChatMessages | 搜索聊天记录 |
src/modules/tg-webhooks/tg-webhooks.service.ts — 事件推送机制
* 匹配所有事件X-Webhook-Signature header)共 16 个实体,覆盖 Telegram 操作的所有数据面。
| 实体 | 表名 | 关键字段 |
|---|---|---|
Tenant | tenants | name, maxAccounts, isActive |
User | users | email, passwordHash, tenantId |
TgAccount | tg_accounts | phoneNumber, status, tenantId, tdlibClientId |
TgProxy | tg_proxies | type, host, port, username, password |
TgChat | tg_chats | tgChatId, title, type, accountId |
TgMessage | tg_messages | tgMessageId, content, chatId, accountId |
TgSendJob | tg_send_jobs | bullJobId, status, payload, accountId |
TgWebhookSubscription | tg_webhook_subscriptions | url, events, secret, isActive |
TgWebhookDelivery | tg_webhook_deliveries | url, payload, status, attempt |
TG 账号的生命周期通过状态机管理:
| 状态 | 含义 | 触发条件 |
|---|---|---|
CREATED | 已创建,未启动 | 创建 TG 账号记录 |
WAITING_TDLIB | TDLib 初始化中 | 调用 startClient() |
WAITING_PHONE | 等待提交手机号 | TDLib 就绪 |
WAITING_CODE | 等待验证码 | 提交手机号成功 |
WAITING_PASSWORD | 等待 2FA 密码 | 提交验证码后检测到 2FA |
READY | 已授权,可用 | 授权流程完成 |
ERROR | 出错 | 任何步骤失败 |
DISABLED | 已禁用 | 手动禁用或登出 |
| 路径 | 方法 | 功能 | 认证 |
|---|---|---|---|
/api/auth/register | POST | 用户注册 | 公开 |
/api/auth/login | POST | 用户登录(返回 JWT) | 公开 |
/api/admin/health | GET | 健康检查 | 公开 |
/api/admin/metrics | GET | 应用指标 | JWT |
/api/admin/status | GET | TDLib 状态 | JWT |
/api/tenants | CRUD | 租户管理 | JWT |
/api/tg-accounts | CRUD | TG 账号管理 | JWT + 租户 |
/api/tg-accounts/:id/start | POST | 启动 TDLib 客户端 | JWT + 租户 |
/api/tg-accounts/:id/stop | POST | 停止 TDLib 客户端 | JWT + 租户 |
/api/tg-accounts/:id/auth/phone | POST | 提交手机号 | JWT + 租户 |
/api/tg-accounts/:id/auth/code | POST | 提交验证码 | JWT + 租户 |
/api/tg-accounts/:id/auth/password | POST | 提交 2FA 密码 | JWT + 租户 |
/api/tg-accounts/:id/chats | GET | 获取聊天列表 | JWT + 租户 |
/api/tg-accounts/:id/messages | POST | 发送消息 | JWT + 租户 |
/api/tg-accounts/:id/messages/search | POST | 搜索消息 | JWT + 租户 |
/api/tg-accounts/:id/webhooks | CRUD | Webhook 订阅管理 | JWT + 租户 |
/api/docs | GET | Swagger API 文档 | 公开 |
APP_GUARD 注册 JwtAuthGuard@Public() 装饰器标记x-tenant-id header + TenantGuardmaxAccounts 限制(默认 5)AuditLogInterceptor 自动记录关键操作tg_audit_logs 表定义了 60+ 错误码,覆盖所有异常场景:
GENERAL: INTERNAL_ERROR, VALIDATION_ERROR, NOT_FOUND...TENANT: TENANT_NOT_FOUND, TENANT_DISABLEDAUTH: INVALID_CREDENTIALS, TOKEN_EXPIREDTG_ACCOUNT: TG_ACCOUNT_NOT_FOUND, TG_ACCOUNT_LIMIT_REACHEDTDLib: TDLIB_CLIENT_NOT_FOUND, TDLIB_ERRORWEBHOOK: WEBHOOK_NOT_FOUND, WEBHOOK_DELIVERY_FAILEDDB_TYPE | sqlite |
REDIS_ENABLED | false |
TDLIB_PATH | 自动检测 |
| 消息发送 | 直接模式 |
无需外部依赖,开箱即用
DB_TYPE | postgres |
REDIS_ENABLED | true |
TDLIB_PATH | 指定路径 |
| 消息发送 | 队列模式 |
需要 PostgreSQL + Redis 服务
| 变量 | 说明 | 默认值 |
|---|---|---|
PORT | 服务端口 | 3000 |
DB_TYPE | 数据库类型 (postgres/sqlite) | postgres |
TG_API_ID | Telegram API ID | — |
TG_API_HASH | Telegram API Hash | — |
JWT_SECRET | JWT 签名密钥 | — |
REDIS_ENABLED | 是否启用 Redis | true |
TDLIB_PATH | TDLib 库路径 | 自动检测 |
MAX_FILE_SIZE | 最大上传文件大小 | 200MB |
{"email": "admin@example.com", "password": "***"}{"token": "eyJhbGciOiJIUzI1NiIs..."}x-tenant-id: xxx{"phoneNumber": "+8613800138000"}{"code": "12345"}{"chatId": 123456, "text": "Hello!"}{"url": "https://example.com/hook", "events": ["newMessage"]}# 安装依赖 npm install # 开发模式(热重载) npm run start:dev # 访问 Swagger 文档 http://localhost:3000/api/docs
# 编译 TypeScript npm run build # 启动生产服务 npm run start:prod # 运行数据库迁移 npm run migration:run
# 一键启动(PostgreSQL + Redis + App) docker-compose -f docker/docker-compose.yml up -d # 查看日志 docker-compose -f docker/docker-compose.yml logs -f app
# 单元测试 npm run test # 端到端测试 npm run test:e2e