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

专题: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/`)
};

然后遍历显示到页面中

            
热搜榜: {{item.keyword}}

BLOG_20190814_140115_37

你可能感兴趣的:(【Vue+DRF生鲜电商】32.商品操作后计数更改,热搜榜关键字功能实现)