feat: ADRF async views (phase1) + native async serializers (phase2) + async cache infra
This commit is contained in:
+73
-61
@@ -1,6 +1,7 @@
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from adrf.views import APIView
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from asgiref.sync import sync_to_async
|
||||
from django.db import transaction
|
||||
from django.db.models import F
|
||||
from django.utils import timezone
|
||||
@@ -121,7 +122,7 @@ class TaskListAPIView(APIView):
|
||||
operation_description='获取所有活跃任务列表及当前用户的进度和等级信息',
|
||||
responses={200: success_response, 401: unauthorized_response, 500: error_response},
|
||||
)
|
||||
def get(self, request):
|
||||
async def get(self, request):
|
||||
try:
|
||||
user = request.user
|
||||
now = timezone.localtime(timezone.now())
|
||||
@@ -131,7 +132,7 @@ class TaskListAPIView(APIView):
|
||||
tasks = TaskDefinition.objects.filter(is_active=True)
|
||||
|
||||
task_list = []
|
||||
for task in tasks:
|
||||
async for task in tasks:
|
||||
if task.task_type == 'daily':
|
||||
period_key = daily_period
|
||||
elif task.task_type == 'weekly':
|
||||
@@ -139,9 +140,9 @@ class TaskListAPIView(APIView):
|
||||
else:
|
||||
period_key = 'permanent'
|
||||
|
||||
progress = UserTaskProgress.objects.filter(
|
||||
progress = await UserTaskProgress.objects.filter(
|
||||
user=user, task=task, period_key=period_key
|
||||
).first()
|
||||
).afirst()
|
||||
|
||||
task_list.append({
|
||||
'id': task.id,
|
||||
@@ -160,15 +161,19 @@ class TaskListAPIView(APIView):
|
||||
'is_claimed': progress.is_claimed if progress else False,
|
||||
})
|
||||
|
||||
user_level = UserLevel.objects.filter(user=user).first()
|
||||
next_threshold = LevelThreshold.objects.filter(
|
||||
user_level = await UserLevel.objects.filter(user=user).afirst()
|
||||
next_threshold = await LevelThreshold.objects.filter(
|
||||
level__gt=user_level.level if user_level else 1
|
||||
).order_by('level').first() if user_level else None
|
||||
).order_by('level').afirst() if user_level else None
|
||||
|
||||
cur_threshold = await LevelThreshold.objects.filter(
|
||||
level=user_level.level
|
||||
).afirst() if user_level else None
|
||||
|
||||
level_data = {
|
||||
'level': user_level.level if user_level else 1,
|
||||
'xp': user_level.xp if user_level else 0,
|
||||
'title': (LevelThreshold.objects.filter(level=user_level.level).first().title if user_level and LevelThreshold.objects.filter(level=user_level.level).exists() else '新手'),
|
||||
'title': cur_threshold.title if cur_threshold else '新手',
|
||||
'next_level_xp': next_threshold.xp_required if next_threshold else None,
|
||||
'next_level_title': next_threshold.title if next_threshold else None,
|
||||
}
|
||||
@@ -208,7 +213,7 @@ class TaskTrackAPIView(APIView):
|
||||
),
|
||||
responses={200: success_response, 400: error_response, 401: unauthorized_response, 500: error_response},
|
||||
)
|
||||
def post(self, request):
|
||||
async def post(self, request):
|
||||
action_type = request.data.get('action_type')
|
||||
count = int(request.data.get('count', 1))
|
||||
|
||||
@@ -228,7 +233,8 @@ class TaskTrackAPIView(APIView):
|
||||
)
|
||||
|
||||
try:
|
||||
updated_tasks = track_user_action(request.user, action_type, count)
|
||||
# track_user_action 为同步 helper(user.py 也以 sync_to_async 调用),线程池兜底
|
||||
updated_tasks = await sync_to_async(track_user_action)(request.user, action_type, count)
|
||||
return create_standardized_response(
|
||||
data={'updated_tasks': updated_tasks},
|
||||
code=ResponseCode.SUCCESS,
|
||||
@@ -256,10 +262,10 @@ class TaskClaimAPIView(APIView):
|
||||
],
|
||||
responses={200: success_response, 400: error_response, 401: unauthorized_response, 500: error_response},
|
||||
)
|
||||
def post(self, request, task_id):
|
||||
async def post(self, request, task_id):
|
||||
try:
|
||||
user = request.user
|
||||
task = TaskDefinition.objects.filter(id=task_id, is_active=True).first()
|
||||
task = await TaskDefinition.objects.filter(id=task_id, is_active=True).afirst()
|
||||
|
||||
if not task:
|
||||
return create_standardized_error_response(
|
||||
@@ -276,9 +282,9 @@ class TaskClaimAPIView(APIView):
|
||||
else:
|
||||
period_key = 'permanent'
|
||||
|
||||
progress = UserTaskProgress.objects.filter(
|
||||
progress = await UserTaskProgress.objects.filter(
|
||||
user=user, task=task, period_key=period_key
|
||||
).first()
|
||||
).afirst()
|
||||
|
||||
if not progress or not progress.is_completed:
|
||||
return create_standardized_error_response(
|
||||
@@ -294,53 +300,59 @@ class TaskClaimAPIView(APIView):
|
||||
status_code=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
with transaction.atomic():
|
||||
locked_user = FUser.objects.select_for_update().get(pk=user.pk)
|
||||
user_level, _ = UserLevel.objects.select_for_update().get_or_create(
|
||||
user=locked_user,
|
||||
defaults={'xp': 0, 'level': 1}
|
||||
)
|
||||
|
||||
if task.reward_points > 0:
|
||||
locked_user.points = F('points') + task.reward_points
|
||||
locked_user.save(update_fields=['points'])
|
||||
locked_user.refresh_from_db()
|
||||
PointTransaction.objects.create(
|
||||
# select_for_update + 事务 + refresh_from_db 整体在同步函数内执行,线程池兜底
|
||||
def _claim_reward():
|
||||
with transaction.atomic():
|
||||
locked_user = FUser.objects.select_for_update().get(pk=user.pk)
|
||||
user_level, _ = UserLevel.objects.select_for_update().get_or_create(
|
||||
user=locked_user,
|
||||
transaction_type='earn',
|
||||
currency_type='points',
|
||||
amount=task.reward_points,
|
||||
balance_after=locked_user.points,
|
||||
description=f'完成任务: {task.name}',
|
||||
defaults={'xp': 0, 'level': 1}
|
||||
)
|
||||
|
||||
if task.reward_coins > 0:
|
||||
locked_user.coins = F('coins') + task.reward_coins
|
||||
locked_user.save(update_fields=['coins'])
|
||||
locked_user.refresh_from_db()
|
||||
PointTransaction.objects.create(
|
||||
user=locked_user,
|
||||
transaction_type='earn',
|
||||
currency_type='coins',
|
||||
amount=task.reward_coins,
|
||||
balance_after=locked_user.coins,
|
||||
description=f'完成任务: {task.name}',
|
||||
)
|
||||
if task.reward_points > 0:
|
||||
locked_user.points = F('points') + task.reward_points
|
||||
locked_user.save(update_fields=['points'])
|
||||
locked_user.refresh_from_db()
|
||||
PointTransaction.objects.create(
|
||||
user=locked_user,
|
||||
transaction_type='earn',
|
||||
currency_type='points',
|
||||
amount=task.reward_points,
|
||||
balance_after=locked_user.points,
|
||||
description=f'完成任务: {task.name}',
|
||||
)
|
||||
|
||||
if task.reward_coins > 0:
|
||||
locked_user.coins = F('coins') + task.reward_coins
|
||||
locked_user.save(update_fields=['coins'])
|
||||
locked_user.refresh_from_db()
|
||||
PointTransaction.objects.create(
|
||||
user=locked_user,
|
||||
transaction_type='earn',
|
||||
currency_type='coins',
|
||||
amount=task.reward_coins,
|
||||
balance_after=locked_user.coins,
|
||||
description=f'完成任务: {task.name}',
|
||||
)
|
||||
|
||||
if task.reward_xp > 0:
|
||||
user_level.xp = F('xp') + task.reward_xp
|
||||
user_level.save(update_fields=['xp', 'updated_at'])
|
||||
user_level.refresh_from_db()
|
||||
check_level_up(user_level)
|
||||
|
||||
progress.is_claimed = True
|
||||
progress.claimed_at = now
|
||||
progress.save(update_fields=['is_claimed', 'claimed_at'])
|
||||
|
||||
if task.reward_xp > 0:
|
||||
user_level.xp = F('xp') + task.reward_xp
|
||||
user_level.save(update_fields=['xp', 'updated_at'])
|
||||
user_level.refresh_from_db()
|
||||
check_level_up(user_level)
|
||||
next_threshold = LevelThreshold.objects.filter(
|
||||
level__gt=user_level.level
|
||||
).order_by('level').first()
|
||||
|
||||
progress.is_claimed = True
|
||||
progress.claimed_at = now
|
||||
progress.save(update_fields=['is_claimed', 'claimed_at'])
|
||||
return locked_user, user_level, next_threshold
|
||||
|
||||
user_level.refresh_from_db()
|
||||
next_threshold = LevelThreshold.objects.filter(
|
||||
level__gt=user_level.level
|
||||
).order_by('level').first()
|
||||
locked_user, user_level, next_threshold = await sync_to_async(_claim_reward)()
|
||||
|
||||
return create_standardized_response(
|
||||
data={
|
||||
@@ -375,19 +387,19 @@ class UserLevelAPIView(APIView):
|
||||
operation_description='获取当前用户的等级、经验值、等级称号和升级进度',
|
||||
responses={200: success_response, 401: unauthorized_response, 500: error_response},
|
||||
)
|
||||
def get(self, request):
|
||||
async def get(self, request):
|
||||
try:
|
||||
user = request.user
|
||||
user_level, _ = UserLevel.objects.get_or_create(
|
||||
user_level, _ = await UserLevel.objects.aget_or_create(
|
||||
user=user, defaults={'xp': 0, 'level': 1}
|
||||
)
|
||||
|
||||
current_threshold = LevelThreshold.objects.filter(
|
||||
current_threshold = await LevelThreshold.objects.filter(
|
||||
level=user_level.level
|
||||
).first()
|
||||
next_threshold = LevelThreshold.objects.filter(
|
||||
).afirst()
|
||||
next_threshold = await LevelThreshold.objects.filter(
|
||||
level__gt=user_level.level
|
||||
).order_by('level').first()
|
||||
).order_by('level').afirst()
|
||||
|
||||
current_xp = current_threshold.xp_required if current_threshold else 0
|
||||
next_xp = next_threshold.xp_required if next_threshold else user_level.xp
|
||||
|
||||
Reference in New Issue
Block a user