Django仅针对新生成的告警发送邮件提醒的实现问题
Alert告警仅新创建时发送邮件的实现方案
原有代码核心问题
- 仅查询单条告警:使用
latest('pk')仅获取最新一条告警,完全无法覆盖多告警同时触发的场景,会出现漏发 - 标记位逻辑错误:使用
created_on作为邮件发送判断依据不合理,该字段本质是告警创建时间,天然应该在告警生成时就赋值;且逻辑中额外调用Alert.create()会产生冗余的无效告警记录 - 无并发控制:未加锁或事务控制,高并发场景下会出现同一条告警重复发送邮件的问题
注:latest('pk')本身语法没有问题,只是不符合当前批量处理未发送告警的业务需求。
正确实现方案
1. 模型层调整:新增专用发送状态标记
给Alert表新增独立的邮件发送状态字段,保证字段职责单一,不要复用created_on做业务状态判断:
from django.db import models class Alert(models.Model): # 原有字段保持不变 kiosk = models.ForeignKey(Kiosk, on_delete=models.CASCADE) description = models.TextField() alert_type = models.IntegerField() created_on = models.DateTimeField(auto_now_add=True) # 告警创建时自动赋值,无需手动修改 # 新增邮件发送状态字段,默认未发送 is_email_sent = models.BooleanField(default=False, help_text="告警邮件是否已发送")
2. 业务逻辑调整:批量处理未发送告警+并发控制
通过事务+行级锁避免并发重复发送,同时批量处理所有未发送的告警,覆盖多告警同时触发的场景:
from django.db import transaction from django.utils import timezone @transaction.atomic def send_alert_emails_for_kiosk(kiosk): # 加行级锁,避免并发请求重复获取同一条未发送告警 unsent_alerts = Alert.objects.select_for_update().filter(kiosk=kiosk, is_email_sent=False) # 无未发送告警直接返回 if not unsent_alerts.exists(): return [] # 预计算时间,避免循环中重复计算消耗性能 current_dt = timezone.now().astimezone(kiosk.location.timezone) current_time = current_dt.strftime('%I:%M %p') current_date = current_dt.strftime('%m/%d/%Y') sent_alert_pks = [] for alert in unsent_alerts: # 构造邮件参数 email_props = { 'email': 'john@example.com', 'data': { 'facility': kiosk.location.name, 'description': alert.description, 'pk': alert.pk, 'time': current_time, 'date': current_date, 'kioskName': kiosk.name, 'alert_type_display': alert.alert_type_display } } # 发送邮件 _send_email( [email_props['email']], {'data': email_props['data']}, ALERT_TEMPLATE_ID ) # 标记为已发送,仅更新指定字段提升性能 alert.is_email_sent = True alert.save(update_fields=['is_email_sent']) sent_alert_pks.append(alert.pk) return sent_alert_pks
可选优化点
- 如果需要合并多告警为单封邮件,可在循环中收集所有告警信息,循环结束后统一调用一次
_send_email即可 - 可新增
email_sent_time字段记录邮件发送时间,方便后续问题排查 - 若告警量较大,可改用异步任务(如Celery)处理邮件发送,避免阻塞主业务流程
内容的提问来源于stack exchange,提问作者Sog t
相关产品推荐
相关产品推荐

