python-django框架-电商项目-订单模块开发
提交订单页面:
- 在购物车中点击提交订单,就应该到达提交订单页面了,
- 显示:
- 1,收获地址,
- 2,支付方式
- 3,用户购买的商品信息,数量,小计,
- 4,总金额,运费,实际付多少,
- 5,提交订单按钮,
- 点击提交按钮,需要传递什么?
- 注意:价格这个是给用户看的,不要传到后台,传了后端也不用,
- 商品的id要传过去,另外商品的数量,我们也是从redis中拿的,不是页面上的
- 我们可以把要传的信息放入一个表单,
- 表单中的checkbox,只有被选中时,值才会被提交,
- 后端增加一个提交订单的视图,
- 1,接收数据,
- 2,遍历用户购买的商品信息,
- 总的数量,总的价格,
- 运费,实际开发的时候,是一个子系统,现在我们把这个写死,
- 实际支付,就是总价格+运费,
- 获取用户的地址,只要是这个用户的,我们就从数据库查出来,注意页面流出来编辑收获地址的按钮,
- 组织上下文,
- 注意:没有登陆的时候是不能进入这个页面的,跳转到登陆页面,
创建订单前端js:
- 订单相关的有两张表,订单信息表,订单商品表,
- 前端的订单提交页面,我们点击提交订单,应该提交哪些数据???
- 商品的id,商品的运费,商品的收获地址,支付方式,
- 至于总件数,总金额,这些传了也不会用,
- 点击提交的时候,使用的是ajax请求,
创建订单后端的操作
- 前端有了js提交之后,我们后端就可以去接收这个请求了,
- 创建订单的核心业务,
- 1,用户下一个订单,我要往订单信息表中加入一条信息,这个时候的总数量和总金额,都是0,支付编号不用管,有默认值,订单状态一开始都是未支付,
- 2,往订单商品表中加入多条记录,评论刚刚下订单是没有的,默认是空,
- 3,下单成功了之后还需要更新商品的库存信息,因为之前都是0
- 4,把购物车中的商品清除,
- 5,返回一个应答,下单成功,
订单生成-mysql的事物的概念:
- 如果一共两个库存,但是两个用户都加了两个商品,一个客户先买了,第二个客户再次去购买,库存就是零了,这个时候怎么办?
- 我们需要在生成订单的时候判断库存,如果大于库存,我们应该返回商品库存不足,
- 所以整个创建订单的流程,需要做成一个事务,
- 什么是mysql事务,要么全都执行,要么全部执行,
- 特点:
- 1,原子性,一组事务,要么成功,要么撤回
- 2,稳定性,如果有非法的数据,可以撤回,
- 3,事务的隔离性,一个事务的处理结果,如果影响了其他的事物,其他的也会撤回
- 4,可靠性,事务会保存到日志里面,软硬件崩溃了,恢复之后可以做一个重新执行
- mysql中事务控制的语句,
- 1,开启事务, begin; # 开始事务,后面写的语句都是在事务里面,
- 2,事务的提交 ,commit; # 提交事务
- 3,事务的回滚,rollback; # 回滚,提交和回滚是事务的两个操作,
- 还可以创建保存点,叫做标记点,
- 设置保持点的命令:
- SAVEPOINT savepoint_name; // 声明一个 savepoint
- ROLLBACK TO savepoint_name; // 回滚到savepoint,这个点之前的没有回滚,之后的回滚了,
- RELEASE SAVEPOINT savepoint_name; // 删除指定保留点
- 一旦开始了一个事务,
- 要么是提交了,要么是回滚,才是结束,否则都不是结束,
- 事务中可以设置保存点,
django中使用事务,
- 怎么把django中的是一系列操作放入一个事务里面呢?
- 我们需要一个django的模块:from django.db import transaction
- 然后使用 @transaction.atomic,这个装饰器,装饰我们的函数,
- 把涉及到的数据库操作放入一个事务里面,
- 你只要一装饰,这个函数就是一个事务,
- 什么时候使用保存点呢?
- 对数据库的操作分为两段,我们把核心也为设置一个保存点,
- # 设置事务保存点,,save_id = transaction.savepoint()
- # 商品不存在需要回滚,transaction.savepoint_rollback(save_id)
- # 提交事务,否则不会提交,,transaction.savepoint_commit(save_id)
订单生成-订单兵法的问题:
- 举例:用户1要买一个鸡腿,这个鸡腿只有一个了,这是一个进程,
- 1,要往数据库中添加一条记录,
- 2,查询商品鸡腿的信息,然后紧接着往订单商品表加入数据之前,我们进行一个库存的判断,
- 3,判断没有问题,添加记录,
- 4,进行商品库存的更新,
- 如果在用户1买的时候,又有一个人去买了这个鸡腿,这是第二个进程,
- 然后也执行这个函数,
- 这两个进程是没有关系的,
- 多于多进程多线程的时候,调哪一个进程是随机,
- 假设先调的进程1,判断库存的时候假设是可以的,
- 现在我们的电脑切换进程了,切换到了用户2,这个时候查询库存也是够的,
- cpu再次进行切换,换进程1,那就更新库存为0 了
- cpu再次进行切换,换进程2,去更新库存,这个时候你就会发现只有一个鸡腿,但是你卖出了两份,
- 什么时候会发生, 就是大量的用户买一个商品的时候,比如秒杀,比如iPhone,比如小米,这时候就会有订单并发的问题,
订单并发的处理:悲观锁,
- 卖出的量超过了库存,这种并发问题怎么解决,
- 有两种解决方法
- 1,悲观锁,
- 2,乐观锁。
- 首先第一种悲观锁,什么是悲观锁,这个就是对应进程中锁的概念,谁拿到锁谁就可以去改,没有拿到的不能改,
- 悲观锁就是这样,这个里面查询商品的时候我加一个锁,我拿到这个记录就把这条记录锁定,别的进程就拿不到了,拿不到就要等待,拿到了才可以处理,
- 事务提交或者事务回滚的时候,也就是事务结束的时候,就会把这个锁释放,
- 这样悲观锁就解决了这个问题,
- 怎么加锁?
- 就是查询的时候,加一个for update,
- select * from df_goods_sku where id=sku_id for update;
- 在django中怎么去写?
- sku = GoodsSKU.objects.select_for_update().get(id=sku_id)
- 在事务中可以写保存点:
- # 设置事务保存点
- save_id = transaction.savepoint()
- transaction.savepoint_rollback(save_id)
- # 提交事务,否则不会提交
- transaction.savepoint_commit(save_id)
订单并发-乐观锁:
- 在查询数据的时候不加锁,在更新时进行判断,
- 判断更新时的库存和之前的查出的库存是否一致,
- 也就是说,我查到的库存是1,我更新的时候库存也是1,那就是没有人对这条数据操作,我就可以操作这个数据了,
- # 返回受影响的行数,表示1更新成功,返回0表示更新失败
- res = GoodsSKU.objects.filter(id=sku_id, stock=orgin_stock) .update(stock=new_stock, sales=new_sales)
- 根据这个条件查询,更新,要么是1,要么是0,1就是更新成功了,
- 更新失败了,要回滚
- 更新失败,说明确实之前被人改了,但是我还是尝试3次,再加一个for循环,
- #######
- 模拟两个用户操作同一个商品,使用悲观锁,
- 1,两个都提交,都是第一次循环,
- 2,第一个人提交了之后,查出来还是库存没有变这是怎么回事
- 这是因为事务的隔离性,
- 有四个隔离级别:
- 1,读取未提交的内容,
- 假设有两个事务,A和B,我在事务A中执行了插入语句,但是我还没有提交,但是这个隔离级别,事务B就可以查到事务A为提交的插入语句的内容改变,
- 这是脏读,dirty read,这是读取未提交的内容,
- 2,读取提交的内容,
- 假设有两个事务,A和B,我在事务A中执行了插入语句,但是我还没有提交,这个时候事务B是拿不到内容的,只有事务A提交了,事务B才可以查看到,这是大多数数据库的默认隔离级别,但是不是mysql的
- 3,可重复读,
- 这是mysql的默认的隔离级别,假设有两个事务,A和B,我在事务A中执行了插入语句,但是我还没有提交,这个隔离级别,即使你提交了,我还是不拿你提交后的,还是拿到事务A更新之前的,这就是可重复读,
- 这种会出现的问题就是幻读,在一个事务的两次查询中数据笔数不一致,什么意思?就是数据的列数不一致,
- 举例:一个事务查询了3列数据,而另一个事务却在此时插入了几条新的数据,先前的事务在接下来的查询中,就会发现有几列数据是我之前没有查到的,这就是幻读,
- 4,可串行化
- 这是*别的隔离,强制事务进行排序,使事务之间不可能之间相互冲突,从而解决幻读的问题,但是会导致大量的超时现象和锁的竞争,处理效率低
- ############
- 之前我们出现的就是因为mysql默认是可重复读,这种我们拿不到上一个事务更新之后的库存,导致我更新是失败的,所以我们要更改mysql的事物隔离级别,改为读取提交的内容,
- 怎么设置?
- 找到mysql的日志文件,sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
- transaction-isolation = READ-COMMITTED
- 重启mysql的服务,sudo service mysql restart
- 这样就可以了,
订单并发-总结:
- 在冲突比较少的时候,建议使用乐观锁,我没有加锁,没有释放锁,就减少了开销,提交性能,
- 冲突比较多的时候,使用悲观锁
- 乐观锁重复操作的代价比较大,也使用悲观锁,
用户中心-订单页面:
- 提交订单页面,点击提交订单之后,提交成功,会进入用户中心的订单查看页面,
- 用户中心有一个单独的用户中心订单页面,
- 后端需要一个视图,用来返回订单的信息,
- 我们在用户中心的订单页面,看到之前下的但是待支付的状态,点击去支付我们就可以支付了,
- 点击支付会跳转到支付宝的二维码页面,然后登陆就可以支付了,
- 需要研究一下支付宝支付的内容,
订单支付代码:
- 点击订单列表页面,点击去付款,采用post请求,给django网站传递参数,
- 我们需要给支付宝传参数,我们要使用一个sdk,这样就不用我们自己传参数了,
- 安装这个包,https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
- 先卸载一个包:pip uninstall pycrypto
- 然后安装:pip install python-alipay-sdk --upgrade
- 安装好了之后,你就有这个包了,你就可以使用了,
- ########
- 先搞清楚支付宝的沙箱环境,
- 进入支付宝点开放平台网站,https://open.alipay.com/
- 击“开放平台-开发者中心-沙箱环境”。进入沙箱环境页面,系统已经自动为你创建一个应用,在 信息配置 中可以看到应用信息。
- 二.生成密钥文件
- 1. 使用OpenSSL
- openssl
- 2. 生成私钥
- genrsa -out app_private_key.pem 2048
- 3. 生成公钥
- rsa -in app_private_key.pem -pubout -out app_public_key.pem
- 4. 退出OpenSSL
- exit
- ###########
- 有了自己的公钥和私钥了,下一步就是要配置,在支付宝的沙箱环境,设置自己的公钥,支付宝就会反复支付宝的公钥,
- 这个支付宝的公钥,是我们接收到支付宝的内容之后进行解密的,
- 所以我们要把这个支付宝的公钥保存到我们的项目里面,
- 我们在order应用下面,把支付宝的公钥,和我们私钥都放到这个文件夹下,
- 这样就配置玩了,
- ##################
- 怎么使用?
- 前端点击支付的时候使用ajax+post请求,
- 需要传递的参数:order_id,
- 后端查看这个订单,有几个查询条件,订单号带上,是这个用户的,是支付宝支付方式,是待支付的状态的,能查到就是一个有效的订单,
- 没有问题下一步就开始调用支付宝的接口了,
- 1,初始化
- 2,调接口,
- 3,返回应答,这里就是方法pay_url = 'https://openapi.alipaydev.com/gateway.do?' + order_string
- 报错: raise ValueError("RSA key format is not supported")
- 把接入支付宝的时候的参数改为:app_private_key_string-----> app_private_key_path
- 报错: raise ValueError("Not a valid PEM pre boundary")
- 原来是我的前后的标记多谢了横杠,两边只能是五个,我写了7个,-----END RSA PRIVATE KEY-----注意这个标记的两边必须要是5个,不是5个就会报这个错,
- 现在通了,
- 现在登录沙箱环境,查看沙箱账号,买家账号密码
订单支付-获取支付结果:
- 因为我们现在不是公网环境,所以支付宝不能返回给我们支付结果,
- 所以我们自己去查询交易的结果,
- 获取了支付宝的交易结果之后,给用户一个支付的结果,支付成功,或者失败,
- 用户浏览器访问我们的网站,然后看支付是否成功,
- 用户的浏览器什么时候访问我们的地址,
- 怎么设计??
- 我们把用户引导到支付页面之后,
- 检查支付状态,支付成功了,
- 弹出支付成功,然后刷新页面,把数据库里面的订单状态改掉,
订单评论:
- 前端有单独的评论的页面,
- 一个订单有多个商品,应该有多个评论框,
- 评论之后提交,订单的状态从去评价变为完成,
- 在商品详情页面就有一个评价,应该要显示评价,