网上商城之订单
订单
- 结算订单
- 1. 结算订单逻辑分析
- 2. 结算订单接口设计和定义
- 3. 结算订单后端逻辑实现
- 4.结算订单页面渲染(直接拷贝)
- 笔记
- 提交订单
- 创建订单数据库表
- 笔记
- 保存订单基本信息和订单商品信息
- 1. 提交订单接口设计和定义
- 笔记
- 2. 保存订单基本信息
- 3. 保存订单商品信息
- 笔记
- 使用事务保存订单数据
- 1. Django中事务的使用
- 2. 使用事务保存订单数据
- 笔记
- 使用乐观锁并发下单
- 1. 并发下单问题演示和解决方案
- 2. 使用乐观锁并发下单
- 笔记
- 3. MySQL事务隔离级别
- 展示提交订单成功页面
- 我的订单(这个自己做)
- 笔记
结算订单
用来确认订单信息有没有问题,只会结算勾选了的商品
1. 结算订单逻辑分析
结算订单是从Redis购物车中查询出被勾选的商品信息进行结算并展示。
2. 结算订单接口设计和定义
- 请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /orders/settlement/ |
- 请求参数:
无
- 响应结果:HTML
place_order.html
- 后端接口定义
class OrderSettlementView(LoginRequiredMixin, View):
"""结算订单"""
def get(self, request):
"""提供订单结算页面"""
return render(request, 'place_order.html')
3. 结算订单后端逻辑实现
class OrderSettlementView(LoginRequiredMixin, View):
"""结算订单"""
def get(self, request):
"""提供订单结算页面"""
# 获取登录用户
user = request.user
# 查询地址信息
try:
addresses = Address.objects.filter(user=request.user, is_deleted=False)
except Address.DoesNotExist:
# 如果地址为空,渲染模板时会判断,并跳转到地址编辑页面
addresses = None
# 从Redis购物车中查询出被勾选的商品信息
redis_conn = get_redis_connection('carts')
redis_cart = redis_conn.hgetall('carts_%s' % user.id)
cart_selected = redis_conn.smembers('selected_%s' % user.id)
cart = {}
for sku_id in cart_selected:
cart[int(sku_id)] = int(redis_cart[sku_id])
# 准备初始值
total_count = 0
total_amount = Decimal(0.00)
# 查询商品信息
skus = SKU.objects.filter(id__in=cart.keys())
for sku in skus:
sku.count = cart[sku.id]
sku.amount = sku.count * sku.price
# 计算总数量和总金额
total_count += sku.count
total_amount += sku.count * sku.price
# 补充运费
freight = Decimal('10.00')
# 渲染界面
context = {
'addresses': addresses,
'skus': skus,
'total_count': total_count,
'total_amount': total_amount,
'freight': freight,
'payment_amount':total_amount + freight
}
return render(request, 'place_order.html', context)
4.结算订单页面渲染(直接拷贝)
<h3 class="common_title">确认收货地址</h3>
<div class="common_list_con clearfix" id="get_site">
<dl>
{% if addresses %}
<dt>寄送到:</dt>
{% for address in addresses %}
<dd @click="nowsite={{ address.id }}"><input type="radio" v-model="nowsite" value="{{ address.id }}">{{ address.province }} {{ address.city }} {{ address.district }} ({{ address.receiver }} 收) {{ address.mobile }}</dd>
{% endfor %}
{% endif %}
</dl>
<a href="{{ url('users: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_method" value="1" v-model="pay_method">
<label class="cash">货到付款</label>
<input type="radio" name="pay_method" value="2" v-model="pay_method">
<label class="zhifubao"></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">{{loop.index}}</li>
<li class="col02"><img src="{{ sku.default_image.url }}"></li>
<li class="col03">{{ sku.name }}</li>
<li class="col04">台</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_amount }}元</b></div>
<div class="transit">运费:<b>{{ freight }}元</b></div>
<div class="total_pay">实付款:<b>{{ payment_amount }}元</b></div>
</div>
</div>
<div class="order_submit clearfix">
<a @click="on_order_submit" id="order_btn">提交订单</a>
</div>
笔记
- 创建子应用时,子应用创建于当前目录下
注册
因为响应的是html数据
创建完子应用写url,这个需要注册
要检查模板
-
字典,列表最后一项数据都可以加逗号
-
作为键的字段在查询的时候可以赋值一个对象 -
查询指定用户的整个hash表 -
查询指定用户的全部被勾选的信息 -
集合数据可以被遍历 -
给出一组sku_id,可以获得一组sku
有address 就进行渲染
-
operand 操作数
Decimal 是一个更精确的浮点类型,会将小数拆成整数
float 也是浮点类型,是用机器存储的
Decimal 用于高精度小数的存储
-
发现请求页面静态资源没有更新首先考虑缓存问题
-
当捕获到异常是不会打印栈追踪路径的,最好手动加上print(e),以便于发现异常
直接获取默认地址
- 写代码的时候没有特别熟悉的需求,先从容易想到的步骤出发,逐步补全,最后针对具体错误进行改错
对于这种报错可以加引号也可以自己导入
import 后好像就可以跟多个东西,导入当前域中的工具
提交订单
就是收集结算页面的数据存到数据库中
提示:
- 确认了要结算的商品信息后,就可以去提交订单了。
创建订单数据库表
生成的订单数据要做持久化处理,而且需要在《我的订单》页面展示出来。
-
订单数据库表分析
注意:-
订单号不再采用数据库自增主键,而是由后端生成。
一个订单中可以有多个商品信息,订单基本信息和订单 -
商品信息是一对多的关系。
-
由时间和user_id 拼起来的
不使用id作为订单基本信息
orm对于有自定义主键的使用自定义的,没有的话就自动生成主键
- 订单模型类迁移建表
class OrderInfo(BaseModel):
"""订单信息"""
PAY_METHODS_ENUM = {
"CASH": 1,
"ALIPAY": 2
}
PAY_METHOD_CHOICES = (
(1, "货到付款"),
(2, "支付宝"),
)
ORDER_STATUS_ENUM = {
"UNPAID": 1,
"UNSEND": 2,
"UNRECEIVED": 3,
"UNCOMMENT": 4,
"FINISHED": 5
}
ORDER_STATUS_CHOICES = (
(1, "待支付"),
(2, "待发货"),
(3, "待收货"),
(4, "待评价"),
(5, "已完成"),
(6, "已取消"),
)
order_id = models.CharField(max_length=64, primary_key=True, verbose_name="订单号")
user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="下单用户")
address = models.ForeignKey(Address, on_delete=models.PROTECT, verbose_name="收货地址")
total_count = models.IntegerField(default=1, verbose_name="商品总数")
total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="商品总金额")
freight = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="运费")
pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=1, verbose_name="支付方式")
status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name="订单状态")
class Meta:
db_table = "tb_order_info"
verbose_name = '订单基本信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.order_id
class OrderGoods(BaseModel):
"""订单商品"""
SCORE_CHOICES = (
(0, '0分'),
(1, '20分'),
(2, '40分'),
(3, '60分'),
(4, '80分'),
(5, '100分'),
)
order = models.ForeignKey(OrderInfo, related_name='skus', on_delete=models.CASCADE, verbose_name="订单")
sku = models.ForeignKey(SKU, on_delete=models.PROTECT, verbose_name="订单商品")
count = models.IntegerField(default=1, verbose_name="数量")
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="单价")
comment = models.TextField(default="", verbose_name="评价信息")
score = models.SmallIntegerField(choices=SCORE_CHOICES, default=5, verbose_name='满意度评分')
is_anonymous = models.BooleanField(default=False, verbose_name='是否匿名评价')
is_commented = models.BooleanField(default=False, verbose_name='是否评价了')
class Meta:
db_table = "tb_order_goods"
verbose_name = '订单商品'
verbose_name_plural = verbose_name
def __str__(self):
return self.sku.name
笔记
choices用于限制字段值,值的格式是元祖套元祖
ENUM 是用来防止代码中出现整数的
进行建表
- 在地址栏里发出的请求时get方式
保存订单基本信息和订单商品信息
1. 提交订单接口设计和定义
- 请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /orders/commit/ |
- 请求参数:JSON
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
address_id | int | 是 | 用户地址编号 |
pay_method | int | 是 | 用户支付方式 |
- 响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
order_id | 订单编号 |
- 后端接口定义
class OrderCommitView(LoginRequiredJSONMixin, View):
"""订单提交"""
def post(self, request):
"""保存订单信息和订单商品信息"""
pass
提示:
- 订单数据分为订单基本信息和订单商品信息,二者为一对多的关系。
- 保存到订单的数据是从Redis购物车中的已勾选的商品信息。
笔记
- 保存失败是以弹框的方式进行提示
- 说了很多次 响应json不需要进行反向解析 但是不是很理解
2. 保存订单基本信息
class OrderCommitView(LoginRequiredJSONMixin, View):
"""提交订单"""
def post(self, request):
"""保存订单信息和订单商品信息"""
# 获取当前要保存的订单数据
json_dict = json.loads(request.body.decode())
address_id = json_dict.get('address_id')
pay_method = json_dict.get('pay_method')
# 校验参数
if not all([address_id, pay_method]):
return http.HttpResponseForbidden('缺少必传参数')
# 判断address_id是否合法
try:
address = Address.objects.get(id=address_id)
except Exception:
return http.HttpResponseForbidden('参数address_id错误')
# 判断pay_method是否合法
if pay_method not in [OrderInfo.PAY_METHODS_ENUM['CASH'], OrderInfo.PAY_METHODS_ENUM['ALIPAY']]:
return http.HttpResponseForbidden('参数pay_method错误')
# 获取登录用户
user = request.user
# 生成订单编号:年月日时分秒+用户编号
order_id = timezone.localtime().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id)
# 保存订单基本信息 OrderInfo(一)
order = OrderInfo.objects.create(
order_id=order_id,
user=user,
address=address,
total_count=0,
total_amount=Decimal('0'),
freight=Decimal('10.00'),
pay_method=pay_method,
status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else
OrderInfo.ORDER_STATUS_ENUM['UNSEND']
)
pass
3. 保存订单商品信息
class OrderCommitView(LoginRequiredJSONMixin, View):
"""提交订单"""
def post(self, request):
"""保存订单信息和订单商品信息"""
# 获取当前保存订单时需要的信息
......
# 保存订单基本信息 OrderInfo(一)
......
# 从redis读取购物车中被勾选的商品信息
redis_conn = get_redis_connection('carts')
redis_cart = redis_conn.hgetall('carts_%s' % user.id)
selected = redis_conn.smembers('selected_%s' % user.id)
carts = {}
for sku_id in selected:
carts[int(sku_id)] = int(redis_cart[sku_id])
sku_ids = carts.keys()
# 遍历购物车中被勾选的商品信息
for sku_id in sku_ids:
# 查询SKU信息
sku = SKU.objects.get(id=sku_id)
# 判断SKU库存
sku_count = carts[sku.id]
if sku_count > sku.stock:
return http.JsonResponse({'code': RETCODE.STOCKERR, 'errmsg': '库存不足'})
# SKU减少库存,增加销量
sku.stock -= sku_count
sku.sales += sku_count
sku.save()
# 修改SPU销量
sku.goods.sales += sku_count
sku.goods.save()
# 保存订单商品信息 OrderGoods(多)
OrderGoods.objects.create(
order=order,
sku=sku,
count=sku_count,
price=sku.price,
)
# 保存商品订单中总价和总数量
order.total_count += sku_count
order.total_amount += (sku_count * sku.price)
# 添加邮费和保存订单信息
order.total_amount += order.freight
order.save()
# 清除购物车中已结算的商品
pl = redis_conn.pipeline()
pl.hdel('carts_%s' % user.id, *selected)
pl.srem('selected_%s' % user.id, *selected)
pl.execute()
# 响应提交订单结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '下单成功', 'order_id': order.order_id})
笔记
-
接收参数,校验参数的代码直接进行拷贝
新的校验参数的方式,好像可以用列表生成式 -
localtime 先获取时间, strftime 格式化时间 ,拼接9位用户id -
赋值语句应该先把表达式计算出来吧
-
给某一个变量赋值的时候加判断 -
传递参数可以 以 k=v, 或者 v, 的形式写多行
这些代码直接进行拷贝
新增数据时可以只穿部分字段,未给出字段用默认值
使用事务保存订单数据
重要提示:
- 在保存订单数据时,涉及到多张表(OrderInfo、OrderGoods、SKU、SPU)的数据修改,对这些数据的修改应该是一个整体事务,即要么一起成功,要么一起失败。(这一个任务流程走到一半终止不合理)
- **Django中对于数据库的事务,默认每执行一句数据库操作,便会自动提交。**所以我们需要在保存订单中自己控制数据库事务的执行流程。
1. Django中事务的使用
- Django中事务的使用方案
-
在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务。
-
atomic提供两种方案实现事务:
- 装饰器用法:
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# 这个view中的mysql会在一个事务中执行
# 但是这样不够灵活,因为代码可能不是都要在一个事务中执行
# 最好是可以指定哪些sql需要在一个事务中执行
......
- with语句用法:
from django.db import transaction
def viewfunc(request):
# 这部分代码不在事务中,会被Django自动提交
......
with transaction.atomic():
# 这部分sql会在事务中执行
......
- 事务方案的选择:
-
**装饰器用法**
:整个视图中所有MySQL数据库的操作都看做一个事务,范围太大,不够灵活。而且无法直接作用于类视图。 -
with语句用法:可以灵活的有选择性的把某些MySQL数据库的操作看做一个事务。而且不用关心视图的类型。
-
综合考虑后我们选择 with语句实现事务
-
- 事务中的保存点:
-
在Django中,还提供了保存点的支持,可以在事务中创建保存点来记录数据的特定状态,数据库出现错误时,可以回滚到数据保存点的状态。
from django.db import transaction
# 创建保存点,保存点相当于打了一个标记,用来提交和还原的
# 标记了数据库的状态
save_id = transaction.savepoint()
# 回滚到保存点
transaction.savepoint_rollback(save_id)
# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)
2. 使用事务保存订单数据
class OrderCommitView(LoginRequiredJSONMixin, View):
"""订单提交"""
def post(self, request):
"""保存订单信息和订单商品信息"""
# 获取当前保存订单时需要的信息
......
# 显式的开启一个事务
with transaction.atomic():
# 创建事务保存点,在操作数据库之前指定保存点
save_id = transaction.savepoint()
# 暴力回滚
try:
# 保存订单基本信息 OrderInfo(一)
order = OrderInfo.objects.create(
order_id=order_id,
user=user,
address=address,
total_count=0,
total_amount=Decimal('0'),
freight=Decimal('10.00'),
pay_method=pay_method,
status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else
OrderInfo.ORDER_STATUS_ENUM['UNSEND']
)
# 从redis读取购物车中被勾选的商品信息
redis_conn = get_redis_connection('carts')
redis_cart = redis_conn.hgetall('carts_%s' % user.id)
selected = redis_conn.smembers('selected_%s' % user.id)
carts = {}
for sku_id in selected:
carts[int(sku_id)] = int(redis_cart[sku_id])
sku_ids = carts.keys()
# 遍历购物车中被勾选的商品信息
for sku_id in sku_ids:
# 查询SKU信息
sku = SKU.objects.get(id=sku_id)
# 判断SKU库存
sku_count = carts[sku.id]
if sku_count > sku.stock:
# 出错就回滚
transaction.savepoint_rollback(save_id)
return http.JsonResponse({'code': RETCODE.STOCKERR, 'errmsg': '库存不足'})
# SKU减少库存,增加销量
sku.stock -= sku_count
sku.sales += sku_count
sku.save()
# 修改SPU销量
sku.spu.sales += sku_count
sku.spu.save()
# 保存订单商品信息 OrderGoods(多)
OrderGoods.objects.create(
order=order,
sku=sku,
count=sku_count,
price=sku.price,
)
# 保存商品订单中总价和总数量
order.total_count += sku_count
order.total_amount += (sku_count * sku.price)
# 添加邮费和保存订单信息
order.total_amount += order.freight
order.save()
except Exception as e:
logger.error(e)
transaction.savepoint_rollback(save_id)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '下单失败'})
# 提交订单成功,显式的提交一次事务,
transaction.savepoint_commit(save_id)
# 清除购物车中已结算的商品
pl = redis_conn.pipeline()
pl.hdel('carts_%s' % user.id, *selected)
pl.srem('selected_%s' % user.id, *selected)
pl.execute()
# 响应提交订单结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '下单成功', 'order_id': order.order_id})
笔记
- mysql执行sql语句的时候会默认开启一个事务,执行完之后会提交这个事务
- create()会创造insert
- 将应该放在一个事务中的sql放在with语句内,没有错就提交,有错就进行回滚
使用乐观锁并发下单
重要提示:
- 在多个用户同时发起对同一个商品的下单请求时,先查询商品库存,再修改商品库存,会出现资源竞争问题,导致库存的最终结果出现异常。
1. 并发下单问题演示和解决方案
解决办法:
-
悲观锁
- 当查询某条记录时,即让数据库为该记录加锁,锁住记录后别人无法操作,使用类似如下语法
select stock from tb_sku where id=1 for update;
SKU.objects.select_for_update().get(id=1)
# 可以
-
悲观锁类似于我们在多线程资源竞争时添加的互斥锁, 容易出现死锁现象,采用不多。
-
性能不好,凡是加锁性能都会打折扣,
-
乐观锁
- 性能比悲观锁好
- 乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新。类似如下操作
- 乐观锁就是在更新之前看看这个数据有没有被别人动过
update tb_sku set stock=2 where id=1 and stock=7;
SKU.objects.filter(id=1, stock=7).update(stock=2)
- 任务队列
- 将下单的逻辑放到任务队列中(如celery),将并行转为串行,所有人排队下单。比如开启只有一个进程的Celery,一个订单一个订单的处理。
- 任务队列比乐观锁性能好些,但是代码实现的难度比较大
- 秒杀一般用任务队列,下单用乐观锁就可以
2. 使用乐观锁并发下单
思考:
- 下单成功的条件是什么?
- 首先库存大于购买量,然后更新库存和销量时原始库存没变。
结论:
- 所以在用户库存满足的情况下,如果更新库存和销量时原始库存有变,那么继续给用户下单的机会。
class OrderCommitView(LoginRequiredJSONMixin, View):
"""订单提交"""
def post(self, request):
"""保存订单信息和订单商品信息"""
# 获取当前保存订单时需要的信息
......
# 显式的开启一个事务
with transaction.atomic():
# 创建事务保存点
save_id = transaction.savepoint()
# 暴力回滚
try:
# 保存订单基本信息 OrderInfo(一)
order = OrderInfo.objects.create(
order_id=order_id,
user=user,
address=address,
total_count=0,
total_amount=Decimal('0'),
freight=Decimal('10.00'),
pay_method=pay_method,
status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else
OrderInfo.ORDER_STATUS_ENUM['UNSEND']
)
# 从redis读取购物车中被勾选的商品信息
redis_conn = get_redis_connection('carts')
redis_cart = redis_conn.hgetall('carts_%s' % user.id)
selected = redis_conn.smembers('selected_%s' % user.id)
carts = {}
for sku_id in selected:
carts[int(sku_id)] = int(redis_cart[sku_id])
sku_ids = carts.keys()
# 遍历购物车中被勾选的商品信息
for sku_id in sku_ids:
while True:
# 查询SKU信息
sku = SKU.objects.get(id=sku_id)
# 读取原始库存
origin_stock = sku.stock
origin_sales = sku.sales
# 判断SKU库存
sku_count = carts[sku.id]
if sku_count > origin_stock:
# 事务回滚
transaction.savepoint_rollback(save_id)
return http.JsonResponse({'code': RETCODE.STOCKERR, 'errmsg': '库存不足'})
# 模拟延迟
# import time
# time.sleep(5)
# SKU减少库存,增加销量
# sku.stock -= sku_count
# sku.sales += sku_count
# sku.save()
# 乐观锁更新库存和销量
new_stock = origin_stock - sku_count
new_sales = origin_sales + sku_count
result = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
# 如果下单失败,但是库存足够时,继续下单,直到下单成功或者库存不足为止
if result == 0:
continue
# 修改SPU销量
sku.spu.sales += sku_count
sku.spu.save()
# 保存订单商品信息 OrderGoods(多)
OrderGoods.objects.create(
order=order,
sku=sku,
count=sku_count,
price=sku.price,
)
# 保存商品订单中总价和总数量
order.total_count += sku_count
order.total_amount += (sku_count * sku.price)
# 下单成功或者失败就跳出循环
break
# 添加邮费和保存订单信息
order.total_amount += order.freight
order.save()
except Exception as e:
logger.error(e)
# 事务回滚
transaction.savepoint_rollback(save_id)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '下单失败'})
# 保存订单数据成功,显式的提交一次事务
transaction.savepoint_commit(save_id)
# 清除购物车中已结算的商品
pl = redis_conn.pipeline()
pl.hdel('carts_%s' % user.id, *selected)
pl.srem('selected_%s' % user.id, *selected)
pl.execute()
# 响应提交订单结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '下单成功', 'order_id': order.order_id})
笔记
- 可以在写代码途中进行import
- continue 作用于最近的一层循环
- 写后端逻辑的时候,先分析页面,而后知道要查询和准备哪些数据
- python可以动态为对象添加属性,但是objective-c 不行
- 动态为其添加属性,可以方便的进行页面渲染
3. MySQL事务隔离级别
事务中有数据,数据库中也有数据,这指的是两个东西
-
事务隔离级别指的是在处理同一个数据的多个事务中,一个事务修改数据后,其他事务何时能看到修改后的结果。
-
MySQL数据库事务隔离级别主要有四种:
- Serializable:串行化,一个事务一个事务的执行。
- Repeatable read:可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响。
- Read committed:读取已提交,其他事务提交了对数据的修改后(提交到数据库中),本事务就能读取到修改后的数据值。
- Read uncommitted:读取未提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值。
- MySQL数据库默认使用可重复读( Repeatable read)。
-
使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别(默认),应该修改为读取已提交(Read committed)。
-
修改方式:
网络上有很多使用命令修改隔离级别的,使用命令修改的方式只是临时生效
修改配置就要重启
展示提交订单成功页面
支付方式:货到付款
支付方式:支付宝
- 请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /orders/success/ |
- 请求参数:
无
- 响应结果:HTML
order_success.html
- 后端接口定义和实现
class OrderSuccessView(LoginRequiredMixin, View):
"""提交订单成功"""
def get(self, request):
order_id = request.GET.get('order_id')
payment_amount = request.GET.get('payment_amount')
pay_method = request.GET.get('pay_method')
context = {
'order_id':order_id,
'payment_amount':payment_amount,
'pay_method':pay_method
}
return render(request, 'order_success.html', context)
- 渲染提交订单成功页面信息(直接拷贝,调整缩进)
<div class="common_list_con clearfix">
<div class="order_success">
<p><b>订单提交成功,订单总价<em>¥{{ payment_amount }}</em></b></p>
<p>您的订单已成功生成,选择您想要的支付方式,订单号:{{ order_id }}</p>
<p><a href="{{ url('orders:info', args=(1, )) }}">您可以在【用户中心】->【我的订单】查看该订单</a></p>
</div>
</div>
<div class="order_submit clearfix">
{% if pay_method == '1' %}
<a href="{{ url('contents:index') }}">继续购物</a>
{% else %}
<a @click="order_payment" class="payment">去支付</a>
{% endif %}
</div>
我的订单(这个自己做)
- 请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /orders/info/(?P<page_num>\d+)/ |
- 请求参数:路径参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
page_num | int | 是 | 当前页码 |
- 响应结果:HTML
user_center_order.html
- 后端接口定义和实现
class UserOrderInfoView(LoginRequiredMixin, View):
"""我的订单"""
def get(self, request, page_num):
"""提供我的订单页面"""
user = request.user
# 查询订单
orders = user.orderinfo_set.all().order_by("-create_time")
# 遍历所有订单
for order in orders:
# 绑定订单状态
order.status_name = OrderInfo.ORDER_STATUS_CHOICES[order.status-1][1]
# 绑定支付方式
order.pay_method_name = OrderInfo.PAY_METHOD_CHOICES[order.pay_method-1][1]
order.sku_list = []
# 查询订单商品
order_goods = order.skus.all()
# 遍历订单商品
for order_good in order_goods:
sku = order_good.sku
sku.count = order_good.count
sku.amount = sku.price * sku.count
order.sku_list.append(sku)
# 分页
page_num = int(page_num)
try:
paginator = Paginator(orders, constants.ORDERS_LIST_LIMIT)
page_orders = paginator.page(page_num)
total_page = paginator.num_pages
except EmptyPage:
return http.HttpResponseNotFound('订单不存在')
context = {
"page_orders": page_orders,
'total_page': total_page,
'page_num': page_num,
}
return render(request, "user_center_order.html", context)
- 渲染我的订单信息
<div class="right_content clearfix">
<h3 class="common_title2">全部订单</h3>
{% for order in page_orders %}
<ul class="order_list_th w978 clearfix">
<li class="col01">{{ order.create_time.strftime('%Y-%m-%d %H:%M:%S') }}</li>
<li class="col02">订单号:{{ order.order_id }}</li>
</ul>
<table class="order_list_table w980">
<tbody>
<tr>
<td width="55%">
{% for sku in order.sku_list %}
<ul class="order_goods_list clearfix">
<li class="col01"><img src="{{ sku.default_image.url }}"></li>
<li class="col02"><span>{{ sku.name }}</span><em>{{ sku.price }}元</em></li>
<li class="col03">{{ sku.count }}</li>
<li class="col04">{{ sku.amount }}元</li>
</ul>
{% endfor %}
</td>
<td width="15%">{{ order.total_amount }}元<br>含运费:{{ order.freight }}元</td>
<td width="15%">{{ order.pay_method_name }}</td>
<td width="15%">
<a @click="oper_btn_click('{{ order.order_id }}', {{ order.status }})" class="oper_btn">{{ order.status_name }}</a>
</td>
</tr>
</tbody>
</table>
{% endfor %}
<div class="pagenation">
<div id="pagination" class="page"></div>
</div>
</div>
笔记
不使用LoginRequiredMixin这个类时