You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django查询集ID缓存与多Viewset复用及数据返回方案咨询

解决Django电商数据返回的最优方案

首先,你提到的第一种扁平JSON方案确实存在严重的数据冗余问题——长描述重复、图片导致数据条目爆炸,不仅浪费带宽,前端处理也会很麻烦,完全不推荐。

第二种分请求的方案是可行的,但实现时需要注意HTTP的无状态特性,而且其实还有更简单高效的替代方案,我先给你拆解清楚:

最优方案:嵌套序列化器+预查询(单请求无冗余)

Django Rest Framework(DRF)的嵌套序列化器可以完美解决你的问题:一次请求获取所有关联数据,返回结构化的嵌套JSON,完全避免数据冗余。

步骤1:优化查询集

使用select_related(处理多对一关联:商品→店铺→城市)和prefetch_related(处理一对多反向关联:商品→图片)预加载所有关联数据,避免N+1查询问题:

products = Product.objects.filter(name__icontains=keyword) \
    .select_related('shop', 'shop__city') \
    .prefetch_related('related_product')  # 对应Image模型的related_name

步骤2:定义嵌套序列化器

从最底层的City开始,逐层向上嵌套:

from rest_framework import serializers
from .models import City, Shop, Product, Image

class CitySerializer(serializers.ModelSerializer):
    class Meta:
        model = City
        fields = ('id', 'name')

class ShopSerializer(serializers.ModelSerializer):
    city = CitySerializer()  # 嵌套城市序列化器
    class Meta:
        model = Shop
        fields = ('id', 'name', 'city')

class ImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = Image
        fields = ('id', 'image')  # image字段会返回配置的媒体文件路径

class ProductSerializer(serializers.ModelSerializer):
    shop = ShopSerializer()  # 嵌套店铺序列化器
    related_product = ImageSerializer(many=True)  # 嵌套多图片序列化器
    class Meta:
        model = Product
        fields = ('id', 'name', 'description', 'shop', 'related_product')

步骤3:在Viewset中使用

from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer

class ProductSearchViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = ProductSerializer
    queryset = Product.objects.all()

    def get_queryset(self):
        keyword = self.request.query_params.get('keyword', '')
        if keyword:
            return Product.objects.filter(name__icontains=keyword) \
                .select_related('shop', 'shop__city') \
                .prefetch_related('related_product')
        return super().get_queryset()

返回的JSON结构会是这样的(无任何冗余):

[
    {
        "id": 1,
        "name": "iPhone 15",
        "description": "最新款苹果手机,搭载A17 Pro芯片...",
        "shop": {
            "id": 1,
            "name": "Apple官方旗舰店",
            "city": {
                "id": 1,
                "name": "北京"
            }
        },
        "related_product": [
            {
                "id": 1,
                "image": "/media/images/iphone15_front.jpg"
            },
            {
                "id": 2,
                "image": "/media/images/iphone15_back.jpg"
            }
        ]
    }
]

如果你坚持要使用第二种分请求方案:实现方式

如果因为某些原因必须分多次请求获取数据,有两种可行的实现思路:

思路1:客户端携带商品ID列表

让客户端在获取商品列表后,保存商品ID,然后在请求图片/店铺数据时,将ID列表作为参数传递(比如用逗号分隔):

# 图片Viewset示例
class ImageViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = ImageSerializer
    queryset = Image.objects.all()

    def get_queryset(self):
        product_ids = self.request.query_params.get('product_ids', '')
        if product_ids:
            id_list = [int(id) for id in product_ids.split(',') if id.isdigit()]
            return self.queryset.filter(product_id__in=id_list)
        return super().get_queryset()

客户端请求示例:GET /api/images/?product_ids=1,2,3

这种方式简单无状态,但如果商品ID过多,可能会超出URL长度限制(一般浏览器支持的URL长度在2000字符左右)。

思路2:缓存搜索结果ID(用Redis)

通过生成唯一搜索token,将商品ID列表缓存到Redis中,客户端后续请求携带token即可:

  1. 先安装依赖:pip install redis django-redis
  2. 配置settings.py的缓存:
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}
  1. 商品搜索Viewset:
import uuid
from django.core.cache import cache
from rest_framework import viewsets
from rest_framework.response import Response
from .models import Product
from .serializers import ProductWithoutImagesSerializer  # 不含图片的商品序列化器

class ProductSearchViewSet(viewsets.ViewSet):
    def list(self, request):
        keyword = request.query_params.get('keyword', '')
        if not keyword:
            return Response([])
        products = Product.objects.filter(name__icontains=keyword) \
            .select_related('shop', 'shop__city')
        product_ids = list(products.values_list('id', flat=True))
        # 生成唯一搜索token
        search_token = str(uuid.uuid4())
        # 缓存ID列表,10分钟过期
        cache.set(search_token, product_ids, 60*10)
        # 返回商品数据和token
        serializer = ProductWithoutImagesSerializer(products, many=True)
        return Response({
            'products': serializer.data,
            'search_token': search_token
        })
  1. 图片Viewset:
from django.core.cache import cache
from rest_framework import viewsets
from rest_framework.response import Response
from .models import Image
from .serializers import ImageSerializer

class ImageViewSet(viewsets.ViewSet):
    def list(self, request):
        search_token = request.query_params.get('search_token', '')
        if not search_token:
            return Response([])
        product_ids = cache.get(search_token)
        if not product_ids:
            return Response([])
        images = Image.objects.filter(product_id__in=product_ids)
        serializer = ImageSerializer(images, many=True)
        return Response(serializer.data)

这种方式避免了客户端存储大量ID,但引入了缓存依赖,增加了系统复杂度,适合ID数量极大的场景。

后续ElasticSearch适配

如果后续引入ElasticSearch,嵌套序列化的方案也能很好适配:你可以将商品、店铺、城市、图片的嵌套结构直接存储到ES中,搜索时直接返回结构化结果,无需再查询数据库(或仅补充少量数据库数据),性能会更优。

内容的提问来源于stack exchange,提问作者dejandenib

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:28:35