233 lines
9.4 KiB
Python
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
|