Skip to content

PCL-Community/Cod.Bot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GitHub Webhook Bot

一个功能强大、可扩展的 GitHub Webhook 处理机器人,支持自定义处理器、队列管理和资源锁定。

功能特性

  • 🚀 高性能: 基于 Express.js 和 TypeScript 构建
  • 🔧 可扩展: 支持自定义处理器,通过配置文件或代码注册
  • 📦 队列系统: FIFO 队列确保事件按顺序处理
  • 🔒 资源锁定: 防止并发冲突,确保数据一致性
  • 📝 完整日志: 支持多种日志格式和级别
  • 🔍 自动发现: 自动扫描和加载处理器
  • ⚙️ 灵活配置: 支持 YAML/JSON 配置文件和环境变量

快速开始

安装

# 克隆仓库
git clone <repository-url>
cd cod.bot

# 安装依赖
pnpm install

# 复制配置文件
cp .env.example .env
cp config/config.example.yaml config/config.yaml

配置

1. 环境变量 (.env)

# 必需配置
WEBHOOK_SECRET=your-webhook-secret-here

# 可选配置
WEBHOOK_PORT=3000
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
LOG_LEVEL=info

2. 配置文件 (config/config.yaml)

server:
  port: 3000
  host: "0.0.0.0"
  webhookPath: "/webhook"

github:
  webhookSecret: "your-webhook-secret-here"
  token: "ghp_xxxxxxxxxxxx"

handlers:
  - id: "my-handler"
    name: "My Handler"
    enabled: true
    script: "./handlers/my-handler.js"
    events:
      - event: "issues"
        actions: ["opened", "closed"]

queue:
  maxSize: 100
  lockTimeout: 30000
  overflowStrategy: "block"

logging:
  level: "info"
  format: "pretty"

运行

# 开发模式 (热重载)
pnpm dev

# 生产模式
pnpm build
pnpm start

# 监视模式 (仅编译)
pnpm watch

设置 GitHub Webhook

  1. 在 GitHub 仓库/组织中,进入 Settings > Webhooks
  2. 点击 Add webhook
  3. 配置以下选项:
    • Payload URL: http://your-server:3000/webhook
    • Content type: application/json
    • Secret: 与 WEBHOOK_SECRET 相同的值
    • Events: 选择需要的事件类型
  4. 点击 Add webhook

创建自定义处理器

方式一: 使用 HandleEntry 注册 (推荐)

// handlers/issue-handler.ts
import { HandleEntry } from '../src/handlers/handle-entry';
import type { EventHandler, HandlerContext } from '../src/types/handler';

class IssueHandler implements EventHandler {
  name = 'issue-handler';

  async handle(context: HandlerContext): Promise<void> {
    const { payload, event, action } = context;
    
    console.log(`处理 ${event}.${action} 事件`);
    console.log(`Issue 标题: ${payload.issue.title}`);
    
    // 你的业务逻辑
    if (action === 'opened') {
      // 新 Issue 被创建
      await this.handleNewIssue(context);
    }
  }

  private async handleNewIssue(context: HandlerContext): Promise<void> {
    // 实现你的逻辑
  }
}

// 注册处理器
HandleEntry.register(new IssueHandler());

// 或者使用选项注册
HandleEntry.registerWithOptions(
  new IssueHandler(),
  'issues',  // 事件类型
  {
    priority: 10,
    enabled: true,
    actions: ['opened', 'closed'],
    repositories: ['owner/repo'],
    useLock: true,
    lockKey: '{owner}/{repo}/issues/{issue_number}',
    sequential: true,
  }
);

方式二: 使用装饰器

// handlers/pr-handler.ts
import { handler, on } from '../src/handlers/handle-entry';
import type { EventHandler, HandlerContext } from '../src/types/handler';

@handler({
  events: ['pull_request'],
  actions: ['opened', 'synchronize'],
  priority: 5,
  useLock: true,
})
class PullRequestHandler implements EventHandler {
  name = 'pr-handler';

  async handle(context: HandlerContext): Promise<void> {
    const { payload, action } = context;
    
    if (action === 'opened') {
      console.log(`PR 创建: ${payload.pull_request.title}`);
    }
  }
}

方式三: 配置文件注册

# config/config.yaml
handlers:
  - id: "push-handler"
    name: "Push Handler"
    enabled: true
    script: "./handlers/push-handler.js"
    events:
      - event: "push"
        branch: ["main", "develop"]
    options:
      notifyOnCommit: true
// handlers/push-handler.js
module.exports = {
  name: 'push-handler',
  
  async handle(context) {
    const { payload } = context;
    const branch = payload.ref.replace('refs/heads/', '');
    
    console.log(`代码推送到 ${branch}`);
    console.log(`提交者: ${payload.pusher.name}`);
    console.log(`提交数: ${payload.commits.length}`);
  }
};

HandleEntry API

注册处理器

// 基础注册
HandleEntry.register(handler);

// 带选项注册
HandleEntry.registerWithOptions(handler, event, options);

// 装饰器方式
@handler(options)
class MyHandler {}

处理器选项

选项 类型 说明
priority number 优先级,数值越大越先执行
enabled boolean 是否启用
events string[] 监听的事件类型
actions string[] 监听的动作类型
repositories string[] 限制的仓库列表
useLock boolean 是否使用资源锁
lockKey string 锁的键名,支持占位符
sequential boolean 是否顺序执行
queueName string 队列名称
metadata object 自定义元数据

锁键占位符

lockKey 中可以使用以下占位符:

  • {owner} - 仓库所有者
  • {repo} - 仓库名称
  • {issue_number} - Issue 编号
  • {pr_number} - PR 编号
  • {branch} - 分支名称
  • {sha} - 提交 SHA

示例:

lockKey: '{owner}/{repo}/issues/{issue_number}'
// 生成: myorg/myrepo/issues/123

队列和锁定系统

队列系统

队列系统确保同一资源的操作按顺序执行:

queue:
  # 队列最大长度
  maxSize: 100
  # 溢出策略
  overflowStrategy: "block"  # drop-new, drop-old, block, error
  # 并发设置
  concurrent: true
  maxConcurrent: 10
  # 超时设置
  jobTimeout: 60000

资源锁定

防止并发操作同一资源:

// 在处理器中使用锁
async handle(context: HandlerContext): Promise<void> {
  const { lock } = context;
  
  if (lock) {
    console.log(`已获取锁: ${lock.resourceKey}`);
    
    // 执行需要锁保护的操作
    await this.updateResource(context);
    
    // 锁会在处理完成后自动释放
    // 也可以手动延长锁时间
    await lock.extend(30000);  // 延长 30 秒
  }
}

溢出策略

策略 说明
drop-new 丢弃新任务,返回错误
drop-old 丢弃最旧的任务,处理新任务
block 阻塞等待队列空间
error 立即返回错误

HandlerContext 对象

处理器接收的上下文对象包含以下信息:

interface HandlerContext {
  // 原始事件数据
  payload: any;
  
  // 事件类型 (如: "issues", "pull_request")
  event: string;
  
  // 动作类型 (如: "opened", "closed")
  action: string;
  
  // 仓库信息
  repository: {
    owner: string;
    name: string;
    fullName: string;
  };
  
  // 触发者信息
  sender: {
    login: string;
    id: number;
  };
  
  // 请求头
  headers: Record<string, string>;
  
  // 处理器配置选项
  options: Record<string, any>;
  
  // GitHub API 客户端 (需要配置 token)
  octokit?: any;
  
  // 资源锁对象
  lock?: ResourceLock;
}

部署指南

使用 Docker 部署

FROM node:20-alpine

WORKDIR /app

COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --prod

COPY dist/ ./dist/
COPY config/ ./config/

EXPOSE 3000

CMD ["node", "dist/index.js"]
# 构建镜像
docker build -t github-webhook-bot .

# 运行容器
docker run -d \
  -p 3000:3000 \
  -e WEBHOOK_SECRET=your-secret \
  -e GITHUB_TOKEN=ghp_xxx \
  -v $(pwd)/config:/app/config \
  github-webhook-bot

使用 PM2 部署

# 安装 PM2
npm install -g pm2

# 创建 ecosystem.config.js
module.exports = {
  apps: [{
    name: 'github-webhook-bot',
    script: './dist/index.js',
    instances: 1,
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env: {
      NODE_ENV: 'production',
      WEBHOOK_SECRET: 'your-secret',
      GITHUB_TOKEN: 'ghp_xxx'
    },
    log_file: './logs/combined.log',
    out_file: './logs/out.log',
    error_file: './logs/error.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
  }]
};

# 启动
pm2 start ecosystem.config.js

# 保存配置
pm2 save
pm2 startup

使用 systemd 部署

# /etc/systemd/system/github-webhook-bot.service
[Unit]
Description=GitHub Webhook Bot
After=network.target

[Service]
Type=simple
User=bot
WorkingDirectory=/opt/github-webhook-bot
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure
RestartSec=10
Environment=NODE_ENV=production
Environment=WEBHOOK_SECRET=your-secret
Environment=GITHUB_TOKEN=ghp_xxx

[Install]
WantedBy=multi-user.target
# 启用并启动服务
sudo systemctl enable github-webhook-bot
sudo systemctl start github-webhook-bot
sudo systemctl status github-webhook-bot

使用 Nginx 反向代理

server {
    listen 80;
    server_name webhook.your-domain.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

API 端点

端点 方法 说明
/webhook POST GitHub Webhook 接收端点
/health GET 健康检查

日志

日志文件默认输出到 ./logs/ 目录:

logs/
├── bot.log          # 主日志
├── bot.log.1        # 轮转日志
├── error.log        # 错误日志
└── out.log          # 输出日志

故障排除

签名验证失败

  • 检查 WEBHOOK_SECRET 是否与 GitHub 设置一致
  • 确保 Webhook URL 可公开访问

处理器未触发

  • 检查处理器是否已启用 (enabled: true)
  • 验证事件类型和动作是否匹配
  • 查看日志确认处理器已加载

队列堆积

  • 增加 maxConcurrent
  • 检查处理器执行时间是否过长
  • 考虑优化处理器逻辑

贡献

欢迎提交 Issue 和 Pull Request!

许可证

MIT

About

A Cod or Code Github Automation Bot

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors