11-商品数量、缓存、限速功能开发
一、商品数量、缓存、限速功能开发
1.1、轮播图接口实现和vue调试
分析完,轮播图需要视图、序列化器、路由以及vue联调
apps/goods/views.py:
from .models import Goods,GoodsCategory,Banner from .serializers import GoodsSerializer,GoodsCategorySerializer,BannerSerializer class BannerViewset(mixins.ListModelMixin,viewsets.GenericViewSet): """ 获取轮播图列表 """ queryset = Banner.objects.all().order_by("index") serializer_class = BannerSerializer
apps/goods/serializers.py:
from .models import Goods,GoodsCategory,GoodsImage,Banner class BannerSerializer(serializers.ModelSerializer): class Meta: model = Banner # 指明model fields = "__all__" # 将全部字段显示出来
Mxshop/urls.py:
from rest_framework.routers import DefaultRouter from goods.views import GoodsListViewSet,CategoryViewset,BannerViewset router = DefaultRouter() #轮播图 router.register(r"banners",BannerViewset,base_name="banners")
添加好轮播商品后访问接口,成功返回数据与vue联调成功
1.2、新品功能接口开发
在商品列表页视图中的过滤器中添加is_new选项(goods/filters.py)
class GoodsFilter(django_filters.rest_framework.FilterSet): """ 商品过滤器 """ pricemin = django_filters.NumberFilter(field_name="shop_price",lookup_expr="gte",help_text="最低价格") pricemax = django_filters.NumberFilter(field_name="shop_price", lookup_expr="lte",help_text="最大价格") name = django_filters.CharFilter(field_name="name", lookup_expr="icontains",help_text="名字") #这里过滤的字段要和前端传过来的字段一样 top_category = django_filters.NumberFilter(method="top_category_filter",help_text="搜索栏关键字") def top_category_filter(self,queryset,name,value): return queryset.filter(Q(category_id=value)|Q(category__parent_category_id=value)|Q(category__parent_category__parent_category_id=value)) class Meta: model = Goods fields = ["pricemin","pricemax","name","top_category","is_hot","is_new"]
1.3、首页商品分类显示功能
goods/views.py:
class IndexCategoryViewset(mixins.ListModelMixin,viewsets.GenericViewSet): """ 首页商品分类数据 """ queryset = GoodsCategory.objects.filter(is_tab=True,name__in=["生鲜食品","酒水饮料"]) serializer_class = IndexCategorySerializer
goods/serializers.py:
from rest_framework import serializers from django.db.models import Q from .models import Goods,GoodsCategory,GoodsImage,Banner,GoodsCategoryBrand,IndexAd class GoodsCategorySerializer3(serializers.ModelSerializer): """ 商品三级分类 """ class Meta: model = GoodsCategory # 指明model fields = "__all__" # 将全部字段显示出来 class GoodsCategorySerializer2(serializers.ModelSerializer): """ 商品二级分类 """ sub_cat = GoodsCategorySerializer3(many=True) class Meta: model = GoodsCategory # 指明model fields = "__all__" # 将全部字段显示出来 class GoodsCategorySerializer(serializers.ModelSerializer): """ 商品类别序列化 """ sub_cat = GoodsCategorySerializer2(many=True) class Meta: model = GoodsCategory # 指明model fields = "__all__" # 将全部字段显示出来 class GoodsImageSerializer(serializers.ModelSerializer): class Meta: model = GoodsImage fields = ("image",) class GoodsSerializer(serializers.ModelSerializer): """ 在这里可以利用新写的字段来覆盖已有字段 """ category = GoodsCategorySerializer() #在这里实例化外键的序列化器,就可以完成 images = GoodsImageSerializer(many=True) #名字一定要和related_name="images"中的名字一样 class Meta: model = Goods #指明model #fields = ['category', 'goods_sn', 'name', 'click_num','sold_num','fav_num','add_time'] #指明字段 fields = "__all__" #将全部字段显示出来 class BannerSerializer(serializers.ModelSerializer): class Meta: model = Banner # 指明model fields = "__all__" # 将全部字段显示出来 class BrandsSerializer(serializers.ModelSerializer): class Meta: model = GoodsCategoryBrand fields = "__all__" class IndexCategorySerializer(serializers.ModelSerializer): brands = BrandsSerializer(many=True) goods = serializers.SerializerMethodField() sub_cat = GoodsCategorySerializer2(many=True) ad_goods = serializers.SerializerMethodField() def get_ad_goods(self,obj): goods_json = {} ad_goods = IndexAd.objects.filter(category_id=obj.id) if ad_goods: goods_ins = ad_goods[0].goods ##########关于图片加域名问题:mixins会自动加载,序列化不会原因判断request是否存在##################### goods_json = GoodsSerializer(goods_ins,many=False,context={"request":self.context["request"]}) return goods_json.data def get_goods(self,obj): all_goods = Goods.objects.filter(Q(category_id=obj.id)|Q(category__parent_category_id=obj.id)|Q(category__parent_category__parent_category_id=obj.id)) goods_serializer = GoodsSerializer(all_goods,many=True,context={"request":self.context["request"]}) return goods_serializer.data class Meta: model = GoodsCategory fields = "__all__"
新建品牌模型类goods/models.py,新建好之后数据库迁移生成表
class IndexAd(models.Model): category = models.ForeignKey(GoodsCategory, related_name="category",null=True, blank=True, verbose_name="商品类目", on_delete=models.CASCADE) goods = models.ForeignKey(Goods,related_name="goods",on_delete=models.CASCADE) class Meta: verbose_name = '首页商品类别广告' verbose_name_plural = verbose_name def __str__(self): return self.goods.name
注册后台品牌管理goods.adminx.py:
class IndexAdAdmin(object): list_display = ["category", "goods"] xadmin.site.register(IndexAd, IndexAdAdmin)
设置访问路由MxShop/urls.py:
#首页商品系列数据 router.register(r"indexgoods",IndexCategoryViewset,base_name="indexgoods")
后台管理系统添加品牌相关数据:
与vue联调首页展示:
1.4、商品点击数、收藏数修改
为了将商品点击数与收藏数进行修改,点击数可以直接重载Mixins的具体类方法,但为了使得我们逻辑清楚,收藏数利用django的信号量机制来完成收藏数修改。每当点击进去详情页的时候,就点击数加一。因此重载mixins.RetrieveModelMixin类的retrieve方法。
goods/views.py:
class GoodsListViewSet(mixins.ListModelMixin,mixins.RetrieveModelMixin,viewsets.GenericViewSet): """ 商品列表页 分页 搜索 过滤 排序 """ queryset = Goods.objects.all() serializer_class = GoodsSerializer pagination_class = GoodsSetPagination filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter] #这是精确搜索过滤,我们需要的是模糊搜索 # filterset_fields = ['name', 'shop_price'] filter_class = GoodsFilter search_fields = ("name","goods_brief","goods_desc") ordering_fields = ("shop_price","sold_num","add_time") def retrieve(self, request, *args, **kwargs): instance = self.get_object() instance.click_num += 1 #重写retrive方法,自定义详情页操作 instance.save() serializer = self.get_serializer(instance) return Response(serializer.data)
收藏数利用django信号机制完成user_operation/signals.py:
from django.db.models.signals import post_save,post_delete from django.dispatch import receiver from rest_framework.authtoken.models import Token from django.contrib.auth import get_user_model from user_operation.models import UserFav @receiver(post_save, sender=UserFav) def create_userfav(sender, instance=None, created=False, **kwargs): if created: goods = instance.goods goods.fav_num += 1 goods.save() # Token.objects.create(user=instance) 用了JWT的方式,就不用Token #完成这个我们还要在apps中配置 @receiver(post_delete, sender=UserFav) def delete_userfav(sender, instance=None, created=False, **kwargs): goods = instance.goods goods.fav_num -= 1 goods.save()
user_operation/apps.py:
from django.apps import AppConfig class UserOperationConfig(AppConfig): name = 'user_operation' verbose_name = "用户操作" def ready(self): import user_operation.signals
这样就完成点击数与收藏数的修改了。
1.5、商品库存和销量修改
当加入购物车的时候,库存数量减少,当删除购物车的时候库存增加,当完成订单支付成功的时候,销量增加。
trade/views.py:
class ShoppingCartViewset(viewsets.ModelViewSet): """ 购物车功能 list: 获取购物车详情 create: 加入购物车 delete: 删除购物车 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) serializer_class = ShoppingCartSerializers lookup_field = "goods_id"#传商品Id过来 def get_serializer_class(self): if self.action == "list": return ShopCartDetailSerilizer else: return ShoppingCartSerializers def perform_create(self, serializer): """ 添加购物车库存数量会减少 """ shop_cart = serializer.save() goods = shop_cart.goods goods.goods_num -= shop_cart.nums goods.save() def perform_destroy(self, instance): """ 删除购物车库存数量会增加 """ goods = instance.goods goods.goods_num += instance.nums goods.save() instance.delete() def perform_update(self, serializer): """ 更新购物车数量 """ existed_record = ShoppingCart.objects.get(id=serializer.instance.id) existed_num = existed_record.nums saved_record = serializer.save() nums = saved_record.nums - existed_num goods = saved_record.goods goods.goods_num -= nums goods.save() def get_queryset(self): return ShoppingCart.objects.filter(user=self.request.user)
商品销量的修改应该在订单支付成功后修改trade/views.py:
from rest_framework.views import APIView from utils.alipay import AliPay from MxShop.settings import ali_pub_key_path, private_key_path from rest_framework.response import Response class AliPayview(APIView): def get(self, request): """ 处理支付宝的return_url返回 :param request: :return: """ processed_dict = {} for key, value in request.GET.items(): processed_dict[key] = value sign = processed_dict.pop("sign", None) alipay = AliPay( appid="2016101600698988", app_notify_url="http://127.0.0.1:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=True, # 默认False, return_url="http://127.0.0.1:8000/alipay/return/" ) verify_re = alipay.verify(processed_dict, sign) if verify_re is True: order_sn = processed_dict.get('out_trade_no', None) trade_no = processed_dict.get('trade_no', None) trade_status = processed_dict.get('trade_status', None) existed_orders = OrderInfo.objects.filter(order_sn=order_sn) for existed_order in existed_orders: #***************************************完成销量的更改******************************************************************* order_goods = existed_orders.goods.all()#所有订单的详情信息 for order_good in order_goods: goods = order_good.goods goods.sold_num += order_good.goods_num goods.save() existed_order.pay_status = "TRADE_SUCCESS" existed_order.trade_no = trade_no existed_order.pay_time = datetime.now() existed_order.save() response = redirect("index") response.set_cookie("nextPath", "pay", max_age=3) return response else: response = redirect("index") return response def post(self, request): """ 处理支付宝的notify_url :param request: :return: """ processed_dict = {} for key, value in request.POST.items(): processed_dict[key] = value sign = processed_dict.pop("sign", None) alipay = AliPay( appid="", app_notify_url="http://127.0.0.1:8000/alipay/return/", app_private_key_path=private_key_path, alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=True, # 默认False, return_url="http://127.0.0.1:8000/alipay/return/" ) verify_re = alipay.verify(processed_dict, sign) if verify_re is True: order_sn = processed_dict.get('out_trade_no', None) trade_no = processed_dict.get('trade_no', None) trade_status = processed_dict.get('trade_status', None) existed_orders = OrderInfo.objects.filter(order_sn=order_sn) for existed_order in existed_orders: order_goods = existed_order.goods.all() for order_good in order_goods: goods = order_good.goods goods.sold_num += order_good.goods_num goods.save() existed_order.pay_status = trade_status existed_order.trade_no = trade_no existed_order.pay_time = datetime.now() existed_order.save() return Response("success")
数据库修改商品库存数量,测试成功
1.6、DRF的缓存设置
安装包(3种方法安装):
pip3 install drf-extensions pip3 install -i https://pypi.douban.com/simple drf-extensions #豆瓣源镜像安装 pip3 install https://github.com/chibisov/drf-extensions/archive/master.zip
It is common to cache standard viewset retrieve
and list
methods.:(意思是说我们访问商品列表和商品详情的时候要用到缓存(公共部分),)
Mixin example usage:(Mixin的使用例子)按照例子以及结合上面使用缓存,我们可以直接使用
from myapps.serializers import UserSerializer from rest_framework_extensions.cache.mixins import CacheResponseMixin class UserViewSet(CacheResponseMixin, viewsets.ModelViewSet): serializer_class = UserSerializer
goods/views.py:
from rest_framework_extensions.cache.mixins import CacheResponseMixin #将缓存添加到类继承中 class GoodsListViewSet(CacheResponseMixin,mixins.ListModelMixin,mixins.RetrieveModelMixin,viewsets.GenericViewSet): """ 商品列表页 分页 搜索 过滤 排序 """ queryset = Goods.objects.all() serializer_class = GoodsSerializer pagination_class = GoodsSetPagination filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter] #这是精确搜索过滤,我们需要的是模糊搜索 # filterset_fields = ['name', 'shop_price'] filter_class = GoodsFilter search_fields = ("name","goods_brief","goods_desc") ordering_fields = ("shop_price","sold_num","add_time") def retrieve(self, request, *args, **kwargs): instance = self.get_object() instance.click_num += 1 #重写retrive方法,自定义详情页操作 instance.save() serializer = self.get_serializer(instance) return Response(serializer.data)
Mxshop、settings.py(设置缓存过期时间):
REST_FRAMEWORK_EXTENSIONS = { 'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 15 }
测试一对比,第二次是不是比第一次快了很多啊。
1.7、DRF的Redis缓存
1、安装django-redis
pip3 install -i https://pypi.douban.com/simple django-redis
为了使用 django-redis , 你应该将你的 django cache setting 改成这样(Mxshop/settings.py):
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } }
为了使用redis一定要将本地的redis服务端启动起来。然后访问网页。最后去redis客户端查看数据
1.8、DRF的throttle设置api的访问速率(REST Framework Throttling)
为了防止爬虫以及非正常用户多次访问服务器我们需要对api设置访问速率。Mxshop/settings:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.TokenAuthentication', ], "DEFAULT_SCHEMA_CLASS" : "rest_framework.schemas.AutoSchema", 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle',#不登陆情况 'rest_framework.throttling.UserRateThrottle' #登录情况 ], 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', #每天访问多少次second
,minute
,hour
orday
参数可以是这些 'user': '1000/day' } }
我将获取商品列表详情页设置为限速,为了测试并将限速一分钟两次以及登录三次访问goods/views.py:
from rest_framework.throttling import AnonRateThrottle,UserRateThrottle class GoodsListViewSet(CacheResponseMixin,mixins.ListModelMixin,mixins.RetrieveModelMixin,viewsets.GenericViewSet): """ 商品列表页 分页 搜索 过滤 排序 """ throttle_classes = (AnonRateThrottle,UserRateThrottle) #限速设置 queryset = Goods.objects.all() serializer_class = GoodsSerializer pagination_class = GoodsSetPagination filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter] #这是精确搜索过滤,我们需要的是模糊搜索 # filterset_fields = ['name', 'shop_price'] filter_class = GoodsFilter search_fields = ("name","goods_brief","goods_desc") ordering_fields = ("shop_price","sold_num","add_time") def retrieve(self, request, *args, **kwargs): instance = self.get_object() instance.click_num += 1 #重写retrive方法,自定义详情页操作 instance.save() serializer = self.get_serializer(instance) return Response(serializer.data)
改动设置里限速测试,测试成功