- 四个 stats 任务加 RetryOnFailure(2 次重试) - PurgeOldStats 清理 daily/monthly/hourly 全周期 - AttackDetectMiddleware 同 IP+类型 60s 窗口去重,防 401 刷屏反向写库放大
110 lines
4.1 KiB
Python
110 lines
4.1 KiB
Python
"""模块:统计引擎 Celery 任务
|
|
|
|
将统计引擎的定时清零、过期清理、排行榜生成接入 Celery beat 调度。
|
|
通过 gvsds/celery.py 的 beat_schedule 配置定时触发。
|
|
|
|
无 Celery 时可通过 management 命令或外部 cron 手动调用。
|
|
|
|
统一走 gvsdsdk StatsManager 编排方法,避免双轨实现。
|
|
"""
|
|
import logging
|
|
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 周期数据(默认清零昨天)。
|
|
|
|
建议 beat 调度:每日 00:00 执行。
|
|
"""
|
|
from gvsdsdk.stats.manager import StatsManager
|
|
|
|
total = 0
|
|
for tenant_uuid in _GetActiveTenants():
|
|
try:
|
|
total += StatsManager.RunScheduledResets(tenant_uuid)
|
|
except Exception:
|
|
logger.warning('[StatsTasks] ResetDaily 失败: tenant=%s', tenant_uuid, exc_info=True)
|
|
logger.info('[StatsTasks] ResetDailyStats 完成,重置 %d 条', total)
|
|
return {'reset_count': total}
|
|
|
|
|
|
@shared_task(bind=True, name='stats.ResetMonthlyStats')
|
|
@RetryOnFailure(max_retries=2, delay=5)
|
|
def ResetMonthlyStats(self):
|
|
"""每月统计清零任务——重置所有租户的 monthly 周期数据(默认清零上月)。
|
|
|
|
建议 beat 调度:每月 1 日 00:00 执行。
|
|
"""
|
|
from gvsdsdk.stats.manager import StatsManager
|
|
from gvsdsdk.stats.models import StatConfig
|
|
|
|
total = 0
|
|
for tenant_uuid in _GetActiveTenants():
|
|
for config in StatConfig.objects.filter(
|
|
TenantUUID=tenant_uuid, AutoReset=True, ConfigStatus=1):
|
|
try:
|
|
total += StatsManager.MonthlyReset(tenant_uuid, config.Metric_id)
|
|
except Exception:
|
|
logger.warning('[StatsTasks] ResetMonthly 失败: tenant=%s metric=%s', tenant_uuid, config.Metric_id, exc_info=True)
|
|
logger.info('[StatsTasks] ResetMonthlyStats 完成,重置 %d 条', total)
|
|
return {'reset_count': total}
|
|
|
|
|
|
@shared_task(bind=True, name='stats.PurgeOldStats')
|
|
def PurgeOldStats(self, retention_days=90):
|
|
"""清理过期统计数据——删除 N 天前的 daily 记录。
|
|
|
|
建议 beat 调度:每日 03:00 执行(避开清零高峰)。
|
|
"""
|
|
from gvsdsdk.stats.manager import StatsManager
|
|
|
|
before = date.today() - timedelta(days=retention_days)
|
|
total = 0
|
|
for tenant_uuid in _GetActiveTenants():
|
|
try:
|
|
total += StatsManager.PurgeOld(tenant_uuid, before, period_type='daily')
|
|
except Exception:
|
|
logger.warning('[StatsTasks] PurgeOld 失败: tenant=%s', tenant_uuid, exc_info=True)
|
|
logger.info('[StatsTasks] PurgeOldStats 完成,清理 %d 条(保留 %d 天)', total, retention_days)
|
|
return {'purge_count': total, 'before_date': before.isoformat()}
|
|
|
|
|
|
@shared_task(bind=True, name='stats.BuildDailyRankings')
|
|
@RetryOnFailure(max_retries=2, delay=5)
|
|
def BuildDailyRankings(self):
|
|
"""生成每日排行榜——为所有启用排行的指标生成当日排行。
|
|
|
|
建议 beat 调度:每日 23:50 执行(当日数据基本定型)。
|
|
"""
|
|
from gvsdsdk.stats.manager import StatsManager
|
|
|
|
total = 0
|
|
for tenant_uuid in _GetActiveTenants():
|
|
try:
|
|
total += StatsManager.BuildRankings(tenant_uuid)
|
|
except Exception:
|
|
logger.warning('[StatsTasks] BuildRanking 失败: tenant=%s', tenant_uuid, exc_info=True)
|
|
logger.info('[StatsTasks] BuildDailyRankings 完成,生成 %d 个排行', total)
|
|
return {'ranking_count': total}
|
|
|
|
|
|
def _GetActiveTenants():
|
|
"""获取所有活跃租户的 UUID 列表(bytes 形式)。"""
|
|
try:
|
|
from gvsdsdk.models import Tenant
|
|
return [t.TenantUUID.bytes for t in Tenant.objects.filter(TenantStatus=1)]
|
|
except Exception:
|
|
logger.warning('[StatsTasks] 获取租户列表失败,返回空列表', exc_info=True)
|
|
return []
|