mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 05:56:29 +00:00
feat: migrate from Element Plus to Ant Design Vue and update Vite configuration for better dependency management
- Updated Vite configuration to manually chunk Ant Design Vue for improved dependency management. - Added a comprehensive migration testing checklist for transitioning from Element Plus 2.13.0 to Ant Design Vue 4.x, covering various components and functionalities.
This commit is contained in:
@@ -1,24 +1,33 @@
|
||||
import smtplib
|
||||
"""
|
||||
邮件业务服务 (高级)
|
||||
|
||||
职能:提供业务相关的邮件操作
|
||||
- 新用户注册通知
|
||||
- 用户审批通知
|
||||
- 打卡结果通知
|
||||
- Token 到期提醒
|
||||
- 调用底层 EmailNotifier 发送邮件
|
||||
"""
|
||||
|
||||
import logging
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from typing import List
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from backend.config import settings
|
||||
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: 收件人邮箱列表
|
||||
@@ -28,39 +37,7 @@ class EmailService:
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
# 检查邮件配置
|
||||
if not all([settings.SMTP_SERVER, settings.SMTP_SENDER_EMAIL, settings.SMTP_SENDER_PASSWORD]):
|
||||
logger.warning("邮件配置不完整,跳过发送邮件")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 创建邮件
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['From'] = settings.SMTP_SENDER_EMAIL
|
||||
msg['To'] = ', '.join(to_emails)
|
||||
msg['Subject'] = subject
|
||||
|
||||
# 添加 HTML 正文
|
||||
html_part = MIMEText(body_html, 'html', 'utf-8')
|
||||
msg.attach(html_part)
|
||||
|
||||
# 连接 SMTP 服务器并发送
|
||||
if settings.SMTP_USE_SSL:
|
||||
server = smtplib.SMTP_SSL(settings.SMTP_SERVER, settings.SMTP_PORT)
|
||||
else:
|
||||
server = smtplib.SMTP(settings.SMTP_SERVER, settings.SMTP_PORT)
|
||||
server.starttls()
|
||||
|
||||
server.login(settings.SMTP_SENDER_EMAIL, settings.SMTP_SENDER_PASSWORD)
|
||||
server.sendmail(settings.SMTP_SENDER_EMAIL, to_emails, msg.as_string())
|
||||
server.quit()
|
||||
|
||||
logger.info(f"邮件发送成功: {subject} -> {', '.join(to_emails)}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"邮件发送失败: {e}")
|
||||
return False
|
||||
return EmailNotifier.send_email(to_emails, subject, body_html)
|
||||
|
||||
@staticmethod
|
||||
def notify_new_user_registration(user: User, db: Session) -> bool:
|
||||
@@ -76,7 +53,12 @@ class EmailService:
|
||||
"""
|
||||
# 查询所有管理员邮箱
|
||||
admins = db.query(User).filter(User.role == "admin", User.email.isnot(None)).all()
|
||||
admin_emails = [admin.email for admin in admins if admin.email]
|
||||
# 使用 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("没有找到管理员邮箱,无法发送通知")
|
||||
@@ -85,6 +67,10 @@ class EmailService:
|
||||
# 构建邮件内容
|
||||
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"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -161,11 +147,7 @@ class EmailService:
|
||||
</tr>
|
||||
<tr>
|
||||
<td>注册时间</td>
|
||||
<td>{user.created_at.strftime('%Y-%m-%d %H:%M:%S') if user.created_at else '未知'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>注册 IP</td>
|
||||
<td>{user.registered_ip or '未记录'}</td>
|
||||
<td>{created_time}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -175,7 +157,7 @@ class EmailService:
|
||||
<p>请登录管理后台进行审批操作。</p>
|
||||
</div>
|
||||
|
||||
<p>登录地址:<a href="http://localhost:5173/admin/users">http://localhost:5173/admin/users</a></p>
|
||||
<p>登录地址:<a href="{settings.FRONTEND_URL}/admin/users">{settings.FRONTEND_URL}/admin/users</a></p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>此邮件由系统自动发送,请勿直接回复。</p>
|
||||
@@ -188,6 +170,366 @@ class EmailService:
|
||||
|
||||
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"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}}
|
||||
.container {{
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}}
|
||||
.header {{
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}}
|
||||
.content {{
|
||||
background-color: #f9f9f9;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0 0 5px 5px;
|
||||
}}
|
||||
.info-table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 15px 0;
|
||||
}}
|
||||
.info-table td {{
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}}
|
||||
.info-table td:first-child {{
|
||||
font-weight: bold;
|
||||
width: 120px;
|
||||
}}
|
||||
.footer {{
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}}
|
||||
.success-box {{
|
||||
background-color: #d4edda;
|
||||
border-left: 4px solid #28a745;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}}
|
||||
.btn {{
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h2>🎉 恭喜!账户审批通过</h2>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>您好,{user.alias}!</p>
|
||||
<p>恭喜您的账户已通过管理员审批,现在可以使用所有功能了。</p>
|
||||
|
||||
<div class="success-box">
|
||||
<strong>✅ 审批结果:</strong> 已通过
|
||||
<br>
|
||||
<strong>审批时间:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||
</div>
|
||||
|
||||
<table class="info-table">
|
||||
<tr>
|
||||
<td>用户名</td>
|
||||
<td>{user.alias}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>账户角色</td>
|
||||
<td>{user.role}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>注册时间</td>
|
||||
<td>{created_time}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p><strong>接下来您可以:</strong></p>
|
||||
<ul>
|
||||
<li>登录系统创建自动打卡任务</li>
|
||||
<li>配置打卡时间和内容</li>
|
||||
<li>查看打卡记录和统计</li>
|
||||
</ul>
|
||||
|
||||
<p style="text-align: center;">
|
||||
<a href="{settings.FRONTEND_URL}/login" class="btn">立即登录</a>
|
||||
</p>
|
||||
|
||||
<p style="color: #666; font-size: 14px;">
|
||||
💡 <strong>温馨提示:</strong>如果您还没有设置密码,建议在个人设置中设置密码,方便后续登录。
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>此邮件由系统自动发送,请勿直接回复。</p>
|
||||
<p>接龙自动打卡系统 © {datetime.now().year}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
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"<p><strong>拒绝原因:</strong>{reason}</p>" if reason else ""
|
||||
|
||||
body_html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}}
|
||||
.container {{
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}}
|
||||
.header {{
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}}
|
||||
.content {{
|
||||
background-color: #f9f9f9;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0 0 5px 5px;
|
||||
}}
|
||||
.footer {{
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}}
|
||||
.error-box {{
|
||||
background-color: #f8d7da;
|
||||
border-left: 4px solid #dc3545;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h2>账户审批结果通知</h2>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>您好,{user.alias}!</p>
|
||||
<p>很遗憾,您的账户注册申请未能通过审批。</p>
|
||||
|
||||
<div class="error-box">
|
||||
<strong>❌ 审批结果:</strong> 未通过
|
||||
<br>
|
||||
<strong>处理时间:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||
</div>
|
||||
|
||||
{reason_html}
|
||||
|
||||
<p>如有疑问,请联系系统管理员。</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>此邮件由系统自动发送,请勿直接回复。</p>
|
||||
<p>接龙自动打卡系统 © {datetime.now().year}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</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"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}}
|
||||
.container {{
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}}
|
||||
.header {{
|
||||
background-color: #ff9800;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}}
|
||||
.content {{
|
||||
background-color: #f9f9f9;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0 0 5px 5px;
|
||||
}}
|
||||
.warning-box {{
|
||||
background-color: #fff3cd;
|
||||
border-left: 4px solid #ff9800;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}}
|
||||
.footer {{
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}}
|
||||
.btn {{
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h2>⚠️ 登录凭证即将过期</h2>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>您好,{user.alias}!</p>
|
||||
<p>您的 QQ 登录凭证即将在 <strong>{minutes_left} 分钟</strong>后过期。</p>
|
||||
|
||||
<div class="warning-box">
|
||||
<strong>⚠️ 重要提示:</strong>
|
||||
<ul style="margin: 10px 0; padding-left: 20px;">
|
||||
<li>登录凭证过期后,系统将无法自动执行您的打卡任务</li>
|
||||
<li>建议尽快登录系统刷新凭证</li>
|
||||
<li>如果您已设置密码,可以使用密码登录后扫码刷新凭证</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p><strong>如何刷新凭证:</strong></p>
|
||||
<ol style="margin: 10px 0; padding-left: 20px;">
|
||||
<li>登录系统(扫码或密码登录)</li>
|
||||
<li>在个人设置中点击"刷新凭证"</li>
|
||||
<li>使用手机 QQ 扫描二维码完成刷新</li>
|
||||
</ol>
|
||||
|
||||
<p style="text-align: center;">
|
||||
<a href="{settings.FRONTEND_URL}/login" class="btn">立即登录刷新</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>此邮件由系统自动发送,请勿直接回复。</p>
|
||||
<p>接龙自动打卡系统 © {datetime.now().year}</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
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:
|
||||
"""
|
||||
@@ -202,7 +544,8 @@ class EmailService:
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
if not user.email:
|
||||
user_email = user.email
|
||||
if user_email is None:
|
||||
logger.info(f"用户 {user.alias} 未设置邮箱,跳过打卡通知")
|
||||
return False
|
||||
|
||||
@@ -298,4 +641,4 @@ class EmailService:
|
||||
</html>
|
||||
"""
|
||||
|
||||
return EmailService.send_email([user.email], subject, body_html)
|
||||
return EmailService.send_email([str(user_email)], subject, body_html)
|
||||
|
||||
Reference in New Issue
Block a user