正文共: 5342字 7图 预计阅读时间: 14分钟
https://github.com/EthanYan6/E-commerce-sites.git
结合代码查看笔记,效果更佳。笔记只是记录重点或者难点。
Dream big dreams. Small dreams have no magic.
要拥有大梦想,小的想法没有魔力。
小闫语录:
梦想还是要有的,万一实现了呢?先定一个小目标,挣他一个亿......
高三的时候,老师曾说『定目标要定的大一点,一个较低的目标影响你的实际发挥,难以激发你的潜力』。你的梦想应该有足够的驱动力,促使你的成长,前行。轻易就实现的目标适合在逆境时,为自己加油打气。
用户所要结算的商品就是购物车中被勾选的商品。
API: GET /orders/settlement/
参数:
通过请求头传递jwt token
响应:
{
"freight": "订单运费",
"skus": [
{
"id": "商品id",
"name": "商品名称",
"price": "商品价格",
"default_image_url": "默认图片",
"count": "结算数量"
},
...
]
}
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.将数据序列化并返回。
API: POST /orders/
参数:
通过请求头传递jwt token
{
"address": "收获地址id",
"pay_method": "支付方式"
}
响应:
{
"order_id": "订单编号"
}
1.向订单基本信息表中添加一条记录。
2.订单中包含几个商品就需要向订单商品表中添加几条记录。
3.删除redis中对应购物车记录。
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
hdel <key> <field> ...
删除redis hash中指定的field属性和值。
3.返回应答,订单创建成功。
对于订单保存中,涉及到数据库操作的过程,应该放在同一个事务中,要么都成功,要么都失败。
mysql事务:一组sql语句,要么都成功,要么都失败。
开启事务:
begin;或 start transaction;
事务提交,让事务中sql语句的执行结果永久生效:
commit;
事务回滚,撤销事务中sql语句的执行结果:
rollback;
在事务中,可以设置事务的保存点,设置了事务保存点之后,在进行事务的回滚时,可以不回滚整个事务,而是回滚到指定的保存点,该保存点之后的sql语句执行结果会撤销。
设置事务的保存点:
savepoint <保存点名称>
回滚到指定的保存点,该保存点之后的sql语句执行结果会撤销:
rollback to <保存点名称>
from django.db import transaction
with transaction.atomic():
# with语句块下面的代码,凡是涉及到数据库操作的代码,在进行数据库操作时,都会放在同一个事务中
# 设置事务的保存点
sid = transaction.savepoint()
...(代码)
# 回滚到指定的保存点
transaction.savepoint_rollback(sid)
atomic会自动进行事务的提交commit和回滚rollback。 只有操作数据库时sql语句有错的时候才能自动进行提交和回滚。 我们可以将涉及到数据库操作的部分进行错误捕获,有错统一返回下单失败;如果想让代码部分中的涉及到不同的异常抛出,可以在统一返回下单失败之前再进行一次捕获异常,抛出不同的异常。
当多个人同时购买同一件商品时,有可能会产生订单并发问题。
id为16的商品库存有10件,两人同时购买这件商品,每人购买5件,产生订单并发问题之后,两个下单都能成功,但是商品的库存变为了5件,而不是0件。
用户A:进程1
用户B:进程2
从上到下模拟cpu进程切换,用户A和B同时下单过程的模拟
1.向tborderinfo中添加一条记录。
2.获取商品的信息(库存为10)。
3.判断商品库存(5<10)。
4进程切换,调度进程2,开始处理用户B的请求。
5.向tborderinfo中添加一条记录。
6.获取商品的信息(库存为10)。
7.判断商品库存(5<10)。
8.进程切换,重新调度进程1,处理用户A的请求。
9.减少商品库存,增加销量(10-5=5)。
10.向tborderinfo中添加一条记录。
11.下单成功,开始调度进程2处理用户B请求。
12.由于用户之前的过程已经处理完,继续进行下面步骤-->减少商品库存,增加销量(10-5=5)。
13.向tborderinfo中添加一条记录。
14.下单成功。
14.库存显示5。
在事务中查询数据的时候尝试对数据进行加锁(互斥锁),获取到锁的事务可以对数据进行操作,获取不到锁的事务会阻塞,直到锁被释放。
用户A和用户B还是按照之前的举例过程进行操作。用户A先获取到锁,可以进行操作,用户B却拿不到锁,事务会阻塞,等待用户A操作完释放锁之后再进行事务操作,所以库存就不会出现问题。
MySQL数据库中sql语句:
select stock from tb_sku where id=1 for update;
Django中使用selectforupdate()可以获取锁:
sku = SKU.objects.select_for_update().get(id=sku_id)
乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新。
乐观锁需要配合mysql事务的隔离级别,将mysql事务的隔离级别改为Read committed
用户A和用户B按照之前的举例过程进行操作。此次,用户A和用户B在获取商品信息之后都记录一下原始库存,在下单成功之前,再进行一次库存查询。用户A执行完后,用户B进行操作时,两次库存不一致,更新失败,重新进行尝试。
更新失败需要重新进行尝试,最多尝试3次,否则下单失败。
MySQL数据库中的sql语句:
update tb_sku set stock=2 where id=1 and stock=7;
Django中的使用:
SKU.objects.filter(id=1, stock=7).update(stock=2)
事务隔离级别指的是在处理同一个数据的多个事务中,一个事务修改数据后,其他事务何时能看到修改后的结果。
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
采用异步下单,将下单的过程封装成celery任务函数,同时在启动worker时只创建一个进程。
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/
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
在payment应用中新建keys目录,用来保存秘钥文件。
将应用私钥文件appprivatekey.pem复制到payment/keys目录下。
cat app_publict_key.pem
将公钥内容复制给支付宝
在创建的支付子应用payment/keys目录下新建alipaypublickey.pem文件,用于保存支付宝的公钥文件。
将支付宝的公钥内容复制到alipaypublickey.pem文件中
注意:还需要在公钥文件中补充开始与结束标志
-----BEGIN PUBLIC KEY-----
此处是公钥内容
-----END PUBLIC KEY-----
优质文章推荐: