guanghulab/webhook/github-webhook.js
2026-05-10 13:12:44 +08:00

154 lines
4.2 KiB
JavaScript
Raw 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.

// GitHub Webhook 接收器 · github-webhook.js
// HoloLake · M-DINGTALK Phase 3
// DEV-004 之之 × 秋秋
//
const crypto = require('crypto');
// Webhook密钥后续从GitHub Settings配置现在用模拟值
const WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET || 'hololake-webhook-secret-2026';
// 事件处理器注册表
const eventHandlers = {};
/**
* 验证GitHub Webhook签名
* GitHub发来的每个请求都有签名防止伪造
*/
function verifySignature(payload, signature) {
if (!signature) {
console.log('[Webhook] ⚠️ 请求没有签名,跳过验证(开发模式)');
return true; // 开发模式下允许无签名
}
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
const digest = 'sha256=' + hmac.update(payload).digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(digest),
Buffer.from(signature)
);
} catch (e) {
return false;
}
}
/**
* 注册事件处理器
* @param {string} eventType - GitHub事件类型 (push/pull_request等)
* @param {Function} handler - 处理函数
*/
function on(eventType, handler) {
if (!eventHandlers[eventType]) {
eventHandlers[eventType] = [];
}
eventHandlers[eventType].push(handler);
console.log(`[Webhook] 注册事件处理器: ${eventType}`);
}
/**
* 处理收到的Webhook请求
* @param {Object} req - Express请求对象
* @param {Object} res - Express响应对象
*/
async function handleWebhook(req, res) {
const event = req.headers['x-github-event'];
const delivery = req.headers['x-github-delivery'];
const signature = req.headers['x-hub-signature-256'];
console.log('\n[Webhook] ====================');
console.log(`[Webhook] 收到GitHub事件${event}`);
console.log(`[Webhook] Delivery ID: ${delivery}`);
console.log(`[Webhook] 时间:${new Date().toLocaleString('zh-CN')}`);
// 验证签名
const payload = JSON.stringify(req.body);
if (!verifySignature(payload, signature)) {
console.log('[Webhook] ❌ 签名验证失败!可能是伪造请求');
return res.status(401).json({ error: '签名验证失败' });
}
// 记录事件日志
const eventLog = {
event,
delivery,
timestamp: new Date().toISOString(),
sender: req.body.sender ? req.body.sender.login : 'unknown',
repository: req.body.repository ? req.body.repository.full_name : 'unknown'
};
// 特殊处理push事件
if (event === 'push') {
eventLog.branch = req.body.ref;
eventLog.commits = (req.body.commits || []).length;
eventLog.changedFiles = [];
// 收集所有变更文件
for (const commit of (req.body.commits || [])) {
eventLog.changedFiles.push(...(commit.added || []));
eventLog.changedFiles.push(...(commit.modified || []));
}
eventLog.changedFiles = [...new Set(eventLog.changedFiles)];
console.log(`[Webhook] 分支:${eventLog.branch}`);
console.log(`[Webhook] 提交数:${eventLog.commits}`);
console.log(`[Webhook] 变更文件:${eventLog.changedFiles.join(', ')}`);
}
// 调用注册的事件处理器
const handlers = eventHandlers[event] || [];
console.log(`[Webhook] 匹配到 ${handlers.length} 个处理器`);
for (const handler of handlers) {
try {
await handler(req.body, eventLog);
} catch (err) {
console.error('[Webhook] 处理器执行错误:', err.message);
}
}
// 也调用通配符处理器
const wildcardHandlers = eventHandlers['*'] || [];
for (const handler of wildcardHandlers) {
try {
await handler(req.body, eventLog);
} catch (err) {
console.error('[Webhook] 通配符处理器错误:', err.message);
}
}
console.log('[Webhook] 事件处理完成 ✅');
res.status(200).json({ status: 'ok', event, delivery });
}
/**
* 创建模拟的GitHub push事件用于测试
*/
function createMockPushEvent(branch, files) {
return {
ref: `refs/heads/${branch || 'main'}`,
repository: {
full_name: 'qinfendebingshuo/guanghulab'
},
sender: {
login: 'test-user'
},
commits: [
{
message: '测试提交',
added: files || ['test-file.js'],
modified: [],
removed: []
}
]
};
}
module.exports = {
verifySignature,
on,
handleWebhook,
createMockPushEvent
};