fix(tasks): stats 任务套重试 + PurgeOld 全周期清理;攻击日志写库限频去重

- 四个 stats 任务加 RetryOnFailure(2 次重试)
- PurgeOldStats 清理 daily/monthly/hourly 全周期
- AttackDetectMiddleware 同 IP+类型 60s 窗口去重,防 401 刷屏反向写库放大
This commit is contained in:
TermiNexus committed 2026-08-13 17:55:56 +08:00
1 parent 221c5d9e9d
commit 727fbba272
2 files changed
+26

No files matched your search

+8
View File
@@ -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):
"""生成每日排行榜——为所有启用排行的指标生成当日排行。
+18
View File
@@ -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,