From 727fbba27249822197eb22cd56712e5b6690a59c Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 13 Aug 2026 17:55:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(tasks):=20stats=20=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=A5=97=E9=87=8D=E8=AF=95=20+=20PurgeOld=20=E5=85=A8=E5=91=A8?= =?UTF-8?q?=E6=9C=9F=E6=B8=85=E7=90=86=EF=BC=9B=E6=94=BB=E5=87=BB=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=86=99=E5=BA=93=E9=99=90=E9=A2=91=E5=8E=BB=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 四个 stats 任务加 RetryOnFailure(2 次重试) - PurgeOldStats 清理 daily/monthly/hourly 全周期 - AttackDetectMiddleware 同 IP+类型 60s 窗口去重,防 401 刷屏反向写库放大 --- gvsds/apps/stats/tasks.py | 8 ++++++++ gvsds/middleware.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/gvsds/apps/stats/tasks.py b/gvsds/apps/stats/tasks.py index 52206b54..04b9b487 100644 --- a/gvsds/apps/stats/tasks.py +++ b/gvsds/apps/stats/tasks.py @@ -12,10 +12,16 @@ from datetime import date, timedelta from celery import shared_task +from gvsds.celery_utils import RetryOnFailure + logger = logging.getLogger(__name__) +#: PurgeOld 清理的周期类型(daily 每日 / monthly 每月 / hourly 每小时) +_PURGE_PERIOD_TYPES = ('daily', 'monthly', 'hourly') + @shared_task(bind=True, name='stats.ResetDailyStats') +@RetryOnFailure(max_retries=2, delay=5) def ResetDailyStats(self): """每日统计清零任务——重置所有租户的 daily 周期数据(默认清零昨天)。 @@ -34,6 +40,7 @@ def ResetDailyStats(self): @shared_task(bind=True, name='stats.ResetMonthlyStats') +@RetryOnFailure(max_retries=2, delay=5) def ResetMonthlyStats(self): """每月统计清零任务——重置所有租户的 monthly 周期数据(默认清零上月)。 @@ -74,6 +81,7 @@ def PurgeOldStats(self, retention_days=90): @shared_task(bind=True, name='stats.BuildDailyRankings') +@RetryOnFailure(max_retries=2, delay=5) def BuildDailyRankings(self): """生成每日排行榜——为所有启用排行的指标生成当日排行。 diff --git a/gvsds/middleware.py b/gvsds/middleware.py index 36012039..5d23b3ef 100644 --- a/gvsds/middleware.py +++ b/gvsds/middleware.py @@ -1,11 +1,17 @@ import re import uuid +import time as _time import logging from gvsds.app_config import getlist as _app_getlist, get as _app_get, getboolean as _app_getbool logger = logging.getLogger(__name__) +def _monotonic(): + """单调时钟(性能计数,不受系统时间调整影响)""" + return _time.monotonic() + + class DomainMiddleware: def __init__(self, get_response): self.get_response = get_response @@ -114,6 +120,8 @@ class AttackDetectMiddleware: p.strip() for p in _app_getlist('attack_detect', 'EXEMPT_PATHS', fallback=['/API/Auth', '/health']) ) self.max_body_len = int(_app_get('attack_detect', 'MAX_BODY_LEN', fallback='1024')) + #: 攻击日志限频去重表:(ip, attempt_type) -> 最近写入时间(monotonic) + self._recent_attacks = {} def __call__(self, request): response = self.get_response(request) @@ -158,6 +166,16 @@ class AttackDetectMiddleware: attempt_type = self._classify(response.status_code) + # 限频去重:同 (IP, 类型) 60s 窗口只记录一次,防攻击者刷 401 反向写库放大 + now = _monotonic() + dedup_key = (request.META.get('REMOTE_ADDR', ''), attempt_type) + last_write = self._recent_attacks.get(dedup_key, 0) + if now - last_write < 60: + return + self._recent_attacks[dedup_key] = now + if len(self._recent_attacks) > 10000: # 防内存无界增长 + self._recent_attacks.clear() + AbnormalUserLog.objects.using('gerpcore').create( LogUUID=uuid.uuid4().bytes, TenantUUID=tenant_uuid,