import aiosmtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from sqlalchemy import select from app.core.database import async_session from app.core.config import get_settings from app.models.models import SystemSetting settings = get_settings() async def get_site_url() -> str: async with async_session() as db: result = await db.execute(select(SystemSetting).where(SystemSetting.key == "site_url")) setting = result.scalar_one_or_none() if setting and setting.value.strip(): return setting.value.strip().rstrip("/") return "http://localhost:806" async def get_smtp_config() -> dict: async with async_session() as db: result = await db.execute( select(SystemSetting).where( SystemSetting.key.in_([ "smtp_host", "smtp_port", "smtp_user", "smtp_password", "smtp_from", "smtp_use_tls", ]) ) ) rows = {r.key: r.value for r in result.scalars().all()} if rows.get("smtp_host") and rows.get("smtp_user"): return { "host": rows.get("smtp_host", ""), "port": int(rows.get("smtp_port", "465")), "user": rows.get("smtp_user", ""), "password": rows.get("smtp_password", ""), "from_addr": rows.get("smtp_from") or rows.get("smtp_user", ""), "use_tls": rows.get("smtp_use_tls", "true") == "true", } return { "host": settings.SMTP_HOST, "port": settings.SMTP_PORT, "user": settings.SMTP_USER, "password": settings.SMTP_PASSWORD, "from_addr": settings.SMTP_FROM or settings.SMTP_USER, "use_tls": settings.SMTP_USE_TLS, } async def send_email(to_email: str, subject: str, body: str) -> bool: smtp = await get_smtp_config() if not smtp["host"] or not smtp["user"]: return False msg = MIMEMultipart("alternative") msg["From"] = smtp["from_addr"] msg["To"] = to_email msg["Subject"] = subject msg.attach(MIMEText(body, "html", "utf-8")) try: await aiosmtplib.send( msg, hostname=smtp["host"], port=smtp["port"], username=smtp["user"], password=smtp["password"], use_tls=smtp["use_tls"], ) return True except Exception: return False def build_repayment_reminder_html( nickname: str, creditor: str, amount_yuan: float, due_date: str, days_left: int, is_overdue: bool = False ) -> str: status_text = f"已逾期 {abs(days_left)} 天" if is_overdue else f"还有 {days_left} 天到期" color = "#ff3b30" if is_overdue else "#0071e3" title = "逾期提醒" if is_overdue else "还款提醒" return f"""
Hi {nickname},
您有一笔借款即将到期:
债权人:{creditor}
金额:¥{amount_yuan:,.2f}
到期日:{due_date}
{status_text}
—— 债务管理系统
Hi {nickname},
{cfg['desc']}
异常登录次数:{count} 次
触发来源 IP:{ip}
时间:{__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
此链接 {ttl_minutes} 分钟内有效,仅可使用一次。
如非本人操作,请忽略此邮件并尽快检查账户安全。
—— 债务管理系统
Hi {nickname},
您的账户密码已被{action}。
操作类型:密码{action}
操作时间:{now}
{"操作人:" + operator + "
" if operator else ""}如非本人操作,请立即联系管理员并修改密码。
—— 债务管理系统
Hi {nickname},
管理员已为您发起密码重置,请点击下方按钮设置新密码。
操作人:{operator}
发送时间:{now}
有效期:{ttl_hours} 小时内有效
如非本人操作,请忽略此邮件。
—— 债务管理系统