昨日内容回顾

  1. 1. 为什么要开发路飞学城?
  2. 提供在线教育的学成率;
  3. 特色:
  4. 学,看视频,单独录制增加趣味性。
  5. 练,练习题
  6. 改,改学生代码
  7. 管,管理
  8. 测,阶段考核
  9. 线下:8次留级考试
  10. 2. 组织架构
  11. - 开发
  12. - 后端
  13. - 前端
  14. - 测试
  15. - UI
  16. - 产品经理
  17. - 运维
  18. - 销售
  19. - 运营
  20. - 全职导师
  21. - 行政
  22. - 财务
  23. 3. 项目架构
  24. - 主站
  25. - nginx + uwsgi + django
  26. - 导师后台
  27. - 管理后台
  28. 4. 开发周期和人数
  29. 2017-07:开始做
  30. 2017-11:第一版上线
  31. 2018-02:功能完善和迭代
  32. 2018-03:题库系统/wiki
  33. 5. 前后端分离
  34. vue.js
  35. restful api
  36. 6. 跨域相关
  37. 正式:无
  38. 测试:有跨域,使用CORS解决;
  39. - 简单请求
  40. - 复杂请求
  41. 7. 你负责项目中的什么?
  42. 课程
  43. 购物车
  44. 深科技
  45. 15个接口

一、结算中心(详细)

完整代码

先来看payment.py完整代码

  1. import json
  2. import redis
  3. from django.conf import settings
  4. from rest_framework.views import APIView
  5. from rest_framework.viewsets import ViewSetMixin
  6. from rest_framework.response import Response
  7. from api.utils.auth import LuffyAuthentication
  8. from api import models
  9. from api.utils.response import BaseResponse
  10. # CONN = redis.Redis(host='192.168.11.61',port=6379)
  11. from django_redis import get_redis_connection
  12. CONN = get_redis_connection("default")
  13. class CourseNotExistsException(Exception):
  14. def __init__(self,msg):
  15. self.msg = msg
  16. class PaymentView(ViewSetMixin,APIView):
  17. authentication_classes = [LuffyAuthentication,]
  18. def create(self,request,*args,**kwargs):
  19. """
  20. 在结算中添加课程
  21. :param request:
  22. :param args:
  23. :param kwargs:
  24. :return:
  25. """
  26. response = BaseResponse()
  27. try:
  28. # 1.接受用户选择的要结算的课程ID列表
  29. userid = request.user.id
  30. # [1,3,55]
  31. courseid_list = request.data.get('courseid') # 拿到的是一个列表
  32. # 2.清空当前用户request.user.id结算中心的数据
  33. pattern = 'payment_%s_%s' % (userid, '*')
  34. # 方式一:
  35. key_list = CONN.keys(pattern)
  36. CONN.delete(*key_list)
  37. # 方式二:
  38. # for key in CONN.keys(pattern):
  39. # CONN.delete(key) # 清空结算中心
  40. # 3.循环要加入结算中的所有课程ID列表
  41. import datetime
  42. today = datetime.date.today()
  43. for course_id in courseid_list:
  44. shop_car_key = "shopping_car_%s_%s" %(userid,course_id)
  45. # 3.1 判断购物车中是否存在此key
  46. if not CONN.exists(shop_car_key):
  47. raise CourseNotExistsException('购物车中不存在该课程')
  48. # 3.2 去购物车中获取课程信息
  49. id = CONN.hget(shop_car_key, 'id').decode('utf-8')
  50. name = CONN.hget(shop_car_key, 'name').decode('utf-8')
  51. img = CONN.hget(shop_car_key, 'img').decode('utf-8')
  52. default_price_id = CONN.hget(shop_car_key, 'default_price_id').decode('utf-8')
  53. price_policy_dict = json.loads(CONN.hget(shop_car_key, 'price_policy_dict').decode('utf-8'))
  54. price_policy = price_policy_dict[default_price_id]
  55. """
  56. {
  57. 'id':1,
  58. 'price':99.99,
  59. 'valid_period':60,
  60. 'valid_period_display':2个月
  61. }
  62. """
  63. # 3.3 根据课程ID获取该课程可用的优惠券
  64. coupon_list = models.CouponRecord.objects.filter(account_id=userid,
  65. status=0,
  66. coupon__valid_begin_date__lte=today,
  67. coupon__valid_end_date__gte=today,
  68. coupon__object_id=course_id,
  69. coupon__content_type__model='course'
  70. )
  71. # 加入结算中心
  72. """
  73. for course_id in 用户提交课程ID列表:
  74. 3.1 根据course_id,request.user.id去购物车中获取商品信息:商品名称、图片、价格(id,周期,显示周期,价格)
  75. 3.2 根据course_id,request.user.id获取
  76. - 当前用户
  77. - 当前课程
  78. - 可用的优惠券
  79. 加入结算中心
  80. 提示:可以使用contenttypes
  81. """
  82. # 4.获取当前用户所有未绑定课程优惠券
  83. # - 未使用
  84. # - 有效期内
  85. # - 加入结算中心:glocal_coupon_用户ID
  86. global_coupon_list = models.CouponRecord.objects.filter(account_id=userid,
  87. status=0,
  88. coupon__valid_begin_date__lte=today,
  89. coupon__valid_end_date__gte=today,
  90. coupon__content_type__isnull=True
  91. )
  92. # 加入到结算中心
  93. except CourseNotExistsException as e:
  94. response.code = 1010
  95. response.error = e.msg
  96. except Exception as e:
  97. pass
  98. return Response('...')
  99. def list(self,request,*args,**kwargs):
  100. """
  101. 查看结算中心
  102. :param request:
  103. :param args:
  104. :param kwargs:
  105. :return:
  106. """
  107. # 1. 根据用户ID去结算中心获取该用户所有要结算课程
  108. # 2. 根据用户ID去结算中心获取该用户所有可用未绑定课程的优惠券
  109. # 3. 用户表中获取贝里余额
  110. # 4. 以上数据构造成一个字典
  111. return Response('...')
  112. def update(self,request,*args,**kwargs):
  113. """
  114. 更新优惠券
  115. :param request:
  116. :param args:
  117. :param kwargs:
  118. :return:
  119. """
  120. # 1. 获取用户提交:
  121. # course_id=1,coupon_id=3
  122. # course_id=0,coupon_id=6
  123. # 2. course_id=1 --> 去结算中心获取当前用户所拥有的绑定当前课程优惠,并进行校验
  124. # - 成功:defaul_coupon_id=3
  125. # - 否则:非法请求
  126. # 2. course_id=0 --> 去结算中心获取当前用户所拥有的未绑定课程优惠,并进行校验
  127. # - 成功:defaul_coupon_id=3
  128. # - 否则:非法请求

代码解释

1

接收用户选择的要结算的课程ID列表

  1. userid = request.user.id

当用户认证成功后,request.user就是一个Account表的一个对象。所以request.user.id就能取到用户id。至于为什么request.user就是一个Account表的一个对象,请参考DRF认证源码解析

2

清空当前用户request.user.id结算中心的数据

结算中心的数据在redis中,它的键值为payment用户id课程id。由于redis存储的是key-value,所以清空就是删除!

查询当前用户的结算中心,使用模糊匹配

  1. pattern = 'payment_%s_%s' % (userid, '*')

删除有2种方式:

方式一:

  1. key_list = CONN.keys(pattern)
  2. CONN.delete(*key_list)

方式二:

  1. for key in CONN.keys(pattern):
  2. CONN.delete(key)

推荐使用方式一,*key_list表示打散,再将每一个值分别传给CONN.delete

3

循环要加入结算中的所有课程ID列表

结算中心的数据,是来源于购物车,由于购物车数据在redis中,直接查询就可以了

  1. for course_id in courseid_list:
  2. shop_car_key = "shopping_car_%s_%s" %(userid,course_id)

前端传给后端的课程是一个列表,它是使用html的复选框,默认数据类型为列表。所以需要使用for循环

购物车的键值为shoppingcar用户id_课程id,它的数据结构如下:

  1. 购物车 = {
  2. 'shopping_car_用户id_课程id':{
  3. id:'课程id',
  4. name:'课程名'
  5. img:'课程图片'
  6. default_price_id:'默认价格策略id',
  7. # 所有价格策略
  8. price_policy_dict = {
  9. '价格策略id':{
  10. 'id':价格策略id,
  11. 'price':'原价',
  12. 'valid_period':'有效期',
  13. 'valid_period_display':'有效期中文显示'
  14. }
  15. }
  16. },
  17. }

注意:下面展示的部分代码是在for循环中的

3.1 判断购物车中是否存在此key

if not CONN.exists(shop_car_key):
    raise CourseNotExistsException('购物车中不存在该课程')

CourseNotExistsException是自定义的一个方法,用来做自定义的异常。

raise 表示主动抛出异常,那么下面的代码,将不会执行!也不会被Exception捕获到!

这里为什么要判断购物车课程是否存在呢?如果是正常用户,当然不会有问题。但如果是爬虫用户,它可以伪造数据,发给后端服务器。所以这里要判断数据的真实性!

3.2 去购物车中获取课程信息

下面的代码,分别获取课程id,课程名,课程图片,默认价格策略id,所有价格策略,当前课程价格策略。

id = CONN.hget(shop_car_key, 'id').decode('utf-8')
name = CONN.hget(shop_car_key, 'name').decode('utf-8')
img = CONN.hget(shop_car_key, 'img').decode('utf-8')
default_price_id = CONN.hget(shop_car_key, 'default_price_id').decode('utf-8')
price_policy_dict = json.loads(CONN.hget(shop_car_key, 'price_policy_dict').decode('utf-8'))
price_policy = price_policy_dict[default_price_id]

价格策略说明:有些课程有3个价格策略,比如1周,1个月,3个月。那么用户选择一个价格策略后,点击加入购物车之后。这个默认价格策略id,就是用户选择的。通过这个,就可以从所有的价格策略中,取出当前价格策略的相关信息。

price_policy应该是这个样子

{
    'id':1,
    'price':99,
    'valid_period':60,
    'valid_period_display':1个月
}

上面之所以,取出这么多数据,是为了得到这个效果:

Day106 结算中心(详细),立即支付 - 图1

3.3 根据课程ID获取该课程可用的优惠券

需要从CouponRecord,它是优惠券发放、消费纪录表。从这个表中,取出相关信息!

先来看完整的ORM查询语句

coupon_list = models.CouponRecord.objects.filter(account_id=userid,
                                                       status=0,
                                                       coupon__valid_begin_date__lte=today,
                                                       coupon__valid_end_date__gte=today,
                                                       coupon__object_id=course_id,
                                                       coupon__content_type__model='course'
                                                       )

再来对一个条件,做具体分析!

account_id=userid 表示用户id等于当前登录用户id。通俗来讲,就是谁领用的优惠券!

status=0 表示优惠券状态为:未使用。当用户结算时,使用了优惠券,并付款成功后。这个优惠券的状态,应该要改成已使用。

重点来了,优惠券是有时间限制的。如何判断优惠券是否过期?

根据当前时间来判断!举例:

import datetime
#比较大小
today = datetime.date.today().strftime("%Y-%m-%d")  # 当前日期
start_time= "2017-01-04"  # 开始时间

print("%s大于等于%s:"%(today,start_time),today>=start_time)

执行输出:

2018-08-16大于2017-01-04: True

那么在ORM中,也是这么比较的。大于使用gte,小于使用lte

由于Course和coupon表做了content_type关联。

所以couponcontent_typemodel=’course’ 表示django_content_type表中的model字段的值为course,也就表示course表

coupon__object_id=course_id 表示course表的主键id。

那么这2句,就可以查询到优惠券具体绑定到课哪个课程了!

二、立即支付

点击立即支付,就会跳转到支付宝支付,那么在这个过程中,经历了怎样的步骤呢?

Day106 结算中心(详细),立即支付 - 图2

伪代码

在views目录中,创建文件order.py,伪代码如下:

import json
import redis
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from api.utils.auth import LuffyAuthentication
from api import models
from api.utils.response import BaseResponse

# CONN = redis.Redis(host='192.168.11.61',port=6379)
from django_redis import get_redis_connection
CONN = get_redis_connection("default")

"""
{
    payment_2_1:{
        id:1,
        name:'Python基础',
        img:'xxx',
        price:99.99,
        period:90,
        period_display:3个月,
        default_coupon_id:0,
        coupon_dict:{
            '1':{'type':0,'text':'立减','money_equivalent_value':'xx','off_percent':'xx','minimum_consume'},
            '2':{'type':0,'text':'立减','money_equivalent_value':'xx','off_percent':'xx','minimum_consume'},
            '3':{'type':0,'text':'立减','money_equivalent_value':'xx','off_percent':'xx','minimum_consume'},
        }
    },
    payment_2_3:{
        id:2,
        name:'Python进阶',
        img:'xxx',
        price:99.99,
        period:90,
        period_display:3个月,
        default_coupon_id:0,
        coupon_dict:{
            '1':{'type':0,'text':'立减','money_equivalent_value':'xx','off_percent':'xx','minimum_consume'},
            '2':{'type':0,'text':'立减','money_equivalent_value':'xx','off_percent':'xx','minimum_consume'},
            '3':{'type':0,'text':'立减','money_equivalent_value':'xx','off_percent':'xx','minimum_consume'},
        }
    },
    global_coupon_2:{
        '1':{'type':0,'text':'立减','money_equivalent_value':'xx','off_percent':'xx','minimum_consume'},
            '2':{'type':0,'text':'立减','money_equivalent_value':'xx','off_percent':'xx','minimum_consume'},
            '3':{'type':0,'text':'立减','money_equivalent_value':'xx','off_percent':'xx','minimum_consume'},
    }
}


"""

class OrderView(ViewSetMixin,APIView):
    authentication_classes = [LuffyAuthentication, ]


    def create(self,request,*args,**kwargs):
        """
        立即支付
        :param args:
        :param kwargs:
        :return:
        """
        response = BaseResponse()

        try:
            # 1. 接收用户发送的数据
            """
            {'balance':1000,'alipay':228 }
            """
            balance = request.data.get('balance')
            alipay = request.data.get('alipay')

            # 2. 检验贝里余额是否够用
            if request.user.balance < balance:
                raise Exception('贝里余额不足')

            # 3.获取结算中心的每个课程信息并应用优惠券
            """
                3.1 获取当前用户结算中心的所有key
                    key = "payment_%s*" %request.user.id
                    key_list = CONN.keys(key)


                total_price = 0
                discount = 0

                coupon_id_list = []

                course_dict = {}

                3.2 根据key获取结算中心的课程
                    for key in key_list:
                        id = CONN.hget(key,'id').decode()
                        name = CONN.hget(key,'name').decode()
                        price = CONN.hget(key,'price').decode()
                        period = CONN.hget(key,'period').decode()
                        default_coupon_id = CONN.hget(key,'default_coupon_id').decode()
                        coupon_dict = json.loads(CONN.hget(key,'coupon_dict').decode())

                        # 用于计算总原价
                        total_price += price

                        if default_coupon_id == 0:
                            # 未使用
                            discount += 0
                        else:
                            # 使用优惠券
                            if coupon_dict['type'] == 0:    
                                discount += price if coupon_dict['money_equivalent_value'] > price else coupon_dict['money_equivalent_value']
                            elif coupon_dict['type'] == 1:
                                pass 
                            elif coupon_dict['type'] == 2:
                                discount += price * (100-折扣)/ 100

                            coupon_id_list.append(default_coupon_id)


                        course_dict[id] = {
                            id = CONN.hget(key,'id').decode()
                            name = CONN.hget(key,'name').decode()
                            price = CONN.hget(key,'price').decode()
                            period = CONN.hget(key,'period').decode()
                            default_coupon_id = CONN.hget(key,'default_coupon_id').decode(),
                            price:999,
                            discount:99,

                        }

                3

            """


            # 4.处理未绑定课程的优惠券
            """
                4.1 去redis中获取 global_coupon_2

                    default_coupon_id = CONN.hget('global_coupon_2','default_coupon_id')
                    coupon_dict = CONN.hget('global_coupon_2','coupon_dict')

                4.2 判断是否使用优惠券
                    if default_coupon_id == 0:
                        pass
                    else:
                         # 使用优惠券
                            if coupon_dict['type'] == 0:    
                                discount += price if coupon_dict['money_equivalent_value'] > price else coupon_dict['money_equivalent_value']
                            elif coupon_dict['type'] == 1:
                                pass 
                            elif coupon_dict['type'] == 2:
                                discount += price * (100-折扣)/ 100

                            coupon_id_list.append(default_coupon_id)

            """


            # 5. 判断是否:total_price-discount-balance/10 = alipay
            # total_price = 0
            # discount = 0
            # balance
            # alipay
            # raise Exception('价格对不上')


            # 6. 生成订单
            """
            with transcation.atomic():
                6.1  obj = models.Order.objects.create(...)

                6.2  创建多个订单详细
                    for k,v in course_dict.items():
                        detail = OrderDetail.objects.create(order=obj)
                                 EnrolledCourse.objects.create(..,order_detail=detail)

                6.3 更新优惠券
                    count = models.CouponRecord.objects.filter(id__in=coupon_id_list).update(status=2)
                    if count != len(coupon_id_list):
                         报错..

                6.4 更新贝里余额
                    models.account.objects.filter(id=request.user.id).update(balance=F('balance')-balance)

                6.5 创建贝里转账记录
                    models.TransactionRecord.objects.create(,,balance)

            """

            # 7. 生成去支付宝支付的连接


        except Exception as e:
            pass

代码解释

1

接收用户发送的数据

"""
{'balance':1000,'alipay':228 }
"""
balance = request.data.get('balance')
alipay = request.data.get('alipay')

点击立即支付后,前端只需要向后端发送2个数据就可以了

一个是花费的贝里,一个是要付款的总价!那课程信息从哪里获取呢?从结算中心获取!

结算中心的数据,就是用户要买的所有东西。

为什么要发这2个数据呢?首先用户使用贝里,可以抵扣价格。比如10贝里,可以抵扣1块钱。

前端计算好金额后,发送给后端。后端再计算一遍金额。确保金额一致,跳转到支付宝!为什么不直接跳转到支付宝呢?

因为前端发送的数据,用爬虫手段可以伪造!

2

检验贝里余额是否够用

if request.user.balance < balance:
    raise Exception('贝里余额不足')

为什么要检测余额呢?因为数据可以伪造嘛,我发送1000万的贝里到后端,实际账号的贝里只有10。

不验证的话,岂不血亏!

3

获取结算中心的每个课程信息并应用优惠券

3.1 获取当前用户结算中心的所有key

key = "payment_%s*" %request.user.id
key_list = CONN.keys(key)

结算中心的key规则为:payment用户id课程id。使用模糊匹配payment_用户id*就可以得到当前登录用户的所有课程。

结算中心的数据结构为:

{
    payment_用户id_课程id:{
        id:课程id,
        name:'课程名',
        img:'课程图片',
        price:'原价',
        period:'有效期',
        period_display:'有效期中文显示',
        default_coupon_id:'默认优惠券id',
        # 当前课程所有绑定的优惠券
        coupon_dict:{
            # 这里的1,2,3是正序的序号而已。可以有几十张优惠券!
            '1':{'type':0,'text':'立减','money_equivalent_value':'等值货币','off_percent':None,'minimum_consume':None},
            '2':{'type':0,'text':'满减','money_equivalent_value':'等值货币','off_percent':None,'minimum_consume':'最低消费'},
            '3':{'type':0,'text':'折扣','money_equivalent_value':None,'off_percent':'百分比','minimum_consume':None},
        }
    },
    # 未绑定课程优惠券,也就是通用优惠券
    global_coupon_用户id:{
        default_coupon_id:'默认优惠券id',
        # 当前用户所有通用优惠券
        coupon_dict:{
            # 这里的1,2,3是正序的序号而已。可以有几十张优惠券!
            '1':{'type':0,'text':'立减','money_equivalent_value':'等值货币','off_percent':None,'minimum_consume':None},
            '2':{'type':0,'text':'满减','money_equivalent_value':'等值货币','off_percent':None,'minimum_consume':'最低消费'},
            '3':{'type':0,'text':'折扣','money_equivalent_value':None,'off_percent':'百分比','minimum_consume':None},
        }
    }
}

3.2 根据key获取结算中心的课程

下面获取的数据,分别为:课程id,课程名,课程图片,原价,有效期,有效期中文显示,默认优惠券id,当前课程所有绑定的优惠券

for key in key_list:
    id = CONN.hget(key,'id').decode('utf-8')
    name = CONN.hget(key,'name').decode('utf-8')
    img = CONN.hget(key,'img').decode('utf-8')
    price = CONN.hget(key,'price').decode('utf-8')
    period = CONN.hget(key,'period').decode('utf-8')
    period_display = CONN.hget(key,'period_display').decode('utf-8')
    default_coupon_id = CONN.hget(key,'default_coupon_id').decode('utf-8')
    coupon_dict = json.loads(CONN.hget(key,'coupon_dict').decode('utf-8'))

3.3 计算总原价

total_price += price

这里表示结算中心所有课程的总原价

3.4 计算要抵扣的价格

if default_coupon_id == 0:
    # 未使用
    discount += 0
else:
    # 使用优惠券
    if coupon_dict['type'] == 0:    
        discount += price if coupon_dict['money_equivalent_value'] > price else coupon_dict['money_equivalent_value']
    elif coupon_dict['type'] == 1:
        pass 
    elif coupon_dict['type'] == 2:
        discount += price * (100-折扣)/ 100

    coupon_id_list.append(default_coupon_id)

coupon_dict[‘type’] 为0,表示立减。它只和money_equivalent_value(等值货币)有关。等值货币,相当于人民币,可以直接扣

coupon_dict[‘type’] 为1,表示满减券,它与money_equivalent_value和minimum_consume(最低消费)有关。

coupon_dict[‘type’] 为2,表示折扣券,它只和off_percent有关。80表示80%,所以计算的时候,要除以100

4

处理未绑定课程的优惠券(通用优惠券)

4.1 去redis中获取 globalcoupon用户id

下面的代码,表示获取默认优惠券以及所有的

default_coupon_id = CONN.hget('global_coupon_1','default_coupon_id')
coupon_dict = CONN.hget('global_coupon_1','coupon_dict')

4.2 判断是否使用优惠券

if default_coupon_id == 0:
    pass
else:
     # 使用优惠券
        if coupon_dict['type'] == 0:    
            discount += price if coupon_dict['money_equivalent_value'] > price else coupon_dict['money_equivalent_value']
        elif coupon_dict['type'] == 1:
            pass 
        elif coupon_dict['type'] == 2:
            discount += price * (100-折扣)/ 100

        coupon_id_list.append(default_coupon_id)

5

判断前端发送的金额和后端计算的金额是否一致

if total_price-discount-balance/10 != alipay:
    raise Exception('价格对不上')

意思是总价格-要抵扣的价格-贝里/10。10贝里,表示1块钱。

alipay 表示后台计算的价格

6

生成订单

6.1 插入订单表,注意:使用事务。因为接下来要插入多张表

with transcation.atomic():
    obj = models.Order.objects.create(...)

6.2 创建多个订单详细

for k,v in course_dict.items():
    detail = OrderDetail.objects.create(order=obj)
    EnrolledCourse.objects.create(..,order_detail=detail)

EnrolledCourse 是 报名专题课表

6.3 更新优惠券

count = models.CouponRecord.objects.filter(id__in=coupon_id_list).update(status=2)
if count != len(coupon_id_list):
     报错..

上面这条数据,表示批量更新。至于count的返回值是什么,我不确定。

在使用更新一条数据时,如果成功返回1,否则返回0

6.4 更新贝里余额

models.account.objects.filter(id=request.user.id).update(balance=F('balance')-balance)

因为需要对某条字段的数值进行更新,需要用到F查询。

6.5 创建贝里转账记录

models.TransactionRecord.objects.create(,,balance)

如果用户使用了贝里抵扣,这里需要创建一条记录才行


7. 生成去支付宝支付的连接

生成链接,主要让ajax跳转的。前端不能直接跳转,需要后端提供链接才行

到这里基本上,就结束了。有兴趣的,可以继续写下面的需求


8. 更新订单状态

obj = models.Order.objects.filter(id=xx).update(payment_type_choices=1,payment_number=xxx,status=0)

payment_type_choices 表示支付方式,1为支付宝。目前只支持支付宝!

payment_number 表示支付第3方订单号。用户付款成功后,支付宝会发送一条POST请求,此时会携带支付宝的订单号。这个字段就是记录它的,用于以后的账单查询!

status=0 表示交易成功

9. 发送一条信息给boss

有人买了课程了,boss当然是很开心的!

相关代码,请访问github

https://github.com/987334176/luffycity/archive/v1.6.zip

作业

1. 结算中心

2. 立即支付

这2个功能,继续完善代码!

路飞学城项目,到这里,就结束了!

没有前端页面,真不好调试。

使用django增加加了几个页面,功能只到购物车!

首页

Day106 结算中心(详细),立即支付 - 图3

课程

Day106 结算中心(详细),立即支付 - 图4

课程详情

Day106 结算中心(详细),立即支付 - 图5

购物车

Day106 结算中心(详细),立即支付 - 图6

完整代码,请参数github

https://github.com/987334176/luffycity/archive/v1.7.zip