feat(email): 添加发件人邮箱字段并优化邮件发送逻辑

添加独立的sender_email字段作为发件人邮箱,优先使用该字段而非smtp_username
更新相关表单、模型和测试用例以支持新字段
重构邮件发送逻辑,统一邮箱格式验证和错误提示
This commit is contained in:
2026-01-18 18:35:09 +08:00
parent 2320133c20
commit e22bd4a8c3
5 changed files with 107 additions and 45 deletions

View File

@@ -52,6 +52,7 @@ def test_celery_email_config():
'smtp_server': config.smtp_server,
'smtp_port': config.smtp_port,
'smtp_username': config.smtp_username,
'sender_email': config.sender_email,
'recipient_email': config.recipient_email,
}
@@ -81,9 +82,10 @@ def test_celery_email_config():
import re
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
# 验证发件人邮箱格式
if config.smtp_username and not re.match(email_pattern, config.smtp_username):
logger.error(f"发件人邮箱格式不正确: {config.smtp_username}")
# 验证发件人邮箱格式优先使用sender_email其次使用smtp_username
sender_email = config.sender_email or config.smtp_username
if sender_email and not re.match(email_pattern, sender_email):
logger.error(f"发件人邮箱格式不正确: {sender_email}")
logger.error("请在系统配置页面输入有效的邮箱地址")
return False
@@ -200,9 +202,10 @@ def celery_send_test_email(self, test_mode=True):
from core.models import SystemConfig
from django.core.mail.backends.smtp import EmailBackend
config = SystemConfig.get_config()
from_email = config.smtp_username
# 优先使用sender_email其次使用smtp_username作为发件人
from_email = config.sender_email or config.smtp_username
if not from_email:
raise ValueError("未配置发件邮箱 (smtp_username)")
raise ValueError("未配置发件邮箱")
# 验证发件人邮箱格式
import re
@@ -265,7 +268,7 @@ def celery_send_test_email(self, test_mode=True):
任务ID: {task_id}
这是一封测试邮件用于验证Celery异步邮件发送功能。
""
"""
# 创建邮件
email = EmailMessage(
@@ -342,7 +345,8 @@ def celery_send_html_report_email(self, include_attachment=False):
from core.models import SystemConfig
from django.core.mail.backends.smtp import EmailBackend
config = SystemConfig.get_config()
from_email = config.smtp_username
# 优先使用sender_email其次使用smtp_username作为发件人
from_email = config.sender_email or config.smtp_username
if not from_email:
raise ValueError("未配置发件邮箱")
@@ -380,29 +384,29 @@ def celery_send_html_report_email(self, include_attachment=False):
'insights': {'count': 0, 'items': []},
}
# 创建HTML邮件内容
html_content = f"""
# 创建HTML邮件内容使用普通字符串拼接避免f-string中的%语法问题)
html_content = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {{ font-family: 'Microsoft YaHei', Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; }}
.header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px 20px; text-align: center; }}
.header h1 {{ margin: 0; font-size: 24px; }}
.header .date {{ font-size: 14px; opacity: 0.9; margin-top: 10px; }}
.content {{ padding: 20px; background: #f9f9f9; }}
.section {{ background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
.section h2 {{ color: #667eea; font-size: 18px; margin-top: 0; border-bottom: 2px solid #667eea; padding-bottom: 10px; }}
.stat {{ display: inline-block; background: #667eea; color: white; padding: 5px 15px; border-radius: 20px; margin: 5px; }}
.footer {{ text-align: center; padding: 20px; color: #666; font-size: 12px; }}
.success-badge {{ background: #28a745; color: white; padding: 10px 20px; border-radius: 5px; display: inline-block; }}
body { font-family: 'Microsoft YaHei', Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px 20px; text-align: center; }
.header h1 { margin: 0; font-size: 24px; }
.header .date { font-size: 14px; opacity: 0.9; margin-top: 10px; }
.content { padding: 20px; background: #f9f9f9; }
.section { background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.section h2 { color: #667eea; font-size: 18px; margin-top: 0; border-bottom: 2px solid #667eea; padding-bottom: 10px; }
.stat { display: inline-block; background: #667eea; color: white; padding: 5px 15px; border-radius: 20px; margin: 5px; }
.footer { text-align: center; padding: 20px; color: #666; font-size: 12px; }
.success-badge { background: #28a745; color: white; padding: 10px 20px; border-radius: 5px; display: inline-block; }
</style>
</head>
<body>
<div class="header">
<h1>📊 家庭日报</h1>
<div class="date">{report_data['today']}</div>
<div class="date">{today}</div>
</div>
<div class="content">
@@ -410,15 +414,15 @@ def celery_send_html_report_email(self, include_attachment=False):
<h2>✅ 邮件发送测试</h2>
<p>这是一封通过 <strong>Celery异步任务</strong> 发送的HTML格式邮件。</p>
<p><strong>任务ID:</strong> {task_id}</p>
<p><strong>发送时间:</strong> {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p><strong>发送时间:</strong> {send_time}</p>
<p><strong>状态:</strong> <span class="success-badge">发送成功</span></p>
</div>
<div class="section">
<h2>📈 统计信息</h2>
<p>阅读记录: <span class="stat">{report_data['reading']['count']} </span></p>
<p>感悟记录: <span class="stat">{report_data['insights']['count']} </span></p>
<p>家庭事项: <span class="stat">{len(report_data['tasks'])} </span></p>
<p>阅读记录: <span class="stat">{reading_count} 篇</span></p>
<p>感悟记录: <span class="stat">{insights_count} 条</span></p>
<p>家庭事项: <span class="stat">{tasks_count} 项</span></p>
</div>
<div class="section">
@@ -435,7 +439,14 @@ def celery_send_html_report_email(self, include_attachment=False):
</div>
</body>
</html>
"""
'''.format(
today=report_data['today'],
task_id=task_id,
send_time=timezone.now().strftime('%Y-%m-%d %H:%M:%S'),
reading_count=report_data['reading']['count'],
insights_count=report_data['insights']['count'],
tasks_count=len(report_data['tasks'])
)
# 创建邮件后端
backend = EmailBackend(
@@ -462,13 +473,16 @@ def celery_send_html_report_email(self, include_attachment=False):
# 添加附件(如果需要)
if include_attachment:
attachment_content = f"""家庭日报测试报告
发送时间: {timezone.now().isoformat()}
attachment_content = '''家庭日报测试报告
发送时间: {send_time}
任务ID: {task_id}
发送方式: Celery异步任务
这是一份自动生成的测试报告
"""
这是一份自动生成的测试报告
'''.format(
send_time=timezone.now().isoformat(),
task_id=task_id
)
email.attach('daily_report_test.txt', attachment_content, 'text/plain')
logger.info(f"[任务 {task_id}] 已添加测试附件")
@@ -604,7 +618,8 @@ def main():
# 从数据库获取邮件配置
config = SystemConfig.get_config()
from_email = config.smtp_username
# 优先使用sender_email其次使用smtp_username作为发件人
from_email = config.sender_email or config.smtp_username
if not from_email:
raise ValueError("未配置发件邮箱")
@@ -628,19 +643,21 @@ def main():
fail_silently=False
)
subject = f"[直接测试] Celery邮件测试 - {timezone.now().strftime('%H:%M:%S')}"
body = f"""
这是直接发送的测试邮件用于验证SMTP配置
subject = "[直接测试] Celery邮件测试 - {}".format(timezone.now().strftime('%H:%M:%S'))
body = '''
这是直接发送的测试邮件用于验证SMTP配置
发送时间: {timezone.now().isoformat()}
发送时间: {send_time}
发送方式: 直接发送非Celery
如果Celery异步任务测试失败但此测试成功
说明SMTP配置正确问题可能在Celery或Redis配置
如果Celery异步任务测试失败但此测试成功
说明SMTP配置正确问题可能在Celery或Redis配置
---
家庭日报系统
"""
'''.format(
send_time=timezone.now().isoformat()
)
email = EmailMessage(
subject=subject,