Django电商网站项目(6)--订单模块
设计的订单相关的表如下所示:
由于每一个订单中的商品种类与数量都不定,因此单独将订单商品提出为一个表,为一对多的关系。
订单的提交
从购物车页面提交是通过form形式提交的,在checkbox元素中定义参数value并设为对应的商品id,则传递到后端的为一个由选中商品id组成的列表,在后端中的业务流程为:
①获取参数并校验,表单中的checkbox只有被选中时其value才会被提交,若不选中则不提交,若有多个name相同,则使用POST.getlist获取到一个由多个value(checkbox的value)组成的列表;
②从redis中获取该用户购物车的信息,从mysql中获取商品id对应的信息,计算数量与总价格,并将其动态添加到查询到的sku模型类实例中;
③处理运费与实付款;
④组织参数,渲染订单创建的页面。
订单的提交并不复杂,其中重点在于如何将选中的商品id传入后端,以及传入订单创建页面的参数选择。
订单的创建
订单创建的前端页面
共分为三部分:
①收货地址的选择,此处将默认收货地址默认选中,可以在该用户已有的收货地址中选择,也可以跳转到收货地址的编辑页面;
②支付方式的选择,因为此处只做了支付宝的接口因此其他支付方式暂时无法支付,但是可以提交;
③将要提交的商品按条目显示(在订单提交后端中传入),并显示其数量、单价、小计、单位等信息,显示总价、运费、总数量信息;
注:①在订单创建页面不使用form进行post提交,使用ajax post提交,订单创建成功时跳转到订单页面,创建失败时不跳转,显示后端传递的具体信息;
②由于订单中的信息是从购物车中查询到的,这个信息可能与真实情况不符(如商品下架,库存卖完等),因此在订单创建时必须要进行多次校验之后才可以确定订单是否可以创建成功。
订单创建的参数传递
从购物车提交至订单创建页面再到订单创建后端,其传递选中的商品都是通过传递sku_id然后通过查询redis中的用户购物车信息来进行的(并不直接传递具体的商品信息),但由于订单创建功能不与购物车页面交互,因此将被选中商品的sku_ids重构为一个字符串,直接从购物车传递到订单创建页面,并在订单创建页面中动态添加一个属性为sku_ids(此方法常用于在页面中获取变量)并使用ajax post请求发回后端,在订单创建流程中使用。
订单创建的注意事项
(1)关于订单提交失败:由于可能因为各种原因导致的订单无法完成,但订单记录会在数据库中生成,因此使用mysql事务来对订单提交(即把在两个表中的创建打包为一组事务,不允许出现空订单的情况);
(2)关于并发订单的处理:
①悲观锁:在某一条ORM查询语句上加锁(objects.select_for_update()),则多个用户同时进行订单提交时,哪个线程先执行到该语句则获取锁,待事务结束后才会释放,其他线程在执行至此时阻塞等待获取锁,保证了同一时刻只有一个事务在运行;
②乐观锁:查询时不加锁,在变更时对比原数据与当前重新查询的数据,若不一致则失败(即乐观的认为当前没有其他线程在同时进行此过程),一般采用3次循环重复此过程,但由于数据库事务的隔离性,查询到的原数据可能不会更新,因此需要修正数据库事务隔离的级别为read committed(在Django2.0版本中已经自动将所有数据库的事务隔离级别修改为read-committed,因此无需专门修改mysql数据库隔离级别),此时乐观锁可执行;
③使用方式:在冲突较少时使用乐观锁(省去加锁、释放锁的开销,提高性能),在冲突较多时使用悲观锁(省去大量无用的循环),且若乐观锁重复操作的代价比较大也选用悲观锁。
注:一般将整个事务的过程都放置于try中。
订单创建的后端流程
①校验参数;
②添加事务,在事务中进行乐观/悲观锁设置,创建订单模型类实例,通过传入的sku_ids在redis购物车中进行查询,查询到sku商品实例后操作库存值并添加订单商品实例;
③重复②中对sku_ids的操作,对所有选中的商品都进行此操作,其中在操作失效、校验失败时需要进行回滚,操作成功后更新订单模型类实例的内容并提交;
④清空用户购物车中已提交的记录,提交事务,并返回应答,当创建成功时跳转到个人订单页面,创建失败时提示错误信息。
乐观锁代码如下:
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 = int(request.POST.get('pay_method'))sku_ids = request.POST.get('sku_ids')# 校验参数if not all([addr_id, pay_method, sku_ids]):return JsonResponse({'res':1, 'errmsg':'数据不完整'})# 校验支付方式if pay_method not in OrderInfo.PAY_METHOD.keys():print(pay_method, type(pay_method))return JsonResponse({'res':2, 'errmsg':'非法的支付方式'})# 校验地址try:addr = Address.objects.get(id=addr_id)except Address.DoesNotExist:return JsonResponse({'res':3, 'errmsg':'地址不存在'})# 创建订单核心业务# 创建订单信息缺少的内容# 订单ID,使用年月日时分秒+用户ID创建订单编号order_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:# 向订单信息表中添加记录order = OrderInfo.objects.create(order_id=order_id,user=user,addr=addr,pay_method=pay_method,transit_price=transit_price,total_price=total_price,total_count=total_count)if order.pay_method == 1:order.order_status = 2# 获取订单商品表的参数conn = get_redis_connection('default')cart_key = 'cart_{}'.format(user.id)sku_ids = sku_ids.split(',')for sku_id in sku_ids:for i in range(3):try:sku = GoodsSKU.objects.get(id=sku_id)except GoodsSKU.DoesNotExist:# 回滚到保存点,此处的回滚是为了撤销已创建的表transaction.savepoint_rollback(save_id)return JsonResponse({'res':4, 'errmsg':'商品不存在'})# 获取商品的数量count = conn.hget(cart_key, sku_id)# 校验库存值if int(count)>sku.stock:# 回滚到保存点transaction.savepoint_rollback(save_id)return JsonResponse({'res':5, 'errmsg':'商品库存不足'})# 保存原库存与新库存origin_stock = sku.stocknew_stock = origin_stock - int(count)new_sales = sku.sales + int(count)print('user:{} times:{} stock:{}'.format(user.id, i, sku.stock))# 返回受影响的行数,0/1res = GoodsSKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)if res == 0:if i == 2:transaction.savepoint_rollback(save_id)return JsonResponse({'res':7, 'errmsg':'订单创建失败'})continue# 向订单商品表中添加记录,由于此处并没有设置保存点,因此将判断放在添加记录的前面,防止重复添加OrderGoods.objects.create(order=order,sku=sku,count=count,price=sku.price)# 更新相关商品的销量和库存sku.stock -= int(count)sku.sales += int(count)sku.save()# 计算订单商品的总数量和总价格amount = sku.price*int(count)total_count += int(count)total_price += amountbreak# 更新订单详情表中的总数量和总价格order.total_count = total_countorder.total_price = total_priceorder.save()# 清除用户购物车中的记录conn.hdel(cart_key, *sku_ids)except Exception:transaction.savepoint_rollback(save_id)return JsonResponse({'res':7, 'errmsg':'订单创建失败'})# 提交事务,返回应答transaction.savepoint_commit(save_id)return JsonResponse({'res':6, 'message':'订单创建成功'})
订单的显示
订单显示在个人中心中,根据用户从订单模型类中取出所有的订单实例,再根据每个订单实例从订单商品类中取出对应的商品(此处不从sku商品表中取,因为订单提交时的价格与当前价格可能不同),计算小计并动态添加属性,然后进行分页,对分页对象进行处理。
注:①操作与商品列表分页类似,对于订单的排序此处略过,默认按照创建时间进行排序;
②在前端中,根据用户订单的支付状态和支付方式确定提供给用户的按钮文字,并根据支付状态来判断点击时进行的逻辑(去支付/去评论)。
订单的支付
此处调用支付宝的测试接口进行支付,关于支付宝接口的调用详情可参支付宝沙箱环境开发文档,其交互流程如图所示:
支付需要前端使用ajax post提交,需要传递的参数为订单id,使用订单id查询到订单实例,调用支付宝接口(此处将订单id,金额等信息传入)生成支付链接,在前端中打开一个新的窗口用于用户支付。
订单支付结果的查询
如上图所示,支付宝在支付结果产生后会向网站返回支付结果,但可能由于网络原因并不准确,因此主动去调用支付宝接口查询支付状态,在引导用户去支付页面之后就发起ajax post请求用于获取支付结果,循环调用支付宝接口进行查询(此接口的返回值中包括等待用户付款),若支付成功则更改订单支付状态并返回,若支付失败则返回错误信息。
注:订单的支付是由用户与支付宝交互完成的,网站服务器不参与,网站服务器只负责提供支付链接与查询支付结果,通过支付宝的返回状态来对当前用户订单状态进行更改。
商品评论
①评论页面显示:当订单支付成功,直接跳转到商品评论页面,商品评论页面中单个订单的多个商品都有自己的评论框,使用form表单的形式进行提交。
注:由于有多个商品,而显示的时候是循环显示,因此使用forloop.counter给商品和评论框起名,这样可以对它们进行绑定,避免混淆。
②评论内容提交:如上所述,在后端中获取提交的评论内容并将其添加到订单商品类中,更改订单状态,订单完成,重定向到订单页面。
注:由于一个订单中的商品可能有的评价有的不评价,会造成订单状态的不确定,因此可以设置若不评价则给出默认值或评论框不允许为空,即只要点击进入评论页面,无论如何处理只要逻辑完成则更改订单状态为已完成。
订单评论代码如下:
class OrderCommentView(LoginRequiredMixin, View):def get(self, request, order_id):user = request.user# 校验参数if not order_id:return redirect(reverse('user:order'))try:order = OrderInfo.objects.get(order_id=order_id, user=user)except OrderInfo.DoesNotExist:return redirect(reverse('user:order'))# 获取订单商品信息order_skus = OrderGoods.objects.filter(order_id=order_id)# 遍历order_skus计算商品的小计,并动态增加小计for order_sku in order_skus:amount = order_sku.count*order_sku.priceorder_sku.amount = amountorder.order_skus = order_skus# 将支付状态从对应字典中取出,并赋予属性order.status_name = OrderInfo.ORDER_STATUS[order.order_status]order.pay_method_name = OrderInfo.PAY_METHOD[order.pay_method]return render(request, 'order_comment.html', {'order':order})def post(self, request, order_id):'''提交评论内容'''user = request.user# 校验参数if not order_id:return redirect(reverse('user:order'))try:order = OrderInfo.objects.get(order_id=order_id, user=user)except OrderInfo.DoesNotExist:return redirect(reverse('user:order'))total_count = int(request.POST.get('total_count'))for i in range(1, total_count+1):# 获取表单提交的评论内容sku_id = request.POST.get('sku_{}'.format(i))comment = request.POST.get('comment_{}'.format(i))try:order_good = OrderGoods.objects.get(order=order, sku_id=sku_id)except OrderGoods.DoesNotExist:continueorder_good.comment = commentorder_good.save()order.order_status = 5order.save()return redirect(reverse('user:order', kwargs={'page':1}))
商品评论的显示
在商品详情页面的讨论中,评论信息是被隐藏的,但在传入参数时已经传入了该商品的具体评论信息,因此只需在前端中添加点击逻辑即可将商品详情/商品评论进行显示,注意需要同时处理相应按钮的激活状态,如下:
$('#tag_detail').click(function(){$('#tag_comment').removeClass('active')$(this).addClass('active')$('#tab_detail').show()$('#tab_comment').hide()
})$('#tag_comment').click(function(){$('#tag_detail').removeClass('active')$(this).addClass('active')$('#tab_comment').show()$('#tab_detail').hide()
})
以上即订单模块的工作流程及注意事项,订单模块要注意数据库的查询与并发情况的处理,且由于其与第三方(支付宝等)交互,因此也可能需要一些异步处理的方法,为了避免并发情况下的冲突,数据库的事务也是比较重要的处理手段。
完整代码见https://github.com/Icemelon99/test_project。
Django电商网站项目(6)--订单模块相关推荐
- Django电商网站项目(7)-部署与总结
将前述4个模块完成后项目的全部功能就完成了,在本地已经可以实现网站的基本功能(从用户注册到订单评论),但开启服务器时使用的是Django自带的测试用服务器,因此仍需要将其部署到真正的可用于工程的服务器 ...
- 订单支付和评论——基于Django框架的天天生鲜电商网站项目系列博客(十五)
系列文章目录 需求分析--基于Django框架的天天生鲜电商网站项目系列博客(一) 网站框架搭建--基于Django框架的天天生鲜电商网站项目系列博客(二) 用户注册模块--基于Django框架的天天 ...
- 网站框架搭建——基于Django框架的天天生鲜电商网站项目系列博客(二)
系列文章目录 需求分析--基于Django框架的天天生鲜电商网站项目系列博客(一) 网站框架搭建--基于Django框架的天天生鲜电商网站项目系列博客(二) 用户注册模块--基于Django框架的天天 ...
- estore电商网站项目
estore电商网站项目 项目背景: 电子商务的发展为网络购物的发展开拓了更广阔的市场.所以,我实现了这个项目,诣在模拟网络购物,从登陆,到页面浏览再到购物车结账的全过程. 使用技术和代码流程: js ...
- 电商网站中用户评论模块剖析
用户评论是电商网站交互设计中的重要模块之一,用户体验的好坏将直接影响到产品的销售数量,乃至整站的访问量.网上购物,我们只能看到一张张商品图片和描述,对于产品的优劣无法准确判断,因此,已购买者的评论对于 ...
- 电商系统搭建(商品订单模块)
借助直播的东风,电商系统正在飞速发展,那么如何从0开始搭建电商系统. 这篇文章就介绍一下怎么简单的搭建一个电商系统,首先从电商系统的核心(订单)来开讲. 数据结构设计 商品表,商品细节表,订单表,订单 ...
- 电商网站之更新订单状态
电商上面订单状态的修改是非常普通的业务 随着商品的购买流程,订单的状态有: 0 = 未支付 1 = 已关闭 (超时未支付) 2 = 已取消 3 = 已支付 4 = 已签收 5 = 已拒收 6 = 退款 ...
- 10.2 黑马Vue电商后台管理系统 完善订单模块--搜索订单(修改后端)
效果如下: 搜索时列表动态变化,在我专栏下另一篇文章写了,这篇文章不再讲述,这篇文章只讲述如何从后端(打开vue_api_server这个文件夹,而不是vue_shop)修改代码来实现这个功能 我的思 ...
- 电商网站项目开发开篇
许久没有写博客,这一次一定要坚持写下三月份的开发任务,算是给自己做一个交代. 以下是要做的练手项目思维导图.
最新文章
- 关于无法创建aps.web项目的解决办法
- AI 名校课程书籍 需要学习
- php 获取浏览器时区,获取用户时区
- lpc2000 filash utility 程序烧写工具_单片机烧录程序的次数
- Cacti添加IO模板并监控磁盘IO
- Window10上使用Git与Github远程仓库互连
- Centos6.5之yum安装LAMP+wordpress
- Julia:1.0与0.6 的几点不同
- 【电子书资源】数值方法最优化理论算法凸优化 ---书籍调研(附网盘下载地址)...
- cloud2声卡_带你解惑HyperX Cloud2(飓风)和Alpha(阿尔法)的终极选择
- SpringBoot 错误:Field userService in com.lyh.Controller.UserController required a bean of...
- 如何在Outlook上正确设置雅虎邮箱
- gmail通讯录同步
- C#图片加载与内存释放
- windows关机命令
- 一个大二博主的一年来写博的年终总结与未来展望——致2019努力的自己,也致2020更好的自己
- Selenium3自动化测试【21】find_element定位元素
- hautoj 1262 魔法宝石【最短路】
- 浏览器控制台接口测试以及造测试数据
- 山洪灾害监测预警系统解决方案