Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

149 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.

/**
* services/zhuyuan-bridge/server.js
*
* 铸渊桥接服务 — Webhook Server可选增强
*
* 功能:
* - 接收 GitHub App Webhook 事件
* - 健康检查端点
* - 实时响应(不经过 Issue 中转)
*
* 部署:
* pm2 start server.js --name zhuyuan-bridge
*
* 环境变量:
* GHAPP_WEBHOOK_SECRET — GitHub App Webhook Secret
* PORT — 服务端口(默认 3800
*/
'use strict';
const express = require('express');
const crypto = require('crypto');
const app = express();
const PORT = process.env.PORT || 3800;
// ===== Webhook 签名验证 =====
function verifySignature(payload, signature) {
if (!signature || !process.env.GHAPP_WEBHOOK_SECRET) return false;
const hmac = crypto.createHmac('sha256', process.env.GHAPP_WEBHOOK_SECRET);
hmac.update(payload);
const expected = 'sha256=' + hmac.digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
} catch (_) {
return false;
}
}
// 解析 JSON body但保留原始 buffer 供签名验证
app.use(function(req, res, next) {
let rawBody = '';
req.on('data', function(chunk) { rawBody += chunk; });
req.on('end', function() {
req.rawBody = rawBody;
try {
req.body = JSON.parse(rawBody);
} catch (_) {
req.body = {};
}
next();
});
});
// ===== 简易速率限制 =====
const rateLimitMap = new Map();
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const RATE_LIMIT_MAX = 30; // max requests per window
function rateLimit(req, res, next) {
const ip = req.ip || req.connection.remoteAddress || 'unknown';
const now = Date.now();
const entry = rateLimitMap.get(ip);
if (!entry || now - entry.start > RATE_LIMIT_WINDOW) {
rateLimitMap.set(ip, { start: now, count: 1 });
return next();
}
entry.count++;
if (entry.count > RATE_LIMIT_MAX) {
res.status(429).json({ error: 'Too many requests' });
return;
}
next();
}
// ===== 健康检查 =====
app.get('/health', function(req, res) {
res.json({
status: 'alive',
agent: 'zhuyuan-bridge',
version: '1.0.0',
timestamp: new Date().toISOString()
});
});
// ===== GitHub App Webhook 入口 =====
app.post('/webhook/github-app', rateLimit, function(req, res) {
const event = req.headers['x-github-event'];
const sig = req.headers['x-hub-signature-256'];
// 签名验证(如果配置了 secret
if (process.env.GHAPP_WEBHOOK_SECRET) {
if (!verifySignature(req.rawBody, sig)) {
console.warn('[铸渊桥接] ⚠️ Webhook 签名验证失败');
res.status(401).json({ error: 'Signature mismatch' });
return;
}
}
console.log('[铸渊桥接] 收到事件:', event);
// 立即响应 200避免 GitHub 超时)
res.status(200).json({ received: true, event: event });
// 异步处理事件
handleWebhookEvent(event, req.body).catch(function(err) {
console.error('[铸渊桥接] 事件处理失败:', err.message);
});
});
/**
* 处理 Webhook 事件
*/
async function handleWebhookEvent(event, payload) {
switch (event) {
case 'issues':
if (payload.action === 'opened' || payload.action === 'labeled') {
const labels = (payload.issue.labels || []).map(function(l) { return l.name; });
if (labels.includes('zhuyuan-exec')) {
console.log('[铸渊桥接] 检测到执行指令 Issue #' + payload.issue.number);
// 在 Webhook 模式下,可以直接启动执行
// 但默认推荐通过 Workflow 执行(更安全、有日志)
console.log('[铸渊桥接] 由 GitHub Actions Workflow 处理执行');
}
}
break;
case 'push':
console.log('[铸渊桥接] Push 事件 → ' + (payload.ref || ''));
break;
case 'pull_request':
console.log('[铸渊桥接] PR 事件 → #' + (payload.pull_request ? payload.pull_request.number : '?'));
break;
default:
console.log('[铸渊桥接] 未处理的事件类型:', event);
}
}
// ===== 启动服务 =====
app.listen(PORT, function() {
console.log('[铸渊桥接] 🚀 启动在端口 ' + PORT);
console.log('[铸渊桥接] 健康检查: http://localhost:' + PORT + '/health');
console.log('[铸渊桥接] Webhook: http://localhost:' + PORT + '/webhook/github-app');
});