欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-02 13:48:06
十四、首页、商品数量、缓存和限速功能开发 1、首页轮播图 为了方便测试,还是将pycharm的环境改成本地的,Vue中的host也改成本地地址,注意要修改数据库的地址。 然后在goods/serializers.py中加入轮播图字段的序列化: 在goods/views.py中编写轮播图的接口: 注册 ......

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

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)

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

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