""" 邮件业务服务 (高级) 职能:提供业务相关的邮件操作 - 新用户注册通知 - 用户审批通知 - 打卡结果通知 - Token 到期提醒 - 调用底层 EmailNotifier 发送邮件 """ import logging from datetime import datetime from typing import List from sqlalchemy.orm import Session from backend.models import User from backend.workers.email_notifier import EmailNotifier from backend.config import settings logger = logging.getLogger(__name__) class EmailService: """邮件业务服务(高级服务)""" @staticmethod def send_email(to_emails: List[str], subject: str, body_html: str) -> bool: """ 发送邮件(业务层方法,调用底层 EmailNotifier) Args: to_emails: 收件人邮箱列表 subject: 邮件主题 body_html: 邮件正文(HTML 格式) Returns: 是否发送成功 """ return EmailNotifier.send_email(to_emails, subject, body_html) @staticmethod def notify_new_user_registration(user: User, db: Session) -> bool: """ 通知管理员有新用户注册 Args: user: 新注册的用户 db: 数据库会话 Returns: 是否发送成功 """ # 查询所有管理员邮箱 admins = db.query(User).filter(User.role == "admin", User.email.isnot(None)).all() # 使用 str() 转换避免类型检查问题,并过滤空值 admin_emails: List[str] = [] for admin in admins: email_value = admin.email if email_value is not None: # 使用 is not None 避免布尔转换 admin_emails.append(str(email_value)) if not admin_emails: logger.warning("没有找到管理员邮箱,无法发送通知") return False # 构建邮件内容 subject = f"【接龙自动打卡系统】新用户注册通知 - {user.alias}" # 安全获取创建时间 created_at_value = user.created_at created_time = created_at_value.strftime('%Y-%m-%d %H:%M:%S') if created_at_value is not None else '未知' body_html = f"""

🔔 新用户注册通知

尊敬的管理员,

有新用户注册了接龙自动打卡系统,请及时审批。

用户名 {user.alias}
用户 ID {user.id}
注册时间 {created_time}
⚠️ 重要提示:

该用户需要在 24 小时内通过审批,否则账户将被自动删除。

请登录管理后台进行审批操作。

登录地址:{settings.FRONTEND_URL}/admin/users

""" return EmailService.send_email(admin_emails, subject, body_html) @staticmethod def notify_user_approved(user: User) -> bool: """ 通知用户审批已通过 Args: user: 已通过审批的用户 Returns: 是否发送成功 """ user_email = user.email if user_email is None: logger.info(f"用户 {user.alias} 未设置邮箱,跳过审批通知") return False # 构建邮件内容 subject = f"【接龙自动打卡系统】账户审批通过 - {user.alias}" # 安全获取创建时间 user_created_at = user.created_at created_time = user_created_at.strftime('%Y-%m-%d %H:%M:%S') if user_created_at is not None else '未知' body_html = f"""

🎉 恭喜!账户审批通过

您好,{user.alias}!

恭喜您的账户已通过管理员审批,现在可以使用所有功能了。

✅ 审批结果: 已通过
审批时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
用户名 {user.alias}
账户角色 {user.role}
注册时间 {created_time}

接下来您可以:

立即登录

💡 温馨提示:如果您还没有设置密码,建议在个人设置中设置密码,方便后续登录。

""" return EmailService.send_email([str(user_email)], subject, body_html) @staticmethod def notify_user_rejected(user: User, reason: str = "") -> bool: """ 通知用户审批被拒绝 Args: user: 被拒绝的用户 reason: 拒绝原因(可选) Returns: 是否发送成功 """ user_email = user.email if user_email is None: logger.info(f"用户 {user.alias} 未设置邮箱,跳过拒绝通知") return False # 构建邮件内容 subject = f"【接龙自动打卡系统】账户审批结果 - {user.alias}" reason_html = f"

拒绝原因:{reason}

" if reason else "" body_html = f"""

账户审批结果通知

您好,{user.alias}!

很遗憾,您的账户注册申请未能通过审批。

❌ 审批结果: 未通过
处理时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
{reason_html}

如有疑问,请联系系统管理员。

""" return EmailService.send_email([str(user_email)], subject, body_html) @staticmethod def notify_token_expiring(user: User, jwt_exp: str) -> bool: """ 通知用户 Token 即将过期 Args: user: 用户对象 jwt_exp: Token 过期时间戳 Returns: 是否发送成功 """ user_email = user.email if user_email is None: logger.info(f"用户 {user.alias} 未设置邮箱,跳过 Token 过期通知") return False # 计算剩余时间 try: exp_timestamp = int(jwt_exp) current_timestamp = int(datetime.now().timestamp()) minutes_left = (exp_timestamp - current_timestamp) // 60 except ValueError: minutes_left = 0 # 构建邮件内容 subject = f"【接龙自动打卡系统】登录凭证即将过期 - {user.alias}" body_html = f"""

⚠️ 登录凭证即将过期

您好,{user.alias}!

您的 QQ 登录凭证即将在 {minutes_left} 分钟后过期。

⚠️ 重要提示:
  • 登录凭证过期后,系统将无法自动执行您的打卡任务
  • 建议尽快登录系统刷新凭证
  • 如果您已设置密码,可以使用密码登录后扫码刷新凭证

如何刷新凭证:

  1. 登录系统(扫码或密码登录)
  2. 在个人设置旁的按钮中进行刷新 Token
  3. 使用手机 QQ 扫描二维码完成刷新

立即登录刷新

""" return EmailService.send_email([str(user_email)], subject, body_html) @staticmethod def notify_token_expired(user: User) -> bool: """ 通知用户 Token 已过期 Args: user: 用户对象 Returns: 是否发送成功 """ user_email = user.email if user_email is None: logger.info(f"用户 {user.alias} 未设置邮箱,跳过 Token 已过期通知") return False # 构建邮件内容 subject = f"【接龙自动打卡系统】登录凭证已过期 - {user.alias}" body_html = f"""

❌ 登录凭证已过期

您好,{user.alias}!

您的 QQ 登录凭证已过期,系统已无法自动执行打卡任务。

⚠️ 重要提示:
  • 登录凭证已过期,所有自动打卡任务已暂停
  • 请尽快登录系统刷新凭证以恢复服务
  • 如果您已设置密码,可以使用密码登录后扫码刷新凭证

如何刷新 Token:

  1. 登录系统(扫码或密码登录)
  2. 在个人设置旁的按钮中进行刷新 Token
  3. 使用手机 QQ 扫描二维码完成刷新

立即登录刷新

""" return EmailService.send_email([str(user_email)], subject, body_html) @staticmethod def notify_check_in_result(user: User, task_info: dict, success: bool, message: str = "") -> bool: """ 通知用户打卡结果 Args: user: 用户对象 task_info: 打卡任务信息(包含 thread_id, texts, values 等) success: 打卡是否成功 message: 额外消息 Returns: 是否发送成功 """ user_email = user.email if user_email is None: logger.info(f"用户 {user.alias} 未设置邮箱,跳过打卡通知") return False # 构建邮件内容 status_text = "✅ 成功" if success else "❌ 失败" status_color = "#28a745" if success else "#dc3545" subject = f"【接龙自动打卡】打卡{status_text} - {user.alias}" body_html = f"""

打卡通知 {status_text}

您好,{user.alias}!

您的接龙自动打卡任务已执行。

{f'' if message else ''}
执行时间 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
任务 ID {task_info.get('thread_id', '未知')}
打卡状态 {status_text}
详细信息{message}

如有问题,请及时检查您的打卡配置。

""" return EmailService.send_email([str(user_email)], subject, body_html)