Logo
热心市民王先生

解决方案设计

技术研究 人工智能 Telegram

步骤 1:通过 BotFather 创建 Bot 并获取 Token 步骤 2:配置 Slash Commands 步骤 3:设置 Webhook(部署后执行)

架构概述

基于前两章的分析,本方案采用 Serverless Webhook 中继 架构,实现 Telegram 与 GitHub Actions 的双向集成。

flowchart TD
    A[用户输入 /deploy] -->|Bot API| B[Telegram 服务器]
    B -->|Webhook POST| C[Cloudflare Worker<br/>或 Vercel Function]
    C -->|解析命令| D{命令验证}
    D -->|有效| E[调用 GitHub API]
    D -->|无效| F[返回错误提示]
    E -->|workflow_dispatch| G[GitHub Actions]
    G -->|执行结果| H[Actions Job]
    H -->|回调通知| C
    C -->|Bot API| I[发送结果到 Telegram]
    I --> J[用户收到反馈]

方案对比

方案 A:Cloudflare Workers(推荐)

优点

  • 全球边缘网络,延迟低(< 50ms)
  • 免费额度充足(10万次/天)
  • 原生支持环境变量和密钥管理
  • 无需维护服务器

缺点

  • 有 50ms CPU 时间限制(但 HTTP 请求不计入)
  • 需要学习 Workers 特定 API

适用场景:生产环境、高可用性要求

方案 B:Vercel Serverless Functions

优点

  • 与 Next.js 等框架集成好
  • 开发体验优秀
  • 免费 Hobby 计划足够

缺点

  • 冷启动时间较长(几百毫秒)
  • 函数执行时间限制(10s Hobby / 60s Pro)

适用场景:已有 Vercel 项目、快速原型

方案 C:Pipedream(低代码)

优点

  • 无需编写代码
  • 预置集成组件
  • 可视化工作流

缺点

  • 免费额度有限(10k/月)
  • 灵活性受限
  • vendor lock-in

适用场景:概念验证、无开发资源

方案对比表

维度Cloudflare WorkersVercel FunctionsPipedream
成本免费(10万/天)免费( generous )免费(10k/月)
延迟< 50ms100-500ms200-1000ms
维护
灵活性
学习曲线
适用规模任意中小

详细设计(以 Cloudflare Workers 为例)

1. Telegram Bot 配置

步骤 1:通过 BotFather 创建 Bot 并获取 Token

/newbot -> 按提示命名 -> 获得 Token

步骤 2:配置 Slash Commands

/setcommands
选择你的 Bot
输入命令列表:
deploy - 部署应用到生产环境
build - 触发构建工作流
status - 查看工作流状态

步骤 3:设置 Webhook(部署后执行)

curl "https://api.telegram.org/bot<TOKEN>/setWebhook" \
  -d "url=https://your-worker.your-subdomain.workers.dev/webhook" \
  -d "secret_token=YOUR_WEBHOOK_SECRET"

2. Worker 代码结构

// index.js
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    
    if (url.pathname === '/webhook' && request.method === 'POST') {
      return handleWebhook(request, env);
    }
    
    return new Response('Not Found', { status: 404 });
  }
};

async function handleWebhook(request, env) {
  // 验证 Webhook Secret
  const secretToken = request.headers.get('X-Telegram-Bot-Api-Secret-Token');
  if (secretToken !== env.WEBHOOK_SECRET) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  const update = await request.json();
  
  // 只处理消息类型的更新
  if (!update.message || !update.message.text) {
    return new Response('OK');
  }
  
  const text = update.message.text;
  const chatId = update.message.chat.id;
  
  // 解析命令
  if (!text.startsWith('/')) {
    return new Response('OK');
  }
  
  const [command, ...args] = text.slice(1).split(' ');
  
  // 路由命令
  switch (command) {
    case 'deploy':
      return handleDeploy(chatId, args, env);
    case 'build':
      return handleBuild(chatId, args, env);
    case 'status':
      return handleStatus(chatId, env);
    default:
      await sendMessage(env.BOT_TOKEN, chatId, 'Unknown command');
      return new Response('OK');
  }
}

async function handleDeploy(chatId, args, env) {
  const environment = args[0] || 'staging';
  
  try {
    // 触发 GitHub Actions
    const response = await fetch(
      `https://api.github.com/repos/${env.GITHUB_OWNER}/${env.GITHUB_REPO}/actions/workflows/deploy.yml/dispatches`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${env.GITHUB_TOKEN}`,
          'Accept': 'application/vnd.github+json',
          'X-GitHub-Api-Version': '2022-11-28'
        },
        body: JSON.stringify({
          ref: 'main',
          inputs: { environment }
        })
      }
    );
    
    if (response.ok) {
      await sendMessage(
        env.BOT_TOKEN, 
        chatId, 
        `✅ Deployment triggered for *${environment}* environment`
      );
    } else {
      const error = await response.text();
      await sendMessage(
        env.BOT_TOKEN, 
        chatId, 
        `❌ Failed to trigger deployment: ${error}`
      );
    }
  } catch (error) {
    await sendMessage(
      env.BOT_TOKEN, 
      chatId, 
      `❌ Error: ${error.message}`
    );
  }
  
  return new Response('OK');
}

async function sendMessage(token, chatId, text) {
  await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      chat_id: chatId,
      text: text,
      parse_mode: 'Markdown'
    })
  });
}

3. GitHub Actions 工作流示例

# .github/workflows/deploy.yml
name: Deploy

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deployment environment'
        required: true
        type: choice
        options:
          - staging
          - production
        default: 'staging'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy
        run: |
          echo "Deploying to ${{ github.event.inputs.environment }}"
          # 你的部署脚本
      
      - name: Notify Telegram
        if: always()
        uses: appleboy/telegram-action@master
        with:
          to: ${{ secrets.TELEGRAM_CHAT_ID }}
          token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          message: |
            Deployment ${{ job.status == 'success' && '✅ succeeded' || '❌ failed' }}
            Environment: ${{ github.event.inputs.environment }}
            Commit: ${{ github.sha }}

4. 安全加固

Webhook 验证

必须使用 secret_token 防止伪造请求:

const secretToken = request.headers.get('X-Telegram-Bot-Api-Secret-Token');
if (secretToken !== env.WEBHOOK_SECRET) {
  return new Response('Unauthorized', { status: 401 });
}

命令白名单

只允许特定用户或群组执行命令:

const ALLOWED_CHAT_IDS = env.ALLOWED_CHAT_IDS.split(',');
if (!ALLOWED_CHAT_IDS.includes(chatId.toString())) {
  await sendMessage(env.BOT_TOKEN, chatId, '⛔ Unauthorized');
  return new Response('OK');
}

GitHub Token 权限

使用 Fine-grained PAT,仅授予:

  • Actions: Read and write
  • Contents: Read(用于验证工作流文件)

扩展性设计

命令路由系统

支持多仓库、多工作流:

/deploy repo-name staging
/build repo-name branch-name
/status repo-name workflow-id

配置映射表:

const REPO_MAP = {
  'frontend': { owner: 'myorg', repo: 'frontend-app' },
  'backend': { owner: 'myorg', repo: 'backend-api' }
};

异步状态跟踪

由于 GitHub Actions 执行是异步的,可以通过以下方式跟踪状态:

  1. Actions 中使用 appleboy/telegram-action 发送完成通知
  2. Worker 中使用 Cloudflare KV 存储运行 ID,轮询检查状态

日志与监控

// 记录到 Cloudflare Analytics
ctx.waitUntil(
  fetch('https://analytics.cloudflare.com/log', {
    method: 'POST',
    body: JSON.stringify({
      command,
      chatId,
      timestamp: Date.now(),
      success: response.ok
    })
  })
);

参考资料