fix(problemset): 奖章判定修复 N+1 与并发冲突并接入通知

This commit is contained in:
2026-08-04 04:40:17 -06:00
parent 1ef681d5e9
commit 8ecfeb5e36

View File

@@ -3,6 +3,7 @@ from django.utils import timezone
from account.decorators import login_required, teacher_admin_required from account.decorators import login_required, teacher_admin_required
from account.models import User from account.models import User
from achievement.notify import notify_badges
from problem.models import Problem from problem.models import Problem
from problemset.models import ( from problemset.models import (
BadgeConditionType, BadgeConditionType,
@@ -229,22 +230,39 @@ class ProblemSetProgressAPI(APIView):
return self.success("进度已更新") return self.success("进度已更新")
def _check_badges(self, progress): def _check_badges(self, progress):
"""检查是否获得奖章""" """检查是否获得奖章
badges = ProblemSetBadge.objects.filter(problemset=progress.problemset)
一次查询取出全部已获奖章 id内存比对避免逐条 exists()(原来的 N+1
用 get_or_create 避免并发重复提交撞 unique constraint 抛异常。
新获得的奖章推送给用户——接入前奖章是静默入库的,学生根本不知道自己拿到了。
"""
badges = list(ProblemSetBadge.objects.filter(problemset=progress.problemset))
if not badges:
return
owned = set(UserBadge.objects.filter(user=progress.user, badge__in=badges).values_list("badge_id", flat=True))
earned = []
for badge in badges: for badge in badges:
if UserBadge.objects.filter(user=progress.user, badge=badge).exists(): if badge.id in owned:
continue continue
if badge.condition_type == BadgeConditionType.ALL_PROBLEMS: if badge.condition_type == BadgeConditionType.ALL_PROBLEMS:
if progress.total_problems_count > 0 and progress.completed_problems_count == progress.total_problems_count: hit = progress.total_problems_count > 0 and progress.completed_problems_count == progress.total_problems_count
UserBadge.objects.create(user=progress.user, badge=badge)
elif badge.condition_type == BadgeConditionType.PROBLEM_COUNT: elif badge.condition_type == BadgeConditionType.PROBLEM_COUNT:
if progress.completed_problems_count >= badge.condition_value: hit = progress.completed_problems_count >= badge.condition_value
UserBadge.objects.create(user=progress.user, badge=badge)
elif badge.condition_type == BadgeConditionType.SCORE: elif badge.condition_type == BadgeConditionType.SCORE:
if progress.total_score >= badge.condition_value: hit = progress.total_score >= badge.condition_value
UserBadge.objects.create(user=progress.user, badge=badge) else:
hit = False
if hit:
_, created = UserBadge.objects.get_or_create(user=progress.user, badge=badge)
if created:
earned.append(badge)
if earned:
notify_badges(progress.user_id, earned)
# DEPRECATED: 前端未调用 (2026-05-26) # DEPRECATED: 前端未调用 (2026-05-26)