美文网首页Django+Vue生鲜电商
【Vue+DRF生鲜电商】32.商品操作后计数更改,热搜榜关键字

【Vue+DRF生鲜电商】32.商品操作后计数更改,热搜榜关键字

作者: 吾星喵 | 来源:发表于2019-08-14 14:11 被阅读2次

    专题:Vue+Django REST framework前后端分离生鲜电商

    Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。
    Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket
    Django版本:2.2、djangorestframework:3.9.2。
    前端Vue模板可以直接联系我拿。

    更多内容请点击 我的博客 查看,欢迎来访。

    商品操作数值更改

    商品点击数、收藏数修改

    商品点击数修改

    当访问商品详情时,将点击数+1

    在 apps/goods/views.py 中的GoodsListViewSet

    class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
        """
        list:
            显示商品列表,分页、过滤、搜索、排序
    
        retrieve:
            显示商品详情
        """
        queryset = Goods.objects.all()  # 使用get_queryset函数,依赖queryset的值
        serializer_class = GoodsSerializer
        pagination_class = GoodsPagination
        filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,)  # 将过滤器后端添加到单个视图或视图集
        filterset_class = GoodsFilter
        # authentication_classes = (TokenAuthentication, )  # 只在本视图中验证Token
        search_fields = ('name', 'goods_desc', 'category__name')  # 搜索字段
        ordering_fields = ('click_num', 'sold_num', 'shop_price')  # 排序
    

    因为显示详情时继承了mixins.RetrieveModelMixin

    按住Ctrl点击它,然后可以复制它的retrieve(self, request, *args, **kwargs)方法

    from rest_framework.response import Response
    
    
    class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
        """
        list:
            显示商品列表,分页、过滤、搜索、排序
    
        retrieve:
            显示商品详情
        """
        queryset = Goods.objects.all()  # 使用get_queryset函数,依赖queryset的值
        serializer_class = GoodsSerializer
        pagination_class = GoodsPagination
        filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,)  # 将过滤器后端添加到单个视图或视图集
        filterset_class = GoodsFilter
        # authentication_classes = (TokenAuthentication, )  # 只在本视图中验证Token
        search_fields = ('name', 'goods_desc', 'category__name')  # 搜索字段
        ordering_fields = ('click_num', 'sold_num', 'shop_price')  # 排序
    
        def retrieve(self, request, *args, **kwargs):
            # 增加点击数
            instance = self.get_object()
            instance.click_num += 1
            instance.save()
            serializer = self.get_serializer(instance)
            return Response(serializer.data)
    

    BLOG_20190814_140437_18

    进入商品详情时,增加点击数。

    商品收藏数修改,用信号修改

    用户点击收藏+1,取消收藏-1

    在 apps/user_operation/views.py 中UserFavViewSet继承了mixins.CreateModelMixinmixins.DestroyModelMixin

    按住Ctrl点击进去查看其中的方法,可以重写perform_create(self, serializer)方法,将收藏数+1;重写perform_create(self, serializer)方法,将收藏数-1。

    class UserFavViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
        """
        create:
            用户收藏商品
    
        destroy:
            取消收藏商品
    
        list:
            显示收藏商品列表
    
        retrieve:
            根据商品id显示收藏详情
        """
        queryset = UserFav.objects.all()
    
        # serializer_class = UserFavSerializer
        def get_serializer_class(self):
            """
            不同的action使用不同的序列化
            :return:
            """
            if self.action == 'list':
                return UserFavListSerializer  # 显示用户收藏列表序列化
            else:
                return UserFavSerializer
    
        permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
        authentication_classes = (JWTAuthentication, SessionAuthentication)  # 配置登录认证:支持JWT认证和DRF基本认证
        lookup_field = 'goods_id'
    
        def get_queryset(self):
            # 过滤当前用户的收藏记录
            return self.queryset.filter(user=self.request.user)
    
        def perform_create(self, serializer):
            # 添加收藏商品,商品收藏数+1
            # 序列化保存,然后将它赋值给一个实例,也就是UserFav(models.Model)对象
            instance = serializer.save()
            # 获取其中的商品
            goods = instance.goods
            # 商品收藏数+1
            goods.fav_num += 1
            goods.save()
    
        def perform_destroy(self, instance):
            # 删除收藏商品,商品收藏数-1
            goods = instance.goods
            # 商品收藏数-1
            goods.fav_num -= 1
            if goods.fav_num < 0:
                goods.fav_num = 0
            goods.save()
            instance.delete()
    

    BLOG_20190814_140427_72

    进入调试模式,在这两个方法中添加断点,可以在API中添加收藏和删除收藏,当添加收藏时,收藏数+1,删除该收藏时,收藏数-1。

    下面可以用Django的信号来实现,收藏注释掉上方的perform_create(self, serializer)perform_destroy(self, instance)

    在 apps/user_operation/ 目录下创建 signals.py 文件,增加信号相关的代码

    from django.db.models.signals import post_save, pre_delete
    from django.dispatch import receiver
    from .models import UserFav
    
    
    @receiver(post_save, sender=UserFav)
    def userfav_post_save_handler(sender, instance=None, created=False, **kwargs):
        # 首次创建时收藏数+1
        if created:
            goods = instance.goods
            goods.fav_num += 1
            goods.save()
    
    
    @receiver(pre_delete, sender=UserFav)
    def userfav_pre_delete_handler(sender, instance, **kwargs):
        # 删除前发送信号
        goods = instance.goods
        # 商品收藏数-1
        goods.fav_num -= 1
        if goods.fav_num < 0:
            goods.fav_num = 0
        goods.save()
    

    修改 apps/user_operation/init.py 增加以下代码

    default_app_config = 'user_operation.apps.UserOperationConfig'
    

    修改 apps/user_operation/apps.py 引入信号配置

    from django.apps import AppConfig
    
    
    class UserOperationConfig(AppConfig):
        name = 'user_operation'
        verbose_name = '操作'
    
        def ready(self):
            """
            在子类中重写此方法,以便在Django启动时运行代码。
            :return:
            """
            from .signals import userfav_post_save_handler, userfav_pre_delete_handler
    

    接下来访问 http://127.0.0.1:8000/userfavs/ 进行收藏,取消收藏接口的测试,可以在信号相关代码上打上断点,看程序是否正常进入该位置。

    BLOG_20190814_140415_62

    收藏数已经从0变为1

    BLOG_20190814_140408_31

    进入收藏详情,删除收藏

    BLOG_20190814_140403_13

    刷新数据表,此时收藏数变为0

    BLOG_20190814_140359_11

    商品库存和销量修改

    商品库存修改

    会引起商品库存数变化的请况有:

    • 新增商品到购物车
    • 修改购物车商品数量
    • 删除购物车记录
    • 订单取消,库存增加

    也就是与购物车相关功能有关

    在 apps/trade/views.py 中有关于购物车相关的类ShoppingCartViewSet(viewsets.ModelViewSet),可以重写继承类的方法,来完成库存数修改,相较于信号来说更加灵活。

    添加到购物车,重写方法

        def perform_create(self, serializer):
            serializer.save()
    

    删除购物车,重写方法

        def perform_destroy(self, instance):
            instance.delete()
    

    更新购物车,重写方法

        def perform_update(self, serializer):
            serializer.save()
    

    先来打上断点,观察下serializer的值,可以获取到更新前的库存量

    BLOG_20190814_140348_77

    修改ShoppingCartViewSet,重写相关方法

    class ShoppingCartViewSet(viewsets.ModelViewSet):
        """
        购物车功能实现
        list:
            获取购物车列表
        create:
            添加商品到购物车
        update:
            更新购物车商品数量
        delete:
            从购物车中删除商品
        """
        # 权限问题:购物车和用户权限关联,这儿和用户操作差不多
        permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)  # 用户必须登录才能访问
        authentication_classes = (JWTAuthentication, SessionAuthentication)  # 配置登录认证:支持JWT认证和DRF基本认证
        # serializer_class = ShoppingCartSerializer  # 使用get_serializer_class(),这个就不需要了
        queryset = ShoppingCart.objects.all()
        lookup_field = 'goods'
    
        def get_serializer_class(self):
            if self.action == 'list':
                # 当获取购物车列表时,使用ModelSerializer,可以显示购物车商品详情
                return ShoppingCartListSerializer
            else:
                return ShoppingCartSerializer
    
        def get_queryset(self):
            # 只能显示当前用户的购物车列表
            return self.queryset.filter(user=self.request.user)
    
        def perform_create(self, serializer):
            # 添加到购物车,库存数减少
            shop_cart = serializer.save()
            goods = shop_cart.goods
            # 商品的库存量goods_num,减去购物车中的数量
            goods.goods_num -= shop_cart.nums
            goods.save()
    
        def perform_destroy(self, instance):
            # 从购物车中删除,库存量减少
            goods = instance.goods
            # 商品的库存量goods_num,加上删除的数量
            goods.goods_num += instance.nums
            goods.save()
            instance.delete()
    
        def perform_update(self, serializer):
            # 更新购物车中数量,先获取原来的数量,再进行更新
            cart_goods = serializer.instance
            old_cart_goods_num = cart_goods.nums  # 获取购物车中该商品原来的数量
            update_cart_goods = serializer.save()
            diff_nums = update_cart_goods.nums - old_cart_goods_num  # 现在的数量减去以前的数量
            goods = cart_goods.goods
            # 得到商品对象,更改库存量
            goods.goods_num -= diff_nums
            goods.save()
    

    测试,首先将goods_num初始值给10

    选择该商品添加3个到购物车

    BLOG_20190814_140334_79

    库存量减少到7

    BLOG_20190814_140326_49

    修改购物车中的数量

    BLOG_20190814_140321_92

    库存量减少到5

    BLOG_20190814_140314_98

    从购物车删除该商品

    BLOG_20190814_140310_65

    库存量恢复到10

    BLOG_20190814_140258_15

    用户下单后,将购物车中的商品放到订单商品中。订单一旦取消,商品并未返回购物车,所以,需要将库存数量修改。

    修改 apps/trade/views.py 中的OrderInfoViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet),重写删除订单的方法

        def perform_destroy(self, instance):
            instance.delete()
    

    复制到OrderInfoViewSet

    class OrderInfoViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):
        """
        订单管理
        list:
            获取个人订单
        create:
            新建订单
        delete:
            删除订单
        detail:
            订单详情
        """
        permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)  # 用户必须登录才能访问
        authentication_classes = (JWTAuthentication, SessionAuthentication)  # 配置登录认证:支持JWT认证和DRF基本认证
        queryset = OrderInfo.objects.all()
        # serializer_class = OrderInfoSerializer  # 添加序列化
    
        def get_queryset(self):
            return self.queryset.filter(user=self.request.user)
    
        def get_serializer_class(self):  # 动态序列化,当显示订单详情,用另一个Serializer
            if self.action == 'retrieve':
                return OrderInfoDetailSerializer
            else:
                return OrderInfoSerializer
    
        def perform_create(self, serializer):
            # 完成创建后保存到数据库,可以拿到保存的值
            order = serializer.save()
            shopping_carts = ShoppingCart.objects.filter(user=self.request.user)
            # 将该用户购物车所有商品都取出来放在订单商品中
            for shopping_cart in shopping_carts:
                OrderGoods.objects.create(
                    order=order,
                    goods=shopping_cart.goods,
                    goods_nums=shopping_cart.nums
                )
            # 然后清空该用户购物车
            shopping_carts.delete()
    
        def perform_destroy(self, instance):
            # 取消(删除)商品库存量增加
            for order_goods in instance.order_goods.all():
                goods = order_goods.goods
                # 获取订单商品的数量,修改库存量
                goods.goods_num += order_goods.goods_nums
                goods.save()
            instance.delete()
    

    测试下单,取消订单

    访问 http://127.0.0.1:8080/#/app/home/productDetail/105 直接在前端操作吧

    添加1个到购物车

    BLOG_20190814_140247_48

    商品库存量减少

    BLOG_20190814_140241_28

    但不进入支付,进入我的订单,点击取消

    BLOG_20190814_140231_95

    商品库存恢复10

    BLOG_20190814_140220_53

    商品销量修改

    一般销量变化发生在订单支付成功后。

    修改 apps/trade/views.py 中的AliPayView(APIView)

    class AliPayView(APIView):
        def get(self, request):
            # ......
    
        def post(self, request):
            """
            处理支付宝notify_url异步通知
            :param request:
            :return:
            """
            processed_dict = {}
            for key, value in request.POST.items():
                processed_dict[key] = value
    
            print('request.POST的值:', processed_dict)
            sign = processed_dict.pop('sign', None)  # 直接就是字符串了
    
            server_ip = get_server_ip()
            alipay = AliPay(
                app_id=app_id,  # 自己支付宝沙箱 APP ID
                notify_url="http://{}:8000/alipay/return/".format(server_ip),
                app_private_key_path=app_private_key_path,  # 可以使用相对路径那个
                alipay_public_key_path=alipay_public_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
                debug=alipay_debug,  # 默认False,
                return_url="http://{}:8000/alipay/return/".format(server_ip)
            )
    
            verify_result = alipay.verify(processed_dict, sign)  # 验证签名,如果成功返回True
            if verify_result:
                order_sn = processed_dict.get('out_trade_no')  # 原支付请求的商户订单号
                trade_no = processed_dict.get('trade_no')  # 支付宝交易凭证号
                trade_status = processed_dict.get('trade_status')  # 交易目前所处的状态
    
                # 更新数据库订单状态
                """
                OrderInfo.objects.filter(order_sn=order_sn).update(
                    trade_no=trade_no,  # 更改交易号
                    pay_status=trade_status,  # 更改支付状态
                    pay_time=timezone.now()  # 更改支付时间
                )
                """
                orderinfos = OrderInfo.objects.filter(order_sn=order_sn)
                for orderinfo in orderinfos:
                    orderinfo.trade_no = trade_no,  # 更改交易号
                    orderinfo.pay_status = trade_status,  # 更改支付状态
                    orderinfo.pay_time = timezone.now()  # 更改支付时间
                    # 更改商品的销量
                    order_goods = orderinfo.order_goods.all()
                    for item in order_goods:
                        # 获取订单中商品和商品数量,然后将商品的销量进行增加
                        goods = item.goods
                        goods.sold_num += item.goods_nums
                        goods.save()
    
                    orderinfo.save()
    
                # 给支付宝返回一个消息,证明已收到异步通知
                # 当商户收到服务器异步通知并打印出 success 时,服务器异步通知参数 notify_id 才会失效。
                # 也就是说在支付宝发送同一条异步通知时(包含商户并未成功打印出 success 导致支付宝重发数次通知),服务器异步通知参数 notify_id 是不变的。
                return Response('success')
    

    现在下单1个商品,并支付完成。确保Django运行到服务器,支付宝可以通过公网POST订单支付信息

    刷新数据库,可以看到库存减1,销量加1

    BLOG_20190814_140209_61

    商品详情页数据也发生了变化

    BLOG_20190814_140202_55

    结合Redis实现热搜关键字显示

    采用redis的有序集合实现,每次搜索的关键字保存在redis,重复时将对应的分数+1

    获取参数中的关键字

    修改 apps/goods/views.py GoodsListViewSet在商品过滤时提取集中的搜索关键字参数

    class GoodsListViewSet(CacheResponseMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
        """
        list:
            显示商品列表,分页、过滤、搜索、排序
    
        retrieve:
            显示商品详情
        """
        queryset = Goods.objects.all()  # 使用get_queryset函数,依赖queryset的值
        serializer_class = GoodsSerializer
        pagination_class = GoodsPagination
        filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,)  # 将过滤器后端添加到单个视图或视图集
        filterset_class = GoodsFilter
        # authentication_classes = (TokenAuthentication, )  # 只在本视图中验证Token
        search_fields = ('name', 'goods_desc', 'category__name')  # 搜索字段
        ordering_fields = ('click_num', 'sold_num', 'shop_price')  # 排序
        # throttle_classes = [UserRateThrottle, AnonRateThrottle]  # DRF默认限速类,可以仿照写自己的限速类
        throttle_scope = 'goods_list'
    
        def retrieve(self, request, *args, **kwargs):
            # 增加点击数
            instance = self.get_object()
            instance.click_num += 1
            instance.save()
            serializer = self.get_serializer(instance)
            return Response(serializer.data)
    
        def get_queryset(self):
            keyword = self.request.query_params.get('search')
            if keyword:
                from utils.hotsearch import HotSearch
                hot_search = HotSearch()
                hot_search.save_keyword(keyword)
            return self.queryset
    

    热搜关键字保存和查询

    在 utils/ 目录下创建 hotsearch.py ,用于关键字的保存和热搜排行

    # 使用redis来记录热搜,并根据分数进行排序
    
    import redis  # 首先pip install redis安装好
    
    
    class HotSearch(object):
        def __init__(self):
            pool = redis.ConnectionPool(host='localhost', port=6379, db=6, decode_responses=True)
            self.r_conn = redis.Redis(connection_pool=pool)  # 创建连接池,并进行连接
            self.name = 'keyword:hot:search'
    
        def save_keyword(self, keyword):
            # 如果关键字已存在,分数+1
            if self.r_conn.zscore(self.name, keyword):
                self.r_conn.zincrby(self.name, amount=1, value=keyword)
            else:
                self.r_conn.zadd(self.name, {keyword: 1})
            # print(self.r_conn.zrevrange(self.name, 0, 5, withscores=True))
    
        def get_hotsearch(self):
            hot_5 = self.r_conn.zrevrange(self.name, 0, 5)
            # 得到一个关键字的列表
            return hot_5
    

    热搜榜数据序列化显示

    当然这个只能自己写api来序列化查询结果了,修改 apps/user_operation/views.py 添加HotSearchView

    class HotSearchView(APIView):
        def get(self, request):
            from utils.hotsearch import HotSearch
            from django.http import JsonResponse
            from rest_framework.response import Response
            from rest_framework import exceptions, status
            import json
            hot_search = HotSearch()
            result = []
            for keyword in hot_search.get_hotsearch():
                tmp = dict()
                tmp['keyword'] = keyword
                result.append(tmp)
            # return JsonResponse(result, safe=False)
            return Response(result, status=status.HTTP_200_OK)
    

    修改 DjangoOnlineFreshSupermarket/urls.py 添加路由

    from user_operation.views import HotSearchView
    
    urlpatterns = [
        # ......
    
        # 获取热搜
        path('hotsearchs/', HotSearchView.as_view(), name='hotsearchs')
    ]
    

    访问 http://127.0.0.1:8000/hotsearchs/ 可以查看热搜磅

    BLOG_20190814_140143_50

    在Vue前端页面中多搜索几个关键字

    BLOG_20190814_140133_40

    然后访问 http://127.0.0.1:8000/hotsearchs/ 可以查看结果

    BLOG_20190814_140128_47

    Vue和热搜接口联调

    在 src/views/head/head.vue 获取热搜榜的函数是

                getHotSearch() {  //获取热搜
                    getHotSearch()
                        .then((response) => {
                            console.log('获取热搜榜:');
                            console.log(response.data);
                            this.hotSearch = response.data
                        })
                        .catch(function (error) {
                            console.log(error);
                        });
                }
    

    组件创建时,就请求 src/api/api.js 中的接口

    //获取热门搜索关键词
    export const getHotSearch = params => {
        return axios.get(`${local_host}/hotsearchs/`)
    };
    

    然后遍历显示到页面中

                <div class="head_search_hot">
                    <span>热搜榜:</span>
                    <!--router-link :to="'/app/home/search/'+'牛肉'">牛肉<手动增加></router-link-->
                    <router-link v-for="item in hotSearch" :to="'/app/home/search/'+item.keyword" :key="item.keyword">
                        {{item.keyword}}
                    </router-link>
                </div>
    

    BLOG_20190814_140115_37

    相关文章

      网友评论

        本文标题:【Vue+DRF生鲜电商】32.商品操作后计数更改,热搜榜关键字

        本文链接:https://www.haomeiwen.com/subject/yxygjctx.html