V1: DAG多任务编排 + AI规划WBS + RAG知识库 + 预算告警中心 + 开放API + 飞书/企微/邮件通知

This commit is contained in:
2026-08-12 00:48:52 +08:00
parent e046770bad
commit 29874e6ca9
13 changed files with 1402 additions and 111 deletions
+111
View File
@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
"""
通知渠道:飞书/企微群机器人 Webhook + 邮件(可选 SMTP
事件:task_review(待审核)/ task_done(完成)/ task_failed(失败)
budget_alert(预算告警)/ worker_alertWorker 异常)/ wbs_ready(规划完成)
"""
import json
import smtplib
import requests
import db
EVENTS = {
'task_review': '任务待审核',
'task_done': '任务完成',
'task_failed': '任务失败',
'budget_alert': '预算告警',
'worker_alert': 'Worker 异常',
'wbs_ready': '规划完成',
}
def _channels_for(event):
rows = db.q('SELECT * FROM notify_channels WHERE enabled=1')
out = []
for r in rows:
try:
evs = json.loads(r['events'] or '[]')
except Exception:
evs = []
if event in evs:
out.append(r)
return out
def _send_feishu(ch, title, text):
payload = {'msg_type': 'text', 'content': {'text': f'【AI Worker 平台】{title}\n{text}'}}
r = requests.post(ch['webhook'], json=payload, timeout=10)
try:
body = r.json()
if body.get('code', 0) not in (0, None):
raise ValueError(f'飞书返回错误: {body.get("msg", r.text[:100])}')
except ValueError:
raise
except Exception:
pass
return r
def _send_wecom(ch, title, text):
payload = {'msgtype': 'text', 'text': {'content': f'【AI Worker 平台】{title}\n{text}'}}
return requests.post(ch['webhook'], json=payload, timeout=10)
def _send_email(ch, title, text):
from config import EMAIL # 邮件配置在 config.py
if not EMAIL.get('host'):
return None
msg = f'From: {EMAIL["from_name"]} <{EMAIL["user"]}>\nTo: {ch["email"]}\n' \
f'Subject: 【AI Worker 平台】{title}\nContent-Type: text/plain; charset=utf-8\n\n{text}'
try:
s = smtplib.SMTP(EMAIL['host'], EMAIL['port'])
if EMAIL.get('starttls'):
s.starttls()
if EMAIL.get('user'):
s.login(EMAIL['user'], EMAIL['password'])
s.sendmail(EMAIL['user'], [ch['email']], msg.encode('utf-8'))
s.quit()
return True
except Exception as e:
return f'邮件发送失败: {e}'
def notify(event, title, text, save_alert=True, level='info', atype=None):
"""触发事件:写告警记录 + 推送所有订阅渠道"""
if save_alert:
db.w('INSERT INTO alerts (type, level, title, detail, read, created_at) '
'VALUES (?,?,?,?,0,?)',
(atype or event, level, title, text[:500], db.now()))
ok, fail = [], []
for ch in _channels_for(event):
try:
if ch['type'] == 'feishu':
r = _send_feishu(ch, title, text)
(ok if r.status_code == 200 else fail).append(f'{ch["name"]}({r.status_code})')
elif ch['type'] == 'wecom':
r = _send_wecom(ch, title, text)
(ok if r.status_code == 200 else fail).append(f'{ch["name"]}({r.status_code})')
elif ch['type'] == 'email':
r = _send_email(ch, title, text)
(ok if r is True else fail).append(f'{ch["name"]}({r})')
except Exception as e:
fail.append(f'{ch["name"]}({e})')
return {'ok': ok, 'fail': fail}
def test_channel(ch):
"""渠道连通性测试"""
text = '这是一条来自 AI Worker 项目管理平台的连通性测试消息 ✅'
try:
if ch['type'] == 'feishu':
r = _send_feishu(ch, '连通测试', text)
return r.status_code == 200, f'HTTP {r.status_code}'
if ch['type'] == 'wecom':
r = _send_wecom(ch, '连通测试', text)
return r.status_code == 200, f'HTTP {r.status_code}'
if ch['type'] == 'email':
r = _send_email(ch, '连通测试', text)
return r is True, str(r)
except Exception as e:
return False, str(e)
return False, '未知渠道类型'