From ec7cf71ea7e5da1c7466df88a1c74764241ed141 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Wed, 5 Aug 2026 04:15:36 -0600 Subject: [PATCH] =?UTF-8?q?refactor(problem):=20=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E9=80=BB=E8=BE=91=E6=94=B6=E6=95=9B=E5=88=B0?= =?UTF-8?q?=20services.resolve=5Ftags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原本 4 处题目保存逻辑各自复制了一份 get-or-create,且大小写敏感、有竞态。 Co-Authored-By: Claude Sonnet 5 --- problem/services.py | 52 ++++++++++++++++++++++++++++++++++++++++++ problem/views/admin.py | 34 +++++---------------------- 2 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 problem/services.py diff --git a/problem/services.py b/problem/services.py new file mode 100644 index 0000000..7a7a314 --- /dev/null +++ b/problem/services.py @@ -0,0 +1,52 @@ +from django.core.cache import cache +from django.db import IntegrityError + +from utils.constants import CacheKey + +from .models import ProblemTag + + +def clear_tag_cache(): + """标签列表接口按 keyword 分键缓存,标签一变就整批清掉""" + cache.delete_pattern(f"{CacheKey.problem_tags}:*") + + +def find_tags(names): + """按名字大小写不敏感查已有标签,查不到就跳过,不创建""" + tags = [] + seen = set() + for raw in names: + name = (raw or "").strip() + if not name or name.lower() in seen: + continue + seen.add(name.lower()) + tag = ProblemTag.objects.filter(name__iexact=name).first() + if tag is not None: + tags.append(tag) + return tags + + +def resolve_tags(names): + """把前端传来的标签名解析成 ProblemTag:去空格、大小写不敏感复用已有标签,没有才新建""" + tags = [] + seen = set() + created = False + for raw in names: + name = (raw or "").strip() + if not name or name.lower() in seen: + continue + seen.add(name.lower()) + tag = ProblemTag.objects.filter(name__iexact=name).first() + if tag is None: + try: + tag = ProblemTag.objects.create(name=name) + created = True + except IntegrityError: + # 并发下另一个请求刚建好同名标签,回查复用 + tag = ProblemTag.objects.filter(name__iexact=name).first() + if tag is None: + continue + tags.append(tag) + if created: + clear_tag_cache() + return tags diff --git a/problem/views/admin.py b/problem/views/admin.py index 0b94951..9a4b436 100644 --- a/problem/views/admin.py +++ b/problem/views/admin.py @@ -19,7 +19,7 @@ from utils.api import APIError, APIView, CSRFExemptAPIView, validate_serializer from utils.openai import get_ai_client from utils.shortcuts import natural_sort_key, rand_str -from ..models import Problem, ProblemTag +from ..models import Problem from ..serializers import ( AddContestProblemSerializer, ContestProblemMakePublicSerializer, @@ -32,6 +32,7 @@ from ..serializers import ( SQLTestCasePreviewSerializer, TestCaseUploadForm, ) +from ..services import resolve_tags from ..utils import generate_sql_display @@ -248,12 +249,7 @@ class ProblemAPI(ProblemBase): data["created_by"] = request.user problem = Problem.objects.create(**data) - for item in tags: - try: - tag = ProblemTag.objects.get(name=item) - except ProblemTag.DoesNotExist: - tag = ProblemTag.objects.create(name=item) - problem.tags.add(tag) + problem.tags.set(resolve_tags(tags)) return self.success(ProblemAdminSerializer(problem).data) @problem_permission_required @@ -310,14 +306,7 @@ class ProblemAPI(ProblemBase): setattr(problem, k, v) problem.save() - problem.tags.remove(*problem.tags.all()) - for tag in tags: - try: - tag = ProblemTag.objects.get(name=tag) - except ProblemTag.DoesNotExist: - tag = ProblemTag.objects.create(name=tag) - problem.tags.add(tag) - + problem.tags.set(resolve_tags(tags)) return self.success() @problem_permission_required @@ -364,12 +353,7 @@ class ContestProblemAPI(ProblemBase): data["created_by"] = request.user problem = Problem.objects.create(**data) - for item in tags: - try: - tag = ProblemTag.objects.get(name=item) - except ProblemTag.DoesNotExist: - tag = ProblemTag.objects.create(name=item) - problem.tags.add(tag) + problem.tags.set(resolve_tags(tags)) return self.success(ProblemAdminSerializer(problem).data) def get(self, request): @@ -434,13 +418,7 @@ class ContestProblemAPI(ProblemBase): setattr(problem, k, v) problem.save() - problem.tags.remove(*problem.tags.all()) - for tag in tags: - try: - tag = ProblemTag.objects.get(name=tag) - except ProblemTag.DoesNotExist: - tag = ProblemTag.objects.create(name=tag) - problem.tags.add(tag) + problem.tags.set(resolve_tags(tags)) return self.success() def delete(self, request):