十四、首页、商品数量、缓存和限速功能开发
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)
然后在登录或者未登录状态下访问该接口,超出次数如下: