From faacb3f5e300deefd302fde6f45df61a63ef2bf6 Mon Sep 17 00:00:00 2001 From: dujiao-next Date: Thu, 14 May 2026 09:45:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E6=8E=A8=E9=80=81=EF=BC=8C=E9=98=B2=E6=AD=A2=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E8=BD=B0=E7=82=B8=E6=94=BB=E5=87=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/queue/client.go | 8 +++++++- internal/worker/asynq_worker.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/internal/queue/client.go b/internal/queue/client.go index 4af699ba..9e8b6985 100644 --- a/internal/queue/client.go +++ b/internal/queue/client.go @@ -59,7 +59,13 @@ func (c *Client) EnqueueOrderStatusEmail(payload OrderStatusEmailPayload, opts . if err != nil { return err } - options := append([]asynq.Option{asynq.Queue(c.defaultQueue)}, opts...) + // 限制重试次数与保留期,避免 SMTP 长时间不可用时邮件任务堆积成雪崩; + // 调用方可通过 opts 覆盖。 + options := append([]asynq.Option{ + asynq.Queue(c.defaultQueue), + asynq.MaxRetry(3), + asynq.Retention(24 * time.Hour), + }, opts...) _, err = c.client.Enqueue(task, options...) return err } diff --git a/internal/worker/asynq_worker.go b/internal/worker/asynq_worker.go index ec6b4355..34e1b6bd 100644 --- a/internal/worker/asynq_worker.go +++ b/internal/worker/asynq_worker.go @@ -74,6 +74,23 @@ func (c *Consumer) handleOrderStatusEmail(_ context.Context, task *asynq.Task) e logger.Debugw("worker_order_status_email_skip_invalid_payload", "order_id", payload.OrderID) return nil } + // 消费侧二次校验开关:避免 SMTP/订单通知关闭后,旧任务(含 retry/scheduled 中的任务)继续触发发送。 + if c.SettingService != nil && c.Config != nil { + smtpSetting, smtpErr := c.SettingService.GetSMTPSetting(c.Config.Email) + if smtpErr != nil { + // fail-closed:读不到设置就视作关闭,不重试,避免 DB 抖动放大成邮件雪崩。 + logger.Warnw("worker_order_status_email_load_smtp_setting_failed", "order_id", payload.OrderID, "error", smtpErr) + return nil + } + if !smtpSetting.Enabled || !smtpSetting.OrderNotificationEnabled { + logger.Debugw("worker_order_status_email_skip_setting_disabled", + "order_id", payload.OrderID, + "smtp_enabled", smtpSetting.Enabled, + "order_notification_enabled", smtpSetting.OrderNotificationEnabled, + ) + return nil + } + } order, err := c.OrderRepo.GetByID(payload.OrderID) if err != nil { logger.Warnw("worker_order_status_email_fetch_order_failed", "order_id", payload.OrderID, "error", err) @@ -83,6 +100,21 @@ func (c *Consumer) handleOrderStatusEmail(_ context.Context, task *asynq.Task) e logger.Debugw("worker_order_status_email_skip_order_not_found", "order_id", payload.OrderID) return nil } + // 游客订单的"取消"通知价值极低(用户大概率已离开),但极易被滥用为退信轰炸载体: + // 攻击者可用伪造邮箱批量下单等过期取消触发发送。统一不发,保留登录用户与其它状态。 + if order.UserID == 0 { + emailStatus := strings.TrimSpace(payload.Status) + if emailStatus == "" { + emailStatus = order.Status + } + if emailStatus == constants.OrderStatusCanceled { + logger.Debugw("worker_order_status_email_skip_guest_canceled", + "order_id", order.ID, + "order_no", order.OrderNo, + ) + return nil + } + } var receiverEmail string var locale string if order.UserID != 0 {