From e22bd4a8c35b7cbe9059bdac49d85dec1007ebab Mon Sep 17 00:00:00 2001 From: xiaji Date: Sun, 18 Jan 2026 18:35:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(email):=20=E6=B7=BB=E5=8A=A0=E5=8F=91?= =?UTF-8?q?=E4=BB=B6=E4=BA=BA=E9=82=AE=E7=AE=B1=E5=AD=97=E6=AE=B5=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=82=AE=E4=BB=B6=E5=8F=91=E9=80=81=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加独立的sender_email字段作为发件人邮箱,优先使用该字段而非smtp_username 更新相关表单、模型和测试用例以支持新字段 重构邮件发送逻辑,统一邮箱格式验证和错误提示 --- core/forms.py | 3 +- core/models.py | 1 + logs/app.log | 42 ++++++++++++++++++++ test_celery_email.py | 91 ++++++++++++++++++++++++++------------------ test_email.py | 15 ++++---- 5 files changed, 107 insertions(+), 45 deletions(-) diff --git a/core/forms.py b/core/forms.py index 6f02b10..069d23b 100644 --- a/core/forms.py +++ b/core/forms.py @@ -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': '请输入收件人邮箱'}), } \ No newline at end of file diff --git a/core/models.py b/core/models.py index 5f01760..67deb5b 100644 --- a/core/models.py +++ b/core/models.py @@ -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="创建时间") diff --git a/logs/app.log b/logs/app.log index 7d8e266..9859101 100644 --- a/logs/app.log +++ b/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 diff --git a/test_celery_email.py b/test_celery_email.py index 682248d..ff76cf8 100644 --- a/test_celery_email.py +++ b/test_celery_email.py @@ -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 = '''

📊 家庭日报

-
{report_data['today']}
+
{today}
@@ -410,15 +414,15 @@ def celery_send_html_report_email(self, include_attachment=False):

✅ 邮件发送测试

这是一封通过 Celery异步任务 发送的HTML格式邮件。

任务ID: {task_id}

-

发送时间: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}

+

发送时间: {send_time}

状态: 发送成功

📈 统计信息

-

阅读记录: {report_data['reading']['count']} 篇

-

感悟记录: {report_data['insights']['count']} 条

-

家庭事项: {len(report_data['tasks'])} 项

+

阅读记录: {reading_count} 篇

+

感悟记录: {insights_count} 条

+

家庭事项: {tasks_count} 项

@@ -435,7 +439,14 @@ def celery_send_html_report_email(self, include_attachment=False):
- """ + '''.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, diff --git a/test_email.py b/test_email.py index aca1852..b8ba454 100644 --- a/test_email.py +++ b/test_email.py @@ -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 # 获取收件人(如果没有配置,使用发件人自己)