Django电商项目

  • 订单生成
    • mysql事务
    • django使用事务
    • 提交订单页面
    • 创建订单后台view

订单生成

mysql事务

  1. 事务概念
    一组mysql语句,要么执行,要么全不不执行。

  2. 事务的特点
    1、原子性:一组事务,要么成功;要么撤回。
    2、稳定性 :有非法数据(外键约束之类),事务撤回。
    3、隔离性:事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。
    4、可靠性:软、硬件崩溃后,InnoDB数据表驱动会利用日志文件重构修改。可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit 选项 决定什么时候吧事务保存到日志里。

  3. 事务控制语句
    BEGIN或START TRANSACTION;显式地开启一个事务;
    COMMIT;也可以使用COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改称为永久性的;
    ROLLBACK;有可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的务,并撤销正在进行的所有未提交的修改;
    SAVEPOINT identifier;SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT;
    RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
    ROLLBACK TO identifier;把事务回滚到标记点;

  4. mysql事务隔离级别
    SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
    Read Uncommitted(读取未提交内容)
    在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
    Read Committed(读取提交内容)
    这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
    Repeatable Read(可重读)
    这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
    ==Serializable(可串行化) ==
    这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
    这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:
    脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
    不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
    幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几行(Row)数据,而另一个事务却在此时插入了新的几行数据,先前的事务在接下来的查询中,就会发现有几行数据是它先前所没有的。

  5. 设置mysql事务的隔离级别
    打开mysql配置文件: sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf, 添加如下行。

保存配置文件,重启mysql服务。
sudo service mysql restart

django使用事务

参考文档:https://doc.codingdict.com/django/topics/db/transactions.html
导包
from django.db import transaction
使用装饰器
@transaction.atomic

提交订单页面

修改cart模板

创建函数

# /order/place
class OrderPlaceView(LoginRequiredMixin, View):'''提交订单页面显示'''def post(self, request):'''提交订单页面显示'''# 获取登录的用户user = request.user# 获取参数sku_idssku_ids = request.POST.getlist('sku_ids') # [1,26]# 校验参数if not sku_ids:# 跳转到购物车页面return redirect(reverse('cart:show'))conn = get_redis_connection('default')cart_key = 'cart_%d'%user.idskus = []# 保存商品的总件数和总价格total_count = 0total_price = 0# 遍历sku_ids获取用户要购买的商品的信息for sku_id in sku_ids:# 根据商品的id获取商品的信息sku = GoodsSKU.objects.get(id=sku_id)# 获取用户所要购买的商品的数量count = conn.hget(cart_key, sku_id)# 计算商品的小计amount = sku.price*int(count)# 动态给sku增加属性count,保存购买商品的数量sku.count = count# 动态给sku增加属性amount,保存购买商品的小计sku.amount = amount# 追加skus.append(sku)# 累加计算商品的总件数和总价格total_count += int(count)total_price += amount# 运费:实际开发的时候,属于一个子系统transit_price = 10 # 写死# 实付款total_pay = total_price + transit_price# 获取用户的收件地址addrs = Address.objects.filter(user=user)# 组织上下文sku_ids = ','.join(sku_ids) # [1,25]->1,25context = {'skus':skus,'total_count':total_count,'total_price':total_price,'transit_price':transit_price,'total_pay':total_pay,'addrs':addrs,'sku_ids':sku_ids}# 使用模板return render(request, 'place_order.html', context)

修改place_order模板 创建订单前端js

{% extends 'base_no_cart.html' %}
{% load staticfiles %}
{% block title %}天天生鲜-提交订单{% endblock title %}
{% block page_title %}提交订单{% endblock page_title %}
{% block body %}<h3 class="common_title">确认收货地址</h3><div class="common_list_con clearfix"><dl><dt>寄送到:</dt>{% for addr in addrs %}<dd><input type="radio" name="addr_id" value="{{ addr.id }}" {% if addr.is_default %}checked{% endif %}>{{ addr.addr }} ({{ addr.receiver }} 收) {{ addr.phone }}</dd>{% endfor %}</dl><a href="{% url 'user:address' %}" class="edit_site">编辑收货地址</a></div><h3 class="common_title">支付方式</h3>    <div class="common_list_con clearfix"><div class="pay_style_con clearfix"><input type="radio" name="pay_style" value="1" checked><label class="cash">货到付款</label><input type="radio" name="pay_style" value="2"><label class="weixin">微信支付</label><input type="radio" name="pay_style" value="3"><label class="zhifubao"></label><input type="radio" name="pay_style" value="4"><label class="bank">银行卡支付</label></div></div><h3 class="common_title">商品列表</h3><div class="common_list_con clearfix"><ul class="goods_list_th clearfix"><li class="col01">商品名称</li><li class="col02">商品单位</li><li class="col03">商品价格</li><li class="col04">数量</li><li class="col05">小计</li>       </ul>{% for sku in skus %}<ul class="goods_list_td clearfix"><li class="col01">{{ forloop.counter }}</li><li class="col02"><img src="{{ sku.image.url }}"></li><li class="col03">{{ sku.name }}</li><li class="col04">{{ sku.unite }}</li><li class="col05">{{ sku.price }}元</li><li class="col06">{{ sku.count }}</li><li class="col07">{{ sku.amount }}元</li></ul>{% endfor %}</div><h3 class="common_title">总金额结算</h3><div class="common_list_con clearfix"><div class="settle_con"><div class="total_goods_count">共<em>{{ total_count }}</em>件商品,总金额<b>{{ total_price }}元</b></div><div class="transit">运费:<b>{{ transit_price }}元</b></div><div class="total_pay">实付款:<b>{{ total_pay }}元</b></div></div></div><div class="order_submit clearfix">{% csrf_token %}<a href="javascript:;" sku_ids={{ sku_ids }} id="order_btn">提交订单</a></div>
{% endblock body %}
{% block bottom %}<div class="popup_con"><div class="popup"><p>订单提交成功!</p></div><div class="mask"></div></div>
{% endblock bottom %}
{% block bottomfiles %}<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script><script type="text/javascript">$('#order_btn').click(function() {// 获取用户选择的地址id, 支付方式, 要购买的商品id字符串addr_id = $('input[name="addr_id"]:checked').val()pay_method = $('input[name="pay_style"]:checked').val()sku_ids = $(this).attr('sku_ids')csrf = $('input[name="csrfmiddlewaretoken"]').val()// alert(addr_id+":"+pay_method+':'+sku_ids)// 组织参数params = {'addr_id':addr_id, 'pay_method':pay_method, 'sku_ids':sku_ids,'csrfmiddlewaretoken':csrf}// 发起ajax post请求,访问/order/commit, 传递的参数: addr_id pay_method, sku_ids$.post('/order/commit', params, function (data) {if (data.res == 5){// 创建成功localStorage.setItem('order_finish',2);$('.popup_con').fadeIn('fast', function() {setTimeout(function(){$('.popup_con').fadeOut('fast',function(){window.location.href = '/user/order/1';});},3000)});}else{alert(data.errmsg)}})});</script>
{% endblock bottomfiles %}

创建订单后台view

# 前端传递的参数:地址id(addr_id) 支付方式(pay_method) 用户要购买的商品id字符串(sku_ids)
# mysql事务: 一组sql操作,要么都成功,要么都失败
# 高并发:秒杀
# 支付宝支付
class OrderCommitView(View):'''订单创建'''@transaction.atomicdef post(self, request):'''订单创建'''# 判断用户是否登录user = request.userif not user.is_authenticated():# 用户未登录return JsonResponse({'res':0, 'errmsg':'用户未登录'})# 接收参数addr_id = request.POST.get('addr_id')pay_method = request.POST.get('pay_method')sku_ids = request.POST.get('sku_ids') # 1,3# 校验参数if not all([addr_id, pay_method, sku_ids]):return JsonResponse({'res':1, 'errmsg':'参数不完整'})# 校验支付方式if pay_method not in OrderInfo.PAY_METHODS.keys():return JsonResponse({'res':2, 'errmsg':'非法的支付方式'})# 校验地址try:addr = Address.objects.get(id=addr_id)except Address.DoesNotExist:# 地址不存在return JsonResponse({'res':3, 'errmsg':'地址非法'})# todo: 创建订单核心业务# 组织参数# 订单id: 20171122181630+用户idorder_id = datetime.now().strftime('%Y%m%d%H%M%S')+str(user.id)# 运费transit_price = 10# 总数目和总金额total_count = 0total_price = 0# 设置事务保存点save_id = transaction.savepoint()try:# todo: 向df_order_info表中添加一条记录order = OrderInfo.objects.create(order_id=order_id,user=user,addr=addr,pay_method=pay_method,total_count=total_count,total_price=total_price,transit_price=transit_price)# todo: 用户的订单中有几个商品,需要向df_order_goods表中加入几条记录conn = get_redis_connection('default')cart_key = 'cart_%d'%user.idsku_ids = sku_ids.split(',')for sku_id in sku_ids:for i in range(3):# 获取商品的信息try:sku = GoodsSKU.objects.get(id=sku_id)except:# 商品不存在transaction.savepoint_rollback(save_id)return JsonResponse({'res':4, 'errmsg':'商品不存在'})# 从redis中获取用户所要购买的商品的数量count = conn.hget(cart_key, sku_id)# todo: 判断商品的库存if int(count) > sku.stock:transaction.savepoint_rollback(save_id)return JsonResponse({'res':6, 'errmsg':'商品库存不足'})# todo: 更新商品的库存和销量orgin_stock = sku.stocknew_stock = orgin_stock - int(count)new_sales = sku.sales + int(count)# print('user:%d times:%d stock:%d' % (user.id, i, sku.stock))# import time# time.sleep(10)# update df_goods_sku set stock=new_stock, sales=new_sales# where id=sku_id and stock = orgin_stock# 返回受影响的行数res = GoodsSKU.objects.filter(id=sku_id, stock=orgin_stock).update(stock=new_stock, sales=new_sales)if res == 0:if i == 2:# 尝试的第3次transaction.savepoint_rollback(save_id)return JsonResponse({'res': 7, 'errmsg': '下单失败2'})continue# todo: 向df_order_goods表中添加一条记录OrderGoods.objects.create(order=order,sku=sku,count=count,price=sku.price)# todo: 累加计算订单商品的总数量和总价格amount = sku.price*int(count)total_count += int(count)total_price += amount# 跳出循环break# todo: 更新订单信息表中的商品的总数量和总价格order.total_count = total_countorder.total_price = total_priceorder.save()except Exception as e:transaction.savepoint_rollback(save_id)return JsonResponse({'res':7, 'errmsg':'下单失败'})# 提交事务transaction.savepoint_commit(save_id)# todo: 清除用户购物车中对应的记录conn.hdel(cart_key, *sku_ids)# 返回应答return JsonResponse({'res':5, 'message':'创建成功'})


(1) 悲观锁
select * from df_goods_sku where id=17 for update;

悲观锁获取数据时对数据行了锁定,其他事务要想获取锁,必须等原事务结束。
(2) 乐观锁
查询时不锁数据,提交更改时进行判断.
update df_goods_sku set stock=0, sales=1 where id=17 and stock=1;
冲突比较少的时候,使用乐观锁。
冲突比较多的时候,使用悲观锁。

Django电商项目(八)订单生成、悲观锁、乐观锁相关推荐

  1. Django电商项目—项目规划和环境搭建

    Django商品 我们要做一个Django电商项目,那么在开始我们需要将项目分析,功能块,还有逻辑弄清楚, 1.功能块 版本 优化思想 商城项目: 买家:购物车 卖家:店铺 商品 我们首先要先把他们的 ...

  2. Django电商项目(六)商品详情页、列表页分页、商品搜索

    Django电商项目 商品详情页 商品列表页 商品搜索 全文检索 安装和配置 索引文件生成 全文检索的使用 改变分词方式 商品详情页 新建detail.html {% extends 'base_de ...

  3. 电商项目实战之缓存与Redis分布式锁

    电商项目实战之缓存与Redis分布式锁 缓存失效 缓存穿透 缓存雪崩 缓存击穿 分布式缓存 分布式锁 SpringBoot整合Redisson实现分布式锁 实现过程 缓存和数据库一致性 场景分析 解决 ...

  4. 电商项目中订单系统到底该怎么设计才好?(至尊典藏版)

    目录 前言 一.订单系统架构 1. 订单系统在企业中的角色 2. 订单系统与各业务系统的关系 3. 订单系统上下游关系​编辑 4. 订单系统的业务架构​编辑 二.订单系统核心功能 1. 订单中所包含的 ...

  5. 【电商】后台订单生成

    结合商品流转的电商系列介绍了一些了,商品已经采购入库.价格税率设置好了.活动及相关模板也已经准备完毕,下面就应该上架销售了,现在接着聊下订单的生成. 订单从产生到最终的关闭需要经历很多的环节,订单也是 ...

  6. [Vue.js]实战 -- 电商项目(八)

    数据统计 数据统计概述 用于统计电商平台运营过程的中的各种统计数据 通过直观的可视化方式展示出来,方便相关运营和管理人员查看 用户来源数据统计报表 Echarts 第三方可视化库的基本使用 安装ech ...

  7. Django电商项目(八)短信验证码的前后端实现

    这篇文章我们开始完成短信验证码的前后端逻辑 后端逻辑 短信验证码接口设计 class SMSCodeView(View):"""短信验证码""&quo ...

  8. Django电商项目(十二)实现QQ登录

    QQ登录:即我们所说的第三方登录,是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录本项目. QQ互联开发者申请步骤 若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现. 相 ...

  9. java电商项目搭建-------订单模块

    努力好了,时间会给你答案.--------magic_guo 订单模块的流程以及用户操作如下: 1.当用户选定购物车需要结算的商品并单击结算按钮时,会跳转到订单页面,此时会将选定的购物车信息展示在订单 ...

最新文章

  1. 打开AI芯片的“万能钥匙”
  2. python 字符串补齐
  3. jquery取值,赋值,以及下拉框获取选中value值
  4. mysql infile local,MySQL:启用LOAD DATA LOCAL INFILE
  5. RocketMQ源码解析-事务消息的二阶段提交
  6. oracle rac redo log,RAC共享online redo log和archived log的官方说明
  7. [转]用 ASP.NET 2.0 改进的 ViewState 加快网站速度
  8. sdk manager的列表怎么消失了_腾讯安全SDK的Dll Dump研究
  9. u盘文件夹变成应用程序怎么恢复?方法来了!
  10. 计算机中常用的数学知识
  11. eclipse导入系统签名
  12. PS抠头发妙法(原创技巧)
  13. Linux系统如何替换新硬盘
  14. QT入门学习之软件程序开发初体验
  15. matlab读取excel数据
  16. 全国计算机一共几集,夏目友人帐第六季一共多少集?至少11集国内已播出
  17. 数据分析方法和思维—RFM用户分群
  18. 如何在mysql官网下载mysql最新或者以前的版本
  19. 在网页中插入一个透明背景的PNG图片
  20. CPU虚拟化的三种技术

热门文章

  1. python xlwt表格写入操作
  2. ssd硬盘 速度慢 linux,固态硬盘速度太慢 快不起来?你或许犯了这四个SSD使用错误 (全文)...
  3. 建筑施工与管理计算机综合应用能力实训报告,建筑施工管理计算机综合应用能力实训报告...
  4. 将一个div中的两个P元素同一行居中显示
  5. 盘点:当今十大备份应用软件(转)
  6. 彗星http_大气与彗星
  7. 围剿慢SQL,工行MySQL研发管控和治理实践
  8. 工具-python包-虚拟环境管理(99.4.1)
  9. jmeter压力测试并发
  10. matlab示波器绘图工具,快速绘制Simulink示波器波形