feat: 集成飞书通知和机器人对话系统

- 新增通知系统 (notifications 表、服务、API)
- 新增飞书定时任务结果推送 (webhook + 应用消息)
- 新增飞书应用消息发送服务 (feishu_app_service)
- 新增飞书 WebSocket 长连接事件监听 (苹果应用)
- 新增飞书账号绑定/解绑 API
- 新增橙子飞书机器人 (独立 WS 连接,固定路由到橙子助手 Agent)
- 执行记录添加 schedule_id,用户添加飞书绑定字段

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
renjianbo
2026-05-02 16:17:49 +08:00
parent 0bbf68d5bb
commit 7ee80c74b2
29 changed files with 4288 additions and 5 deletions

View File

@@ -0,0 +1,96 @@
"""飞书机器人通知 — 通过 Webhook 推送消息到飞书群聊"""
from __future__ import annotations
import logging
from typing import Optional
import httpx
logger = logging.getLogger(__name__)
FEISHU_TIMEOUT_SEC = 10
def send_feishu_text(webhook_url: str, text: str) -> bool:
"""发送纯文本消息到飞书群机器人。
Args:
webhook_url: 飞书机器人 webhook 地址
text: 消息文本
Returns:
是否发送成功
"""
payload = {"msg_type": "text", "content": {"text": text}}
return _do_send(webhook_url, payload)
def send_feishu_card(
webhook_url: str,
title: str,
body: str,
status: str = "info",
detail_link: Optional[str] = None,
) -> bool:
"""发送消息卡片到飞书群机器人。
Args:
webhook_url: 飞书机器人 webhook 地址
title: 卡片标题
body: 卡片正文(支持 Markdown
status: 状态 — info / success / failed
detail_link: 详情链接(可选)
Returns:
是否发送成功
"""
color_map = {"success": "green", "failed": "red", "info": "blue"}
color = color_map.get(status, "blue")
elements = [
{"tag": "markdown", "content": body},
]
if detail_link:
elements.append({
"tag": "action",
"actions": [{"tag": "button", "text": {"tag": "plain_text", "content": "查看详情"}, "url": detail_link, "type": "default"}],
})
payload = {
"msg_type": "interactive",
"card": {
"header": {
"title": {"tag": "plain_text", "content": title},
"template": color,
},
"elements": elements,
},
}
return _do_send(webhook_url, payload)
def _do_send(webhook_url: str, payload: dict) -> bool:
"""底层 POST 发送,统一异常处理。"""
if not webhook_url or not webhook_url.startswith("https://open.feishu.cn/"):
logger.warning("飞书 webhook URL 无效: %s", webhook_url[:50] if webhook_url else "None")
return False
try:
with httpx.Client(timeout=FEISHU_TIMEOUT_SEC) as client:
resp = client.post(webhook_url, json=payload)
result = resp.json()
if resp.is_success and result.get("code") == 0:
logger.info("飞书通知发送成功: %s", result.get("msg"))
return True
else:
logger.warning(
"飞书通知发送失败: status=%s code=%s msg=%s",
resp.status_code, result.get("code"), result.get("msg"),
)
return False
except httpx.TimeoutException:
logger.warning("飞书通知发送超时: %s", webhook_url[:50])
return False
except Exception as e:
logger.warning("飞书通知发送异常: %s", e)
return False