jerry 2 месяцев назад
Родитель
Сommit
4daf763e22

+ 54 - 39
app/services/emails_service.py

@@ -23,12 +23,23 @@ class EmailsService:
         "dynamic code",
         "one time password"
     ]
+    
+    SENDER_BLACKLIST = [
+        "etsy",
+        "dsw",
+    ]
 
     @staticmethod
     def _is_subject_blacklisted(subject: str) -> bool:
         if not subject: return False
         lower = subject.lower()
         return any(kw.lower() in lower for kw in EmailsService.SUBJECT_BLACKLIST)
+    
+    @staticmethod
+    def _is_sender_blacklisted(sender: str) -> bool:
+        if not sender: return False
+        lower = sender.lower()
+        return any(kw.lower() in lower for kw in EmailsService.SENDER_BLACKLIST)
 
     @staticmethod
     def _summarize(text: str, limit: int = 120) -> str:
@@ -82,6 +93,10 @@ class EmailsService:
         if EmailsService._is_subject_blacklisted(rec.subject or ""):
             logger.info(f"Email {rec.uid} skipped: subject '{rec.subject}' is in blacklist.")
             return
+        
+        if EmailsService._is_sender_blacklisted(rec.sender or ""):
+            logger.info(f"Email {rec.uid} skipped: subject '{rec.sender}' is in blacklist.")
+            return
 
         # 1. 尝试创建订单事件 (OrderEventService 内部会根据 rec.recipient 匹配订单)
         try:
@@ -116,8 +131,8 @@ class EmailsService:
 
         # 优先级:数据库用户字段 > 订单输入的联系方式
         email_to = (user.email if user else None) or user_inputs.get("email")
-        phone = (user.phone if user else None) or user_inputs.get("phone")
-        country_code = str(user_inputs.get("phone_country_code") or "")
+        # phone = (user.phone if user else None) or user_inputs.get("phone")
+        # country_code = str(user_inputs.get("phone_country_code") or "")
 
         # 4. 构造通知内容
         summary = EmailsService._summarize(rec.body_text or "")
@@ -140,43 +155,43 @@ class EmailsService:
             )
 
         # --- WhatsApp 推送任务 ---
-        if phone:
-            # 确保国家代码不带 +
-            code = str(user_inputs.get("phone_country_code") or "").replace("+", "")
-            # 去掉手机号开头的 0
-            clean_phone = str(phone).lstrip('0')
+        # if phone:
+        #     # 确保国家代码不带 +
+        #     code = str(user_inputs.get("phone_country_code") or "").replace("+", "")
+        #     # 去掉手机号开头的 0
+        #     clean_phone = str(phone).lstrip('0')
             
-            # 组合纯数字部分
-            digits = f"{code}{clean_phone}"
-            # 再次利用正则确保只剩数字(以防 code 包含特殊字符)
-            digits = re.sub(r"\D", "", digits)
-
-            if "@lid" in str(phone):
-                chat_id = phone
-            elif digits:
-                # 使用不带 + 的纯数字拼接后缀
-                chat_id = f"{digits}@c.us"
-            else:
-                return
-
-            message = (
-                f"🔔 *Progress Update*\n\n"
-                f"We received an update for your appointment:\n"
-                f"_{summary}_\n\n"
-                f"Please log in to your dashboard or check your email for full details."
-            )
-
-            await NotificationService.post_message(
-                db=db,
-                channel="whatsapp",
-                payload={
-                    "template_id": "order_event_update",
-                    "receiver": chat_id,
-                    "payload": {
-                        "message": message,
-                        "order_no": order.id,
-                    },
-                },
-            )
+        #     # 组合纯数字部分
+        #     digits = f"{code}{clean_phone}"
+        #     # 再次利用正则确保只剩数字(以防 code 包含特殊字符)
+        #     digits = re.sub(r"\D", "", digits)
+
+        #     if "@lid" in str(phone):
+        #         chat_id = phone
+        #     elif digits:
+        #         # 使用不带 + 的纯数字拼接后缀
+        #         chat_id = f"{digits}@c.us"
+        #     else:
+        #         return
+
+        #     message = (
+        #         f"🔔 *Progress Update*\n\n"
+        #         f"We received an update for your appointment:\n"
+        #         f"_{summary}_\n\n"
+        #         f"Please log in to your dashboard or check your email for full details."
+        #     )
+
+        #     await NotificationService.post_message(
+        #         db=db,
+        #         channel="whatsapp",
+        #         payload={
+        #             "template_id": "order_event_update",
+        #             "receiver": chat_id,
+        #             "payload": {
+        #                 "message": message,
+        #                 "order_no": order.id,
+        #             },
+        #         },
+        #     )
             
         logger.info(f"Notification tasks queued for Email {rec.uid} (Order: {order.id})")

+ 19 - 6
app/services/slot_snapshot_service.py

@@ -26,16 +26,29 @@ class SlotSnapshotService:
         await db.commit()
         await db.refresh(rec) 
 
-        # 1. 准备序列化数据
+        # 1. 准备序列化数据 (用于 payload 展示)
         earliest_date_str = rec.earliest_date.isoformat() if rec.earliest_date else None
         snapshot_at_str = rec.snapshot_at.isoformat() if rec.snapshot_at else None
 
-        # 2. 使用通用 Redis 限流器
-        # 只要 状态 或 日期 变了,Signature 就会变,从而触发推送
         throttle_key = f"throttle:slot_snapshot:{rec.routing_key}"
-        signature = f"{rec.availability_status}|{earliest_date_str or ''}"
         
-        is_throttled = await RedisThrottler.should_throttle(redis_client, throttle_key, signature, expire_seconds=1800)
+        # 修复 2:针对 Signature,日期只精确到“天”(YYYY-MM-DD),防止时分秒的微小变动触发疯狂推送
+        sig_date = ""
+        if rec.earliest_date:
+            # 如果 earliest_date 有 date() 方法说明它是 datetime,否则认为是 date 对象
+            sig_date = rec.earliest_date.strftime("%Y-%m-%d") if hasattr(rec.earliest_date, "strftime") else str(rec.earliest_date)
+            
+        signature = f"{rec.availability_status}|{sig_date}"
+
+        # 2. 使用通用 Redis 限流器
+        # 这里逻辑:如果 signature 和 redis 里的旧 signature 完全一致,且在 1800 秒内,则 should_throttle 返回 True
+        is_throttled = await RedisThrottler.should_throttle(
+            redis_client, 
+            throttle_key, 
+            signature, 
+            expire_seconds=1800
+        )
+        # ==================== 修复结束 ====================
 
         if not is_throttled:
             await NotificationService.post_message(
@@ -44,7 +57,7 @@ class SlotSnapshotService:
                 payload={
                     "template_id": "slot_snapshot",
                     "payload": {
-                        **data.dict(), # 简化写法
+                        **data.dict(),
                         "earliest_date": earliest_date_str,
                         "snapshot_at": snapshot_at_str
                     }

+ 1 - 1
app/utils/throttler.py

@@ -18,7 +18,7 @@ class RedisThrottler:
         """
         try:
             last_val = await redis.get(key)
-            if last_val and last_val.decode('utf-8') == current_signature:
+            if last_val and last_val == current_signature:
                 return True
             
             # 记录新状态并设置过期时间

+ 1 - 0
scripts/notification_templates.py

@@ -328,6 +328,7 @@ WECHAT_MAP = {
 }
 
 WHATSAPP_MAP = {
+    "slot_subscription": template_whatsapp_slot_snapshot,
     "slot_snapshot": template_whatsapp_slot_snapshot,
     "appointment_confirmation": template_whatsapp_appointment_confirmation,
     "order_event_update": template_whatsapp_order_event_update,