项目仓库
https://github.com/EthanYan6/E-commerce-sites.git
结合代码查看笔记,效果更佳。笔记只是记录重点或者难点。
每日分享
Dream big dreams. Small dreams have no magic.
要拥有大梦想,小的想法没有魔力。
小闫语录:
梦想还是要有的,万一实现了呢?先定一个小目标,挣他一个亿......
高三的时候,老师曾说『定目标要定的大一点,一个较低的目标影响你的实际发挥,难以激发你的潜力』。你的梦想应该有足够的驱动力,促使你的成长,前行。轻易就实现的目标适合在逆境时,为自己加油打气。
美多商城项目(九)
1.获取用户结算商品的信息
用户所要结算的商品就是购物车中被勾选的商品。
API: GET /orders/settlement/ 参数: 通过请求头传递jwt token 响应: { "freight": "订单运费", "skus": [ { "id": "商品id", "name": "商品名称", "price": "商品价格", "default_image_url": "默认图片", "count": "结算数量" }, ... ] }
1.1业务逻辑
1.获取登录用户。
2.从登陆用户的redis购物车记录中获取用户购物车中被勾选的商品id和对应数量count。
2.1获取redis链接。
2.2从redis set中获取用户购物车中被勾选的商品的id。
2.3从redis hash中获取用户购物车中添加的所有商品id和对应数量count。
返回的是一个字典
{ b'<sku_id>': b'<count>', ... }
2.4组织数量,将上面字典中bytes类型的数据,转换成int类型。
3.根据商品id获取对应的商品数据并组织运费。
3.1遍历出每个商品。
3.2给sku对象增加属性count,保存该商品所要结算的数量。
3.3定义『订单结算商品序列化器类』
3.4将商品数据序列化。
3.5组织运费,固定为10元。
decimal意思为十进制,这个模块提供了十进制浮点运算支持。
可以传递给Decimal整型或者字符串参数,但不能是浮点数据,因为浮点数据本身就不准确。
from decimal import * # 下面是常用的两种情况 # 可以限定有效数字位数,让其按我们的需求输出 getcontext().prec = 6 Decimal(1)/Decimal(3) >>> 0.333333 # 输出结果,有效数字为6位 # 可以决定保留几位小数 Decimal('265.2548').quantize(Decimal('0.000')) >>> 265.255 # 输出结果
4.将数据序列化并返回。
2.订单数据的保存(订单创建)
API: POST /orders/ 参数: 通过请求头传递jwt token { "address": "收获地址id", "pay_method": "支付方式" } 响应: { "order_id": "订单编号" }
2.1订单保存的基本流程
1.向订单基本信息表中添加一条记录。
2.订单中包含几个商品就需要向订单商品表中添加几条记录。
3.删除redis中对应购物车记录。
2.2业务逻辑
1.获取参数并进行校验(参数完整性,address是否存在,pay_method是否合法)。
1.1创建『订单序列化器类』
2.保存订单的数据。
2.1『订单序列化器类』中定义create方法保存订单的数据。
2.2获取address和pay_method。
2.3获取登录用户
2.4订单的id设置为年月日时分秒+用户id
# 把一个日期格式化为一个字符串 datetime.now().strftime('%Y%m%d%H%M%S') # 用户id长度不一致,我们可以格式化 '%010d' % user.id
2.5定义订单商品总数和实付款接收参数。
2.6设置运费。
2.7判断支付状态,待发货还是待支付。从而做出相应逻辑。
2.8向订单基本信息表中添加一条记录。
2.9订单中包含几个商品,就需要向订单商品表中添加几条记录。
2.9.1从redis购物车中获取用户所需要购买的商品id(redis set购物车中勾选的商品id)
2.9.2从redis hash中获取用户购物车中添加的商品的id和对应数量count
2.9.3遍历商品的id
2.9.4获取用户所要购买的该商品的数量count。
2.9.5根据sku_id获取商品对象。
2.9.6商品库存判断。
2.9.7减少商品库存,增加销量。
2.9.8向订单商品表中添加一条记录。
2.9.9累加计算订单中商品的总数量和总金额。
2.9.10计算实付款(添加运费)。
2.9.11更新订单商品的总数量和实付款。
2.10删除redis中对应购物车记录。
此处可以使用管道,将所有的数据一次性删除。
hdel 1. hdel <key> <field> ...
删除redis hash中指定的field属性和值。
3.返回应答,订单创建成功。
3.订单事务
对于订单保存中,涉及到数据库操作的过程,应该放在同一个事务中,要么都成功,要么都失败。
mysql事务:一组sql语句,要么都成功,要么都失败。
3.1mysql事务基本操作
开启事务:
begin;或 start transaction;
事务提交,让事务中sql语句的执行结果永久生效:
commit;
事务回滚,撤销事务中sql语句的执行结果:
rollback;
3.2mysql事务保存点
在事务中,可以设置事务的保存点,设置了事务保存点之后,在进行事务的回滚时,可以不回滚整个事务,而是回滚到指定的保存点,该保存点之后的sql语句执行结果会撤销。
设置事务的保存点:
savepoint <保存点名称>
回滚到指定的保存点,该保存点之后的sql语句执行结果会撤销:
rollback to <保存点名称>
3.3django中事务使用
from django.db import transaction with transaction.atomic(): # with语句块下面的代码,凡是涉及到数据库操作的代码,在进行数据库操作时,都会放在同一个事务中 # 设置事务的保存点 sid = transaction.savepoint() ...(代码) # 回滚到指定的保存点 transaction.savepoint_rollback(sid)
atomic会自动进行事务的提交commit和回滚rollback。
只有操作数据库时sql语句有错的时候才能自动进行提交和回滚。
我们可以将涉及到数据库操作的部分进行错误捕获,有错统一返回下单失败;如果想让代码部分中的涉及到不同的异常抛出,可以在统一返回下单失败之前再进行一次捕获异常,抛出不同的异常。
4.订单并发
4.1问题描述
当多个人同时购买同一件商品时,有可能会产生订单并发问题。
4.2举例说明
id为16的商品库存有10件,两人同时购买这件商品,每人购买5件,产生订单并发问题之后,两个下单都能成功,但是商品的库存变为了5件,而不是0件。
4.3原因分析
用户A:进程1
用户B:进程2
从上到下模拟cpu进程切换,用户A和B同时下单过程的模拟
过程1-用户A
1.向tborderinfo中添加一条记录。
2.获取商品的信息(库存为10)。
3.判断商品库存(5<10)。
4进程切换,调度进程2,开始处理用户B的请求。
过程2-用户B
5.向tborderinfo中添加一条记录。
6.获取商品的信息(库存为10)。
7.判断商品库存(5<10)。
8.进程切换,重新调度进程1,处理用户A的请求。
过程3-用户A
9.减少商品库存,增加销量(10-5=5)。
10.向tborderinfo中添加一条记录。
11.下单成功,开始调度进程2处理用户B请求。
过程4-用户B
12.由于用户之前的过程已经处理完,继续进行下面步骤-->减少商品库存,增加销量(10-5=5)。
13.向tborderinfo中添加一条记录。
14.下单成功。
结果
14.库存显示5。
4.4订单并发-解决方案
4.4.1解决方案-悲观『锁』
在事务中查询数据的时候尝试对数据进行加锁(互斥锁),获取到锁的事务可以对数据进行操作,获取不到锁的事务会阻塞,直到锁被释放。
结果
用户A和用户B还是按照之前的举例过程进行操作。用户A先获取到锁,可以进行操作,用户B却拿不到锁,事务会阻塞,等待用户A操作完释放锁之后再进行事务操作,所以库存就不会出现问题。
使用
MySQL数据库中sql语句:
select stock from tb_sku where id=1for update;
Django中使用selectforupdate()可以获取锁:
sku = SKU.objects.select_for_update().get(id=sku_id)
悲观锁在实际开发中不会使用
4.4.2解决方案-乐观『锁』
乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新。
乐观锁需要配合mysql事务的隔离级别,将mysql事务的隔离级别改为Read committed
结果
用户A和用户B按照之前的举例过程进行操作。此次,用户A和用户B在获取商品信息之后都记录一下原始库存,在下单成功之前,再进行一次库存查询。用户A执行完后,用户B进行操作时,两次库存不一致,更新失败,重新进行尝试。
更新失败需要重新进行尝试,最多尝试3次,否则下单失败。
使用
MySQL数据库中的sql语句:
update tb_sku set stock=2 where id=1and stock=7;
Django中的使用:
SKU.objects.filter(id=1, stock=7).update(stock=2)
MySQL事务隔离级别
事务隔离级别指的是在处理同一个数据的多个事务中,一个事务修改数据后,其他事务何时能看到修改后的结果。
MySQL数据库事务隔离级别主要有四种:
Serializable 串行化,一个事务一个事务的执行
Repeatable read 可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响
Read committed 读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值
Read uncommitted 读取未提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。
MySQL数据库默认使用可重复读( Repeatable read),而使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别,应该修改为读取已提交Read committed。
隔离级别 | 说明 |
Repeatable read 可重复读 | 在事务中执行同一个查询语句时,获取到的结果永远和第一次获取的结果一致,即使其他事务修改了对应的数据并且进行了提交,当前事务仍然获取不到更新之后的结果。 |
Read committed 读取为提交 | 其他事务修改了对应的数据并进行了提交,当前事务就能获取更新之后的结果。 |
MySQL事务的默认隔离级别为:Repeatable read可重复读
修改默认隔离级别
进入下面的路径:
/etc/mysql/mysql.conf.d
执行下面命令:
sudo vim mysqld.cnf
在105行添加下列一句命令:
transaction-isolation=READ-COMMITTED
重启mysql服务
sudo service mysql restart
4.4.3解决方案-任务队列
采用异步下单,将下单的过程封装成celery任务函数,同时在启动worker时只创建一个进程。
5.支付宝支付
5.1对接支付宝
线上环境
1.登录支付宝开发平台。
2.创建开发者应用,提交相关配置信息并等待审核。
3.审核通过之后,获取appid,然后就可以开发支付宝相关功能。
电脑网站支付功能需要签约
沙箱环境
对线上环境的模拟,提供给开发者使用,让开发者在不创建线上应用的请求就可以完成相应功能的开发。
SDK
:软件开发工具包
具体对应操作请查看开发者文档:
支付宝开发者文档
文档主页:
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/
5.2配置秘钥
5.2.1生成应用的私钥和公钥
openssl OpenSSL> genrsa -out app_private_key.pem 2048 # 私钥RSA2 OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥 OpenSSL> exit
5.2.2保存应用私钥文件
在payment应用中新建keys目录,用来保存秘钥文件。
将应用私钥文件appprivatekey.pem复制到payment/keys目录下。
5.2.3查看公钥
cat app_publict_key.pem
将公钥内容复制给支付宝
5.2.4保存支付宝公钥
在创建的支付子应用payment/keys目录下新建alipaypublickey.pem文件,用于保存支付宝的公钥文件。
将支付宝的公钥内容复制到alipaypublickey.pem文件中
注意:还需要在公钥文件中补充开始与结束标志
-----BEGIN PUBLIC KEY----- 此处是公钥内容 -----END PUBLIC KEY-----