feat: 服务层配置接线——email/push/rate_limit/AI凭据/行为开关/映射表/种子键
Task 9: 服务层对全局 settings 的业务引用清零,每个 DEFAULT_CONFIG 组 都有真实消费方。resolve_*_settings(email/push/ai)以空槽回退 env (平台默认层);contact 行为开关/种子白名单/国名扩表/限流上限经 manager 的 runtime.config 接线。收口: lead_push session_factory 必填、 config.py 死代码清理、PromptProvider frozen、no_contact 视图直取、 AIService wall 参数定型 CompiledWall。403 测试全绿(389+14)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,33 @@ TAG_LABELS = {
|
||||
}
|
||||
CONTACT_LABELS = {"phone": "手机", "whatsapp": "WhatsApp", "email": "邮箱", "wechat": "微信"}
|
||||
|
||||
# 平台默认发件人显示名(Task 9 前的硬编码值, 租户未配 sender_name 时沿用)
|
||||
_DEFAULT_SENDER_NAME = "HESUPIPE"
|
||||
|
||||
|
||||
def resolve_email_settings(config_data: dict | None) -> dict:
|
||||
"""租户 config.email → 发信参数(Task 9 接线; env settings 回退)。
|
||||
|
||||
回退规则(语义钉在 test_config_merge: 显式 None = 清空/未设置):
|
||||
- smtp 缺省或显式 None → 整段用 env SMTP_*;
|
||||
- smtp 给了 dict → 段内逐键回退(缺键/空串 → env 对应值, 租户可只改
|
||||
密码/只换 host);
|
||||
- sender_name 空 → 平台默认(_DEFAULT_SENDER_NAME);
|
||||
- notify_emails 空列表 → env NOTIFY_EMAIL 单元素。
|
||||
"""
|
||||
email = ((config_data or {}).get("email")) or {}
|
||||
smtp = email.get("smtp") or {}
|
||||
notify = [e for e in (email.get("notify_emails") or []) if e]
|
||||
return {
|
||||
"host": smtp.get("host") or settings.SMTP_HOST,
|
||||
"port": smtp.get("port") or settings.SMTP_PORT,
|
||||
"user": smtp.get("user") or settings.SMTP_USER,
|
||||
"password": smtp.get("password") or settings.SMTP_PASS,
|
||||
"sender_name": email.get("sender_name") or _DEFAULT_SENDER_NAME,
|
||||
"notify_emails": notify or (
|
||||
[settings.NOTIFY_EMAIL] if settings.NOTIFY_EMAIL else []),
|
||||
}
|
||||
|
||||
|
||||
def _esc(v) -> str:
|
||||
return html.escape(str(v)) if v else ""
|
||||
@@ -57,17 +84,17 @@ def _cc_views(contacts: dict, cc_info: dict | None) -> tuple[dict, dict]:
|
||||
return body, subj
|
||||
|
||||
|
||||
async def _send(msg: MIMEMultipart) -> bool:
|
||||
if not all([settings.SMTP_HOST, settings.SMTP_USER, settings.NOTIFY_EMAIL]):
|
||||
async def _send(msg: MIMEMultipart, cfg: dict) -> bool:
|
||||
if not all([cfg["host"], cfg["user"], cfg["notify_emails"]]):
|
||||
logger.warning("SMTP not configured, skipping email")
|
||||
return False
|
||||
try:
|
||||
await aiosmtplib.send(
|
||||
msg, hostname=settings.SMTP_HOST, port=settings.SMTP_PORT,
|
||||
username=settings.SMTP_USER, password=settings.SMTP_PASS,
|
||||
msg, hostname=cfg["host"], port=cfg["port"],
|
||||
username=cfg["user"], password=cfg["password"],
|
||||
use_tls=True,
|
||||
)
|
||||
logger.info("Lead email sent to %s", settings.NOTIFY_EMAIL)
|
||||
logger.info("Lead email sent to %s", cfg["notify_emails"])
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception("Failed to send lead email")
|
||||
@@ -81,9 +108,12 @@ async def send_lead_email(
|
||||
conversation_messages: list[dict],
|
||||
intent_level: str = "",
|
||||
cc_info: dict | None = None,
|
||||
config_data: dict | None = None,
|
||||
) -> bool:
|
||||
"""cc_info = raw_info["_cc"](Task 6, spec §5):正文联系方式行渲染
|
||||
补全号+标注、主题行号码用 e164 无标注;缺省(无 _cc/显式 + 号)行为不变。"""
|
||||
补全号+标注、主题行号码用 e164 无标注;缺省(无 _cc/显式 + 号)行为不变。
|
||||
config_data = 租户配置(Task 9):邮件参数经 resolve_email_settings 解析,
|
||||
None 走 env settings(平台默认)。"""
|
||||
tag = TAG_LABELS.get(intent_level, "未评估")
|
||||
contacts, subj_contacts = _cc_views(contact_info, cc_info)
|
||||
country = (customer_profile or {}).get("目标国家", "")
|
||||
@@ -97,9 +127,10 @@ async def send_lead_email(
|
||||
subject = f"[新询盘·{tag}] {_flat(country)}"
|
||||
if contact_parts:
|
||||
subject += " | " + ", ".join(contact_parts)
|
||||
cfg = resolve_email_settings(config_data)
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = f"【HESUPIPE】 <{settings.SMTP_USER}>"
|
||||
msg["To"] = settings.NOTIFY_EMAIL
|
||||
msg["From"] = f"【{cfg['sender_name']}】 <{cfg['user']}>"
|
||||
msg["To"] = ", ".join(cfg["notify_emails"])
|
||||
|
||||
info_rows = "".join(
|
||||
f"<tr><td style='padding:6px 12px;font-weight:bold;'>{_esc(k)}</td>"
|
||||
@@ -125,39 +156,43 @@ async def send_lead_email(
|
||||
</body></html>
|
||||
"""
|
||||
msg.attach(MIMEText(html_body, "html", "utf-8"))
|
||||
return await _send(msg)
|
||||
return await _send(msg, cfg)
|
||||
|
||||
|
||||
async def send_returning_lead_email(
|
||||
contact_info: dict, cc_info: dict | None = None,
|
||||
config_data: dict | None = None,
|
||||
) -> bool:
|
||||
"""D5: 24h 内同联系方式再访,只发一行轻提醒。主题行号码走 e164(Task 6)。"""
|
||||
_body, subj_contacts = _cc_views(contact_info, cc_info)
|
||||
contact_parts = [
|
||||
f"{CONTACT_LABELS.get(k, k)}: {_flat(v)}" for k, v in subj_contacts.items()
|
||||
]
|
||||
cfg = resolve_email_settings(config_data)
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["Subject"] = f"[线索回访] {' | '.join(contact_parts) or '已知客户'}再次来访"
|
||||
msg["From"] = f"【HESUPIPE】 <{settings.SMTP_USER}>"
|
||||
msg["To"] = settings.NOTIFY_EMAIL
|
||||
msg["From"] = f"【{cfg['sender_name']}】 <{cfg['user']}>"
|
||||
msg["To"] = ", ".join(cfg["notify_emails"])
|
||||
msg.attach(MIMEText(
|
||||
f"<html><body><p>该联系方式 24h 内已发过完整询盘,客户又开启了新对话,"
|
||||
f"详情请查后台。</p></body></html>", "html", "utf-8"))
|
||||
return await _send(msg)
|
||||
return await _send(msg, cfg)
|
||||
|
||||
|
||||
async def send_correction_email(
|
||||
contact_info: dict, count: int = 1, cc_info: dict | None = None,
|
||||
config_data: dict | None = None,
|
||||
) -> bool:
|
||||
"""D4: 联系方式变化(改号/新增/无提示换值)后的轻量更正邮件,每会话上限3封。
|
||||
正文联系方式行渲染补全号+标注(Task 6, spec §5 更正路径)。"""
|
||||
body_contacts, _subj = _cc_views(contact_info, cc_info)
|
||||
cfg = resolve_email_settings(config_data)
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["Subject"] = f"[询盘·更正{count}] 客户联系方式已更新(以本封为准)"
|
||||
msg["From"] = f"【HESUPIPE】 <{settings.SMTP_USER}>"
|
||||
msg["To"] = settings.NOTIFY_EMAIL
|
||||
msg["From"] = f"【{cfg['sender_name']}】 <{cfg['user']}>"
|
||||
msg["To"] = ", ".join(cfg["notify_emails"])
|
||||
msg.attach(MIMEText(
|
||||
f"<html><body><p>这是本会话第 {count} 次更正(上限 3 次),请以本封为准:</p>"
|
||||
f"<table>{_contact_rows(body_contacts)}</table>"
|
||||
f"</body></html>", "html", "utf-8"))
|
||||
return await _send(msg)
|
||||
return await _send(msg, cfg)
|
||||
|
||||
Reference in New Issue
Block a user