Files
wmq 06112602ec feat(gateway): include production Telegram user gateway
Version the NestJS TDLib gateway with Docker deployment, migrations, automated tests and platform integration documentation while excluding all account sessions, credentials, runtime databases and backups.
2026-08-01 18:38:20 +08:00

908 lines
45 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Telegram User Gateway - 项目架构文档</title>
<style>
:root {
--bg: #0d1117;
--card: #161b22;
--border: #30363d;
--text: #c9d1d9;
--heading: #f0f6fc;
--accent: #58a6ff;
--accent2: #3fb950;
--accent3: #d2a8ff;
--accent4: #f78166;
--accent5: #79c0ff;
--code-bg: #0d1117;
--tag-bg: #1f6feb33;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans SC', Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.7;
padding: 0;
}
.hero {
background: linear-gradient(135deg, #1a1e2e 0%, #0d1117 50%, #161b22 100%);
border-bottom: 1px solid var(--border);
padding: 60px 40px;
text-align: center;
}
.hero h1 {
font-size: 2.8em;
color: var(--heading);
margin-bottom: 12px;
letter-spacing: -1px;
}
.hero h1 span { color: var(--accent); }
.hero p {
font-size: 1.15em;
color: #8b949e;
max-width: 700px;
margin: 0 auto;
}
.badge-row {
margin-top: 20px;
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
.badge {
display: inline-block;
padding: 4px 14px;
border-radius: 20px;
font-size: 0.82em;
font-weight: 600;
border: 1px solid var(--border);
background: var(--card);
color: var(--accent5);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 40px 24px;
}
.toc {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 28px 32px;
margin-bottom: 40px;
}
.toc h2 {
color: var(--heading);
font-size: 1.3em;
margin-bottom: 16px;
}
.toc-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 8px;
}
.toc a {
color: var(--accent);
text-decoration: none;
font-size: 0.95em;
padding: 4px 0;
transition: color 0.2s;
}
.toc a:hover { color: var(--accent3); text-decoration: underline; }
.section {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 32px;
margin-bottom: 28px;
}
.section h2 {
color: var(--heading);
font-size: 1.6em;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
}
.section h2 .num {
background: var(--tag-bg);
color: var(--accent);
padding: 2px 12px;
border-radius: 8px;
font-size: 0.7em;
}
.section h3 {
color: var(--accent3);
font-size: 1.15em;
margin: 24px 0 12px;
}
.section p, .section li {
color: var(--text);
margin-bottom: 8px;
}
.section ul, .section ol {
padding-left: 24px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
font-size: 0.92em;
}
th {
background: #21262d;
color: var(--accent);
text-align: left;
padding: 10px 14px;
border: 1px solid var(--border);
font-weight: 600;
}
td {
padding: 9px 14px;
border: 1px solid var(--border);
vertical-align: top;
}
tr:nth-child(even) td { background: #161b22; }
tr:nth-child(odd) td { background: #0d1117; }
code {
background: #21262d;
color: var(--accent5);
padding: 2px 7px;
border-radius: 5px;
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', monospace;
font-size: 0.88em;
}
pre {
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 20px;
overflow-x: auto;
margin: 16px 0;
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', monospace;
font-size: 0.85em;
line-height: 1.6;
color: var(--text);
}
.arch-box {
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 24px;
margin: 16px 0;
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', monospace;
font-size: 0.82em;
line-height: 1.5;
color: var(--accent2);
white-space: pre;
overflow-x: auto;
}
.highlight { color: var(--accent); font-weight: 600; }
.highlight2 { color: var(--accent3); font-weight: 600; }
.highlight3 { color: var(--accent4); font-weight: 600; }
.tag {
display: inline-block;
padding: 2px 10px;
border-radius: 6px;
font-size: 0.78em;
font-weight: 600;
margin: 2px;
}
.tag-green { background: #238636; color: #fff; }
.tag-blue { background: #1f6feb; color: #fff; }
.tag-purple { background: #8957e5; color: #fff; }
.tag-orange { background: #9e6a03; color: #fff; }
.tag-red { background: #da3633; color: #fff; }
.flow-card {
background: #21262d;
border: 1px solid var(--border);
border-radius: 8px;
padding: 16px 20px;
margin: 8px 0;
display: flex;
align-items: flex-start;
gap: 14px;
}
.flow-card .step {
background: var(--tag-bg);
color: var(--accent);
padding: 4px 10px;
border-radius: 6px;
font-weight: 700;
font-size: 0.8em;
white-space: nowrap;
min-width: 50px;
text-align: center;
}
.flow-card .content { flex: 1; }
.flow-card .content strong { color: var(--heading); }
.flow-arrow {
text-align: center;
color: var(--border);
font-size: 1.2em;
margin: 2px 0;
}
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 768px) {
.two-col { grid-template-columns: 1fr; }
.hero h1 { font-size: 1.8em; }
.container { padding: 20px 12px; }
.section { padding: 20px; }
}
.file-tree {
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 20px;
font-family: 'Cascadia Code', 'Fira Code', monospace;
font-size: 0.82em;
line-height: 1.8;
color: var(--text);
overflow-x: auto;
}
.file-tree .dir { color: var(--accent); font-weight: 600; }
.file-tree .file { color: var(--text); }
.file-tree .comment { color: #8b949e; }
.footer {
text-align: center;
padding: 40px;
color: #484f58;
font-size: 0.85em;
border-top: 1px solid var(--border);
margin-top: 40px;
}
.state-machine {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin: 16px 0;
font-size: 0.88em;
}
.state-node {
background: #21262d;
border: 2px solid var(--accent);
color: var(--accent);
padding: 6px 14px;
border-radius: 8px;
font-weight: 600;
font-family: monospace;
}
.state-arrow { color: var(--accent4); font-weight: bold; font-size: 1.2em; }
.info-box {
background: #161b22;
border-left: 4px solid var(--accent);
padding: 16px 20px;
margin: 16px 0;
border-radius: 0 8px 8px 0;
}
.info-box.warning { border-left-color: var(--accent4); }
.info-box.success { border-left-color: var(--accent2); }
</style>
</head>
<body>
<div class="hero">
<h1>Telegram User <span>Gateway</span></h1>
<p>基于 TDLib 的生产级 Telegram 用户账号网关,通过 REST API + Webhook 方式管理多租户多账号的 Telegram 操作</p>
<div class="badge-row">
<span class="badge">Node.js 20+</span>
<span class="badge">NestJS 10</span>
<span class="badge">TypeScript 5.3</span>
<span class="badge">TDLib 1.8.64+</span>
<span class="badge">PostgreSQL / SQLite</span>
<span class="badge">Redis + BullMQ</span>
<span class="badge">Docker</span>
</div>
</div>
<div class="container">
<!-- TOC -->
<div class="toc">
<h2>目录</h2>
<div class="toc-grid">
<a href="#overview">1. 项目概述</a>
<a href="#tech-stack">2. 技术栈</a>
<a href="#architecture">3. 核心架构</a>
<a href="#structure">4. 目录结构</a>
<a href="#modules">5. 核心模块</a>
<a href="#entities">6. 数据库实体</a>
<a href="#state-machine">7. 账号状态机</a>
<a href="#api">8. API 端点</a>
<a href="#security">9. 安全与多租户</a>
<a href="#config">10. 配置模式</a>
<a href="#workflow">11. 完整调用流程</a>
<a href="#deploy">12. 构建与部署</a>
</div>
</div>
<!-- 1. 项目概述 -->
<div class="section" id="overview">
<h2><span class="num">01</span> 项目概述</h2>
<p>这是一个 <strong>生产级 Telegram 用户账号网关</strong>。它不是 Telegram Bot API 的封装,而是通过 <strong>TDLib</strong>(Telegram 的底层 C 语言库)以<strong>用户账号</strong>身份操作 Telegram。</p>
<p>对外暴露标准 REST API,让上层业务系统可以通过 HTTP 请求操控 Telegram 账号,支持多租户、多账号管理。</p>
<h3>典型使用场景</h3>
<div class="two-col">
<div>
<ul>
<li>自动化消息发送与群发</li>
<li>群组/频道管理自动化</li>
<li>用户数据采集与分析</li>
</ul>
</div>
<div>
<ul>
<li>消息监控与实时推送</li>
<li>客户服务自动化</li>
<li>跨平台消息桥接</li>
</ul>
</div>
</div>
</div>
<!-- 2. 技术栈 -->
<div class="section" id="tech-stack">
<h2><span class="num">02</span> 技术栈</h2>
<table>
<thead>
<tr><th>层次</th><th>技术选型</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td>运行时</td><td><code>Node.js 20+</code> / <code>TypeScript 5.3</code></td><td>高性能异步 I/O</td></tr>
<tr><td>Web 框架</td><td><code>NestJS 10.x</code></td><td>模块化架构 + 依赖注入</td></tr>
<tr><td>数据库</td><td><code>PostgreSQL 16</code> / <code>SQLite (sql.js)</code></td><td>生产用 PG,开发用 SQLite</td></tr>
<tr><td>队列</td><td><code>Redis 7</code> + <code>BullMQ</code></td><td>可选,通过 .env 关闭</td></tr>
<tr><td>TDLib 绑定</td><td><code>prebuilt-tdlib</code> + <code>koffi</code></td><td>FFI 调用原生 DLL/SO</td></tr>
<tr><td>线程模型</td><td><code>worker_threads</code></td><td>每账号一个独立线程</td></tr>
<tr><td>认证</td><td><code>JWT</code> + <code>Passport</code></td><td>Bearer Token 认证</td></tr>
<tr><td>API 文档</td><td><code>Swagger</code></td><td>自动生成 /api/docs</td></tr>
<tr><td>日志</td><td><code>Winston</code></td><td>结构化日志</td></tr>
<tr><td>容器化</td><td><code>Docker</code> + <code>Docker Compose</code></td><td>多阶段构建</td></tr>
</tbody>
</table>
</div>
<!-- 3. 核心架构 -->
<div class="section" id="architecture">
<h2><span class="num">03</span> 核心架构</h2>
<p>系统采用 <strong>三层架构</strong>:应用层(NestJS 主线程)、Worker 层(每账号独立线程)、TDLib 层(C 原生库)。</p>
<div class="arch-box"><span class="highlight">┌─────────────────────────────────────────────────────────┐
│ 外部业务系统 │
│ (通过 REST API 调用) │
└──────────────────────┬──────────────────────────────────┘
│ HTTP
<span class="highlight2">┌──────────────────────▼──────────────────────────────────┐
│ NestJS 应用层 (主线程) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ 认证模块 │ │ 账号模块 │ │ 消息模块 │ │Webhook │ │
│ │ Auth │ │ Accounts │ │ Messages │ │ 模块 │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
│ │ │
│ TdlibClientManagerService │
│ (Worker 线程管理器) │
└────────────────┬─────────────────────────────────────────┘
│ worker_threads (postMessage)
<span class="highlight3">┌────────────────▼─────────────────────────────────────────┐
│ Worker 线程层 (每 TG 账号一个) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Worker #1 │ │ Worker #2 │ │ Worker #3 │ │
│ │ (tdjson │ │ (tdjson │ │ (tdjson │ │
│ │ FFI 调用) │ │ FFI 调用) │ │ FFI 调用) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└────────────────┬─────────────────────────────────────────┘
│
┌────────────────▼─────────────────────────────────────────┐
│ TDLib (C 原生库) │
│ tdjson.dll / libtdjson.so │
└──────────────────────────────────────────────────────────┘</span></span></div>
<h3>请求-响应匹配机制</h3>
<div class="info-box">
<p>每个请求通过 <code>UUID @extra</code> 标记,存入 <code>pendingRequests</code> Map。Worker 处理完成后通过相同的 <code>@extra</code> 回传,主线程匹配后 resolve/reject 对应的 Promise。超时 30 秒自动 reject。</p>
</div>
</div>
<!-- 4. 目录结构 -->
<div class="section" id="structure">
<h2><span class="num">04</span> 目录结构</h2>
<h3>项目根目录</h3>
<div class="file-tree">
<span class="dir">telegram-getway/</span>
├── <span class="file">.env</span> <span class="comment"># 运行时配置</span>
├── <span class="file">.env.example</span> <span class="comment"># 配置模板</span>
├── <span class="file">package.json</span> <span class="comment"># 依赖与脚本</span>
├── <span class="file">tsconfig.json</span> <span class="comment"># TypeScript 配置</span>
├── <span class="file">nest-cli.json</span> <span class="comment"># NestJS CLI 配置</span>
├── <span class="dir">src/</span> <span class="comment"># 源代码</span>
├── <span class="dir">data/</span> <span class="comment"># SQLite 数据库</span>
├── <span class="dir">tdlib_data/</span> <span class="comment"># TDLib 数据(每账号子目录)</span>
├── <span class="dir">uploads/</span> <span class="comment"># 文件上传存储</span>
├── <span class="dir">docker/</span> <span class="comment"># Docker 配置</span>
│ ├── <span class="file">Dockerfile</span> <span class="comment"># 多阶段构建</span>
│ └── <span class="file">docker-compose.yml</span> <span class="comment"># PG + Redis + App</span>
└── <span class="dir">test/</span> <span class="comment"># 测试文件</span>
</div>
<h3>源代码目录 (src/)</h3>
<div class="file-tree">
<span class="dir">src/</span>
├── <span class="file">main.ts</span> <span class="comment"># 应用入口</span>
├── <span class="file">app.module.ts</span> <span class="comment"># 根模块(注册 15 个子模块)</span>
├── <span class="dir">config/</span> <span class="comment"># 配置模块</span>
│ ├── <span class="file">app.config.ts</span> <span class="comment"># 应用配置</span>
│ ├── <span class="file">database.config.ts</span> <span class="comment"># 数据库配置(PG/SQLite)</span>
│ ├── <span class="file">redis.config.ts</span> <span class="comment"># Redis 配置</span>
│ └── <span class="file">rate-limit.config.ts</span> <span class="comment"># 速率限制配置</span>
├── <span class="dir">common/</span> <span class="comment"># 通用组件</span>
│ ├── <span class="dir">decorators/</span> <span class="comment"># 装饰器(@CurrentUser, @Tenant)</span>
│ ├── <span class="dir">errors/</span> <span class="comment"># 异常类 + 60+ 错误码</span>
│ ├── <span class="dir">filters/</span> <span class="comment"># 全局异常过滤器</span>
│ ├── <span class="dir">guards/</span> <span class="comment"># JWT 守卫 + 租户守卫</span>
│ ├── <span class="dir">interceptors/</span> <span class="comment"># 响应格式 + 审计日志</span>
│ └── <span class="dir">pipes/</span> <span class="comment"># 参数验证管道</span>
├── <span class="dir">database/</span> <span class="comment"># 数据库层</span>
│ ├── <span class="dir">entities/</span> <span class="comment"># 16 个 TypeORM 实体</span>
│ └── <span class="dir">migrations/</span> <span class="comment"># 数据库迁移</span>
├── <span class="dir">modules/</span> <span class="comment"># 15 个业务模块</span>
│ ├── <span class="dir">auth/</span> <span class="comment"># 认证(注册/登录/JWT)</span>
│ ├── <span class="dir">tenants/</span> <span class="comment"># 租户管理</span>
│ ├── <span class="dir">tg-accounts/</span> <span class="comment"># TG 账号 CRUD</span>
│ ├── <span class="dir">tg-auth/</span> <span class="comment"># TG 授权流程</span>
│ ├── <span class="dir">tdlib/</span> <span class="comment"># TDLib 核心管理器</span>
│ ├── <span class="dir">tg-proxies/</span> <span class="comment"># 代理管理</span>
│ ├── <span class="dir">tg-chats/</span> <span class="comment"># 聊天模块</span>
│ ├── <span class="dir">tg-dialogs/</span> <span class="comment"># 对话模块</span>
│ ├── <span class="dir">tg-contacts/</span> <span class="comment"># 联系人模块</span>
│ ├── <span class="dir">tg-messages/</span> <span class="comment"># 消息发送/编辑/搜索</span>
│ ├── <span class="dir">tg-groups/</span> <span class="comment"># 群组管理</span>
│ ├── <span class="dir">tg-channels/</span> <span class="comment"># 频道管理</span>
│ ├── <span class="dir">tg-files/</span> <span class="comment"># 文件上传/下载</span>
│ ├── <span class="dir">tg-webhooks/</span> <span class="comment"># Webhook 订阅与投递</span>
│ ├── <span class="dir">tg-rate-limit/</span> <span class="comment"># 速率限制</span>
│ ├── <span class="dir">tg-audit/</span> <span class="comment"># 审计日志</span>
│ └── <span class="dir">admin/</span> <span class="comment"># 管理(健康检查/指标)</span>
└── <span class="dir">workers/</span> <span class="comment"># Worker 线程</span>
├── <span class="file">tdlib-client.worker.ts</span> <span class="comment"># TDLib 客户端(核心)</span>
├── <span class="file">send-message.worker.ts</span> <span class="comment"># 消息发送 BullMQ</span>
├── <span class="file">file-download.worker.ts</span><span class="comment"># 文件下载</span>
└── <span class="file">webhook-delivery.worker.ts</span> <span class="comment"># Webhook 投递</span>
</div>
</div>
<!-- 5. 核心模块 -->
<div class="section" id="modules">
<h2><span class="num">05</span> 核心模块详解</h2>
<h3>5.1 TDLib 客户端管理器</h3>
<p><code>src/modules/tdlib/tdlib-client-manager.service.ts</code> — 整个系统的<strong>心脏</strong></p>
<table>
<thead><tr><th>属性</th><th>类型</th><th>作用</th></tr></thead>
<tbody>
<tr><td><code>workers</code></td><td><code>Map&lt;string, Worker&gt;</code></td><td>accountId → Worker 线程映射</td></tr>
<tr><td><code>pendingRequests</code></td><td><code>Map&lt;string, PendingRequest&gt;</code></td><td>UUID → Promise 映射(请求-响应匹配)</td></tr>
<tr><td><code>accountAuthState</code></td><td><code>Map&lt;string, string&gt;</code></td><td>追踪每个账号的授权状态</td></tr>
<tr><td><code>clientIdToAccountId</code></td><td><code>Map&lt;number, string&gt;</code></td><td>clientId → accountId 反向查找</td></tr>
</tbody>
</table>
<p><strong>核心方法:</strong></p>
<ul>
<li><code>startClient(accountId)</code> — 创建 Worker 线程,启动 TDLib 实例</li>
<li><code>stopClient(accountId)</code> — 发送 stop 消息,清理 Worker</li>
<li><code>sendRequest(accountId, request)</code> — 发送请求并返回 Promise(30s 超时)</li>
<li><code>handleTdlibResponse(msg)</code> — 处理 Worker 回传的响应,匹配 pending 请求</li>
<li><code>handleUnsolicitedUpdate(msg)</code> — 处理非请求触发的 TG 更新(如状态变化)</li>
</ul>
<h3>5.2 TDLib Worker</h3>
<p><code>src/workers/tdlib-client.worker.ts</code> — 每个 TG 账号的独立线程</p>
<div class="info-box warning">
<p><strong>关键兼容性</strong>:TDLib 1.8.64+ 使用 <strong>FLAT 格式</strong>,<code>setTdlibParameters</code> 参数不再包裹在 <code>parameters</code> 对象里。</p>
</div>
<p><strong>启动流程:</strong></p>
<div class="flow-card">
<span class="step">1</span>
<div class="content"><strong>loadTdlib()</strong> — 通过 koffi 加载原生 tdjson DLL/SO(支持自动路径检测)</div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">2</span>
<div class="content"><strong>td_json_client_create()</strong> — 创建 TDLib 客户端实例</div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">3</span>
<div class="content"><strong>配置代理</strong> — 支持 SOCKS5 / HTTP / MTProto 代理</div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">4</span>
<div class="content"><strong>mainLoop()</strong> — setImmediate 递归事件循环,非阻塞轮询 TDLib</div>
</div>
<p><strong>事件循环逻辑:</strong></p>
<ol>
<li>处理主线程消息队列(发送请求到 TDLib)</li>
<li><code>td_json_client_receive(timeout=0.1)</code> — 非阻塞轮询响应</li>
<li><code>processResponse()</code> — 处理 TDLib 响应,自动处理授权状态机</li>
<li><code>setImmediate(mainLoop)</code> — 让出事件循环,继续下一轮</li>
</ol>
<h3>5.3 账号授权模块</h3>
<p><code>src/modules/tg-auth/tg-auth.service.ts</code> — 处理 Telegram 完整授权流程</p>
<table>
<thead><tr><th>API 端点</th><th>方法</th><th>TDLib 调用</th><th>状态转换</th></tr></thead>
<tbody>
<tr><td><code>POST /auth/phone</code></td><td>setPhoneNumber()</td><td>setAuthenticationPhoneNumber</td><td>→ WAITING_CODE</td></tr>
<tr><td><code>POST /auth/code</code></td><td>submitCode()</td><td>checkAuthenticationCode</td><td>→ READY / WAITING_PASSWORD</td></tr>
<tr><td><code>POST /auth/password</code></td><td>submitPassword()</td><td>checkAuthenticationPassword</td><td>→ READY</td></tr>
<tr><td><code>POST /auth/logout</code></td><td>logout()</td><td>logOut</td><td>→ CREATED</td></tr>
<tr><td><code>POST /auth/resend</code></td><td>resendCode()</td><td>resendAuthenticationCode</td><td>—</td></tr>
</tbody>
</table>
<h3>5.4 消息模块</h3>
<p><code>src/modules/tg-messages/tg-messages.service.ts</code> — 双模式消息发送</p>
<div class="two-col">
<div class="section" style="margin-bottom:0">
<h3 style="margin-top:0">队列模式(Redis 启用)</h3>
<ul>
<li>BullMQ 异步发送</li>
<li>3 次指数退避重试</li>
<li>TgSendJob 持久化追踪</li>
<li>失败回退到直接模式</li>
</ul>
</div>
<div class="section" style="margin-bottom:0">
<h3 style="margin-top:0">直接模式(Redis 禁用)</h3>
<ul>
<li>同步调用 TDLib</li>
<li>无重试机制</li>
<li>简单直接</li>
<li>适合开发/轻量场景</li>
</ul>
</div>
</div>
<p><strong>支持的操作:</strong></p>
<table>
<thead><tr><th>操作</th><th>TDLib 方法</th><th>说明</th></tr></thead>
<tbody>
<tr><td>sendMessage</td><td>sendMessage</td><td>发送文本/媒体消息</td></tr>
<tr><td>editMessage</td><td>editMessageText</td><td>编辑已发消息</td></tr>
<tr><td>forwardMessage</td><td>forwardMessages</td><td>转发消息</td></tr>
<tr><td>deleteMessage</td><td>deleteMessages</td><td>删除消息(可选 revoke)</td></tr>
<tr><td>searchMessages</td><td>searchChatMessages</td><td>搜索聊天记录</td></tr>
</tbody>
</table>
<h3>5.5 Webhook 模块</h3>
<p><code>src/modules/tg-webhooks/tg-webhooks.service.ts</code> — 事件推送机制</p>
<ul>
<li>为每个 TG 账号创建 Webhook 订阅,指定 URL 和事件类型</li>
<li>支持通配符 <code>*</code> 匹配所有事件</li>
<li><strong>HMAC-SHA256</strong> 签名验证(<code>X-Webhook-Signature</code> header)</li>
<li>指数退避重试(默认最多 8 次)</li>
<li>投递记录持久化到数据库</li>
</ul>
</div>
<!-- 6. 数据库实体 -->
<div class="section" id="entities">
<h2><span class="num">06</span> 数据库实体关系</h2>
<p>共 <strong>16 个实体</strong>,覆盖 Telegram 操作的所有数据面。</p>
<div class="arch-box"><span class="highlight">Tenant (租户)</span>
├── 1:N → <span class="highlight2">User</span> (系统用户: email + password)
└── 1:N → <span class="highlight2">TgAccount</span> (TG账号)
├── 1:N → <span class="highlight3">TgAccountProxyBinding</span> → <span class="highlight3">TgProxy</span> (代理绑定)
├── 1:N → <span class="highlight3">TgChat</span> (聊天)
├── 1:N → <span class="highlight3">TgMessage</span> (消息)
├── 1:N → <span class="highlight3">TgContact</span> (联系人)
├── 1:N → <span class="highlight3">TgGroup</span> (群组)
├── 1:N → <span class="highlight3">TgSendJob</span> (发送任务)
├── 1:N → <span class="highlight3">TgFile</span> (文件)
├── 1:N → <span class="highlight3">TgWebhookSubscription</span> (Webhook订阅)
│ └── 1:N → <span class="highlight3">TgWebhookDelivery</span> (投递记录)
└── 1:N → <span class="highlight3">TgAuditLog</span> (审计日志)
独立实体: <span class="highlight">TgRawUpdate</span> (原始更新), <span class="highlight">TgRateLimitEvent</span> (速率事件)</div>
<table>
<thead><tr><th>实体</th><th>表名</th><th>关键字段</th></tr></thead>
<tbody>
<tr><td><code>Tenant</code></td><td>tenants</td><td>name, maxAccounts, isActive</td></tr>
<tr><td><code>User</code></td><td>users</td><td>email, passwordHash, tenantId</td></tr>
<tr><td><code>TgAccount</code></td><td>tg_accounts</td><td>phoneNumber, status, tenantId, tdlibClientId</td></tr>
<tr><td><code>TgProxy</code></td><td>tg_proxies</td><td>type, host, port, username, password</td></tr>
<tr><td><code>TgChat</code></td><td>tg_chats</td><td>tgChatId, title, type, accountId</td></tr>
<tr><td><code>TgMessage</code></td><td>tg_messages</td><td>tgMessageId, content, chatId, accountId</td></tr>
<tr><td><code>TgSendJob</code></td><td>tg_send_jobs</td><td>bullJobId, status, payload, accountId</td></tr>
<tr><td><code>TgWebhookSubscription</code></td><td>tg_webhook_subscriptions</td><td>url, events, secret, isActive</td></tr>
<tr><td><code>TgWebhookDelivery</code></td><td>tg_webhook_deliveries</td><td>url, payload, status, attempt</td></tr>
</tbody>
</table>
</div>
<!-- 7. 状态机 -->
<div class="section" id="state-machine">
<h2><span class="num">07</span> 账号状态机</h2>
<p>TG 账号的生命周期通过状态机管理:</p>
<div class="state-machine">
<span class="state-node">CREATED</span>
<span class="state-arrow">→</span>
<span class="state-node">WAITING_TDLIB</span>
<span class="state-arrow">→</span>
<span class="state-node">WAITING_PHONE</span>
<span class="state-arrow">→</span>
<span class="state-node">WAITING_CODE</span>
<span class="state-arrow">→</span>
<span class="state-node">WAITING_PASSWORD</span>
<span class="state-arrow">→</span>
<span class="state-node" style="border-color: var(--accent2); color: var(--accent2);">READY</span>
</div>
<div style="text-align:center; margin-top:8px;">
<span class="state-arrow">↓ 异常</span>
</div>
<div class="state-machine" style="justify-content:center;">
<span class="state-node" style="border-color: var(--accent4); color: var(--accent4);">ERROR</span>
<span style="margin: 0 20px; color: #8b949e;">/</span>
<span class="state-node" style="border-color: var(--accent4); color: var(--accent4);">DISABLED</span>
</div>
<table>
<thead><tr><th>状态</th><th>含义</th><th>触发条件</th></tr></thead>
<tbody>
<tr><td><code>CREATED</code></td><td>已创建,未启动</td><td>创建 TG 账号记录</td></tr>
<tr><td><code>WAITING_TDLIB</code></td><td>TDLib 初始化中</td><td>调用 startClient()</td></tr>
<tr><td><code>WAITING_PHONE</code></td><td>等待提交手机号</td><td>TDLib 就绪</td></tr>
<tr><td><code>WAITING_CODE</code></td><td>等待验证码</td><td>提交手机号成功</td></tr>
<tr><td><code>WAITING_PASSWORD</code></td><td>等待 2FA 密码</td><td>提交验证码后检测到 2FA</td></tr>
<tr><td><code>READY</code></td><td>已授权,可用</td><td>授权流程完成</td></tr>
<tr><td><code>ERROR</code></td><td>出错</td><td>任何步骤失败</td></tr>
<tr><td><code>DISABLED</code></td><td>已禁用</td><td>手动禁用或登出</td></tr>
</tbody>
</table>
</div>
<!-- 8. API 端点 -->
<div class="section" id="api">
<h2><span class="num">08</span> API 端点总览</h2>
<table>
<thead><tr><th>路径</th><th>方法</th><th>功能</th><th>认证</th></tr></thead>
<tbody>
<tr><td><code>/api/auth/register</code></td><td>POST</td><td>用户注册</td><td><span class="tag tag-green">公开</span></td></tr>
<tr><td><code>/api/auth/login</code></td><td>POST</td><td>用户登录(返回 JWT)</td><td><span class="tag tag-green">公开</span></td></tr>
<tr><td><code>/api/admin/health</code></td><td>GET</td><td>健康检查</td><td><span class="tag tag-green">公开</span></td></tr>
<tr><td><code>/api/admin/metrics</code></td><td>GET</td><td>应用指标</td><td><span class="tag tag-blue">JWT</span></td></tr>
<tr><td><code>/api/admin/status</code></td><td>GET</td><td>TDLib 状态</td><td><span class="tag tag-blue">JWT</span></td></tr>
<tr><td><code>/api/tenants</code></td><td>CRUD</td><td>租户管理</td><td><span class="tag tag-blue">JWT</span></td></tr>
<tr><td><code>/api/tg-accounts</code></td><td>CRUD</td><td>TG 账号管理</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/tg-accounts/:id/start</code></td><td>POST</td><td>启动 TDLib 客户端</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/tg-accounts/:id/stop</code></td><td>POST</td><td>停止 TDLib 客户端</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/tg-accounts/:id/auth/phone</code></td><td>POST</td><td>提交手机号</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/tg-accounts/:id/auth/code</code></td><td>POST</td><td>提交验证码</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/tg-accounts/:id/auth/password</code></td><td>POST</td><td>提交 2FA 密码</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/tg-accounts/:id/chats</code></td><td>GET</td><td>获取聊天列表</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/tg-accounts/:id/messages</code></td><td>POST</td><td>发送消息</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/tg-accounts/:id/messages/search</code></td><td>POST</td><td>搜索消息</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/tg-accounts/:id/webhooks</code></td><td>CRUD</td><td>Webhook 订阅管理</td><td><span class="tag tag-purple">JWT + 租户</span></td></tr>
<tr><td><code>/api/docs</code></td><td>GET</td><td>Swagger API 文档</td><td><span class="tag tag-green">公开</span></td></tr>
</tbody>
</table>
</div>
<!-- 9. 安全 -->
<div class="section" id="security">
<h2><span class="num">09</span> 安全与多租户</h2>
<div class="two-col">
<div>
<h3>认证机制</h3>
<ul>
<li><strong>JWT 全局守卫</strong>:<code>APP_GUARD</code> 注册 <code>JwtAuthGuard</code></li>
<li>所有路由默认需要认证</li>
<li>公开路由通过 <code>@Public()</code> 装饰器标记</li>
<li>密码使用 <strong>bcrypt</strong> 哈希存储</li>
</ul>
</div>
<div>
<h3>租户隔离</h3>
<ul>
<li><code>x-tenant-id</code> header + <code>TenantGuard</code></li>
<li>每个请求都绑定到特定租户</li>
<li>租户有 <code>maxAccounts</code> 限制(默认 5)</li>
<li>数据完全隔离,不可跨租户访问</li>
</ul>
</div>
</div>
<h3>审计日志</h3>
<ul>
<li><code>AuditLogInterceptor</code> 自动记录关键操作</li>
<li>记录操作人、操作类型、请求参数、响应结果</li>
<li>持久化到 <code>tg_audit_logs</code> 表</li>
</ul>
<h3>错误码体系</h3>
<p>定义了 <strong>60+ 错误码</strong>,覆盖所有异常场景:</p>
<div class="two-col">
<div>
<ul>
<li><code>GENERAL</code>: INTERNAL_ERROR, VALIDATION_ERROR, NOT_FOUND...</li>
<li><code>TENANT</code>: TENANT_NOT_FOUND, TENANT_DISABLED</li>
<li><code>AUTH</code>: INVALID_CREDENTIALS, TOKEN_EXPIRED</li>
</ul>
</div>
<div>
<ul>
<li><code>TG_ACCOUNT</code>: TG_ACCOUNT_NOT_FOUND, TG_ACCOUNT_LIMIT_REACHED</li>
<li><code>TDLib</code>: TDLIB_CLIENT_NOT_FOUND, TDLIB_ERROR</li>
<li><code>WEBHOOK</code>: WEBHOOK_NOT_FOUND, WEBHOOK_DELIVERY_FAILED</li>
</ul>
</div>
</div>
</div>
<!-- 10. 配置 -->
<div class="section" id="config">
<h2><span class="num">10</span> 配置模式</h2>
<div class="two-col">
<div class="section" style="margin-bottom:0">
<h3 style="margin-top:0">开发模式(当前)</h3>
<table>
<tbody>
<tr><td><code>DB_TYPE</code></td><td>sqlite</td></tr>
<tr><td><code>REDIS_ENABLED</code></td><td>false</td></tr>
<tr><td><code>TDLIB_PATH</code></td><td>自动检测</td></tr>
<tr><td>消息发送</td><td>直接模式</td></tr>
</tbody>
</table>
<div class="info-box success">
<p>无需外部依赖,开箱即用</p>
</div>
</div>
<div class="section" style="margin-bottom:0">
<h3 style="margin-top:0">生产模式</h3>
<table>
<tbody>
<tr><td><code>DB_TYPE</code></td><td>postgres</td></tr>
<tr><td><code>REDIS_ENABLED</code></td><td>true</td></tr>
<tr><td><code>TDLIB_PATH</code></td><td>指定路径</td></tr>
<tr><td>消息发送</td><td>队列模式</td></tr>
</tbody>
</table>
<div class="info-box warning">
<p>需要 PostgreSQL + Redis 服务</p>
</div>
</div>
</div>
<h3>关键环境变量</h3>
<table>
<thead><tr><th>变量</th><th>说明</th><th>默认值</th></tr></thead>
<tbody>
<tr><td><code>PORT</code></td><td>服务端口</td><td>3000</td></tr>
<tr><td><code>DB_TYPE</code></td><td>数据库类型 (postgres/sqlite)</td><td>postgres</td></tr>
<tr><td><code>TG_API_ID</code></td><td>Telegram API ID</td><td>—</td></tr>
<tr><td><code>TG_API_HASH</code></td><td>Telegram API Hash</td><td>—</td></tr>
<tr><td><code>JWT_SECRET</code></td><td>JWT 签名密钥</td><td>—</td></tr>
<tr><td><code>REDIS_ENABLED</code></td><td>是否启用 Redis</td><td>true</td></tr>
<tr><td><code>TDLIB_PATH</code></td><td>TDLib 库路径</td><td>自动检测</td></tr>
<tr><td><code>MAX_FILE_SIZE</code></td><td>最大上传文件大小</td><td>200MB</td></tr>
</tbody>
</table>
</div>
<!-- 11. 完整流程 -->
<div class="section" id="workflow">
<h2><span class="num">11</span> 完整调用流程示例</h2>
<div class="flow-card">
<span class="step">1</span>
<div class="content"><strong>POST /api/auth/register</strong> — 注册系统用户<br><code>{"email": "admin@example.com", "password": "***"}</code></div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">2</span>
<div class="content"><strong>POST /api/auth/login</strong> — 登录获取 JWT Token<br><code>{"token": "eyJhbGciOiJIUzI1NiIs..."}</code></div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">3</span>
<div class="content"><strong>POST /api/tenants</strong> — 创建租户<br>Header: <code>x-tenant-id: xxx</code></div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">4</span>
<div class="content"><strong>POST /api/tg-accounts</strong> — 创建 TG 账号(自动启动 TDLib Worker)</div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">5</span>
<div class="content"><strong>POST /api/tg-accounts/:id/auth/phone</strong> — 提交手机号<br><code>{"phoneNumber": "+8613800138000"}</code></div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">6</span>
<div class="content"><strong>POST /api/tg-accounts/:id/auth/code</strong> — 提交验证码<br><code>{"code": "12345"}</code></div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">7</span>
<div class="content"><strong>POST /api/tg-accounts/:id/messages</strong> — 发送消息<br><code>{"chatId": 123456, "text": "Hello!"}</code></div>
</div>
<div class="flow-arrow">↓</div>
<div class="flow-card">
<span class="step">8</span>
<div class="content"><strong>POST /api/tg-accounts/:id/webhooks</strong> — 创建 Webhook 订阅<br><code>{"url": "https://example.com/hook", "events": ["newMessage"]}</code></div>
</div>
</div>
<!-- 12. 构建部署 -->
<div class="section" id="deploy">
<h2><span class="num">12</span> 构建与部署</h2>
<h3>本地开发</h3>
<pre><span class="highlight"># 安装依赖</span>
npm install
<span class="highlight"># 开发模式(热重载)</span>
npm run start:dev
<span class="highlight"># 访问 Swagger 文档</span>
http://localhost:3000/api/docs</pre>
<h3>生产构建</h3>
<pre><span class="highlight"># 编译 TypeScript</span>
npm run build
<span class="highlight"># 启动生产服务</span>
npm run start:prod
<span class="highlight"># 运行数据库迁移</span>
npm run migration:run</pre>
<h3>Docker 部署</h3>
<pre><span class="highlight"># 一键启动(PostgreSQL + Redis + App)</span>
docker-compose -f docker/docker-compose.yml up -d
<span class="highlight"># 查看日志</span>
docker-compose -f docker/docker-compose.yml logs -f app</pre>
<h3>测试</h3>
<pre><span class="highlight"># 单元测试</span>
npm run test
<span class="highlight"># 端到端测试</span>
npm run test:e2e</pre>
</div>
</div>
<div class="footer">
<p>Telegram User Gateway v1.0.0 &nbsp;|&nbsp; 生成于 2026-06-24 &nbsp;|&nbsp; 基于源码自动分析</p>
</div>
</body>
</html>