本部分涉及订单的生成、并发处理、支付、评论等
关键:MySQL事务、并发处理的悲观锁/乐观锁、支付宝SDK 的使用......
仅作为个人笔记!
目录
订单
在购物车中点击去结算后,会将选中的商品id、数量、价格等传到订单页。实际上,只需要传用户要购买的商品的id即可,数量等会从redis数据库中获取。
订单页面大致如下:
1.订单页面显示的视图
表单中的checkbox只有被选中时值才会被提交。request.POST提交的类型为QueryDict,可以一个名字对应多个值;在视图中request.POST.getlist()来获。
前端注意对收货地址的默认选中:
<dl>
<dt>寄送到:</dt>
{% for addr in addrs %}
<dd><input type="radio" name="addr_id" {% if addr.is_default %}checked{% endif %}>{
{addr.addr}} ({
{addr.receviver}} 收) {
{addr.phone}}</dd>
{% endfor %}
</dl>
2.创建订单
用户点击提交订单时,创建订单。前端必须要传递的参数有:收货地址、支付方式、商品id等。
订单相关的MySQL表有:订单信息表:df_order_info、订单商品表:df_order_goods,二者为一对多的关系;
用户每下一个订单,就向df_order_info表中加入一条记录;用户的订单中有几个商品,就需要向df_order_goods表中加入几条记录。
前端js
提交订单使用Ajax提交,关键的js如下:
v
$('#order_btn').click(function() {
// 获取用户选择的地址id, 支付方式, 要购买的商品id字符串
addr_id = $('input[name="addr_id"]:checked').val() // 选择器获取被选中的checkbox的值(收货地址
pay_method = $('input[name="pay_style"]:checked').val()
sku_ids = $(this).attr('sku_ids')
csrf = $('input[name="csrfmiddlewaretoken"]').val()
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){
alert('创建成功')
}
else {
alert(data.errmsg)
}
})
});
注意,Ajax使用cdrf的方法:在html中加上:{% csrf_token %};在js中加上:csrf = $('input[name="csrfmiddlewaretoken"]').val() ;
后台view
关键:用户每下一个订单,就向df_order_info表中加入一条记录;用户的订单中有几个商品,就需要向df_order_goods表中加入几条记录。
除了从前端和数据库中能直接获取的参数,还有些参数需要自己组织:订单id order_id(年月日时分秒+用户id)、总数total_count、总价total_price、运费transit_price等。
部分code如下,但下面的code还存在很多待完善的地方(库存判断等):
def post(self, request):
...
# 组织参数
# 订单id: 20171122181630+用户id (年月日时分秒+用户id)
order_id = datetime.now().strftime('%Y%m%d%H%M%S') + str(user.id)
transit_price = 10
# 总数目和总金额(先假设为0存进去,添加完df_order_goods后再更新
total_count = 0
total_price = 0
# todo: 向df_order_info表中添加一条记录
order = OrderInfo.objects.create(order_id=order_id, ...)
# todo: 用户的订单中有几个商品,需要向df_order_goods表中加入几条记录
conn = get_redis_connection('default')
cart_key = 'cart_%d'%user.id
sku_ids = sku_ids.split(',') # 将字符串转回列表,用于遍历
for sku_id in sku_ids:
try:
sku = GoodsSKU.objects.get(id=sku_id)
except:
return JsonResponse({'res': 4, 'errmsg': '商品不存在'})
# 从redis中获取用户所要购买的商品的数量
count = conn.hget(cart_key, sku_id)
# todo: 向df_order_goods表中添加一条记录
OrderGoods.objects.create(order=order, ...)
# 更新商品的库存和销量
sku.stock -= int(count)
sku.sales += int(count)
sku.save()
# 累加计算订单商品的总数量和总价格
amount = sku.price * int(count)
total_count += int(count)
total_price += amount
# 更新订单信息表中的商品的总数量和总价格
order.total_count = total_count
order.total_price = total_price
order.save()
# 从redis清除用户购物车中对应的记录(注意对列表拆包*sku_ids,如将[1,3]拆为 1,3 传递 )
conn.hdel(cart_key, *sku_ids)
# 返回应答
return JsonResponse({'res': 5, 'message': '创建成功'})
3.订单生成
在上面创建订单的视图中,还可能存在库存不足,特别是当两个用户都将商品加入购物车,先后提交了订单,但后提交时库存不足的现象。这时,不应该再为后者创建订单相关的数据表。这是就可以使用MySQL的事务来处理。
3.1. MySQL事务
mysql事务: 一组sql操作,要么都成功,要么都失败。即一组mysql语句,要么执行,要么全部不执行。创建订单的一系列操作,要么都成功,要么都失败。MySQL 事务 https://www.mysqlzh.com/
事务的特点
- 原子性:一组事务,要么成功;要么撤回。
- 稳定性 :有非法数据(外键约束之类),事务撤回。
- 隔离性:事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。
- 可靠性:软、硬件崩溃后,InnoDB数据表驱动会利用日志文件重构修改。可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit 选项 决定什么时候吧事务保存到日志里。
事务控制语句
- BEGIN 或 START TRANSACTION;显式地开启一个事务;
- COMMIT:与COMMIT WORK 等价。COMMIT会提交事务,并使已对数据库进行的所有修改称为永久性的;
- ROLLBACK:与ROLLBACK WORK 等价。回滚会结束用户的务,并撤销正在进行的所有未提交的修改;
- SAVEPOINT identifier(SAVEPOINT 保存点名);SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT;
- RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
- ROLLBACK TO identifier;把事务回滚到标记点;
mysql事务隔离级别
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。