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

@@ -62,12 +62,13 @@ class SystemConfigForm(forms.ModelForm):
"""系统配置表单"""
class Meta:
model = SystemConfig
fields = ['smtp_server', 'smtp_port', 'smtp_username', 'smtp_password', 'send_time', 'recipient_email']
fields = ['smtp_server', 'smtp_port', 'smtp_username', 'smtp_password', 'sender_email', 'send_time', 'recipient_email']
widgets = {
'smtp_server': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入SMTP服务器'}),
'smtp_port': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '请输入SMTP端口'}),
'smtp_username': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入SMTP用户名'}),
'smtp_password': forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': '请输入SMTP密码'}),
'sender_email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': '请输入发件人邮箱'}),
'send_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
'recipient_email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': '请输入收件人邮箱'}),
}

View File

@@ -149,6 +149,7 @@ class SystemConfig(models.Model):
smtp_port = models.IntegerField(default=587, verbose_name="SMTP端口")
smtp_username = models.CharField(max_length=100, blank=True, null=True, verbose_name="SMTP用户名")
smtp_password = models.CharField(max_length=100, blank=True, null=True, verbose_name="SMTP密码")
sender_email = models.EmailField(blank=True, null=True, verbose_name="发件人邮箱")
send_time = models.TimeField(default='08:00', verbose_name="每日发送时间")
recipient_email = models.EmailField(blank=True, null=True, verbose_name="收件人邮箱")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")

View File

@@ -98,3 +98,45 @@
2026-01-18 18:02:57.874 | INFO | __main__:main:601 - 3. ❌ 检查网络连接
2026-01-18 18:02:57.874 | INFO | __main__:main:602 - 4. 参考README中的邮件配置章节
2026-01-18 18:02:57.874 | INFO | __main__:main:603 - 5. 查看详细错误日志
2026-01-18 18:13:08.550 | INFO | __main__:test_celery_email_config:36 - Celery配置信息:
2026-01-18 18:13:08.550 | INFO | __main__:test_celery_email_config:45 - CELERY_BROKER_URL: redis://:xjjq1234!@***
2026-01-18 18:13:08.552 | INFO | __main__:test_celery_email_config:45 - CELERY_RESULT_BACKEND: redis://:xjjq1234!@***
2026-01-18 18:13:08.552 | INFO | __main__:test_celery_email_config:47 - CELERY_TIMEZONE: Asia/Shanghai
2026-01-18 18:13:08.555 | INFO | __main__:test_celery_email_config:58 - 邮件配置信息:
2026-01-18 18:13:08.555 | WARNING | __main__:test_celery_email_config:61 - smtp_server: 未配置
2026-01-18 18:13:08.555 | INFO | __main__:test_celery_email_config:63 - smtp_port: 587
2026-01-18 18:13:08.555 | WARNING | __main__:test_celery_email_config:61 - smtp_username: 未配置
2026-01-18 18:13:08.555 | WARNING | __main__:test_celery_email_config:61 - recipient_email: 未配置
2026-01-18 18:13:08.555 | ERROR | __main__:test_celery_email_config:77 - 缺少必要的配置: smtp_server, smtp_username, smtp_password
2026-01-18 18:13:08.556 | INFO | __main__:main:597 -
[测试2] Celery Worker状态检查
2026-01-18 18:13:08.556 | INFO | __main__:test_celery_worker_status:119 - 检查Celery Worker状态...
2026-01-18 18:13:12.720 | WARNING | __main__:test_celery_worker_status:148 - 获取Worker状态失败: Error 10061 connecting to localhost:6379. 由于目标计算机积极拒绝,无法连接。.
2026-01-18 18:13:16.768 | WARNING | __main__:test_celery_worker_status:164 - 获取活跃任务失败: Error 10061 connecting to localhost:6379. 由于目标计算机积极拒绝,无法连接。.
2026-01-18 18:13:20.827 | WARNING | __main__:test_celery_worker_status:175 - 获取计划任务失败: Error 10061 connecting to localhost:6379. 由于目标计算机积极拒绝,无法连接。.
2026-01-18 18:13:20.828 | INFO | __main__:main:602 -
[测试3] Celery异步邮件任务测试
2026-01-18 18:13:20.828 | INFO | __main__:test_celery_email_task:520 - 开始测试Celery异步邮件任务...
2026-01-18 18:13:20.830 | INFO | __main__:test_celery_email_task:530 - 发送Celery邮件任务到队列...
2026-01-18 18:15:09.488 | ERROR | __main__:test_celery_email_task:573 - Celery邮件任务测试失败:
Retry limit exceeded while trying to reconnect to the Celery redis result store backend. The Celery application must be restarted.
2026-01-18 18:15:09.490 | INFO | __main__:test_celery_email_task:574 - 可能的原因:
2026-01-18 18:15:09.490 | INFO | __main__:test_celery_email_task:575 - 1. Celery worker未运行
2026-01-18 18:15:09.491 | INFO | __main__:test_celery_email_task:576 - 2. Redis连接问题
2026-01-18 18:15:09.492 | INFO | __main__:test_celery_email_task:577 - 3. 任务执行超时
2026-01-18 18:15:09.493 | INFO | __main__:main:607 -
[测试4] 同步发送测试邮件
2026-01-18 18:15:09.505 | ERROR | __main__:main:672 - 同步邮件测试失败: 未配置发件邮箱
2026-01-18 18:15:09.506 | INFO | __main__:main:675 -
============================================================
2026-01-18 18:15:09.506 | INFO | __main__:main:676 - 测试总结:
2026-01-18 18:15:09.508 | INFO | __main__:main:677 - 通过测试: 1/4
2026-01-18 18:15:09.508 | INFO | __main__:main:678 - ============================================================
2026-01-18 18:15:09.509 | ERROR | __main__:main:700 - 多数测试失败Celery邮件功能异常。
2026-01-18 18:15:09.510 | INFO | __main__:main:701 -
紧急处理:
2026-01-18 18:15:09.510 | INFO | __main__:main:702 - 1. ❌ 检查Celery worker: sudo supervisorctl status celery_worker
2026-01-18 18:15:09.511 | INFO | __main__:main:703 - 2. ❌ 检查Redis: sudo systemctl status redis-server
2026-01-18 18:15:09.512 | INFO | __main__:main:704 - 3. ❌ 检查SMTP配置
2026-01-18 18:15:09.513 | INFO | __main__:main:705 - 4. ❌ 查看日志: tail -f /var/log/celery/worker.log

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,

View File

@@ -63,10 +63,11 @@ def test_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}")
logger.error("请在系统配置页面输入有效的邮箱地址,而不是手机号或其他格式")
# 验证发件人邮箱格式优先使用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
# 验证收件人邮箱格式(如果已配置)
@@ -209,9 +210,9 @@ def test_send_simple_email():
# 从数据库获取配置
config = SystemConfig.get_config()
from_email = config.smtp_username or None
from_email = config.sender_email or config.smtp_username or None
if not from_email:
logger.error("未配置发件邮箱 (smtp_username)")
logger.error("未配置发件邮箱")
return False
# 验证发件人邮箱格式
@@ -219,7 +220,7 @@ def test_send_simple_email():
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, from_email):
logger.error(f"发件人邮箱格式不正确: {from_email}")
logger.error("请在系统配置页面输入有效的邮箱地址,而不是手机号或其他格式")
logger.error("请在系统配置页面输入有效的邮箱地址")
return False
# 获取收件人(如果没有配置,使用发件人自己)