From 2b069818afaf04c171f01c8a11278bc10f2a537e Mon Sep 17 00:00:00 2001 From: OG T Date: Fri, 27 Mar 2026 15:04:53 +0800 Subject: [PATCH] =?UTF-8?q?refactor(api):=20Sentry=20dedup=20=E9=82=8F?= =?UTF-8?q?=E8=BC=AF=E7=A7=BB=E8=87=B3=20Service=20=E5=B1=A4=20(leWOOOgo?= =?UTF-8?q?=20=E6=A8=A1=E7=B5=84=E5=8C=96)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 10.2.1 - 2026-03-27 台北時區 - 將 check_sentry_dedup() 從 Router 移至 SentryService.check_dedup() - Router 層禁止直接存取 Redis (遵循 leWOOOgo 積木化原則) - 保持 10 分鐘 TTL 去重窗口 Co-Authored-By: Claude Opus 4.5 --- apps/api/src/api/v1/sentry_webhook.py | 10 ++++++++ apps/api/src/services/sentry_service.py | 34 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/apps/api/src/api/v1/sentry_webhook.py b/apps/api/src/api/v1/sentry_webhook.py index e189c17d..cc1bfac4 100644 --- a/apps/api/src/api/v1/sentry_webhook.py +++ b/apps/api/src/api/v1/sentry_webhook.py @@ -29,6 +29,7 @@ from src.models.approval import ( RiskLevel, ) from src.services.approval_db import get_approval_service +from src.services.sentry_service import get_sentry_service from src.services.telegram_gateway import get_telegram_gateway from src.utils.timezone import now_taipei_iso @@ -48,6 +49,9 @@ SENTRY_LEVEL_TO_RISK = { "info": RiskLevel.LOW, } +# 去重配置 (Phase 10.2.1 - 2026-03-27) +SENTRY_DEDUP_TTL = 600 # 10 分鐘內不重複發送同一 issue + class SentryIssuePayload(BaseModel): """Sentry Issue Alert Payload (簡化版)""" @@ -94,6 +98,12 @@ async def handle_sentry_error( # 提取錯誤資訊 issue_data = payload.get("data", {}).get("issue", {}) + + # Phase 10.2.1: 去重檢查 (10 分鐘內不重複發送) + issue_id = issue_data.get("id") + sentry_service = get_sentry_service() + if not await sentry_service.check_dedup(issue_id, ttl=SENTRY_DEDUP_TTL): + return {"status": "deduplicated", "issue_id": issue_id, "ttl": SENTRY_DEDUP_TTL} event_data = payload.get("data", {}).get("event", {}) error_context = { diff --git a/apps/api/src/services/sentry_service.py b/apps/api/src/services/sentry_service.py index 93e993fd..209782de 100644 --- a/apps/api/src/services/sentry_service.py +++ b/apps/api/src/services/sentry_service.py @@ -196,6 +196,40 @@ class SentryService: """取得 Issue 在 Sentry UI 的連結""" return f"{self.base_url}/organizations/{self.org}/issues/{issue_id}/" + # ========================================================================= + # Dedup (Phase 10.2.1 - 2026-03-27) + # ========================================================================= + + async def check_dedup(self, issue_id: str, ttl: int = 600) -> bool: + """ + 檢查 Sentry Issue 是否已在去重窗口內 + + Args: + issue_id: Sentry Issue ID + ttl: 去重窗口秒數 (預設 10 分鐘) + + Returns: + bool: True = 應處理, False = 已去重跳過 + """ + from src.core.redis_client import get_redis_pool + + if not issue_id: + return True + + redis = await get_redis_pool() + key = f"sentry_dedup:{issue_id}" + + # 檢查是否已存在 + exists = await redis.exists(key) + if exists: + logger.info("sentry_dedup_hit", issue_id=issue_id) + return False + + # 設置去重標記 + await redis.setex(key, ttl, "1") + logger.debug("sentry_dedup_set", issue_id=issue_id, ttl=ttl) + return True + # ============================================================================= # Singleton