diff --git a/CHANGELOG.md b/CHANGELOG.md index 4300739..a5db873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### 修复 - **WebSocket 初始化**: 修复了 `@larksuiteoapi/node-sdk` v1.56.0+ 中 WebSocket 初始化不正确的 `TypeError`。现在正确使用了 `WSClient` 类并修复了参数类型错误。 - **事件处理**: 修正了 `im.chat.member.bot.added_v1` 事件的 Payload 解析逻辑。 -- **Hono 兼容性**: 修正了 `feishu-event.ts` 中 `lark.adaptDefault` 的错误用法。改为使用手动 Challenge 处理和 `eventDispatcher.invoke`,解决了与 Hono 请求/响应对象的兼容性问题引发的编译错误。 +- **Hono 兼容性**: 修正了 `feishu-event.ts` 中 `lark.adaptDefault` 的错误用法。改为使用手动 Challenge 处理和 `eventDispatcher.invoke`,并通过原型链注入 Header 解决了与 Hono 请求/响应对象的兼容性以及签名校验失败的问题。 - **群聊解绑**: 增加对 `im.chat.member.bot.deleted_v1` 事件的支持。当机器人被移除群聊时,自动清理 `known_group_chats` 和 `topic_group_chats` 关联,确保订阅关系自动解绑。 ### 新增 diff --git a/README.md b/README.md index 46c3bee..502d979 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,16 @@ 支持通过 **Topic (主题)** 订阅模式分发告警,同时也提供 **Personal Inbox (个人信箱)** 功能,无需创建话题即可快速给自己推送消息。 ![Topics View](docs/images/topics_view.png) -### 2. 管理员看板 (Live Stats) +除了个人订阅外,您可以将 Topic 绑定至多个**飞书群聊**。 +> [!TIP] +> **群聊发现**:请先将机器人邀请进入目标群聊。机器人入群后会触发自动感应,此时刷新管理页面即可在下拉菜单中看到并绑定该群组。 + +### 2. 群聊告警分发 +支持将机器人加入飞书群聊,并将话题绑定到群聊中,实现告警的群组广播。 +![Group Binding](docs/images/group_binding.png) +![Group Alert](docs/images/group_alert.png) + +### 3. 管理员看板 (Live Stats) 实时追踪全系统的告警负载、分发成功率以及各话题的热度。 ![Admin Dashboard](docs/images/admin_dashboard.png) @@ -22,11 +31,13 @@ ## 🔥 核心特性 - **🚀 极简推送 (Personal Inbox)**: 每个用户拥有专属的 Webhook Token,直接向 `/dm` 接口发送即可在飞书收到私聊,零配置成本。 -- **📢 主题订阅 (Topic Model)**: 灵活的“发布-订阅”机制。告警发送至 Topic,系统自动分发给所有订阅成员,避免群聊骚扰。 +- **📢 主题订阅 (Topic Model)**: 灵活的“发布-订阅”机制。告警发送至 Topic,系统自动分发给所有订阅成员。 +- **👥 群聊分发 (Group Support)**: 告警可同步分发至绑定的飞书群聊,支持机器人自动发现与解绑。 - **🛡️ 权限与审计**: - 话题创建需经过管理员审批。 - - 记录完整的 `Alert Task` 日志,包含发送者、时间、审批人及分发成功率。 + - 记录完整的 `Alert Task` 日志,实现发送链路可追溯。 - **📊 实时看板**: Grafana 风格的监控界面,直观展示系统运行健壮性。 +- **🔌 长连接模式 (WebSocket)**: 支持飞书开放平台长连接,无需公网 IP 或域名即可在内网环境接收事件回调。 - **⚡ 高性能架构**: 基于 Bun + Hono 的全异步架构,毫秒级分发延迟。 --- diff --git a/apps/server/src/api/feishu-event.ts b/apps/server/src/api/feishu-event.ts index 69ae257..7d55e1f 100644 --- a/apps/server/src/api/feishu-event.ts +++ b/apps/server/src/api/feishu-event.ts @@ -31,11 +31,11 @@ feishuEvent.post('/', async (c) => { } // 2. Dispatch event - // The dispatcher expects an object containing headers and body - const result = await eventDispatcher.invoke({ - ...req.body, - headers: req.headers - }); + // The dispatcher expects an object containing headers and body. + // We use Object.create to put headers on the prototype so they are accessible + // but not included in JSON.stringify, which preserves signature verification. + const payload = Object.assign(Object.create({ headers: headerRecord }), body); + const result = await eventDispatcher.invoke(payload); return c.json(result || {}); } catch (e) { diff --git a/apps/server/src/event-handler.ts b/apps/server/src/event-handler.ts index 1f112de..dbfe924 100644 --- a/apps/server/src/event-handler.ts +++ b/apps/server/src/event-handler.ts @@ -4,7 +4,10 @@ import { eq } from 'drizzle-orm'; import * as lark from '@larksuiteoapi/node-sdk'; import { logger } from './lib/logger'; -export const eventDispatcher = new lark.EventDispatcher({}).register({ +export const eventDispatcher = new lark.EventDispatcher({ + encryptKey: process.env.FEISHU_ENCRYPT_KEY, + verificationToken: process.env.FEISHU_VERIFICATION_TOKEN, +}).register({ 'im.chat.member.bot.added_v1': async (data) => { const { chat_id, name } = data as any; logger.info({ chat_id, name }, '[Feishu Event] Bot added to group'); diff --git a/docs/copilot-context.md b/docs/copilot-context.md index 7ed59f2..26320aa 100644 --- a/docs/copilot-context.md +++ b/docs/copilot-context.md @@ -175,6 +175,7 @@ The database schema is defined in `apps/server/src/db/schema.ts`. ### Feishu Event - `POST /api/feishu/event`: Endpoint for receiving Feishu events (Webhook mode). - **Note**: This endpoint uses **manual challenge handling** (`lark.generateChallenge`) and `eventDispatcher.invoke` instead of the SDK's `adaptDefault` to maintain compatibility with Hono's non-standard Node.js response object. + - **Signature Verification Hack**: To preserve Feishu's signature verification, the internal `invoke` call uses `Object.create({ headers })` to inject HTTP headers on the prototype of the payload object. This ensures headers are accessible to the SDK's internal verification logic but are **excluded** from `JSON.stringify`, which is critical for matching the SHA256 content checksum. ### Webhook - `POST /api/webhook/:token/topic/:slug`: Trigger an alert for a topic. @@ -183,9 +184,9 @@ The database schema is defined in `apps/server/src/db/schema.ts`. ## 6. Future Roadmap (Planned) - [ ] **Message Preview**: Preview Feishu card JSON in the UI. -- [ ] **History/Logs**: Keep a log of sent alerts for auditing. +- [x] **History/Logs**: Tracking for sent alerts (Alert Tasks/Logs). - [ ] **Retry Mechanism**: Handle Feishu API failures. -- [x] **Deployment**: Dockerfile and deployment scripts. +- [x] **Deployment**: Dockerfile and CI/CD. ## 7. Development Conventions @@ -201,6 +202,11 @@ The database schema is defined in `apps/server/src/db/schema.ts`. - **Environment Isolation**: - Each workspace (`apps/server`, `apps/web`) uses its own `.env` file via Bun's `--env-file .env` flag. - Development proxy target for the frontend is configurable via `VITE_API_URL` (default: `http://localhost:3000`). +- **Critical Environment Variables**: + - `FEISHU_ENCRYPT_KEY`: Essential for the `lark.generateChallenge` and event signature verification. + - `FEISHU_VERIFICATION_TOKEN`: Used by `EventDispatcher` for event authentication. + - `FEISHU_USE_WS`: Set to `true` to enable WebSocket mode (bypasses `feishu-event.ts`). + - `ADMIN_EMAILS`: Comma-separated list of emails that automatically receive `isAdmin=true` upon first login. - **CI/CD**: - GitHub Actions automates building a multi-stage Docker image and pushing it to GitHub Container Registry (GHCR). - Image path: `ghcr.io/${USER}/alert-message-center`. diff --git a/docs/images/group_alert.png b/docs/images/group_alert.png new file mode 100644 index 0000000..07f3fb5 Binary files /dev/null and b/docs/images/group_alert.png differ diff --git a/docs/images/group_binding.png b/docs/images/group_binding.png new file mode 100644 index 0000000..268f9fb Binary files /dev/null and b/docs/images/group_binding.png differ diff --git a/docs/images/topics_view.png b/docs/images/topics_view.png index 0a871ff..a95c30f 100644 Binary files a/docs/images/topics_view.png and b/docs/images/topics_view.png differ