Files

306 lines
11 KiB
Python

import logging
from rest_framework.views import APIView
from rest_framework.response import Response
from gvsdsdk import StatsManager
from gvsds.apps.stats.serializers import (
StatMetricSerializer,
StatDimensionSerializer,
StatRecordSerializer,
StatRankingSerializer,
StatConfigSerializer,
MetricDefineSerializer,
DimensionDefineSerializer,
IncrementSerializer,
IncrementMultiSerializer,
StatQuerySerializer,
RankingGenerateSerializer,
RankingQuerySerializer,
ResetSerializer,
PurgeSerializer,
ConfigSerializer,
)
logger = logging.getLogger(__name__)
def _ok(data=None, msg='success'):
return {'code': 200, 'msg': msg, 'data': data}
def _err(msg, code=400):
return {'code': code, 'msg': msg, 'data': None}
def _get_tenant(request):
return request.headers.get('X-Tenant-UUID', '') or getattr(request, 'tenant_uuid', '')
class MetricConfigView(APIView):
def get(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
metrics = StatsManager.ListMetrics(tenant, status=1)
return Response(_ok(StatMetricSerializer(metrics, many=True).data))
def post(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
ser = MetricDefineSerializer(data=request.data)
if not ser.IsValid():
return Response(_err(ser.errors, 400), status=400)
metric = StatsManager.DefineMetric(
tenant_uuid=tenant,
metric_code=ser.ValidatedData['MetricCode'],
metric_name=ser.ValidatedData['MetricName'],
metric_type=ser.ValidatedData.get('MetricType', 'count'),
unit=ser.ValidatedData.get('Unit', '次'),
default_value=ser.ValidatedData.get('DefaultValue', 0),
)
return Response(_ok(StatMetricSerializer(metric).data), status=201)
class DimensionView(APIView):
def get(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
dims = StatsManager.ListDimensions(tenant, status=1)
return Response(_ok(StatDimensionSerializer(dims, many=True).data))
def post(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
ser = DimensionDefineSerializer(data=request.data)
if not ser.IsValid():
return Response(_err(ser.errors, 400), status=400)
dim = StatsManager.DefineDimension(
tenant_uuid=tenant,
dimension_code=ser.ValidatedData['DimensionCode'],
dimension_name=ser.ValidatedData['DimensionName'],
)
return Response(_ok(StatDimensionSerializer(dim).data), status=201)
class IncrementView(APIView):
def post(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
ser = IncrementSerializer(data=request.data)
if not ser.IsValid():
return Response(_err(ser.errors, 400), status=400)
import datetime as _dt
_date = ser.ValidatedData.get('Date') or _dt.date.today()
_period_type = ser.ValidatedData.get('PeriodType', 'daily')
if _period_type == 'daily':
_period_key = _date.strftime('%Y-%m-%d')
elif _period_type == 'monthly':
_period_key = _date.strftime('%Y-%m')
elif _period_type == 'hourly':
_period_key = _date.strftime('%Y-%m-%dT%H')
else:
_period_key = str(_date)
record, _created = StatsManager.AtomicIncrement(
tenant_uuid=tenant,
period_type=_period_type,
period_key=_period_key,
metric_uuid=ser.ValidatedData['MetricUUID'],
dimension_values=ser.ValidatedData['DimensionValues'],
delta=ser.ValidatedData.get('Delta', 1),
)
return Response(_ok(StatRecordSerializer(record).data), status=201)
class IncrementMultiView(APIView):
def post(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
ser = IncrementMultiSerializer(data=request.data)
if not ser.IsValid():
return Response(_err(ser.errors, 400), status=400)
import datetime as _dt
_date = ser.ValidatedData.get('Date') or _dt.date.today()
records = StatsManager.AtomicIncrementMulti(
tenant_uuid=tenant,
period_type='daily',
period_key=_date.strftime('%Y-%m-%d'),
increments=ser.ValidatedData['Increments'],
)
return Response(_ok(StatRecordSerializer(records, many=True).data), status=201)
class QueryView(APIView):
def get(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
MetricUUID = request.query_params.get('MetricUUID', '')
StartDate = request.query_params.get('StartDate')
EndDate = request.query_params.get('EndDate')
PeriodType = request.query_params.get('PeriodType', 'daily')
if StartDate and EndDate:
import datetime
sd = datetime.date.fromisoformat(StartDate)
ed = datetime.date.fromisoformat(EndDate)
records = StatsManager.QueryRange(
tenant_uuid=tenant,
metric_uuid=MetricUUID,
period_type=PeriodType,
start_key=sd.strftime('%Y-%m-%d'),
end_key=ed.strftime('%Y-%m-%d'),
)
elif MetricUUID:
import datetime
records = StatsManager.QueryPeriod(
tenant_uuid=tenant,
metric_uuid=MetricUUID,
period_type='daily',
period_key=datetime.date.today().strftime('%Y-%m-%d'),
)
else:
return Response(_err('缺少查询参数', 400), status=400)
return Response(_ok(StatRecordSerializer(records, many=True).data))
class AggregateView(APIView):
def get(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
MetricUUID = request.query_params.get('MetricUUID', '')
StartDate = request.query_params.get('StartDate')
EndDate = request.query_params.get('EndDate')
DimensionKey = request.query_params.get('DimensionKey', 'UserID')
TopN = int(request.query_params.get('TopN', 50))
if not MetricUUID or not StartDate or not EndDate:
return Response(_err('缺少必要参数', 400), status=400)
import datetime
sd = datetime.date.fromisoformat(StartDate)
ed = datetime.date.fromisoformat(EndDate)
results = StatsManager.AggregateByDimension(
tenant_uuid=tenant,
metric_uuid=MetricUUID,
period_type='daily',
start_key=sd.strftime('%Y-%m-%d'),
end_key=ed.strftime('%Y-%m-%d'),
dimension_key=DimensionKey,
)
return Response(_ok(results[:TopN]))
class RankingView(APIView):
def get(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
RankingPeriod = request.query_params.get('RankingPeriod', 'daily')
RankingDate = request.query_params.get('RankingDate')
MetricUUID = request.query_params.get('MetricUUID', '')
TopN = request.query_params.get('TopN')
import datetime
rd = datetime.date.fromisoformat(RankingDate) if RankingDate else None
rankings = StatsManager.QueryRanking(
tenant_uuid=tenant,
ranking_period=RankingPeriod,
ranking_date=rd,
metric_uuid=MetricUUID or None,
top_n=int(TopN) if TopN else None,
)
return Response(_ok(StatRankingSerializer(rankings, many=True).data))
def post(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
ser = RankingGenerateSerializer(data=request.data)
if not ser.IsValid():
return Response(_err(ser.errors, 400), status=400)
rankings = StatsManager.GenerateDailyRanking(
tenant_uuid=tenant,
metric_uuid=ser.ValidatedData['MetricUUID'],
ranking_date=ser.ValidatedData.get('RankingDate'),
dimension_filter=ser.ValidatedData.get('DimensionFilter'),
top_n=ser.ValidatedData.get('TopN', 50),
)
return Response(_ok(StatRankingSerializer(rankings, many=True).data), status=201)
class ResetView(APIView):
def post(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
ser = ResetSerializer(data=request.data)
if not ser.IsValid():
return Response(_err(ser.errors, 400), status=400)
deleted = StatsManager.DailyReset(
tenant_uuid=tenant,
metric_uuid=ser.ValidatedData['MetricUUID'],
target_date=ser.ValidatedData.get('TargetDate'),
)
return Response(_ok({'deleted_count': deleted}))
class PurgeView(APIView):
def post(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
ser = PurgeSerializer(data=request.data)
if not ser.IsValid():
return Response(_err(ser.errors, 400), status=400)
deleted = StatsManager.PurgeOld(
tenant_uuid=tenant,
before_date=ser.ValidatedData['BeforeDate'],
period_type=ser.ValidatedData.get('PeriodType', 'daily'),
)
return Response(_ok({'deleted_count': deleted}))
class StatConfigView(APIView):
def get(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
configs = StatsManager.ListConfigs(tenant, status=1)
return Response(_ok(StatConfigSerializer(configs, many=True).data))
def post(self, request):
tenant = _get_tenant(request)
if not tenant:
return Response(_err('缺少租户标识', 401), status=401)
ser = ConfigSerializer(data=request.data)
if not ser.IsValid():
return Response(_err(ser.errors, 400), status=400)
config = StatsManager.ConfigureAutoReset(
tenant_uuid=tenant,
metric_uuid=ser.ValidatedData['MetricUUID'],
auto_reset=ser.ValidatedData.get('AutoReset', True),
reset_cron=ser.ValidatedData.get('ResetCron', '0 0 * * *'),
enable_ranking=ser.ValidatedData.get('EnableRanking', False),
ranking_top_n=ser.ValidatedData.get('RankingTopN', 50),
retention_days=ser.ValidatedData.get('RetentionDays', 365),
)
return Response(_ok(StatConfigSerializer(config).data), status=201)