geo/backend/app/services/email/email_scheduler.py

233 lines
9.4 KiB
Python

from __future__ import annotations
import logging
from datetime import date, timedelta
from pathlib import Path
from typing import Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.subscription import Subscription
from app.models.user import User
from app.services.email_service import EmailService
logger = logging.getLogger(__name__)
TEMPLATES_DIR = Path(__file__).resolve().parent.parent.parent / "templates"
class EmailScheduler:
def __init__(self, email_service: EmailService | None = None):
if email_service is None:
from app.config import settings
email_service = EmailService(
simulate_mode=(settings.EMAIL_MODE == "mock"),
smtp_host=settings.SMTP_HOST,
smtp_port=settings.SMTP_PORT,
smtp_user=settings.SMTP_USER,
smtp_password=settings.SMTP_PASSWORD,
)
self.email_service = email_service
def _load_template(self, template_name: str) -> str:
template_path = TEMPLATES_DIR / template_name
return template_path.read_text(encoding="utf-8")
def _render_template(self, template_html: str, context: dict[str, Any]) -> str:
result = template_html
for key, value in context.items():
result = result.replace("{{" + key + "}}", str(value))
return result
async def send_geo_weekly_report(self, db: AsyncSession) -> int:
stmt = select(User).where(User.isActive == True) # noqa: E712
result = await db.execute(stmt)
users = result.scalars().all()
sent_count = 0
template_html = self._load_template("geo_weekly_report.html")
for user in users:
try:
context = {
"user_name": user.name or user.email,
"score_change": "+5",
"score_direction": "up",
"current_score": "78",
"previous_score": "73",
"top_improved": "内容质量 (+12%), AI引用率 (+8%)",
"top_declined": "品牌权威 (-3%)",
"suggestions": "建议增加技术白皮书内容以提升品牌权威度",
"report_link": "https://geo-platform.com/dashboard/monitoring",
"year": str(date.today().year),
}
body_html = self._render_template(template_html, context)
msg = self.email_service.render_template(
"alert_notification",
user.email,
{
"alert_type": "GEO周报",
"brand_name": "全部品牌",
"severity": "info",
"description": "GEO周度变化报告",
"timestamp": date.today().isoformat(),
},
)
msg.body_html = body_html
msg.subject = f"[GEO平台] GEO周报 - {date.today().isoformat()}"
send_result = self.email_service.send_email(msg)
if send_result.success:
sent_count += 1
except Exception as e:
logger.error(f"发送周报邮件失败: {user.email}, 错误: {e}")
logger.info(f"GEO周报发送完成: {sent_count}/{len(users)}")
return sent_count
async def send_renewal_reminder(self, db: AsyncSession) -> int:
today = date.today()
thresholds = [7, 3, 1]
sent_count = 0
template_html = self._load_template("renewal_reminder.html")
for days in thresholds:
target_date = today + timedelta(days=days)
stmt = select(Subscription).where(
Subscription.status == "active",
Subscription.end_date == target_date,
)
result = await db.execute(stmt)
subscriptions = result.scalars().all()
for sub in subscriptions:
try:
user_stmt = select(User).where(User.id == sub.user_id)
user_result = await db.execute(user_stmt)
user = user_result.scalar_one_or_none()
if user is None:
continue
from app.services.subscription import PLANS
plan_data = PLANS.get(sub.plan, {})
plan_name = plan_data.get("name", sub.plan)
plan_price = plan_data.get("price", 0)
context = {
"user_name": user.name or user.email,
"plan_name": plan_name,
"end_date": sub.end_date.isoformat(),
"days_remaining": str(days),
"plan_price": str(plan_price),
"renew_link": "https://geo-platform.com/dashboard/subscription",
"year": str(today.year),
}
body_html = self._render_template(template_html, context)
msg = self.email_service.render_template(
"alert_notification",
user.email,
{
"alert_type": "续费提醒",
"brand_name": plan_name,
"severity": "warning",
"description": f"订阅将在{days}天后到期",
"timestamp": today.isoformat(),
},
)
msg.body_html = body_html
msg.subject = f"[GEO平台] 您的{plan_name}将在{days}天后到期"
send_result = self.email_service.send_email(msg)
if send_result.success:
sent_count += 1
except Exception as e:
logger.error(f"发送续费提醒失败: {sub.user_id}, 错误: {e}")
logger.info(f"续费提醒发送完成: {sent_count}")
return sent_count
async def send_trial_expiring_reminder(self, db: AsyncSession) -> int:
today = date.today()
target_date = today + timedelta(days=3)
stmt = select(Subscription).where(
Subscription.status == "active",
Subscription.plan == "starter",
Subscription.end_date == target_date,
)
result = await db.execute(stmt)
subscriptions = result.scalars().all()
sent_count = 0
template_html = self._load_template("trial_expiring.html")
for sub in subscriptions:
try:
user_stmt = select(User).where(User.id == sub.user_id)
user_result = await db.execute(user_stmt)
user = user_result.scalar_one_or_none()
if user is None:
continue
context = {
"user_name": user.name or user.email,
"days_remaining": "3",
"upgrade_link": "https://geo-platform.com/dashboard/subscription",
"year": str(today.year),
}
body_html = self._render_template(template_html, context)
msg = self.email_service.render_template(
"alert_notification",
user.email,
{
"alert_type": "试用到期提醒",
"brand_name": "入门版",
"severity": "warning",
"description": "试用将在3天后到期",
"timestamp": today.isoformat(),
},
)
msg.body_html = body_html
msg.subject = "[GEO平台] 您的试用将在3天后到期"
send_result = self.email_service.send_email(msg)
if send_result.success:
sent_count += 1
except Exception as e:
logger.error(f"发送试用到期提醒失败: {sub.user_id}, 错误: {e}")
logger.info(f"试用到期提醒发送完成: {sent_count}")
return sent_count
async def send_welcome_email(self, user_email: str, user_name: str) -> bool:
try:
template_html = self._load_template("welcome.html")
context = {
"user_name": user_name or user_email,
"dashboard_link": "https://geo-platform.com/dashboard",
"diagnosis_link": "https://geo-platform.com/dashboard/diagnosis",
"help_link": "https://geo-platform.com/help",
"year": str(date.today().year),
}
body_html = self._render_template(template_html, context)
msg = self.email_service.render_template(
"alert_notification",
user_email,
{
"alert_type": "欢迎",
"brand_name": "GEO平台",
"severity": "info",
"description": "欢迎加入GEO平台",
"timestamp": date.today().isoformat(),
},
)
msg.body_html = body_html
msg.subject = "[GEO平台] 欢迎加入GEO平台"
send_result = self.email_service.send_email(msg)
return send_result.success
except Exception as e:
logger.error(f"发送欢迎邮件失败: {user_email}, 错误: {e}")
return False