一个功能强大、可扩展的 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# 必需配置
WEBHOOK_SECRET=your-webhook-secret-here
# 可选配置
WEBHOOK_PORT=3000
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
LOG_LEVEL=infoserver:
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 仓库/组织中,进入 Settings > Webhooks
- 点击 Add webhook
- 配置以下选项:
- Payload URL:
http://your-server:3000/webhook - Content type:
application/json - Secret: 与
WEBHOOK_SECRET相同的值 - Events: 选择需要的事件类型
- Payload URL:
- 点击 Add webhook
// 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.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 |
立即返回错误 |
处理器接收的上下文对象包含以下信息:
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;
}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
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# /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-botserver {
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;
}
}| 端点 | 方法 | 说明 |
|---|---|---|
/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