Files
JieXi/utils/email.py
2025-11-28 21:20:40 +08:00

153 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import smtplib
import ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formataddr
from email.header import Header
from models import SMTPConfig
import random
class EmailService:
"""邮件服务类"""
@staticmethod
def get_smtp_config():
"""获取可用的SMTP配置负载均衡"""
configs = SMTPConfig.query.filter_by(is_enabled=True).all()
if not configs:
raise Exception("没有可用的SMTP配置")
# 加权随机选择
total_weight = sum(c.weight for c in configs)
if total_weight == 0:
return random.choice(configs)
rand = random.uniform(0, total_weight)
current = 0
for config in configs:
current += config.weight
if rand <= current:
return config
return configs[-1]
@staticmethod
def _do_send(smtp_config, to_email: str, subject: str, content: str, html: bool = True):
"""实际发送邮件的内部方法"""
server = None
try:
print(f"[SMTP调试] 开始发送邮件")
print(f"[SMTP调试] 服务器: {smtp_config.host}:{smtp_config.port}")
print(f"[SMTP调试] 用户名: {smtp_config.username}")
print(f"[SMTP调试] 使用TLS: {smtp_config.use_tls}")
print(f"[SMTP调试] 发件人: {smtp_config.from_email}")
msg = MIMEMultipart('alternative')
from_email = smtp_config.from_email or smtp_config.username
from_name = smtp_config.from_name or smtp_config.username
msg['From'] = formataddr((str(Header(from_name, 'utf-8')), from_email))
msg['To'] = to_email
msg['Subject'] = Header(subject, 'utf-8')
if html:
msg.attach(MIMEText(content, 'html', 'utf-8'))
else:
msg.attach(MIMEText(content, 'plain', 'utf-8'))
print(f"[SMTP调试] 开始连接服务器...")
if smtp_config.port == 465:
print(f"[SMTP调试] 使用 SSL 模式(端口 465")
server = smtplib.SMTP_SSL(smtp_config.host, smtp_config.port, timeout=30)
elif smtp_config.use_tls:
print(f"[SMTP调试] 使用 STARTTLS 模式(端口 {smtp_config.port}")
server = smtplib.SMTP(smtp_config.host, smtp_config.port, timeout=30)
server.ehlo()
context = ssl.create_default_context()
server.starttls(context=context)
server.ehlo()
else:
print(f"[SMTP调试] 使用普通 SMTP 模式(端口 {smtp_config.port}")
server = smtplib.SMTP(smtp_config.host, smtp_config.port, timeout=30)
print(f"[SMTP调试] 连接成功,开始登录...")
server.login(smtp_config.username, smtp_config.password)
print(f"[SMTP调试] 登录成功,开始发送邮件...")
server.sendmail(from_email, [to_email], msg.as_string())
print(f"[SMTP调试] 邮件发送成功!")
# 更新发送统计
smtp_config.send_count += 1
from models import db
db.session.commit()
return True
finally:
if server:
try:
server.quit()
except:
pass
@staticmethod
def send_email(to_email: str, subject: str, content: str, html: bool = True):
"""发送邮件(支持故障自动转移)"""
configs = SMTPConfig.query.filter_by(is_enabled=True).all()
if not configs:
raise Exception("没有可用的SMTP配置")
# 按权重排序,优先尝试高权重的配置
configs.sort(key=lambda x: x.weight, reverse=True)
last_error = None
tried_ids = []
for smtp_config in configs:
tried_ids.append(smtp_config.id)
try:
return EmailService._do_send(smtp_config, to_email, subject, content, html)
except Exception as e:
last_error = str(e)
print(f"[SMTP调试] {smtp_config.name} 发送失败: {last_error}")
# 更新失败统计
smtp_config.fail_count += 1
from models import db
db.session.commit()
# 如果还有其他配置,继续尝试
remaining = len(configs) - len(tried_ids)
if remaining > 0:
print(f"[SMTP调试] 尝试下一个SMTP配置剩余 {remaining}")
continue
# 所有配置都失败了
raise Exception(f"所有SMTP配置均发送失败最后错误: {last_error}")
@staticmethod
def send_verification_code(to_email: str, code: str, purpose: str):
"""发送验证码邮件"""
purpose_text = {
'register': '注册账号',
'reset_password': '重置密码',
'forgot_password': '找回密码'
}.get(purpose, '验证')
subject = f"【短视频解析平台】{purpose_text}验证码"
content = f"""
<html>
<body style="font-family: Arial, sans-serif; padding: 20px;">
<h2>验证码</h2>
<p>您正在进行<strong>{purpose_text}</strong>操作,验证码为:</p>
<h1 style="color: #4CAF50; letter-spacing: 5px;">{code}</h1>
<p>验证码有效期为10分钟请勿泄露给他人。</p>
<hr>
<p style="color: #999; font-size: 12px;">如果这不是您的操作,请忽略此邮件。</p>
</body>
</html>
"""
return EmailService.send_email(to_email, subject, content, html=True)