架构说明
本文档说明 Bubble 机器人的架构设计、工作原理和最佳实践。
整体架构
┌─────────────────┐
│ 您的机器人 │
│ (Bot Client) │
└────────┬────────┘
│
│ ① WebSocket Gateway(实时)
│ ② Webhook(HTTP 回调)
│ ③ HTTP API(主动请求)
│
▼
┌─────────────────────┐
│ Bubble API Server │
├─────────────────────┤
│ - 认证鉴权 │
│ - 事件分发 │
│ - 消息处理 │
│ - 权限管理 │
└──────────┬──────────┘
│
▼
┌────────────────┐
│ 数据库 + Redis │
└────────────────┘
机器人生命周期
1. 创建与配置
- 开发者在平台创建机器人
- 系统生成 Bot Token 和 Bot User
- 机器人被邀请到服务器
2. 连接与认证
sequenceDiagram
Bot->>Gateway: WebSocket 连接 (带 Token)
Gateway->>Bot: OpHello (心跳间隔)
Bot->>Gateway: OpHeartbeat
Gateway->>Bot: OpHeartbeatAck
Gateway->>Bot: BOT_READY 事件
Bot->>Gateway: OpSubscribe (订阅事件)
Gateway->>Bot: EVENTS_SUBSCRIBED
3. 事件接收
机器人连接后:
- 自动订阅资源:系统自动订阅机器人加入的所有服务器频道和私聊
- 手动订阅事件:机器人需要主动订阅感兴趣的事件类型
- 接收事件:只接收已订阅事件类型的消息
4. 消息发送
sequenceDiagram
Bot->>API: POST /channels/{id}/messages
API->>DB: 保存消息
API->>Gateway: 广播 MESSAGE_CREATE
Gateway->>用户们: 推送事件
API->>Bot: 返回消息对象
核心组件
Bot User
每个机器人都有一个关联 的用户账号(Bot User):
- 拥有独立的用户 ID
- 可以加入服务器和频道
- 可以发送和接收消息
- 特殊标识:
isBot: true
Token 认证
Bot Token 是 JWT 格式的访问凭证:
- 包含机器人 ID 和权限信息
- 用于所有 API 请求的认证
- 支持刷新和重置
事件订阅
两层订阅机制:
- 资源订阅(自动):频道、私聊线程
- 事件类型订阅(手动):MESSAGE_CREATE、GUILD_MEMBER_ADD 等
权限模型
服务器级权限
机器人继承 Bot User 在服务器中的权限:
VIEW_CHANNEL- 查看频道SEND_MESSAGES- 发送消息MANAGE_MESSAGES- 管理消息
API 权限
所有 Bot API 都需要有效的 Bot Token:
Authorization: Bearer YOUR_BOT_TOKEN
权限检查流程:
- 验证 Token 有效性
- 获取 Bot User 信息
- 检查操作权限
- 执行请求
消息流转
频道消息
用户发送消息
↓
保存到数据库
↓
发布到 Redis (频道 Topic)
↓
Gateway 广播给所有订阅者
↓
机器人接收(如已订阅 MESSAGE_CREATE)
私聊消息
机器人发送 DM
↓
验证线程参与权限
↓
保存消息
↓
发布到 Redis (DM Topic + User Topics)
↓
推送给双方用户
扩展性设计
水平扩展
- API Server:无状态,可水平扩展
- Gateway:通过 Redis Pub/Sub 跨实例通信
- 数据库:读 写分离,主从复制
高可用
- 多 Gateway 实例负载均衡
- 自动重连机制
- 心跳检测
最佳实践
1. 连接管理
✅ 推荐做法:
let reconnectDelay = 1000;
const maxReconnectDelay = 60000;
function connect() {
const ws = new WebSocket(GATEWAY_URL, {
headers: { 'Authorization': `Bearer ${BOT_TOKEN}` }
});
ws.on('close', () => {
setTimeout(() => {
reconnectDelay = Math.min(reconnectDelay * 2, maxReconnectDelay);
connect();
}, reconnectDelay);
});
ws.on('open', () => {
reconnectDelay = 1000; // 重置延迟
});
}
❌ 避免:
- 不实现重连机制
- 重连过于频繁导致被限速
- 不处理心跳超时
2. 事件处理
✅ 推荐做法:
// 异步处理事件,避免阻塞
async function handleMessage(message) {
// 快速响应 WebSocket
setImmediate(async () => {
try {
await processMessage(message);
} catch (error) {
console.error('处理消息失败:', error);
}
});
}
❌ 避免:
- 同步阻塞处理大量逻辑
- 不处理异常导致程序崩溃
- 忽略事件顺序性
3. 速率限制
✅ 推荐做法:
class RateLimiter {
constructor(rps) {
this.rps = rps;
this.queue = [];
this.processing = false;
}
async execute(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const { fn, resolve, reject } = this.queue.shift();
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
}
setTimeout(() => {
this.processing = false;
this.process();
}, 1000 / this.rps);
}
}
const limiter = new RateLimiter(5); // 5 次/秒
// 使用
await limiter.execute(() => sendMessage(channelId, content));
❌ 避免:
- 不实现速率限制
- 超限后不处理重试
- 忽略 429 响应
4. 错误处理
✅ 推荐做法:
async function sendMessage(channelId, content) {
const maxRetries = 3;
let retries = 0;
while (retries < maxRetries) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// 速率限制,等待后重试
const retryAfter = response.headers.get('Retry-After') || 1;
await sleep(retryAfter * 1000);
retries++;
continue;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return await response.json();
} catch (error) {
if (retries === maxRetries - 1) throw error;
retries++;
await sleep(1000 * Math.pow(2, retries)); // 指数退避
}
}
}
❌ 避免:
- 不处理网络错误
- 无限重试导致死循环
- 不记录错误日志
5. 安全性
✅ 推荐做法:
// 环境变量存储 Token
const BOT_TOKEN = process.env.BOT_TOKEN;
// 验证消息来源
function isFromBot(message) {
return message.author?.isBot;
}
// 避免无限循环
if (!isFromBot(message)) {
await handleUserMessage(message);
}
❌ 避免:
- Token 硬编码在代码中
- 不验证消息来源
- 机器人回复自己的消息
性能优化
1. 批量操作
对于多个独立操作,使用 Promise.all:
const messages = await Promise.all(
channelIds.map(id => getChannelMessages(id))
);
2. 缓存策略
缓存不经常变化的数据:
const cache = new Map();
async function getChannel(channelId) {
if (cache.has(channelId)) {
return cache.get(channelId);
}
const channel = await fetchChannel(channelId);
cache.set(channelId, channel);
// 设置过期时间
setTimeout(() => cache.delete(channelId), 300000); // 5分钟
return channel;
}
3. 连接池
复用 HTTP 连接:
const http = require('http');
const https = require('https');
const agent = new https.Agent({
keepAlive: true,
maxSockets: 50
});
// 在 fetch 中使用
fetch(url, { agent });
监控与调试
日志记录
const logger = {
info: (msg, data) => console.log(`[INFO] ${msg}`, data),
error: (msg, error) => console.error(`[ERROR] ${msg}`, error),
debug: (msg, data) => process.env.DEBUG && console.log(`[DEBUG] ${msg}`, data)
};
// 使用
logger.info('消息已发送', { channelId, messageId });
logger.error('发送失败', error);
性能指标
const metrics = {
messagesSent: 0,
messagesReceived: 0,
errors: 0,
increment(key) {
this[key]++;
},
report() {
console.log('Metrics:', this);
}
};
// 每分钟报告一次
setInterval(() => metrics.report(), 60000);
故障排查
常见问题定位
-
连接失败
- 检查 Token 是否有效
- 验证网络连接
- 查看服务器状态
-
消息发送失败
- 检查权限
- 验证请求格式
- 查看 API 响应
-
收不到事件
- 确认已订阅对应事件类型
- 检查 BOT_READY 中的 subscribedGuilds
- 验证机器人是否在服务器中
总结
理解 Bubble 机器人的架构有助于:
- 设计更可靠的机器人应用
- 优化性能和资源使用
- 快速定位和解决问题
- 遵循最佳实践
建议在开发前仔细阅读相关文档,并在测试环境中充分验证。