refactor(material): 改用显式失效缓存,移除 signals;TTL 30min → 5min

- 删除 signals.py,改在 approve / reject / perform_destroy / import_excel 四个会影响
  已审核材料集合的入口显式调用 invalidate_category_tree_cache(),调用栈可见、易追踪
- reject 与 perform_destroy 仅当原状态为 approved 时才失效,避免无效缓存抖动
- TTL 由 30 分钟降为 5 分钟,作为兜底防止遗漏路径

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-04-27 09:36:11 +08:00
parent 25587dce21
commit d1201d6923
3 changed files with 16 additions and 26 deletions

View File

@ -4,6 +4,3 @@ from django.apps import AppConfig
class MaterialConfig(AppConfig): class MaterialConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.material' name = 'apps.material'
def ready(self):
from . import signals # noqa: F401

View File

@ -1,21 +0,0 @@
from django.core.cache import cache
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from .models import Material
CATEGORY_TREE_CACHE_KEY = 'material:category_tree:approved'
def invalidate_category_tree_cache():
cache.delete(CATEGORY_TREE_CACHE_KEY)
@receiver(post_save, sender=Material)
def _on_material_saved(sender, instance, **kwargs):
invalidate_category_tree_cache()
@receiver(post_delete, sender=Material)
def _on_material_deleted(sender, instance, **kwargs):
invalidate_category_tree_cache()

View File

@ -19,10 +19,16 @@ from django.core.cache import cache
from .models import Material, MaterialCategory, MaterialSubcategory from .models import Material, MaterialCategory, MaterialSubcategory
from .serializers import MaterialSerializer, MaterialListSerializer, MaterialCategorySerializer, MaterialSubcategorySerializer from .serializers import MaterialSerializer, MaterialListSerializer, MaterialCategorySerializer, MaterialSubcategorySerializer
from .importers import import_materials_plan_excel from .importers import import_materials_plan_excel
from .signals import CATEGORY_TREE_CACHE_KEY
from apps.factory.models import COOPERATION_MODE_CHOICES from apps.factory.models import COOPERATION_MODE_CHOICES
CATEGORY_TREE_CACHE_KEY = 'material:category_tree:approved'
def invalidate_category_tree_cache():
cache.delete(CATEGORY_TREE_CACHE_KEY)
def _join_choice_values(values, choices): def _join_choice_values(values, choices):
if not values: if not values:
return "" return ""
@ -296,7 +302,10 @@ class MaterialViewSet(ModelViewSet):
# 普通用户只能删除创建中的材料 # 普通用户只能删除创建中的材料
if self.request.user.role != 'admin' and instance.status != 'draft': if self.request.user.role != 'admin' and instance.status != 'draft':
raise PermissionDenied("只有创建中的材料可以删除") raise PermissionDenied("只有创建中的材料可以删除")
was_approved = instance.status == 'approved'
instance.delete() instance.delete()
if was_approved:
invalidate_category_tree_cache()
@action(detail=True, methods=['post']) @action(detail=True, methods=['post'])
def submit(self, request, pk=None): def submit(self, request, pk=None):
@ -344,6 +353,7 @@ class MaterialViewSet(ModelViewSet):
material.status = 'approved' material.status = 'approved'
material.save() material.save()
invalidate_category_tree_cache()
return Response({"status": "审核通过"}) return Response({"status": "审核通过"})
@action(detail=True, methods=['post']) @action(detail=True, methods=['post'])
@ -365,8 +375,11 @@ class MaterialViewSet(ModelViewSet):
status=status.HTTP_400_BAD_REQUEST status=status.HTTP_400_BAD_REQUEST
) )
was_approved = material.status == 'approved'
material.status = 'draft' material.status = 'draft'
material.save() material.save()
if was_approved:
invalidate_category_tree_cache()
return Response({"status": "审核拒绝"}) return Response({"status": "审核拒绝"})
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
@ -484,6 +497,7 @@ class MaterialViewSet(ModelViewSet):
except Exception as exc: except Exception as exc:
return Response({"detail": f"导入失败: {exc}"}, status=status.HTTP_400_BAD_REQUEST) return Response({"detail": f"导入失败: {exc}"}, status=status.HTTP_400_BAD_REQUEST)
invalidate_category_tree_cache()
return Response(result) return Response(result)
@action(detail=False, methods=['get'], url_path='category-tree', permission_classes=[IsAuthenticated]) @action(detail=False, methods=['get'], url_path='category-tree', permission_classes=[IsAuthenticated])
@ -536,7 +550,7 @@ class MaterialViewSet(ModelViewSet):
], ],
}) })
cache.set(CATEGORY_TREE_CACHE_KEY, data, timeout=60 * 30) cache.set(CATEGORY_TREE_CACHE_KEY, data, timeout=60 * 5)
return Response(data) return Response(data)
@action(detail=False, methods=['get'], url_path='categories-by-major') @action(detail=False, methods=['get'], url_path='categories-by-major')