diff --git a/contest/contest/tasks.py b/contest/contest/tasks.py index c753582..5e56544 100644 --- a/contest/contest/tasks.py +++ b/contest/contest/tasks.py @@ -1,6 +1,9 @@ -import logging +"""celery 任务""" + +from __future__ import annotations + +from typing import TYPE_CHECKING -import redis from celery import shared_task from django.core.cache import cache from django.shortcuts import get_object_or_404 @@ -10,59 +13,50 @@ # 导致运行的时候如果直接import contest.quiz.models会找不到 # 所以就直接默认已经在contest路径里开始索引,因此lint会报错 # 但是实际上是可以运行的,忽略Lint报错即可 -from quiz.models import ( - Choice, - DraftAnswer, - DraftResponse, -) +from quiz.models import Choice, DraftAnswer, DraftResponse -# Get an instance of a logger -logger = logging.getLogger("django") +if TYPE_CHECKING: + from typing import Generator @shared_task def auto_save_redis_to_database() -> None: - # 获取 Redis 连接 - r = redis.Redis(host="127.0.0.1", port=6379, db=1) - # 使用 scan_iter 获取所有键 - keys = r.scan_iter("*_ddl") - if keys is None: - return - for key in keys: - ddl_key = key.decode("utf-8")[3:] - ddl = cache.get(ddl_key) - now = timezone.now() - if ddl is not None: - if ddl < now: - try: - draft_response = DraftResponse.objects.get(id=int(ddl_key[:-4])) - cache_key = f"{ddl_key[:-4]}_json" - # # 从 Redis 获取现有的答案缓存 - cached_answers = cache.get(cache_key, {}) - - if cached_answers is not None: # 防止未提交的是白卷 - for question_id, choice_id in cached_answers.items(): - # Filter out tokens - if not question_id.startswith("question-"): - continue - - if not isinstance(choice_id, str) or not choice_id.startswith( - "choice-" - ): - return - - answer: DraftAnswer = get_object_or_404( - draft_response.answer_set, - question_id=int(question_id.removeprefix("question-")), - ) - - answer.choice = get_object_or_404( - Choice.objects, - pk=int(choice_id.removeprefix("choice-")), - question=answer.question, - ) - - answer.save() + """提交 Redis 缓存中过期的答卷草稿""" + scanner: Generator[str, None, None] = cache.iter_keys("*_ddl") # type: ignore[attr-defined] + # `iter_keys`由 django-redis 提供,django 本身没有 + for ddl_key in scanner: + pk = ddl_key.removesuffix("_ddl") + ddl = cache.get(pk) + if ddl is not None and ddl < timezone.now(): + try: + draft_response = DraftResponse.objects.get(pk=pk) + cached_answers = cache.get(f"{pk}_json", {}) + + # 同步 Redis 缓存到数据库 + # 若未作答,可能 Redis 中无记录,但数据库中仍有 + if cached_answers is not None: + for question_id, choice_id in cached_answers.items(): + # Filter out tokens + if not question_id.startswith("question-"): + continue + + if not isinstance(choice_id, str) or not choice_id.startswith( + "choice-" + ): + return + + answer: DraftAnswer = get_object_or_404( + draft_response.answer_set, + question_id=int(question_id.removeprefix("question-")), + ) + + answer.choice = get_object_or_404( + Choice.objects, + pk=int(choice_id.removeprefix("choice-")), + question=answer.question, + ) + + answer.save() # 1. Convert from draft response, answers = draft_response.finalize(submit_at=timezone.now()) @@ -72,9 +66,11 @@ def auto_save_redis_to_database() -> None: response.answer_set.bulk_create(answers) draft_response.delete() - except DraftResponse.DoesNotExist as e: - print("here is tasks.py 74 line") - print(e) + except DraftResponse.DoesNotExist as e: + print("here is tasks.py 74 line") + print(e) - r.delete(key) - r.delete(":1:" + ddl_key[:-4] + "_json") + # 即使`DraftResponse.DoesNotExist`,也应尝试删除 Redis 中的记录 + # 因为可能是 Django 正常处理过了 + cache.delete(f"{pk}_ddl") + cache.delete(f"{pk}_json")