美多商城项目(九)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 美多商城项目(九)


项目仓库

  1. 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事务基本操作

开启事务:

  1. begin;或 start transaction;

事务提交,让事务中sql语句的执行结果永久生效:

  1. commit;

事务回滚,撤销事务中sql语句的执行结果:

  1. rollback;

3.2mysql事务保存点

在事务中,可以设置事务的保存点,设置了事务保存点之后,在进行事务的回滚时,可以不回滚整个事务,而是回滚到指定的保存点,该保存点之后的sql语句执行结果会撤销。

设置事务的保存点

  1. savepoint <保存点名称>

回滚到指定的保存点,该保存点之后的sql语句执行结果会撤销:

  1. 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语句:

  1. select stock from tb_sku where id=1for update;

Django中使用selectforupdate()可以获取锁:

  1. 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语句:

  1. update tb_sku set stock=2 where id=1and stock=7;

Django中的使用:

  1. 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可重复读

修改默认隔离级别

进入下面的路径:

  1. /etc/mysql/mysql.conf.d

执行下面命令:

  1. sudo vim mysqld.cnf

在105行添加下列一句命令:

  1. transaction-isolation=READ-COMMITTED

重启mysql服务

  1. 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安装:

  1. 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查看公钥

  1. cat app_publict_key.pem

将公钥内容复制给支付宝

5.2.4保存支付宝公钥

在创建的支付子应用payment/keys目录下新建alipaypublickey.pem文件,用于保存支付宝的公钥文件。

将支付宝的公钥内容复制到alipaypublickey.pem文件中

注意:还需要在公钥文件中补充开始与结束标志

-----BEGIN PUBLIC KEY-----
此处是公钥内容
-----END PUBLIC KEY-----



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的大学生二手电子产品交易平台的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的大学生二手电子产品交易平台的详细设计和实现(源码+lw+部署文档+讲解等)
83 0
|
7月前
|
小程序 关系型数据库 MySQL
Gitee项目分享——学之思开源考试系统,食堂大妈看完都学会了
Gitee项目分享——学之思开源考试系统,食堂大妈看完都学会了
|
7月前
|
开发框架 小程序 Java
免费开题报告|基于SpringBoot+Vue的校内跑腿平台
免费开题报告|基于SpringBoot+Vue的校内跑腿平台
100 0
免费开题报告|基于SpringBoot+Vue的校内跑腿平台
|
Linux 数据库 文件存储
美多商城项目(六)
美多商城项目(六)
|
存储 安全 数据安全/隐私保护
美多商城项目(二)
美多商城项目(二)
|
应用服务中间件 API 数据库
美多商城项目(十)
美多商城项目(十)
|
存储 NoSQL 前端开发
美多商城项目(八)
美多商城项目(八)
|
存储 搜索推荐 NoSQL
美多商城项目(七)
美多商城项目(七)
|
存储 Shell 数据库
美多商城项目(四)
美多商城项目(四)
|
API 数据库 数据安全/隐私保护
美多商城项目(三)
美多商城项目(三)