feat: ADRF async views (phase1) + native async serializers (phase2) + async cache infra

This commit is contained in:
async-upgrade
2026-09-06 14:26:17 +08:00
parent 9a6577f71e
commit 8f488fcaaa
55 changed files with 2224 additions and 1513 deletions
+28 -27
View File
@@ -1,12 +1,13 @@
from django.db.models import Count, Avg, Max, Q, F
from django.db.models.functions import TruncHour, TruncDate
from django.utils import timezone
from rest_framework import viewsets, status
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from adrf.viewsets import ModelViewSet, ReadOnlyModelViewSet, ViewSet
from .models import ApiRequestLog, ErrorLog, SystemEventLog
from .serializers import (
@@ -17,7 +18,7 @@ from .serializers import (
from .filters import ApiRequestLogFilter, ErrorLogFilter, SystemEventLogFilter
class ApiRequestLogViewSet(viewsets.ReadOnlyModelViewSet):
class ApiRequestLogViewSet(ReadOnlyModelViewSet):
queryset = ApiRequestLog.objects.all()
permission_classes = [IsAuthenticated, IsAdminUser]
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
@@ -32,7 +33,7 @@ class ApiRequestLogViewSet(viewsets.ReadOnlyModelViewSet):
return ApiRequestLogSerializer
class ErrorLogViewSet(viewsets.ModelViewSet):
class ErrorLogViewSet(ModelViewSet):
queryset = ErrorLog.objects.all()
permission_classes = [IsAuthenticated, IsAdminUser]
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
@@ -54,18 +55,18 @@ class ErrorLogViewSet(viewsets.ModelViewSet):
return ErrorLog.objects.all()
@action(detail=True, methods=['post'])
def resolve(self, request, pk=None):
error_log = self.get_object()
async def resolve(self, request, pk=None):
error_log = await self.aget_object()
error_log.is_resolved = True
error_log.resolved_at = timezone.now()
error_log.save(update_fields=['is_resolved', 'resolved_at'])
await error_log.asave(update_fields=['is_resolved', 'resolved_at'])
return Response({'status': 'resolved'})
@action(detail=False, methods=['post'])
def resolve_all(self, request):
async def resolve_all(self, request):
ids = request.data.get('ids', [])
if ids:
ErrorLog.objects.filter(id__in=ids, is_resolved=False).update(
await ErrorLog.objects.filter(id__in=ids, is_resolved=False).aupdate(
is_resolved=True,
resolved_at=timezone.now()
)
@@ -73,7 +74,7 @@ class ErrorLogViewSet(viewsets.ModelViewSet):
return Response({'error': '请提供 ids 列表'}, status=status.HTTP_400_BAD_REQUEST)
class SystemEventLogViewSet(viewsets.ReadOnlyModelViewSet):
class SystemEventLogViewSet(ReadOnlyModelViewSet):
queryset = SystemEventLog.objects.select_related('user', 'target_user').all()
permission_classes = [IsAuthenticated, IsAdminUser]
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
@@ -88,28 +89,28 @@ class SystemEventLogViewSet(viewsets.ReadOnlyModelViewSet):
return SystemEventLogSerializer
class LogStatsViewSet(viewsets.ViewSet):
class LogStatsViewSet(ViewSet):
permission_classes = [IsAuthenticated, IsAdminUser]
@action(detail=False, methods=['get'])
def summary(self, request):
async def summary(self, request):
days = int(request.query_params.get('days', 7))
start_date = timezone.now() - timezone.timedelta(days=days)
request_qs = ApiRequestLog.objects.filter(timestamp__gte=start_date)
error_qs = ErrorLog.objects.filter(timestamp__gte=start_date)
total_requests = request_qs.count()
total_errors = request_qs.filter(is_error=True).count()
total_requests = await request_qs.acount()
total_errors = await request_qs.filter(is_error=True).acount()
error_rate = (total_errors / total_requests * 100) if total_requests > 0 else 0
duration_stats = request_qs.aggregate(
duration_stats = await request_qs.aaggregate(
avg_duration=Avg('duration_ms'),
max_duration=Max('duration_ms')
)
unique_users = request_qs.exclude(user__isnull=True).values('user').distinct().count()
unique_ips = request_qs.exclude(ip_address__isnull=True).values('ip_address').distinct().count()
unique_users = await request_qs.exclude(user__isnull=True).values('user').distinct().acount()
unique_ips = await request_qs.exclude(ip_address__isnull=True).values('ip_address').distinct().acount()
top_status_codes = (
request_qs.values('status_code')
@@ -118,8 +119,8 @@ class LogStatsViewSet(viewsets.ViewSet):
)
today_start = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
error_count_today = error_qs.filter(timestamp__gte=today_start).count()
request_count_today = request_qs.filter(timestamp__gte=today_start).count()
error_count_today = await error_qs.filter(timestamp__gte=today_start).acount()
request_count_today = await request_qs.filter(timestamp__gte=today_start).acount()
return Response({
'total_requests': total_requests,
@@ -129,13 +130,13 @@ class LogStatsViewSet(viewsets.ViewSet):
'max_duration_ms': round(duration_stats['max_duration'] or 0, 2),
'unique_users': unique_users,
'unique_ips': unique_ips,
'top_status_codes': list(top_status_codes),
'top_status_codes': [item async for item in top_status_codes],
'error_count_today': error_count_today,
'request_count_today': request_count_today,
})
@action(detail=False, methods=['get'])
def by_path(self, request):
async def by_path(self, request):
days = int(request.query_params.get('days', 7))
limit = int(request.query_params.get('limit', 20))
start_date = timezone.now() - timezone.timedelta(days=days)
@@ -153,7 +154,7 @@ class LogStatsViewSet(viewsets.ViewSet):
)
result = []
for item in stats:
async for item in stats:
item['error_rate'] = round(
(item['error_count'] / item['request_count'] * 100) if item['request_count'] > 0 else 0, 2
)
@@ -163,7 +164,7 @@ class LogStatsViewSet(viewsets.ViewSet):
return Response(result)
@action(detail=False, methods=['get'])
def by_hour(self, request):
async def by_hour(self, request):
days = int(request.query_params.get('days', 1))
start_date = timezone.now() - timezone.timedelta(days=days)
@@ -181,14 +182,14 @@ class LogStatsViewSet(viewsets.ViewSet):
)
result = []
for item in stats:
async for item in stats:
item['avg_duration_ms'] = round(item['avg_duration_ms'] or 0, 2)
result.append(item)
return Response(result)
@action(detail=False, methods=['get'])
def slow_requests(self, request):
async def slow_requests(self, request):
days = int(request.query_params.get('days', 7))
threshold = float(request.query_params.get('threshold', 1000))
limit = int(request.query_params.get('limit', 50))
@@ -207,7 +208,7 @@ class LogStatsViewSet(viewsets.ViewSet):
)
result = []
for item in slow_requests:
async for item in slow_requests:
item['avg_duration_ms'] = round(item['avg_duration_ms'] or 0, 2)
item['max_duration_ms'] = round(item['max_duration_ms'] or 0, 2)
result.append(item)
@@ -215,7 +216,7 @@ class LogStatsViewSet(viewsets.ViewSet):
return Response(result)
@action(detail=False, methods=['get'])
def error_types(self, request):
async def error_types(self, request):
days = int(request.query_params.get('days', 7))
limit = int(request.query_params.get('limit', 20))
start_date = timezone.now() - timezone.timedelta(days=days)
@@ -231,4 +232,4 @@ class LogStatsViewSet(viewsets.ViewSet):
.order_by('-count')[:limit]
)
return Response(list(error_types))
return Response([item async for item in error_types])