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 {