feat(email): 添加发件人邮箱字段并优化邮件发送逻辑
添加独立的sender_email字段作为发件人邮箱,优先使用该字段而非smtp_username 更新相关表单、模型和测试用例以支持新字段 重构邮件发送逻辑,统一邮箱格式验证和错误提示
This commit is contained in:
@@ -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': '请输入收件人邮箱'}),
|
||||
}
|
||||
@@ -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="创建时间")
|
||||
|
||||
42
logs/app.log
42
logs/app.log
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
# 获取收件人(如果没有配置,使用发件人自己)
|
||||
|
||||
Reference in New Issue
Block a user