Files
ai-worker-platform/notify.py
T

112 lines
3.9 KiB
Python
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.
# -*- 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, '未知渠道类型'