• (项目)生鲜超市(十一)


    十四、首页、商品数量、缓存和限速功能开发

    1、首页轮播图

      为了方便测试,还是将pycharm的环境改成本地的,Vue中的host也改成本地地址,注意要修改数据库的地址。

      然后在goods/serializers.py中加入轮播图字段的序列化:

    1 class BannerSerializer(serializers.ModelSerializer):
    2     class Meta:
    3         model = Banner
    4         fields = '__all__'

      在goods/views.py中编写轮播图的接口:

    1 class BannerViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    2     """首页轮播图"""
    3 
    4     queryset = Banner.objects.all().order_by('index')
    5     serializer_class = BannerSerializer

      注册url:

    1 router.register(r'banners', BannerViewSet, base_name='banners')  # 首页轮播图

      在后台添加轮播图片,然后访问首页即可看到轮播图:

    2、首页新品

      我们之前在设计Goods的model的时候,有设计过一个字段is_new:is_new = models.BooleanField("是否新品", default=False)。

      实现这个功能只需要在goods/filter/GoodsFilter中添加过滤即可:

     1 class GoodsFilter(django_filters.rest_framework.FilterSet):
     2     """商品过滤"""
     3 
     4     # name是要过滤的字段,lookup是执行的行为
     5     pricemin = django_filters.NumberFilter(field_name="shop_price", lookup_expr='gte')
     6     pricemax = django_filters.NumberFilter(field_name="shop_price", lookup_expr='lte')
     7     top_category = django_filters.NumberFilter(field_name="category", method='top_category_filter')
     8 
     9     def top_category_filter(self, queryset, name, value):
    10         # 不管当前点击的是一级分类二级分类还是三级分类,都能找到
    11         return queryset.filter(Q(category_id=value) | Q(category__parent_category_id=value) | Q(
    12             category__parent_category__parent_category_id=value))
    13 
    14     class Meta:
    15         model = Goods
    16         fields = ['pricemin', 'pricemax', 'is_hot', 'is_new']

      然后在后台添加几个新品即可:

    3、首页商品分类

      首先将商品广告位的字段进行序列化,还需要对商品大类下的分类及品牌进行序列化:

     1 class BrandSerializer(serializers.ModelSerializer):
     2     class Meta:
     3         model = GoodsCategoryBrand
     4         fields = '__all__'
     5 
     6 
     7 class IndexCategorySerializer(serializers.ModelSerializer):
     8     # 一个大类下可以有多个商标
     9     brands = BrandSerializer(many=True)
    10     # good有一个外键category指向三级类,直接反向通过外键category(三级类),取某个大类下面的商品是取不出来的
    11     goods = serializers.SerializerMethodField()
    12     # 取二级商品分类
    13     sub_cat = CategorySerializer2(many=True)
    14     # 广告商品
    15     ad_goods = serializers.SerializerMethodField()
    16 
    17     def get_ad_goods(self, obj):
    18         goods_json = {}
    19         ad_goods = IndexAd.objects.filter(category_id=obj.id)
    20         if ad_goods:
    21             good_ins = ad_goods[0].goods
    22             # 在serializer里面调用serializer,就要添加一个参数context(上下文request),嵌套serializer必须加
    23             # serializer返回的时候一定要加'data',这样才是json数据
    24             goods_json = GoodsSerializer(good_ins, many=False, context={'request': self.context['request']}).data
    25         return goods_json
    26 
    27     def get_goods(self, obj):
    28         # 让这个商品相关父类子类等都可以进行匹配
    29         all_goods = Goods.objects.filter(Q(category_id=obj.id) | Q(category__parent_category_id=obj.id) |
    30                                          Q(category__parent_category__parent_category_id=obj.id))
    31         goods_serializer = GoodsSerializer(all_goods, many=True, context={'request': self.context['request']})
    32         return goods_serializer.data
    33 
    34     class Meta:
    35         model = GoodsCategory
    36         fields = '__all__'

      然后编写首页商品分类的接口:

    1 class IndexCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    2     """首页商品分类"""
    3 
    4     queryset = GoodsCategory.objects.filter(is_tab=True, name__in=["生鲜食品", "酒水饮料"])
    5     serializer_class = IndexCategorySerializer

      注册url:

    1 router.register(r'indexgoods', IndexCategoryViewSet, base_name='indexgoods')  # 首页系列商品分类

      在后台添加宣传品牌和首页广告:

    4、商品点击数和收藏数

    4.1 点击数

      GoodsListViewSet中继承了mixins.RetrieveModelMixin(获取商品详情),我们只需要重写这个类的retrieve方法即可:

     1 class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
     2     """
     3     list:
     4         商品列表,分页,搜索,过滤,排序
     5     retrieve:
     6         获取商品详情
     7     """
     8 
     9     pagination_class = GoodsPagination
    10     queryset = Goods.objects.all().order_by('id')  # 必须定义一个默认的排序,否则会报错
    11     serializer_class = GoodsSerializer
    12     filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
    13 
    14     # 自定义过滤类
    15     filter_class = GoodsFilter
    16 
    17     # 搜索,=name表示精确搜索,也可以使用正则
    18     search_fields = ('name', 'goods_brief', 'goods_desc')
    19 
    20     # 排序
    21     ordering_fields = ('sold_num', 'shop_price')
    22 
    23     # 重写retrieve方法,商品点击数加1
    24     def retrieve(self, request, *args, **kwargs):
    25         instance = self.get_object()
    26         instance.click_num += 1
    27         instance.save()
    28         serializer = self.get_serializer(instance)
    29         return Response(serializer.data)

      点击某一个商品,在数据库中可以看到click_num加了1:

    4.2 收藏数

      在UserFavViewset接口中继承了mixins.CreateModelMixin,添加收藏实际就是创建数据库,这里重写它的perform_create方法即可:

     1 class UserFavViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.DestroyModelMixin,
     2                      mixins.RetrieveModelMixin, viewsets.GenericViewSet):
     3     """用户收藏"""
     4 
     5     # permission是权限验证 IsAuthenticated必须登录用户 IsOwnerOrReadOnly必须是当前登录的用户
     6     permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
     7 
     8     # authentication是用户认证
     9     authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
    10 
    11     # 搜索的字段
    12     lookup_field = 'goods_id'
    13 
    14     # 动态选择serializer
    15     def get_serializer_class(self):
    16         if self.action == "list":
    17             return UserFavDetailSerializer
    18         elif self.action == "create":
    19             return UserFavSerializer
    20         return UserFavSerializer
    21 
    22     # 只能查看当前登录用户的收藏,不会获取所有用户的收藏
    23     def get_queryset(self):
    24         return UserFav.objects.filter(user=self.request.user)
    25 
    26     # 收藏数加1
    27     def perform_create(self, serializer):
    28         instance = serializer.save()
    29         # 这里instance相当于UserFav的model,通过它找到goods
    30         goods = instance.goods
    31         goods.fav_num += 1
    32         goods.save()

      登录点击收藏之后,收藏数会增加:

    4.3 信号量实现收藏数

      用户在delete和create的时候django model都会发送一个信号量出来,可以通过信号量的方式修改收藏数,在user_operation下新建signal.py:

     1 from django.dispatch import receiver
     2 from django.db.models.signals import post_save, post_delete
     3 from user_operation.models import UserFav
     4 
     5 @receiver(post_save, sender=UserFav)
     6 def create_UserFav(sender, instance=None, created=False, **kwargs):
     7     # update的时候也会进行post_save
     8     if created:
     9         goods = instance.goods
    10         goods.fav_num += 1
    11         goods.save()
    12 
    13 @receiver(post_delete, sender=UserFav)
    14 def delete_UserFav(sender, instance=None, created=False, **kwargs):
    15     goods = instance.goods
    16     goods.fav_num -= 1
    17     goods.save()

      然后在apps.py中重写ready方法:

    1 from django.apps import AppConfig
    2 
    3 
    4 class UserOperationConfig(AppConfig):
    5     name = 'user_operation'
    6     verbose_name = '用户操作管理'
    7 
    8     def ready(self):
    9         import user_operation.signals

      现在添加收藏,删除收藏:

    5、商品库存和销量修改

    5.1 库存数

      我们在新增商品到购物车,修改购物车中的数量,删除购物车记录都会对商品库存数产生影响,在trade/views.py中对购物车接口中的相关操作做库存数修改的逻辑:

     1 class ShoppingCartViewSet(viewsets.ModelViewSet):
     2     """
     3     购物车功能
     4     list:
     5         获取购物车详情
     6     create:
     7         加入购物车
     8     delete:
     9         删除购物记录
    10     """
    11 
    12     permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    13     authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
    14 
    15     # 把商品id传回去
    16     lookup_field = 'goods_id'
    17 
    18     def get_serializer_class(self):
    19         if self.action == 'list':
    20             return ShopCartDetailSerializer
    21         else:
    22             return ShopCartSerializer
    23 
    24     def get_queryset(self):
    25         return ShoppingCart.objects.filter(user=self.request.user)
    26 
    27     # 创建购物车,库存数减少
    28     def perform_create(self, serializer):
    29         shop_cart = serializer.save()
    30         goods = shop_cart.goods
    31         goods.goods_num -= shop_cart.nums
    32         goods.save()
    33 
    34     # 移除购物车,库存数增加
    35     def perform_destroy(self, instance):
    36         goods = instance.goods()
    37         goods.goods_num += instance.nums
    38         goods.save()
    39         instance.delete()
    40 
    41     # 跟新购物车,可能是增加也可能是减少
    42     def perform_update(self, serializer):
    43         # 先获取修改之前的库存数量
    44         existed_record = ShoppingCart.objects.get(id=serializer.instance.id)
    45         existed_nums = existed_record.nums
    46         
    47         # 先保存之前的数据existed_nums
    48         save_record = serializer.save()
    49         
    50         # 做更新操作
    51         nums = save_record.nums - existed_nums
    52         goods = save_record.goods
    53         goods.goods_num -= nums
    54         goods.save()

    5.2 销量

      商品的销量只有在支付成功之后才能增加,也就是在AlipayView接口中订单支付成功加入增加销量的逻辑:

     1 class AlipayView(APIView):
     2     """支付宝接口"""
     3 
     4     # 处理支付宝的return_url返回
     5     def get(self, request):
     6         processed_dict = {}
     7 
     8         # 获取GET中的参数
     9         for key, value in request.GET.items():
    10             processed_dict[key] = value
    11 
    12         # 从processed_dict中取出sign
    13         sign = processed_dict.pop("sign", None)
    14 
    15         # 生成AliPay对象
    16         alipay = AliPay(
    17             appid="2016092000557473",
    18             app_notify_url="http://148.70.2.75:8000/alipay/return/",
    19             app_private_key_path=private_key_path,
    20             alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
    21             debug=True,  # 默认False,
    22             return_url="http://148.70.2.75:8000/alipay/return/"
    23         )
    24 
    25         # 验证签名
    26         verify_re = alipay.verify(processed_dict, sign)
    27 
    28         # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。
    29         if verify_re is True:
    30             order_sn = processed_dict.get('out_trade_no', None)
    31             trade_no = processed_dict.get('trade_no', None)
    32             trade_status = processed_dict.get('trade_status', None)
    33 
    34             existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
    35             for existed_order in existed_orders:
    36                 existed_order.pay_status = trade_status
    37                 existed_order.trade_no = trade_no
    38                 existed_order.pay_time = datetime.now()
    39                 existed_order.save()
    40 
    41             # 支付完成跳转到首页
    42             response = redirect("index")
    43             response.set_cookie("nextPath", "pay", max_age=2)
    44             return response
    45         else:
    46             response = redirect("index")
    47             return response
    48 
    49     # 处理支付宝的notify_url
    50     def post(self, request):
    51         processed_dict = {}
    52 
    53         # 取出post里面的数据
    54         for key, value in request.POST.items():
    55             processed_dict[key] = value
    56 
    57         # 去掉sign
    58         sign = processed_dict.pop("sign", None)
    59 
    60         # 生成一个Alipay对象
    61         alipay = AliPay(
    62             appid="2016092000557473",
    63             app_notify_url="http://148.70.2.75:8000/alipay/return/",
    64             app_private_key_path=private_key_path,
    65             alipay_public_key_path=ali_pub_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
    66             debug=True,  # 默认False,
    67             return_url="http://148.70.2.75:8000/alipay/return/"
    68         )
    69 
    70         # 进行验证
    71         verify_re = alipay.verify(processed_dict, sign)
    72 
    73         if verify_re is True:
    74             # 商户网站唯一订单号
    75             order_sn = processed_dict.get('out_trade_no', None)
    76             # 支付宝系统交易流水号
    77             trade_no = processed_dict.get('trade_no', None)
    78             # 交易状态
    79             trade_status = processed_dict.get('trade_status', None)
    80 
    81             # 查询数据库中订单记录
    82             existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
    83             for existed_order in existed_orders:
    84                 # 订单商品项
    85                 order_goods = existed_order.goods.all()
    86                 # 商品销量增加
    87                 for order_good in order_goods:
    88                     goods = order_good.goods
    89                     goods.sold_num += order_good.goods_num
    90                     goods.save()
    91 
    92                 # 更新订单状态
    93                 existed_order.pay_status = trade_status
    94                 existed_order.trade_no = trade_no
    95                 existed_order.pay_time = datetime.now()
    96                 existed_order.save()
    97             # 需要返回一个'success'给支付宝,如果不返回,支付宝会一直发送订单支付成功的消息
    98             return Response("success")

    6、drf的缓存设置

      缓存的作用是为了加速用户访问某一资源的速度,将用户经常访问的数据加入缓存中,取数据的时候直接到缓存中去取,没有的话再去数据库,速度肯定会快很多。我们通过drf的一个扩展来实现缓存,github上有官方使用说明:http://chibisov.github.io/drf-extensions/docs/#caching。

      首先安装drf-extensions库:pip install drf-extensions

      然后在需要访问的数据的接口中加上缓存即可,我们在商品列表的接口中加入缓存,直接继承CacheResponseMixin即可:

     1 class GoodsListViewSet(CacheResponseMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
     2     """
     3     list:
     4         商品列表,分页,搜索,过滤,排序
     5     retrieve:
     6         获取商品详情
     7     """
     8 
     9     pagination_class = GoodsPagination
    10     queryset = Goods.objects.all().order_by('id')  # 必须定义一个默认的排序,否则会报错
    11     serializer_class = GoodsSerializer
    12     filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
    13 
    14     # 自定义过滤类
    15     filter_class = GoodsFilter
    16 
    17     # 搜索,=name表示精确搜索,也可以使用正则
    18     search_fields = ('name', 'goods_brief', 'goods_desc')
    19 
    20     # 排序
    21     ordering_fields = ('sold_num', 'shop_price')
    22 
    23     # 重写retrieve方法,商品点击数加1
    24     def retrieve(self, request, *args, **kwargs):
    25         instance = self.get_object()
    26         instance.click_num += 1
    27         instance.save()
    28         serializer = self.get_serializer(instance)
    29         return Response(serializer.data)

      在settings中设置过期时间:

    1 # 缓存配置
    2 REST_FRAMEWORK_EXTENSIONS = {
    3     'DEFAULT_CACHE_RESPONSE_TIMEOUT': 5   # 5s过期
    4 }

      这个缓存使用的是内存,每次重启都会失效。

      现在我们配置redis缓存,文档说明:http://django-redis-chs.readthedocs.io/zh_CN/latest/#id8

      首先安装包:pip install django-redis

      然后在settings中配置redis缓存:

     1 # redis缓存
     2 CACHES = {
     3     "default": {
     4         "BACKEND": "django_redis.cache.RedisCache",
     5         "LOCATION": "redis://127.0.0.1:6379",
     6         "OPTIONS": {
     7             "CLIENT_CLASS": "django_redis.client.DefaultClient",
     8         }
     9     }
    10 }

      使用redis缓存你得在服务器或者本地安装了redis才能使用。

    7、def的throttle设置api的访问速率

      为了防止爬虫或者黑客恶意攻击,对api的访问速率进行限制就显得非常重要的,官方文档说明:http://www.django-rest-framework.org/api-guide/throttling/

      首先在settings中进行配置:

     1 REST_FRAMEWORK = {
     2     'DEFAULT_AUTHENTICATION_CLASSES': (
     3         'rest_framework.authentication.BasicAuthentication',
     4         'rest_framework.authentication.SessionAuthentication',
     5         # 'rest_framework.authentication.TokenAuthentication'
     6     ),
     7     'DEFAULT_THROTTLE_CLASSES': (
     8             'rest_framework.throttling.AnonRateThrottle',   # 未登陆用户
     9             'rest_framework.throttling.UserRateThrottle'    # 登陆用户
    10         ),
    11     'DEFAULT_THROTTLE_RATES': {
    12         'anon': '3/minute',         # 每分钟可以请求两次
    13         'user': '5/minute'          # 每分钟可以请求五次
    14     }
    15 }

      然后在对应的api接口中加入访问速率限制即可,这里对商品列表接口进行配置:

     1 class GoodsListViewSet(CacheResponseMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
     2     """
     3     list:
     4         商品列表,分页,搜索,过滤,排序
     5     retrieve:
     6         获取商品详情
     7     """
     8 
     9     pagination_class = GoodsPagination
    10     queryset = Goods.objects.all().order_by('id')  # 必须定义一个默认的排序,否则会报错
    11     serializer_class = GoodsSerializer
    12     filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
    13 
    14     # 接口访问速率限制
    15     throttle_classes = (UserRateThrottle, AnonRateThrottle)
    16 
    17     # 自定义过滤类
    18     filter_class = GoodsFilter
    19 
    20     # 搜索,=name表示精确搜索,也可以使用正则
    21     search_fields = ('name', 'goods_brief', 'goods_desc')
    22 
    23     # 排序
    24     ordering_fields = ('sold_num', 'shop_price')
    25 
    26     # 重写retrieve方法,商品点击数加1
    27     def retrieve(self, request, *args, **kwargs):
    28         instance = self.get_object()
    29         instance.click_num += 1
    30         instance.save()
    31         serializer = self.get_serializer(instance)
    32         return Response(serializer.data)

      然后在登录或者未登录状态下访问该接口,超出次数如下:

  • 相关阅读:
    Android笔记(ImageView、BaseLine、进度条ProgressBar)
    Android笔记(dp、sp、px、多选按钮CheckBox、单选按钮RadioButton)
    Android笔记(简介)
    Android Studio安装后Fetching android sdk component information超时的解决方案
    解决The environment variable JAVA_HOME does not point to a valid JVM installation
    使用Genymotiont调试出现错误INSTALL_FAILED_CPU_ABI_INCOMPATIBLE解决办法
    Android Studio导入第三方jar包及.so动态库
    数据库知识记录
    CentOS7.5下yum安装MySQL8.0.11笔记
    MyBatis的mapper.xml中判断集合的size
  • 原文地址:https://www.cnblogs.com/Sweltering/p/10035542.html
Copyright © 2020-2023  润新知