Skip to content

Commit

Permalink
Merge pull request #156 from YDX-2147483647/redis
Browse files Browse the repository at this point in the history
refactor: `auto_save_redis_to_database` 等
  • Loading branch information
everything411 authored Aug 28, 2024
2 parents 97cf6b9 + b9324b4 commit c8c96bf
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 65 deletions.
111 changes: 54 additions & 57 deletions contest/contest/tasks.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,71 +13,65 @@
# 导致运行的时候如果直接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 = int(ddl_key.removesuffix("_ddl"))
ddl = cache.get(f"{pk}_ddl")
if ddl is not None and ddl < timezone.now():
try:
draft = 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.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())
response, answers = draft.finalize(submit_at=draft.deadline)

# 2. Save
response.save()
response.answer_set.bulk_create(answers)
draft_response.delete()
draft.delete()

except DraftResponse.DoesNotExist as e:
print("here is tasks.py 74 line")
print(e)
except DraftResponse.DoesNotExist as e:
# TODO: 需要认真报错
print("here is tasks.py auto_save_redis_to_database")
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")
16 changes: 8 additions & 8 deletions contest/quiz/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from .util import AuthenticatedHttpRequest


def continue_or_finalize(draft_response: DraftResponse) -> bool:
def continue_or_finalize(draft: DraftResponse) -> bool:
"""自动提交
若草稿已超期,则定稿,否则什么也不做。
Expand All @@ -47,9 +47,9 @@ def continue_or_finalize(draft_response: DraftResponse) -> bool:
https://docs.djangoproject.com/en/4.2/ref/models/instances/#django.db.models.Model.delete
https://docs.djangoproject.com/en/4.2/ref/models/instances/#refreshing-objects-from-database
"""
if draft_response.outdated():
if draft.outdated():
# 从 Redis 获取现有的答案缓存
cached_answers = cache.get(f"{draft_response.id}_json", {})
cached_answers = cache.get(f"{draft.id}_json", {})

if cached_answers:
for question_id, choice_id in cached_answers.items():
Expand All @@ -61,7 +61,7 @@ def continue_or_finalize(draft_response: DraftResponse) -> bool:
return False

answer: DraftAnswer = get_object_or_404(
draft_response.answer_set,
draft.answer_set,
question_id=int(question_id.removeprefix("question-")),
)

Expand All @@ -73,18 +73,18 @@ def continue_or_finalize(draft_response: DraftResponse) -> bool:

answer.save()

cache.delete(f"{draft_response.id}_json")
cache.delete(f"{draft_response.id}_ddl")
cache.delete(f"{draft.id}_json")
cache.delete(f"{draft.id}_ddl")

# 提交之前的草稿

# 1. Convert from draft
response, answers = draft_response.finalize(submit_at=timezone.now())
response, answers = draft.finalize(submit_at=draft.deadline)

# 2. Save
response.save()
response.answer_set.bulk_create(answers)
draft_response.delete()
draft.delete()

return True

Expand Down

0 comments on commit c8c96bf

Please sign in to comment.