# Gitea 到飞书 Webhook 中转服务 将 Gitea 的工单事件(创建、更新、关闭等)通过 Webhook 接收,转换为美观的飞书卡片消息,并转发到飞书群聊。 ## 功能特性 - 接收 Gitea Webhook 事件(支持工单相关动作) - 验证 Webhook 签名(可选) - 将事件转换为飞书交互式卡片 - 支持自定义卡片标题、颜色、字段 - 完整的错误处理和日志记录 - 健康检查端点 - 易于部署和配置 ## 系统架构 ``` Gitea → Webhook → 中转服务 (Express) → 飞书机器人 → 飞书群聊 ``` ## 快速开始 ### 前提条件 - Node.js 18+ - 一个飞书机器人,并获取其 Webhook URL - Gitea 实例(版本 1.20+) ### 安装 1. 克隆仓库 ```bash git clone cd gitea-feishu-webhook-relay ``` 2. 安装依赖 ```bash npm install ``` 3. 配置环境变量 复制 `.env.example` 为 `.env` 并填写实际值: ```bash cp .env.example .env ``` 编辑 `.env`: ```env PORT=3000 GITEA_WEBHOOK_SECRET=your_gitea_webhook_secret FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/xxx LOG_LEVEL=info ``` ### 运行 开发模式(使用 nodemon): ```bash npm run dev ``` 生产模式: ```bash npm start ``` 服务将在 `http://localhost:3000` 启动。 ## 配置 Gitea Webhook ### 重要:选择正确的 Webhook 类型 确保在 Gitea 中创建 Webhook 时选择 **“Gitea”** 类型,而不是“飞书”或其他类型。Gitea 类型会发送结构化的 JSON 数据,便于解析。 ### 详细字段填写(根据用户界面) 在 Gitea 的 Webhook 创建页面中,请按以下说明填写: | 字段 | 填写示例 | 说明 | |------|----------|------| | **目标 URL** | `http://192.168.0.15:3001` 或 `http://192.168.0.15:3001/webhook/gitea` | 根据你的服务地址和端口(默认为3000,若修改了环境变量 `PORT` 则对应调整)。如果服务部署在本地,请使用内网 IP。 | | **HTTP 方法** | `POST` | 保持默认。 | | **内容类型** | `application/json` | 保持默认。 | | **授权标头** | (留空) | 除非你的服务需要 Bearer 或 Basic 认证,否则不需要填写。 | | **密钥** | (可选)填写与 `GITEA_WEBHOOK_SECRET` 环境变量相同的字符串 | 用于验证 Webhook 请求的签名,增强安全性。如果留空,服务将跳过签名验证。 | | **触发事件** | 勾选 **“工单事件”**(Issue) | 可根据需要勾选其他事件,但本服务目前只处理工单事件。 | ### 步骤 1. 进入 Gitea 仓库的设置 → Webhooks → 添加 Webhook。 2. 选择 **“Gitea”** 类型(不要选“飞书”或“自定义”)。 3. 按照上表填写各字段。 4. 点击“添加 Webhook”保存。 ### 验证 保存后,Gitea 会立即发送一个测试事件(ping)。检查服务日志以确认接收成功。如果看到日志输出 `Received Gitea webhook` 且没有错误,说明配置正确。 ## 飞书机器人配置 1. 在飞书开放平台创建一个自定义机器人,获取 Webhook URL。 2. 将机器人添加到目标群聊。 3. 将 Webhook URL 填入 `FEISHU_WEBHOOK_URL`。 ## API 端点 - `GET /health` – 健康检查 - `POST /webhook/gitea` – 接收 Gitea Webhook(推荐) - `POST /` – 接收 Gitea Webhook(备用,兼容旧配置) ## 日志 日志使用 Winston 输出到控制台和文件: - `logs/error.log` – 错误日志 - `logs/combined.log` – 所有日志 ## Docker 部署 提供 Dockerfile 便于容器化部署。 构建镜像: ```bash docker build -t gitea-feishu-relay . ``` 运行容器: ```bash docker run -p 3000:3000 --env-file .env gitea-feishu-relay ``` ## 环境变量 | 变量名 | 描述 | 默认值 | |--------|------|--------| | PORT | 服务监听端口 | 3000 | | GITEA_WEBHOOK_SECRET | Gitea Webhook 签名密钥 | (空) | | FEISHU_WEBHOOK_URL | 飞书机器人 Webhook URL | (必需) | | LOG_LEVEL | 日志级别 (error, warn, info, debug) | info | | NODE_ENV | 运行环境 (development, production) | development | ## 开发 ### 项目结构 ``` src/ ├── server.js # Express 服务器入口 ├── config.js # 配置管理 ├── webhooks/ │ └── gitea.js # Webhook 处理器 ├── transformers/ │ └── giteaToFeishu.js # 消息转换器 ├── clients/ │ └── feishu.js # 飞书 API 客户端 └── utils/ └── logger.js # 日志工具 ``` ### 测试 运行单元测试: ```bash npm test ``` ## 测试建议 ### 1. 启动服务 确保服务已运行并监听正确端口: ```bash npm start ``` 检查控制台输出是否有 `Server listening on port 3001`(或你设置的端口)。 ### 2. 健康检查 使用 curl 或浏览器访问健康检查端点: ```bash curl http://localhost:3001/health ``` 应返回 `{"status":"ok","timestamp":"..."}`。 ### 3. 模拟 Gitea Webhook 请求 你可以使用 `curl` 发送一个模拟的 Gitea 工单事件来测试转换和转发功能: ```bash curl -X POST http://localhost:3001/webhook/gitea \ -H "Content-Type: application/json" \ -H "X-Gitea-Signature: sha256=..." \ -d '{ "action": "opened", "issue": { "id": 123, "number": 45, "title": "测试工单", "body": "这是一个测试工单", "state": "open", "created_at": "2025-12-02T05:00:00Z", "user": { "id": 1, "login": "testuser", "full_name": "测试用户" }, "assignee": null, "labels": [], "milestone": null }, "repository": { "id": 1, "name": "test-repo", "full_name": "org/test-repo", "html_url": "https://gitea.example.com/org/test-repo" }, "sender": { "id": 1, "login": "testuser" } }' ``` 如果配置了 `FEISHU_WEBHOOK_URL`,服务会将转换后的卡片发送到飞书群聊。检查服务日志和飞书群聊以确认消息送达。 ### 4. 查看日志 日志文件位于 `logs/` 目录,或直接在控制台查看。关注 `info` 和 `error` 级别的消息。 ## 故障排除 1. **收不到飞书消息** - 检查 `FEISHU_WEBHOOK_URL` 是否正确 - 查看日志中是否有错误信息 - 确认飞书机器人已加入群聊 2. **Gitea Webhook 验证失败** - 确保 `GITEA_WEBHOOK_SECRET` 与 Gitea 中设置的密钥一致 - 检查请求头中是否包含 `X-Gitea-Signature` 3. **服务无法启动** - 确认端口未被占用 - 检查 Node.js 版本 4. **Webhook 请求被忽略** - 确认 Gitea 发送的 JSON 格式符合预期(包含 `action`、`issue`、`repository`) - 检查日志中是否有 `Event ignored` 消息,可能是动作被过滤(如 `labeled`) ## 许可证 MIT