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
+64 -36
View File
@@ -1,5 +1,5 @@
from rest_framework import status
from rest_framework.views import APIView
from adrf.views import APIView
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from django.db import transaction
from django.db.models import F
@@ -9,6 +9,8 @@ from django.views.decorators.cache import never_cache
from datetime import timedelta
import logging
from asgiref.sync import sync_to_async
from utils.response_codes import (
ResponseCode,
create_standardized_response,
@@ -33,7 +35,7 @@ class WalletBalanceAPIView(APIView):
operation_description='获取当前用户的积分和y币余额',
responses={200: success_response, 401: unauthorized_response},
)
def get(self, request):
async def get(self, request):
user = request.user
return create_standardized_response(
data={
@@ -59,7 +61,7 @@ class WalletTransactionsAPIView(APIView):
],
responses={200: success_response, 401: unauthorized_response},
)
def get(self, request):
async def get(self, request):
user = request.user
currency_type = request.query_params.get('currency_type', None)
page = int(request.query_params.get('page', 1))
@@ -70,11 +72,12 @@ class WalletTransactionsAPIView(APIView):
if currency_type:
transactions = transactions.filter(currency_type=currency_type)
total = transactions.count()
total = await transactions.acount()
start = (page - 1) * page_size
end = start + page_size
serializer = PointTransactionSerializer(transactions[start:end], many=True)
rows = [t async for t in transactions[start:end]]
serializer = PointTransactionSerializer(rows, many=True)
return create_standardized_response(
data={
@@ -107,7 +110,7 @@ class EarnPointsAPIView(APIView):
),
responses={200: success_response, 400: error_response, 401: unauthorized_response},
)
def post(self, request):
async def post(self, request):
user_id = request.data.get('user_id')
amount = request.data.get('amount', 0)
description = request.data.get('description', '')
@@ -129,7 +132,7 @@ class EarnPointsAPIView(APIView):
)
try:
target_user = FUser.objects.get(pk=user_id) if user_id else request.user
target_user = await FUser.objects.aget(pk=user_id) if user_id else request.user
except FUser.DoesNotExist:
return create_standardized_error_response(
message='目标用户不存在',
@@ -137,7 +140,7 @@ class EarnPointsAPIView(APIView):
status_code=status.HTTP_400_BAD_REQUEST
)
try:
def _earn_points_txn():
with transaction.atomic():
target_user.points = F('points') + amount
target_user.save(update_fields=['points'])
@@ -152,6 +155,9 @@ class EarnPointsAPIView(APIView):
description=description or '获得积分',
)
try:
await sync_to_async(_earn_points_txn)()
return create_standardized_response(
data={
'points': target_user.points,
@@ -173,9 +179,9 @@ CHECKIN_POINTS = 30
FULL_WEEK_BONUS = 100
def track_checkin_task(user):
async def track_checkin_task(user):
"""
签到成功后,自动更新签到任务的进度
签到成功后,自动更新签到任务的进度(异步版)
"""
try:
now = timezone.localtime(timezone.now())
@@ -187,12 +193,12 @@ def track_checkin_task(user):
is_active=True,
)
for task in checkin_tasks:
async for task in checkin_tasks:
period_key = daily_period if task.task_type == 'daily' else (
now.strftime('%Y-W%W') if task.task_type == 'weekly' else 'permanent'
)
progress, created = UserTaskProgress.objects.get_or_create(
progress, created = await UserTaskProgress.objects.aget_or_create(
user=user,
task=task,
period_key=period_key,
@@ -211,7 +217,7 @@ def track_checkin_task(user):
progress.is_completed = True
progress.completed_at = now
progress.save()
await progress.asave()
except Exception as e:
logger.error(f'签到任务进度更新失败: {e}')
@@ -226,7 +232,7 @@ class CheckinStatusAPIView(APIView):
operation_description='获取当前用户的签到状态,包括本周签到记录和积分信息',
responses={200: success_response, 401: unauthorized_response},
)
def get(self, request):
async def get(self, request):
user = request.user
today = timezone.localdate()
@@ -241,7 +247,7 @@ class CheckinStatusAPIView(APIView):
checkin_date__lte=week_dates[6],
).values_list('checkin_date', flat=True)
checked_dates = set(week_checkins)
checked_dates = set([d async for d in week_checkins])
signed_today = today in checked_dates
week_days = []
@@ -281,18 +287,18 @@ class CheckinAPIView(APIView):
operation_description='执行每日签到,获得积分奖励,连续签到满一周可获得额外奖励',
responses={200: success_response, 400: error_response, 401: unauthorized_response, 500: error_response},
)
def post(self, request):
async def post(self, request):
user = request.user
today = timezone.localdate()
if DailyCheckin.objects.filter(user=user, checkin_date=today).exists():
if await DailyCheckin.objects.filter(user=user, checkin_date=today).aexists():
return create_standardized_error_response(
message='今日已签到,请勿重复签到',
code=ResponseCode.PARAMETER_ERROR,
status_code=status.HTTP_400_BAD_REQUEST
)
try:
def _checkin_txn():
with transaction.atomic():
DailyCheckin.objects.create(user=user, checkin_date=today)
@@ -320,8 +326,13 @@ class CheckinAPIView(APIView):
description=f'每日签到' + (f'(含满周奖励{FULL_WEEK_BONUS}积分)' if bonus else ''),
)
return week_checkin_count, bonus, total_points
try:
week_checkin_count, bonus, total_points = await sync_to_async(_checkin_txn)()
# 更新签到任务进度
track_checkin_task(user)
await track_checkin_task(user)
return create_standardized_response(
data={
@@ -361,7 +372,7 @@ class EarnCoinsAPIView(APIView):
),
responses={200: success_response, 400: error_response, 401: unauthorized_response},
)
def post(self, request):
async def post(self, request):
user_id = request.data.get('user_id')
amount = request.data.get('amount', 0)
description = request.data.get('description', '')
@@ -383,7 +394,7 @@ class EarnCoinsAPIView(APIView):
)
try:
target_user = FUser.objects.get(pk=user_id) if user_id else request.user
target_user = await FUser.objects.aget(pk=user_id) if user_id else request.user
except FUser.DoesNotExist:
return create_standardized_error_response(
message='目标用户不存在',
@@ -391,7 +402,7 @@ class EarnCoinsAPIView(APIView):
status_code=status.HTTP_400_BAD_REQUEST
)
try:
def _earn_coins_txn():
with transaction.atomic():
target_user.coins = F('coins') + amount
target_user.save(update_fields=['coins'])
@@ -406,6 +417,9 @@ class EarnCoinsAPIView(APIView):
description=description or '获得y币',
)
try:
await sync_to_async(_earn_coins_txn)()
return create_standardized_response(
data={
'points': target_user.points,
@@ -440,7 +454,7 @@ class SpendPointsAPIView(APIView):
),
responses={200: success_response, 400: error_response, 401: unauthorized_response, 500: error_response},
)
def post(self, request):
async def post(self, request):
amount = request.data.get('amount', 0)
description = request.data.get('description', '')
@@ -460,16 +474,12 @@ class SpendPointsAPIView(APIView):
status_code=status.HTTP_400_BAD_REQUEST
)
try:
def _spend_points_txn():
with transaction.atomic():
user = FUser.objects.select_for_update().get(pk=request.user.pk)
if user.points < amount:
return create_standardized_error_response(
message='积分余额不足',
code=ResponseCode.PARAMETER_ERROR,
status_code=status.HTTP_400_BAD_REQUEST
)
return None
user.points = F('points') - amount
user.save(update_fields=['points'])
@@ -483,6 +493,17 @@ class SpendPointsAPIView(APIView):
balance_after=user.points,
description=description or '消费积分',
)
return user
try:
user = await sync_to_async(_spend_points_txn)()
if user is None:
return create_standardized_error_response(
message='积分余额不足',
code=ResponseCode.PARAMETER_ERROR,
status_code=status.HTTP_400_BAD_REQUEST
)
return create_standardized_response(
data={
@@ -518,7 +539,7 @@ class SpendCoinsAPIView(APIView):
),
responses={200: success_response, 400: error_response, 401: unauthorized_response, 500: error_response},
)
def post(self, request):
async def post(self, request):
amount = request.data.get('amount', 0)
description = request.data.get('description', '')
@@ -538,16 +559,12 @@ class SpendCoinsAPIView(APIView):
status_code=status.HTTP_400_BAD_REQUEST
)
try:
def _spend_coins_txn():
with transaction.atomic():
user = FUser.objects.select_for_update().get(pk=request.user.pk)
if user.coins < amount:
return create_standardized_error_response(
message='y币余额不足',
code=ResponseCode.PARAMETER_ERROR,
status_code=status.HTTP_400_BAD_REQUEST
)
return None
user.coins = F('coins') - amount
user.save(update_fields=['coins'])
@@ -561,6 +578,17 @@ class SpendCoinsAPIView(APIView):
balance_after=user.coins,
description=description or '消费y币',
)
return user
try:
user = await sync_to_async(_spend_coins_txn)()
if user is None:
return create_standardized_error_response(
message='y币余额不足',
code=ResponseCode.PARAMETER_ERROR,
status_code=status.HTTP_400_BAD_REQUEST
)
return create_standardized_response(
data={