在项目中添加商城购物结算模块:

# 在项目目录下执行如下命令
$ python3 manage.py startapp shopping

  将应用注册到settings.py中:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'Course.apps.CourseConfig',
    'Login.apps.LoginConfig',
    'shopping.apps.ShoppingConfig',
    'rest_framework'    # 注意要注册rest_framework
]

  将shopping添加到路由分发/LuffyCity/LuffyCity/settings.py:

from django.contrib import admin
from django.urls import path, include, re_path
from django.views.static import serve
from LuffyCity import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/course/', include("Course.urls")),
    path('api/shop/', include("shopping.urls")),   # 顺序早于api/很重要
    path('api/', include("Login.urls")),

    # media路径配置
    # path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
    re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
]

一、添加购物车接口

  前端添加购物车,传给购物车接口的信息会包含course_id、price_policy_id,把购物车的数据放入redis中。保存在redis中的信息其实就是会在前端展示的对应信息。

  设计redis保存的购物车信息主要是:商品名、商品图片、商品价格策略对应价格、用户id、商品id,如下所示:

{
    SHOPPINGCAR_USERID_COURSE_ID: {
        "id": '',
        "title": '',
        "course_img": '',
        "price_policy_dict": {
            price_policy_id: "{valid_period, price}",
            price_policy_id2: "{valid_period, price}",
            price_policy_id3: "{valid_period, price}"
        },
        "default_price_policy_id": 1
    }
}

  由于前端显示时,时常需要一个默认的价格,所以还配置一个默认价格策略id字段。

1、购物车路由

  创建商城路由文件:shopping/urls.py

from django.urls import path
from .views import ShoppingCarView

urlpatterns = [
    path('shopping_car', ShoppingCarView.as_view()),   # 购物车
]

2、购物车视图

  购物车视图处理添加购物车的post请求,主要操作:获取前端传递的数据和user_id、校验数据的合法性、构建欲存入redis的key和value、将购物信息写入redis数据库中。

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.base_response import BaseResponse
from utils.my_auth import LoginAuth
from utils.redis_pool import POOL
from Course.models import Course
import json
import redis


SHOPPINGCAR_KEY = "SHOPPINGCAR_%s_%s"
CONN = redis.Redis(connection_pool=POOL)    # redis连接


class ShoppingCarView(APIView):
    authentication_classes = [LoginAuth, ]    # 登录认证

    def post(self, request):
        res = BaseResponse()
        # 1.获取前端传递的数据及user_id
        course_id = request.data.get("course_id", "")
        price_policy_id = request.data.get("price_policy_id", "")
        user_id = request.user.pk    # LoginAuth返回的user_obj即为request.user
        # 2.校验数据的合法性
        # 2.1.校验课程id合法性
        course_obj = Course.objects.filter(id=course_id).first()
        if not course_obj:
            res.code = 1040
            res.error = "课程id不合法"
            return Response(res.dict)
        # 2.2 校验价格策略id是否合法
        price_policy_queryset = course_obj.price_policy.all()
        price_policy_dict = {}
        for price_policy in price_policy_queryset:
            price_policy_dict[price_policy.id] = {
                "price": price_policy.price,   # 价格
                "valid_period": price_policy.valid_period,   # 周期
                "valid_period_display": price_policy.get_valid_period_display()   # 周期中文
            }
        print("价格策略", price_policy_id, price_policy_dict)
        if price_policy_id not in price_policy_dict:
            res.code = 1041
            res.error = "价格策略id不合法"
            return Response(res.dict)
        # 3.构建redis Key
        key = SHOPPINGCAR_KEY % (user_id, course_id)
        # 4.构建redis数据结构
        course_info = {
            "id": course_obj.id,
            "title": course_obj.title,
            "course_img": str(course_obj.course_img),    # 解决字典类型不一致的问题
            # 所有放入redis的字典最好做一下json.dumps,防止出现格式问题或中文乱码
            "price_policy_dict": json.dumps(price_policy_dict, ensure_ascii=False),
            "default_price_policy_id": price_policy_id
        }
        # 5.写入redis
        CONN.hmset(key, course_info)
        res.data = "加入购物车成功"
        return Response(res.dict)

3、添加购物车接口测试

  首先调用登录接口获取token:

  

   然后配置购物车header:

  

  随后调用添加购物车接口

  

二、查看购物车接口

  通过redis拿到购物车的所有信息进行展示。

1、查看购物车接口视图

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.base_response import BaseResponse
from utils.my_auth import LoginAuth
from utils.redis_pool import POOL
from Course.models import Course
import json
import redis


SHOPPINGCAR_KEY = "SHOPPINGCAR_%s_%s"       # 第一个值user_id,第二个值course_id
CONN = redis.Redis(connection_pool=POOL)    # redis连接


class ShoppingCarView(APIView):
    authentication_classes = [LoginAuth, ]    # 登录认证
    def post(self, request):...

    def get(self, request):
        res = BaseResponse()
        # 1.拼接redis KEY
        user_id = request.user.pk
        shopping_car_key = SHOPPINGCAR_KEY % (user_id, "*")   # redis支持模糊匹配
        # 2.去redis中读取数据
        # 2.1 匹配所有的keys
        all_keys = CONN.scan_iter(shopping_car_key)    # 都匹配,返回的是生成器
        ret = []
        for key in all_keys:
            ret.append(CONN.hgetall(key))
        print(ret)
        # 3.构建数据结构展示
        res.data = ret
        return Response(res.dict)

(1)scan_iter(match=None, count=None)

  查看所有元素-迭代器

(2)hgetall(name)

  获取name对应hash的所有键值。

2、查看购物车接口测试

  

三、更新购物车接口

  在更新购物车时,同样需要前端提供course_id和price_policy_id,校验数据合法后,更新redis中的default_price_policy_id字段。

1、更新购物车视图

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.base_response import BaseResponse
from utils.my_auth import LoginAuth
from utils.redis_pool import POOL
from Course.models import Course
import json
import redis


SHOPPINGCAR_KEY = "SHOPPINGCAR_%s_%s"       # 第一个值user_id,第二个值course_id
CONN = redis.Redis(connection_pool=POOL)    # redis连接

class ShoppingCarView(APIView):
    authentication_classes = [LoginAuth, ]    # 登录认证

    def post(self, request):...

    def get(self, request):...

    def put(self, request):
        # 更新购物车:前端需要提供course_id、price_policy_id
        res = BaseResponse()
        # 1.获取前端传过来的数据及user_id
        course_id = request.data.get("course_id", "")
        price_policy_id = request.data.get("price_policy_id", "")
        user_id = request.user.pk
        # 2.校验数据的合法性
        # 2.1 course_id是否合法
        key = SHOPPINGCAR_KEY % (user_id, course_id)
        if not CONN.exists(key):    # exists检查名字是否存在
            res.code = 1043
            res.error = "课程id不合法"
            return Response(res.dict)
        # 2.2 price_policy_id是否合法
        price_policy_dict = json.loads(CONN.hget(key, "price_policy_dict"))
        if str(price_policy_id) not in price_policy_dict:
            res.code = 1044
            res.error = "价格策略不合法"
            return Response(res.dict)
        # 3.更新redis中的default_price_policy_id字段
        CONN.hset(key, "default_price_policy_id", price_policy_id)
        res.data = "更新成功"
        return Response(res.dict)

2、更新购物车测试

  更新token,发送put请求:

  

   发送get请求,验证更新结果:

   

四、删除购物车接口

  考虑可能会批量删除,前端传递过来的数据应该是用一个列表course_list,来保存一个一个course_id。循环校验这些课程id是否合法,再删除redis中的数据。

1、删除购物车视图

class ShoppingCarView(APIView):
    """代码省略"""
    def delete(self, request):
        # course_list = [course_id, ]
        res = BaseResponse()
        # 1.获取前端传来的数据及user_id
        course_list = request.data.get("course_list", "")
        user_id = request.user.pk
        # 2.校验course_id是否合法
        for course_id in course_list:
            key = SHOPPINGCAR_KEY % (user_id, course_id)
            if not CONN.exists(key):   # 如果key不存在
                res.code = 1045
                res.error = "课程id不合法"
                return Response(res.dict)
            # 3.删除redis数据
            CONN.delete(key)
        res.data = "删除成功"
        return Response(res.dict)

2、删除购物车测试

  更新token,发送delete请求:

  

  发送get请求,验证删除结果:

  

五、总结和优化

  这些接口要进一步优化,应该在最外层套一层try,同时应该添加大量的日志打印功能,方便后期运维。

12-22 19:52