mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 06:21:58 +00:00
86d2840e41
邮件(04 细案 B2,集成-I4 / I5,改写应用平台 Q6): - notify/mail.py:标准库 smtplib 放进 asyncio.to_thread,单次超时 10 秒;4xx 和连不上按 1/2/4 秒重试,5xx 不重试; 报错只写主机名和服务器回话,口令一律抹掉;纯文本 + 转义过的 HTML,带 List-Unsubscribe 和 Auto-Submitted;测试可换假的 SMTP 工厂。模块说明写清改写 Q6 的理由:SMTP 是可选配置、退信不追踪照实说「已交给邮件服务器」 - 通知渠道多一种格式「邮件」(core.CHANNEL_FORMATS;定时任务的 Webhook 只会发 HTTP,FORMATS 不加,免得定时任务能选 邮件):个人邮件渠道不收地址,只发到名录里的账号邮箱,没有邮箱记一条失败投递并写明去「设置 → 身份」填;全局、应用的填 固定地址,最多 20 个;邮件服务没启用时不收新的邮件渠道;渠道列表另给 mail(启没启用、本人的账号邮箱),投递记录带格式 - 设置的 mail 一组(只有管理员能改):口令用 core/crypto 加密,读的时候只说配没配;启用时要配全;发件人名称、主机不许 换行;「发测试邮件」只发到管理员自己的账号邮箱,关登录、没有邮箱时 422 说清怎么办 - mail.default_for_assignees(缺省关):开了之后第一次派到待办的人自动有一个个人邮件渠道(订阅待办打开、动作结果未知、 审核有结果),本人删了不再建回来;关登录时不建 待办汇总与密钥到期(04 细案 B3): - notify/digest.py 接进调度循环的步骤列表:每天到 notify.digest_at(缺省 09:00,服务端时区)给订阅了 task.digest 的 个人渠道发汇总(条数、最早时限、最多 5 条节点名、去待办箱的链接,没有待办不发);按日期在 settings 表里插一行认领, 重启不重发;认领行和自动建邮件渠道的名单是通知自己记的账,设置接口不列不收 - key.expiring:到期前 7 天、1 天各推一次,个人密钥推本人的个人渠道、服务账号的推全局渠道,推之前记审计、按审计去重; 轮换下来的旧密钥、撤销了的、建的时候就在这一档里的不推 - 两个事件推送实现了,才在 notify/service.EVENTS 打开它们的订阅范围 设置的 notify 一组加 digest_at;只给一部分时补上存着的另几项;环境变量锁住对外访问地址时只核对那一项,汇总时间照常能改。 apps 一组加 share_links(分享链接的开关,缺省关,下一个提交用)。测试:假 SMTP 工厂下的收件人、失败投递、重试、口令、 设置与权限、自动建渠道;汇总按时、只发一次、重启不重发;密钥两档、谁收、不该推的几种。
41 lines
2.2 KiB
Python
41 lines
2.2 KiB
Python
"""通知的发送核心(附录 C,BE-2 提供)。从 scheduler/webhook.py 抽出:四种格式、签名、发送、脱敏(core.py);
|
|
webhook.py 照原名再导出,定时任务的通知存储不迁移(F14)。新对象(待办、实例、动作)的订阅和投递记录在
|
|
service.py:渠道(notify_channels)、投递记录(notify_deliveries)。渠道的第五种格式「邮件」走 mail.py(SMTP),订阅、
|
|
投递记录、消息结构都和 Webhook 同一套,只是在这里按格式分到另一条发送路上。"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from app.notify.core import Message, Outcome
|
|
|
|
|
|
async def send(channel: Any, message: dict[str, Any]) -> Any:
|
|
"""按渠道的格式发一条通知。message = {title, status, lines: [...], links: [(文字, 地址)]},不含报告里的具体数字。
|
|
|
|
channel 是一行 NotifyChannel(地址、密钥是密文)。返回 core.Outcome。"""
|
|
from app.core.crypto import decrypt
|
|
from app.notify import core
|
|
from app.notify.service import generic_body
|
|
|
|
url = decrypt(channel.url) or ""
|
|
secret = decrypt(channel.secret) if channel.secret else None
|
|
msg = Message(title=str(message.get("title") or ""),
|
|
lines=[(str(k), str(v)) for k, v in message.get("lines") or []],
|
|
links=[(str(k), str(v)) for k, v in message.get("links") or []],
|
|
ok=bool(message.get("ok")))
|
|
if channel.format == "email":
|
|
from app.notify import mail
|
|
|
|
return await mail.deliver(channel, message, msg)
|
|
if not url:
|
|
return Outcome(False, None, 0, "渠道的地址无法解密(加密密钥可能已更换),请重新填写地址")
|
|
if getattr(channel, "scope", None) == "user":
|
|
from app.notify.service import personal_url_problem
|
|
|
|
if problem := await personal_url_problem(url):
|
|
return Outcome(False, None, 0, problem)
|
|
return await core.post(channel.format, url, secret,
|
|
lambda: core.build_request(channel.format, url, secret, msg,
|
|
generic=generic_body(message, msg)),
|
|
label=f"通知渠道「{channel.name}」", retry=bool(message.get("retry", True)))
|