2018-8-11商城订单相关的操作
https://www.cnblogs.com/cerofang/p/9459441.html
订单相关的操作
- 订单数据库表的设计
- 订单的字段分析
- 首先将订单分为两个表格
- 1,订单的基本信息表;
- 2,订单的商品信息,两者之间的关系是一对多的关系
- 订单的基本信息表中主要存储这笔订单的相关信息(订单的id,下单的用户,用户的默认地址,邮费的状态,订单的支付方式,订单的状态)
- 订单商品中保存(订单的id,用户商品的id,商品的数量,商品的价格)
- 在前端中展示还需要的字段有此次订单的总金额,以及该订单中商品的数量,这两个字段虽然经过表格之间的关联可以查询出来,在这里可以将这两个字段一起定义在订单的基本信息的表格中,避免在后续查询订单的过程中对数据库的操作;
具体字段的定义:
from django.db import models
from meiduo_mall.utils.models import BaseModel
from users.models import User, Address
from goods.models import SKU
# Create your models here.
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
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
获取购物车商品逻辑
- 用户必须在登录的状态下才可以进入到购物车商品结算的页面
- 查询到当前订单的用户;
- 在redis数据库中将当前用户的购物车中所有商品查询出来
- 过滤筛选出用户选中的商品信息的id
- 查询出当前订单的运费的
- 将相关信息(例如运费和查询出来的商品的skus对象)传递给序列化器,序列化器将数据从数据库中序列化后返回给前端
- 最终后端返回给前端的数据格式如图所示
{
"freight":"10.00",
"skus":[
{
"id":10,
"name":"华为 HUAWEI P10 Plus 6GB+128GB 钻雕金 移动联通电信4G手机 双卡双待",
"default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRchWAMc8rAARfIK95am88158618",
"price":"3788.00",
"count":1
},
{
"id":16,
"name":"华为 HUAWEI P10 Plus 6GB+128GB 曜石黑 移动联通电信4G手机 双卡双待",
"default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdPeAXNDMAAYJrpessGQ9777651",
"price":"3788.00",
"count":1
}
]
}
保存订单
- id的字段定义,默认情况下采用sql数据库中的自增的id作为主键,但是在考虑到id的可读性和扩展性将主键设置为具有特定格式的CharField字段,自己定义的id的格式的;
- 保存订单的逻辑实现
- 获取当前下单的用户
- 获取用户的基本信息(用户的默认地址,用户选择的支付方式)
- 创建事物,在以下的任何一个操作不成功的情况下就会返回到当前这个保存点
- 组织订单的id
- 创建一个订单基本信息的对象,进行保存
- redis中取出所有的商品,过滤出用户选中的商品
- 给订单的中设计的冗余的字段赋初值
- 从数据库中查询商品信息,并判断用户购买的商品库存状态和销量的状态
- 更新数据库中库存和销量的相关信息。在这个地方用乐观锁的方式来判断在事物保存的过程中是否有其他用户来操作过商品的,如果有则重新返回到事物保存点,没有则继续
- 在这里来查询用户的商品信息表中将订单基本信息的表格中的相关冗余字段计算出来一起保存进入到数据库中
class SaveOrderSerializer(serializers.ModelSerializer):
""" 用户支付订单的创建序列化器"""
class Meta:
model = OrderInfo
fields = ("order_id", "address", "pay_method")
read_only_fields = ("order_id",)
extra_kwargs = {
"address": {
"write_only": True,
"required": True
},
"pay_method": {
"write_only": True,
"required": True
}
}
def create(self, validated_data):
""" 保存订单序列化器"""
# 获取当前下单用户
user = self.context['request'].user
address = validated_data.get("address")
pay_method = validated_data.get("pay_method")
# 组织订单信息20170903153611+user.id
order_id = timezone.now().strftime("%Y%m%d%H%M%S") + ("%09d" % user.id)
# 开启事务
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),
"pay_method": pay_method,
# 如果用户选择现金支付,订单状态为待发货;反之为待支付
"status": OrderInfo.ORDER_STATUS_ENUM['UNSEND'] if validated_data['pay_method'] ==
OrderInfo.PAY_METHODS_ENUM['CASH']
else OrderInfo.ORDER_STATUS_ENUM['UNPAID']
})
redis_conn = get_redis_connection("cart")
# 从redis中获取购物车结算商品数据
selected_sku_id_list = []
redis_cart = redis_conn.get("cart2_%s" % user.id) #
redis_dict = pickle.loads(base64.b64decode(redis_cart)) # 取出的是redis中的数据
# 过滤购物车中被选中的商品的id
for sku_id, value in redis_dict.items():
if value[1]:
selected_sku_id_list.append(sku_id)
# 冗余数据先设置默认值
order.total_amount = Decimal(0)
order.total_count = 0
# 查询商品信息
# skus = SKU.objects.filter(id__in=cart_dict.keys()) # 得到选中的商品objs
# 遍历结算商品:
for sku_id in selected_sku_id_list:
while True:
sku = SKU.objects.get(id=sku_id)
# 要购买的商品的数量
count = redis_dict[sku.id][0]
# 判断库存量和销售量
origin_stock = sku.stock
origin_sales = sku.sales
# 判断商品库存是否充足
if count > origin_stock:
transaction.savepoint_rollback(save_id)
raise serializers.ValidationError({"库存不足"})
# 更新库存和销量信息
new_stock = origin_stock - count
new_sales = origin_sales + count
# sku.stock = new_stock
# sku.sales = new_sales
# 返回受影响的行数
ret = SKU.objects.filter(id=sku.id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
if ret == 0:
continue
# 计算order——info中的两个冗余字段结果并赋值
order.total_count += count
order.total_amount += (sku.price * count)
OrderGoods.objects.create(**{
"order": order,
"sku": sku,
"count": count,
"price": sku.price
})
break
order.save()
except serializers.ValidationError:
raise
except Exception:
transaction.savepoint_rollback(save_id)
raise
# 提交事务
transaction.savepoint_commit(save_id)
# 清除购物车中已经结算的商品
redis_conn.delete('cart2_%s' % user.id)
return order
- 数据库的事物
- Django中对于数据库的操作,默认在每一次的数据库操作之后都会自动提交
- 在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务,atomic提供两种用法
- 使用方法一:
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# 这些代码会在一个事务中执行
...
- 使用方法二
from django.db import transaction
# 创建保存点
save_id = transaction.savepoint()
# 回滚到保存点
transaction.savepoint_rollback(save_id)
# 提交从保存点到当前状态的所有数据库事务操作
transaction.savepoint_commit(save_id)