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,