A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

六.购物车模块1.新建购物车应用
    1)新建carts应用
    2)注册到app中
    3)新建类视图CartView,继承自GenericAPIView
    4)实现序列化器:CartSerializer,继承自Serializer:
            1>自定义是三个字段
            2>实现validate方法校验数据
                (1).商品是否存在
                (2).商品库存是否满足
            3>返回校验后的数据对象
    5)类视图中指定序列化器类
    6)实现post方法(保存购物车):
        1>调用序列化器:serializer=self.get_serializer(data=request.data)
        2>校验:serializer.is_valid(raise_excption=True)
        3>取出校验后的数据
        4>判断用户的登录状态,捕获request.user的异常,抛出异常,则user=None
        5>如果用户存在,并且已经登录user.is_authenticated,保存到redis,否则保存到cookie:
            (1)保存到redis:
                a.获取redis链对象:redis_conn = get_redisconnection('cart'),创建管道:pl = redis_conn.pipeline()
                b.设置hash数据:pl.hincrby("cart_%s"%user.id,sku_id,count)
                c.设置set数据:如果选中,pl.sadd("cart_selected_%s"%user.id,sku_id)
                d.提交
                e.返回响应:参数为serializer.data,响应码为201
            (2).保存到cookie:
                a.取出cookie中的购物车数据
                b.如果取出了cookie,解析:
                    cart_str = cart_str.decode()
                    cart_bytes=base64.b64decode(cart_str)
                    cart_dict = pickle.loads(cart_bytes)
                c.如果没取出,cart_dict={}
                d.如果商品存在购物车中,累加:
                    if sku_id in cart_dict:
                        cart_dict[sku_id]['count']+=count
                        cart_dict[sku_id]['selected']=selected
                e.如果不在:cart_dict[sku_id]={
                            'count':count,
                            'selected':selected
                        }
                f.转字典数据为base64字符串
                g.设置cookie:
                       response=Response(serializer.data)
                       response.set_cookie('cart', cookie_cart, max_age=constants.CART_COOKIE_EXPIRES)
                h.返回response
        6>在carts中新建constants.py 常量文件:
            # 购物车cookie的有效期
            CART_COOKIE_EXPIRES = 365 * 24 * 60 * 60               
    7)在视图类中最开始重写方法:perform_authentication(),实现为pass.将执行具体请求方法前的身份认证关掉,由视图自己来进行身份认证.
    8)在detail.js中编写添加购物车的代码&注册路由
   
    9)类视图中实现get方法:(查询购物车)
        1>判断用户的登录状态
        2>如果用户已登录,从redis中查询
            (1)链接redis库:redis_conn = get_redis_connection('cart')
            (2)查询购物车:redis_cart = redis_conn.hgetall('cart_%s'%user.id),取出的是一个字典,元素为字节类型
            (3)查询购物车中勾选的:redis_cart_selected=redis_conn.smembers('cart_selected_%s'%user.id),取出的是一个集合,元素为字节类型
            (4)遍历查询出的字典,和查询出的集合整合成一个字典.
        3>如果用户未登录,从cookie中查询
            (1)从cookie中取出cart的数据
            (2)取出之后(表示cookie中有购物车数据),解析
            (3)否则表示cookie中没有购物车数据,cart_dict={}
        4>查询数据库:sku_obj_list=SKU.objects.filter(id__in=cart_dict.keys())
        5>遍历sku_obj_list向sku对象中添加count和selected属性
        6>新建序列化器,继承自ModelSerializer
        7>添加两个字段:count和delected
        8>指定模型类为SKU,指明显示字段为接口文档要求的字段
        9>序列化返回,调用序列化器,传入参数为数据库查询出来的对象列表,设置many=True
        10>返回响应,参数为序列化器对象的data
        11>在cart.html文件中增加Vue变量,新建cart.js文件实现js
        
    10)在类视图中实现put方法.(修改购物车):
        1>获取参数
        2>校验
        3>判断用户的登录状态
            (1)如果已登录,修改redis,处理数量,处理勾选状态
            (2)如果未登录,修改cookie
        4>返回
        5>修改前端cart.js
    11)类视图中实现delete方法(删除购物车)
        1>获取参数sku_id
        2>校验数据:序列化器中进行校验,商品是否存在,定义sku_id字段,判断是否接收到sku_id
            serializer=CartDeleteSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            sku_id = serializer.validated_data['sku_id']
        3>判断用户的登录状态
        4>已登录,删除redis
        5>未登录,删除cookie
        6>返回204
        7>修改前端cart.js代码
   
2.购物车全选
    1)新建购物车全选类视图,继承自GenericAPIView
    2)实现put方法
        1>接收参数selected
        2>校验:
            (1)在方法外指明序列化器类
            (2)方法中使用序列化器
        3>判断用户的登录状态
        4>已登录,redis
        5>未登录,cookie
    3)增加序列化器类
    4)取消用户认证,重写perform_authentication方法,实现为pass
    5)返回响应:message:OK
    6)注册路由
    7)修改前端cart.js

3.合并购物车
    1)cart应用中新建utils模块
    2)实现一个合并购物车的方法(将cookie中的数据合并到redis中),以cookie为准
        1>获取cookiel中的购物车的数据
        2>遍历cookie中的购物车
            (1)处理商品的数量,维护在redis中购物车数据数量的最终字典
            (2)处理商品的勾选状态:新建两个列表,一个存勾选的sku_id,一个存不勾选的sku_id
        3>执行redis操作
        4>设置hash类型
        5>设置set类型
        6>删除cookie
        7>返回删除cookie后的响应对象
        
    3)修改登录视图:
        1>在users的view中自己实现用户登录认证视图(UsrAuthorizeView)继承自ObtainJSONWebToken
        2>重写post方法:
            (1)调用父类的post方法,返回一个response对象
            (2)如果用户登录成功,合并购物车:
                serializer = self.get_serializer(data=request.data)
                if serializer.is_valid():
                    user = serializer.validated_data.get('user')
                    response = merge_cart_cookie_to_redis(request, user, response)
            (3)返回response
        3>修改用户登录认证的路由为自己重写的类视图
        4>在oauth中修改OAuthQQUserView类视图中的get方法,在最后返回时:
            (1).使用变量接收Response
            (2).调用合并购物车的方法
            (3).返回方法的返回值
        5>在oauth中修改OAuthQQUserView类视图中的post方法:
            (1).调用父类的post方法,返回值用一个变量接收
            (2).调用合并购物车的方法.user参数为当前对象的user,即self.user,必须是在实现了父类的post方法后才会有这个user属性
            (3).返回方法的返回值
        6>在序列化器中为这个视图添加一个签发了token的user属性:self.context['view'].user = user

    4)修改login.js在表单提交发送请求时设置withCredentials:true
    5)修改oauth_callback.js在发送请求时设置withCredentials:true.保存时设置withCredentials:true.
七.订单模块1.数据库设计
    1)订单基本信息表(tb_order_info)
        1>ordre_id(订单编号):VARCHAR
        2>user_id(下单用户):FK,INTEGER
        3>address_id(收货地址id):FK,INTEGER
        4>total_count(商品总数):INTEGER
        5>total_amount(订单总金额):DECIMAL
        6>freight(运费):DECIMAL
        7>pay_method(支付方式):INTEGER
        8>status(订单状态):INTEGER
    2)订单商品表(tb_order_goods)
        1>id(主键id):INTEGER
        2>order_id(订单编号):FK,VARCHAR
        3>sku_id(商品SKU id):FK,INTEGER
        4>count(商品数量):INTEGER
        5>price(下单时的单价):DECIMAL
        6>comment(商品评论):TEXT
        7>score(商品评分):INTEGER
        8>is_anonymous(是否匿名评价):BOOLEAN
        9>is_commented(是否评价):BOOLEAN
        
2.新建orders应用,添加到app中
3.添加数据库模型类,执行迁移
4.订单结算
    1)后端接口实现(GET /orders/settlement),参数无
    2)orders下的views.py中新建类视图(OrderSettlementView),继承自APIView.权限认证为必须登录的用户
    3)实现get方法:
        1>获取用户对象: user=request.user
        2>从redis中查询购物车,sku_id count selected
        3>查询数据库
        4>设置运费:freight=Decimal("10.00")
    4)新建序列化器类CartSKUSerializer,继承自ModelSerializer:
        count = serializers.IntegerField(label='数量')
        class Meta:
            model = SKU
            fields = ('id', 'name', 'default_image_url', 'price', 'count')
    5)新建序列化器 OrderSettlementSerializer,继承自Serializer:
        freight = serializers.DecimalField(label='运费', max_digits=10, decimal_places=2)
        skus = CartSKUSerializer(many=True)
    6)类视图中调用序列化器:serializer = OrderSettlementSerializer({'freight': freight, 'skus': skus})
    7)序列化返回:Response(serializer.data)
    8)注册路由,包含路由
    9)修改前端place_order.html,新增Vue变量
    10)新建place_order.js

5.保存订单
    1)在orders/views下新建类视图SaveOrderView(),继承自CreateAPIView
    2)指明序列化器
    3)指明权限为用户登录后
    4)创建序列化器SaveOrdreSerializer,继承自ModelSerializer
        1>指明模型类为OrderInfo
        2>指明显示字段为:address,pay_method,order_id
        3>设置order_id为只读字段:read_only_fields=('order_id',)
        4>添加额外的约束:extra_kwargs={
                'address':{'write_only':True
                    
                },
                'pay_method':{
                    'required':True
                }
            }
        5>实现create方法,保存订单(使用乐观锁处理库存变化问题):
            (1)获取用户对象user
            (2)查询购物车redis sku_id count selected
            (3)查询商品数据库,获取商品数据(库存)
            (4)保存订单:创建订单基本信息表记录(OrderInfo)
            (5)遍历需要结算的商品数据
                a.判断库存
                b.库存减少,销量增加
                c.创建订单商品信息表记录(OrderGoods)
            (6)删除购物车中已结算的商品
            (7)返回OrderInfo对象
        6>注册路由
        7>补充前端place_order.js
    5)修改mysql的配置文件,设置数据库的默认事务隔离级别为Read committed 读取已提交:
        1>sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
        2>transaction-isolation=READ-COMMITTED
    6)修改前端order_success.html
    7)新建order_success.js文件
        
            
八.支付宝支付模块1.使用支付宝的沙箱环境进行开发测试
    1)支付宝开发平台登录:https://open.alipay.com/platform/home.htm
    2)沙箱环境:
        是支付宝提供给开发者的模拟支付的环境,跟真实环境是分开的
        沙箱应用:https://docs.open.alipay.com/200/105311
        沙箱账号:https://openhome.alipay.com/platform/appDaily.htm?tab=account
    3)支付宝开发者文档:
        文档主页:https://openhome.alipay.com/developmentDocument.htm
        产品介绍:https://docs.open.alipay.com/270
        快速接入:https://docs.open.alipay.com/270/105899/
        SDK:https://docs.open.alipay.com/270/106291/
        python对接支付宝SDK:https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
        python对接支付宝SDK安装:pip install python-alipay-sdk --upgrade
        API列表:https://docs.open.alipay.com/270/105900/
    4)RSA密钥对:
        支付宝使用私钥签名,应用使用支付宝公钥验签,并使用应用私钥签名,支付宝使用应用公钥验签
        
2.新建应用:payment
    1)添加进apps
    2)创建Payment(支付信息)模型类,继承BaseModel
    3)两个字段,订单(外键)以及支付编号
    4)数据库迁移
3.安装SDK,python-alipay-sdk:
    pip install python-alipay-sdk
4.生成并配置应用的密钥对:
    1)openssl
    2)genrsa -out app_private_key.pem 2048 # 私钥RSA2
    3)rsa -in app_private_key.pem -pubout -out app_public_key.pem #导出公钥
    4)复制公钥,进入支付宝沙箱环境,修改RSA2的公钥
    5)payment应用下新建key文件夹,把私钥复制进去
5.生成并配置支付宝的秘钥对:
    1)查看并复制支付宝沙箱环境下的公钥
    2)在keys文件夹中新建alipay_public_key.pem,把支付宝公钥复制进去,在公钥的前后添加开始与结束标志:
        -----BEGIN PUBLIC KEY-----
            此处是公钥内容
        -----END PUBLIC KEY-----

6.实现发起支付后端接口(GET /orders/(?P<order_id>\d+)/payment/)
    1)参数为order_id,返回值为alipay_url
    2)payment下的views中新建类视图PaymentView(),继承自APIView,获取支付链接
        1>设置权限为已登录的用户:permission_classes = (IsAuthenticated,)
        2>实现get(self,request,order_id)方法
            (1)接收并校验参数:
                try:
            order = OrderInfo.objects.get(order_id=order_id, user=request.user,
                                          pay_method=OrderInfo.PAY_METHODS_ENUM["ALIPAY"],
                                          status=OrderInfo.ORDER_STATUS_ENUM["UNPAID"])
        except OrderInfo.DoesNotExist:
            return Response({'message': '订单信息有误'}, status=status.HTTP_400_BAD_REQUEST)
            (2)向支付宝发起请求,获取支付链接参数
            (3)拼接支付链接网址,需要跳转到https://openapi.alipay.com/gateway.do? + order_string,返回值为支付链接网址
            (4)返回链接网址
        3>配置文件中对支付宝需要的参数进行配置:
            ALIPAY_APPID = "2016081600258081"
            ALIPAY_URL = "https://openapi.alipaydev.com/gateway.do"
            ALIPAY_DEBUG = True  # 是否为沙箱环境,默认为False
    3)注册路由,包含进总路由
    4)前端实现order_success.js
        
7.保存支付结果后端实现
    1)请求方式:PUT /payment/status/?支付宝参数
    2)请求参数为支付宝返回的参数
    3)返回值为支付宝流水号:trade_id
    4)新建类视图PaymentStatusView(APIView)
        1>实现put方法:
            (1)接收参数
            (2)校验
            (3)保存支付结果数据payment
            (4)修改订单状态
            (5)返回
        2>注册路由
        3>前端创建pay_success.html
九.后台管理模块xadmin是Django的第三方扩展,可是使Django的admin站点使用更方便

1.Xadmin
    1)通过如下命令安装xadmin的最新版:
        pip install https://github.com/sshwsfc/xadmin/tarball/master
    2)在配置文件apps中注册应用:
        'xadmin',
        'crispy_forms',
        'reversion',
    3)进行数据库迁移
    4)在总路由中添加xadmin的路由信息,注释掉之前的admin路由,导包(import xadmin):
        url(r'xadmin/', include(xadmin.site.urls)),
   
    5)使用:
        1>新建模块adminx.py进行站点管理(这管理goods)
        2>导包xadmin
        3>注册:注册时把原来的admin改为xadmin
        4>进行xadmin的全局配置和基本配置,放在任意一个全局应用的adminx下即可,其他应用都会应用上.
            class BaseSetting(object):
                """xadmin的基本配置"""
                enable_themes = True  # 开启主题切换功能
                use_bootswatch = True

            xadmin.site.register(views.BaseAdminView, BaseSetting)

            class GlobalSettings(object):
                """xadmin的全局配置"""
                site_title = "美多商城运营管理系统"  # 设置站点标题
                site_footer = "美多商城集团有限公司"  # 设置站点的页脚
                menu_style = "accordion"  # 设置菜单折叠

            xadmin.site.register(views.CommAdminView, GlobalSettings)
        5>具体模型类的管理(基本与Django原生的admin一致):
            class SKUAdmin(object):
                model_icon = 'fa fa-gift'

            xadmin.site.register(models.SKU, SKUAdmin)
    6)对订单(order)进行管理,图表的使用
    7)站点对象数据进行修改时的方法重写

2.用户权限控制
    1)消费者用户与公司内部运营用户使用一个用户数据库来存储
    2)通过is_staff 来区分是运营用户还是消费者用户
    3)对于运营用户通过is_superuser 来区分是运营平台的管理员还是运营平台的普通用户
    4)对于运营平台的普通用户,通过权限、组和组外权限来控制这个用户在平台上可以操作的数据。
    5)对于权限,Django会为每个数据库表提供增、删、改、查四种权限
    6)用户最终的权限为 组权限 + 用户特有权限

3.数据库读写分离
    1)获取mysql的docker镜像
    2)在家目录中(/home/python)中创建目录,将mysql的配置文件放到此目录中:
        cd ~
        mkdir mysql_slave
        cd mysql_slave
        mkdir data
        cp -r /etc/mysql/mysql.conf.d ./
    3)开启前需要修改配置文件,修改 ~/mysql_slave/mysql.conf.d/mysqld.cnf 文件:
        port  =  8306
        general_log  = 0
        server-id  = 2
    4)创建docker容器:
        docker run --name mysql-slave -e MYSQL_ROOT_PASSWORD=mysql -d --network=host -v /home/python/mysql_slave/data:/var/lib/mysql -v /home/python/mysql_slave/mysql.conf.d:/etc/mysql/mysql.conf.d  mysql:5.7.22
    (MYSQL_ROOT_PASSWORD 是创建mysql root用户的密码)
    5)备份主服务器原有数据到从服务器:
        1>在主服务器Ubuntu上进行备份,执行命令:
            mysqldump -uroot -pmysql --all-databases --lock-all-tables > ~/master_db.sql
                -u :用户名
                -p :密码
                --all-databases :导出所有数据库
                --lock-all-tables :执行操作时锁住所有表,防止操作时有数据修改
                ~/master_db.sql :导出的备份数据(sql文件)位置,可自己指定
        2>在docker容器中导入数据:
            mysql -uroot -pmysql -h127.0.0.1 --port=8306 < ~/master_db.sql
    6)配置主服务器master(Ubuntu中的MySQL),编辑设置mysqld的配置文件,设置log_bin和server-id:
            sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
            (server-id   =1
            log_bin      =/var/log/mysql/mysql-bin.log)
    7)重启mysql服务:sudo service mysql restart
    8)登入主服务器Ubuntu中的mysql,创建用于从服务器同步数据使用的帐号:
            (1) mysql –uroot –pmysql
            (2)GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' identified by 'slave';
            (3)FLUSH PRIVILEGES;
     9)获取主服务器的二进制日志信息:SHOW MASTER STATUS;(File为使用的日志文件名字,Position为使用的文件位置,这两个参数须记下,配置从服务器时会用到。)
    10)配置从服务器slave (docker中的mysql)
        1>进入docker中的mysql:mysql -uroot -pmysql -h 127.0.0.1 --port=8306
        2>执行:change master to master_host='127.0.0.1', master_user='slave', master_password='slave',master_log_file='mysql-bin.000006', master_log_pos=590;
            (master_host:主服务器Ubuntu的ip地址
            master_log_file: 前面查询到的主服务器日志文件名
            master_log_pos: 前面查询到的主服务器日志文件位置)
        3>启动slave服务器,并查看同步状态
            在mysql中>
            start slave
            -->Query OK, 0 rows affected(0.00 sec)
            show slave status \G
            -->...
                Slave_IO_Running: Yes
                Slave_SQL_Running: Yes
                ...
                表示同步已正常运行
    11)测试:在主从当中都查看数据库,然后在主里面进行增加数据库,从里面查看是否同步
    12)配置Django实现数据库读写分离:
        1>在配置文件中增加slave数据库的配置:
            DATABASES = {
            'default': {
                'ENGINE': 'django.db.backends.mysql',
                'HOST': '10.211.55.5',
                'PORT': 3306,
                'USER': 'meiduo',
                'PASSWORD': 'meiduo',
                'NAME': 'meiduo_mall'
            },
            'slave': {
                'ENGINE': 'django.db.backends.mysql',
                'HOST': '10.211.55.5',
                'PORT': 8306,
                'USER': 'root',
                'PASSWORD': 'mysql',
                'NAME': 'meiduo_mall'
            }
            }
        2>创建数据库操作的路由分发类,在全局的utils中创建db_router.py,实现数据库主从读写分离路由类:
            class MasterSlaveDBRouter(object):
                """数据库主从读写分离路由"""

                def db_for_read(self, model, **hints):
                    """读数据库"""
                    return "slave"

                def db_for_write(self, model, **hints):
                    """写数据库"""
                    return "default"

                def allow_relation(self, obj1, obj2, **hints):
                    """是否运行关联操作"""
                    return True
        3>配置文件中配置读写分离路由:
            # 配置读写分离
            DATABASE_ROUTERS = ['Meiduo_mall.utils.db_router.MasterSlaveDBRouter']
十.项目部署1.静态文件处理
    1)Django提供了收集静态文件的方法。先在配置文件中配置收集之后存放的目录:
        STATIC_ROOT = os.path.join(os.path.dirname(os.path.dirname(BASE_DIR)), 'front_end_pc/static')
    2)在front_end_pc下新建static文件夹
    3)执行收集命令:
        python manage.py collectstatic
    4)把前端资源目录(front_end_pc)复制到Nginx服务器
    5)使用Nginx服务器作为静态文件服务器.打开Nginx的配置文件(打开之前进行备份):sudo vim /usr/local/nginx/conf/nginx.conf
    6)修改配置:
        server {
         listen       80;
         server_name  www.meiduo.site;

        location / {
             root   /home/python/Desktop/front_end_pc;
             index  index.html index.htm;
         }

        # 余下省略
        }
    7)重启Nginx服务器:sudo /usr/local/nginx/sbin/nginx -s reload
        首次启动nginx服务器-->sudo /usr/local/nginx/sbin/nginx
        停止nginx服务器-->sudo /usr/local/nginx/sbin/nginx -s stop

2.动态接口
    1)安装uwsgi:pip install uwsgi
    2)在项目中复制开发配置文件dev.py 到生产配置prod.py,修改配置文件prod.py:
        DEBUG = True

        ALLOWED_HOSTS = [...,  'www.meiduo.site']  # 添加www.meiduo.site

        CORS_ORIGIN_WHITELIST = (
            '127.0.0.1:8080',
            'localhost:8080',
            'www.meiduo.site:8080',
            'api.meiduo.site:8000',
            'www.meiduo.site',  # 添加
        )
        
    3)修改wsgi.py文件:os.environ.setdefault("DJANGO_SETTINGS_MODULE", "meiduo_mall.settings.prod")
    4)在项目目录/meiduo_mall 下创建uwsgi配置文件 uwsgi.ini:
        [uwsgi]
        #使用nginx连接时使用,Django程序所在服务器地址
        socket=10.211.55.2:8001
        #直接做web服务器使用,Django程序所在服务器地址
        #http=10.211.55.2:8001
        #项目目录
        chdir=/Users/delron/Desktop/meiduo/meiduo_mall
        #项目中wsgi.py文件的目录,相对于项目目录
        wsgi-file=meiduo_mall/wsgi.py
        # 进程数
        processes=4
        # 线程数
        threads=2
        # uwsgi服务器的角色
        master=True
        # 存放进程编号的文件
        pidfile=uwsgi.pid
        # 日志文件,因为uwsgi可以脱离终端在后台运行,日志看不见。我们以前的runserver是依赖终端的
        daemonize=uwsgi.log
        # 指定依赖的虚拟环境
        virtualenv=/Users/delron/.virtualenv/meiduo
    5)启动uwsgi服务器:uwsgi --ini uwsgi.ini
        意如果想要停止服务器,除了可以使用kill命令之外,还可以通过-->uwsgi --stop uwsgi.pid
    6)修改Nginx配置文件,让Nginx接收到请求后转发给uwsgi服务器:
         upstream meiduo {
         server 10.211.55.2:8001;  # 此处为uwsgi运行的ip地址和端口号
         # 如果有多台服务器,可以在此处继续添加服务器地址
        }

        #gzip  on;
        server {
            listen  8000;
            server_name api.meiduo.site;

            location / {
                include uwsgi_params;
                uwsgi_pass meiduo;
            }

        }


        server {
            listen       80;
            server_name  www.meiduo.site;

            #charset koi8-r;

            #access_log  logs/host.access.log  main;
            location /xadmin {
                include uwsgi_params;
                uwsgi_pass meiduo;
            }

            location /ckeditor {
                include uwsgi_params;
                uwsgi_pass meiduo;
            }

            location / {
                root   /home/python/Desktop/front_end_pc;
                index  index.html index.htm;
            }


            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }

        }
    7)重启nginx:
        sudo /usr/local/nginx/sbin/nginx -s reload
   


2 个回复

倒序浏览
牛逼,
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马