订单结算页+下单业务

一、订单结算页

在这里插入图片描述

1.业务分析

(1) 获取用户收货地址信息
一般的收货地址都是多个,使用时选中一个,所以收货地址使用List集合封装

(2)获取购物车商品信息
购物车商品也是多个,使用List集合封装

(3)查询商品库存
查询每个商品是否有库存,显示是否有货,使用Map封装

(4)计算订单金额
使用BigDecimal计算,算出每个商品的总价(价格*件数)然后求和,最后扣除优惠价格得到结算价,还可以做积分业务

(5)页面防重复提交,保证幂等性
重复提交会重复执行下单业务,提交订单前在redis中存一个token令牌,然后在执行下单操作前去redis中去取这个token,如果能取到,代表是第一次提交页面,然后删除令牌,如果取不到证明令牌代表已经被删除了,表示页面不是第一次提交,告诉用户 “请问重复提交页面”
删令牌的操作需要保证原子性,取令牌,校验,删除是三次操作,如果其中一步错误就会导致删除失败,所以必须保证要么全部成功或全部失败,这就是原子性,所以使用redis提供的lua脚本取执行删令牌的操作

2.代码

(1)订单页面实体类设计

public class OrderConfirmVo {@Getter @Setter/** 会员收获地址列表 **/List<MemberAddressVo> memberAddressVos;@Getter @Setter/** 所有选中的购物项 **/List<OrderItemVo> items;/** 发票记录 **/@Getter @Setter/** 优惠券(会员积分) **/private Integer integration;/** 防止重复提交的令牌 **/@Getter @Setterprivate String orderToken;private Integer count;private BigDecimal total;private BigDecimal payPrice;@Getter @SetterMap<Long,Boolean> stocks;/*** 获取商品总数量*/public Integer getCount() {Integer count = 0;if (items != null && items.size() > 0) {for (OrderItemVo item : items) {count += item.getCount();}}return count;}/** 订单总额 **///BigDecimal total;//计算订单总额public BigDecimal getTotal() {BigDecimal totalNum = BigDecimal.ZERO;for (OrderItemVo item : items) {BigDecimal multiply = item.getPrice().multiply(new BigDecimal(Integer.toString(item.getCount())));totalNum = totalNum.add(multiply);}return totalNum;}/** 应付价格 **///BigDecimal payPrice;public BigDecimal getPayPrice() {return getTotal();}
}

(1)业务使用异步编排提升效率

/*** 展示结算页信息** Feign 丢失请求头的问题? 如果使用ThreadLocal的方式直接获取id会获取不到,远程调用没经过拦截器获取不到id* 异步丢失*/@Overridepublic OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {OrderConfirmVo orderConfirmVo = new OrderConfirmVo();MemberOAuthVo memberOAuthVo = OrderLoginIntercepter.threadLocal.get();//TODO :获取当前线程请求头信息(解决Feign异步调用丢失请求头问题)RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();CompletableFuture<Void> memberRunAsync = CompletableFuture.runAsync(new Runnable() {@Overridepublic void run() {//每一个线程都来共享之前的请求数据RequestContextHolder.setRequestAttributes(requestAttributes);//1.查询用户信息List<MemberAddressVo> list = memberFeignService.getMemberReceiveAddress(memberOAuthVo.getId());orderConfirmVo.setMemberAddressVos(list);}}, executor);CompletableFuture<Void> orderRunAsync = CompletableFuture.runAsync(new Runnable() {@Overridepublic void run() {//每一个线程都来共享之前的请求数据RequestContextHolder.setRequestAttributes(requestAttributes);//2.查询商品信息List<OrderItemVo> orderItemVos = cartFeignService.orderFeignCart(memberOAuthVo.getId());orderConfirmVo.setItems(orderItemVos);}}, executor).thenRunAsync(new Runnable() {@Overridepublic void run() {//查库存  怎么查库存  查库存参数List<Long> skuIdsList<Long> skuIds = orderConfirmVo.getItems().stream().map(item -> {return item.getSkuId();}).collect(Collectors.toList());R<List<SkuHasStockVo>> r = wareFeignService.getSkuHasStock(skuIds);if (r.getCode() == 0){List<SkuHasStockVo> data = r.getData(new TypeReference<List<SkuHasStockVo>>() {});// 下界 接收参数的类型,上界 返回的类型//Function<? super T, ? extends K> keyMapper,//Function<? super T, ? extends U> valueMapperMap<Long, Boolean> collect = data.stream().collect(Collectors.toMap(new Function<SkuHasStockVo, Long>() {@Overridepublic Long apply(SkuHasStockVo skuHasStockVo) {return skuHasStockVo.getSkuId();}}, SkuHasStockVo::getHasStock));orderConfirmVo.setStocks(collect);}}});//会员积分orderConfirmVo.setIntegration(memberOAuthVo.getIntegration());//设置订单token,防止页面重复提交,保证幂等性String key = OrderConstant.ORDER_TOKEN_PREFIX + memberOAuthVo.getId();String orderToken = UUID.randomUUID().toString().replace("_","");redisTemplate.opsForValue().set(key,orderToken) ;//set tokenorderConfirmVo.setOrderToken(orderToken);CompletableFuture.allOf(memberRunAsync,orderRunAsync).get();return orderConfirmVo;}

二、下单

1.业务分析

(1)校验页面幂等性
获取token令牌,删除令牌,校验成功进入下单操作,校验失败返回code
(2)创建订单
1.生成订单号,使用唯一ID生成,存入OrderSn
2.获取用户选中的地址信息,存入OrderEntity
3.获取购物车最新的商品信息,这里不能直接获取订单页的商品信息,防止购物车商品信息发生变化与订单页商品不一致
根据上送skuId取获取redis中存储的商品信息,然后筛选出已勾选的商品,并且查出该商品最新价格,最后存入List
(3) 验价
防止商品价格发生变化(可能订单页保存的价格与最新价格存在差异),在下单前进行比对,这里使用两价格相减取绝对值,考虑到存在优惠的问题,只要保证绝对值<0.05就表示验价通过
(4)保存订单数据
验价成功就可以保证订单,但需要添加事务@Transactional
(5)锁定库存
商品下单需要锁定库存,相当于订一批货,那么别人在去买这批货时就无法购买,先占个位置,如果超过支付时间未付款,就解锁库存,锁库存操作是给锁定库存添加需要购买的件数stock_locked + num,最大不能超过商品总库存 stock - stock_locked >= num
锁库存先查该skuId商品在那些仓库有库存,然后循环去锁库存,只要有一个仓库锁库存成功就代表成功,如果没有一个仓库能锁成功代表锁库存失败,告诉用户该商品库存不足,并且锁库存之前需要判断是否有仓库存在库存,没有就直接不用锁库存了

2.代码

(1)下单整体业务

@Transactional@Overridepublic SubmitOrderResponseVo placeOrder(OrderSubmitVo submitVo) {SubmitOrderResponseVo submitOrderResponseVo = new SubmitOrderResponseVo();submitOrderResponseVo.setCode(0);MemberOAuthVo memberOAuthVo = OrderLoginIntercepter.threadLocal.get();String key = OrderConstant.ORDER_TOKEN_PREFIX + memberOAuthVo.getId();//原子性操作:1.取出token 2.对比token 3.删除token//execute(RedisScript<T> script, List<K> keys, Object... args)String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Long luaReturn = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(key), submitVo.getOrderToken());//1-删除成功   0-删除失败if (luaReturn.equals(1L)){//令牌删除成功 -> 创建订单OrderCreateTo order = createOrder(submitVo);//验价 应付价格-BigDecimal payAmount = order.getOrder().getPayAmount();//应付价格BigDecimal payPrice = submitVo.getPayPrice();double abs = Math.abs(payAmount.subtract(payPrice).doubleValue());if (abs < 0.01){//验价成功 -> 保存订单信息 -> 锁定库存//TODO 保存订单信息saveOrder(order.getOrder(),order.getOrderItems());//远程锁定库存List<OrderItemEntity> orderItems = order.getOrderItems();List<OrderItemVo> collect = orderItems.stream().map(item -> {OrderItemVo orderItemVo = new OrderItemVo();orderItemVo.setSkuId(item.getSkuId());orderItemVo.setCount(item.getSkuQuantity());return orderItemVo;}).collect(Collectors.toList());OrderStockRequest orderStockRequest = new OrderStockRequest();orderStockRequest.setOrderSn(order.getOrder().getOrderSn());orderStockRequest.setItemVos(collect);//TODO 远程锁库存R r = wareFeignService.orderStock(orderStockRequest);if (r.getCode() == 0){submitOrderResponseVo.setCode(0);submitOrderResponseVo.setOrder(order.getOrder());return submitOrderResponseVo;}else {//锁库存失败//锁定失败String msg = (String) r.get("msg");throw new SkuNoStockException(msg);}}else {//验价失败submitOrderResponseVo.setCode(2);return submitOrderResponseVo;}}else {//令牌删除失败submitOrderResponseVo.setCode(1);return submitOrderResponseVo;}}

(2)获取订单页信息

/***  创建订单*     (1)用户信息:地址 1*     (2)购物车商品信息:应该直接查询购物车数据,不能获取结算页的商品数据,  list*     (3)下单金额  1*      (4) 验价*/public OrderCreateTo createOrder(OrderSubmitVo submitVo){OrderCreateTo orderCreateTo = new OrderCreateTo();//订单号-唯一idString timeId = IdWorker.getTimeId();OrderEntity orderEntity = buildOrderEntity(submitVo, timeId);List<OrderItemEntity> orderItemEntityList = buildOrderItems(timeId);//封装:订单总额、应付总额OrderEntity order = computePrice(orderEntity,orderItemEntityList);orderCreateTo.setOrder(order);orderCreateTo.setOrderItems(orderItemEntityList);return orderCreateTo;}/*** 封装OrderEntity:* 订单总额、应付总额* 促销优化金额、积分抵扣金额、优惠券抵扣金额**/public OrderEntity computePrice(OrderEntity order,List<OrderItemEntity> orderItems){//订单总额BigDecimal totalAmount = new BigDecimal("0.0");//促销优化金额、积分抵扣金额、优惠券抵扣金额BigDecimal promotionAmount = new BigDecimal("0.0");BigDecimal integrationAmount = new BigDecimal("0.0");BigDecimal couponAmount = new BigDecimal("0.0");//积分、成长值Integer integration = 0;Integer growth = 0;for (OrderItemEntity orderItem : orderItems) {totalAmount = totalAmount.add(orderItem.getRealAmount());promotionAmount = promotionAmount.add(orderItem.getPromotionAmount());integrationAmount = integrationAmount.add(orderItem.getIntegrationAmount());couponAmount = couponAmount.add(orderItem.getCouponAmount());integration += orderItem.getGiftIntegration();growth += orderItem.getGiftGrowth();}//订单总额order.setTotalAmount(totalAmount);//应付总额 = 订单总额 + 运费order.setPayAmount(totalAmount.add(order.getFreightAmount()));//促销优化金额、积分抵扣金额、优惠券抵扣金额order.setPromotionAmount(promotionAmount);order.setIntegrationAmount(integrationAmount);order.setCouponAmount(couponAmount);//积分、成长值order.setIntegration(integration);order.setGrowth(growth);return order;}/*** 订单信息* OrderEntity** 1.订单号* 2.用户地址信息* 3.运费金额*/public OrderEntity buildOrderEntity(OrderSubmitVo submitVo,String timeId){OrderEntity orderEntity = new OrderEntity();orderEntity.setOrderSn(timeId);orderEntity.setCreateTime(new Date());//用户地址MemberOAuthVo memberOAuthVo = OrderLoginIntercepter.threadLocal.get();orderEntity.setMemberId(memberOAuthVo.getId());MemberAddressVo member = memberFeignService.getMember(memberOAuthVo.getId());orderEntity.setReceiverName(member.getName());orderEntity.setReceiverPhone(member.getPhone());orderEntity.setReceiverPostCode(member.getPostCode());orderEntity.setReceiverProvince(member.getProvince());orderEntity.setReceiverCity(member.getCity());orderEntity.setReceiverRegion(member.getRegion());orderEntity.setReceiverDetailAddress(member.getDetailAddress());//用户名orderEntity.setMemberUsername(memberOAuthVo.getUsername());//运费金额R r = wareFeignService.fare(submitVo.getAddrId());if (r.getCode() == 0){FareVo data = (FareVo)r.getData(new TypeReference<FareVo>() {});//运费金额orderEntity.setFreightAmount(data.getFare());}orderEntity.setPayType(submitVo.getPayType());//订单状态-0待付款orderEntity.setStatus(OrderConstant.ORDER_STATUS);//字段确认天数orderEntity.setAutoConfirmDay(7);return orderEntity;}/*** 订单商品* List<OrderItemEntity>  因为商品有多个,所以是list** 1.订单号* 2.sku信息* 3.spu信息*/public List<OrderItemEntity> buildOrderItems(String timeId){//查询redis商品信息MemberOAuthVo memberOAuthVo = OrderLoginIntercepter.threadLocal.get();List<OrderItemVo> orderItemVos = cartFeignService.orderFeignCart(memberOAuthVo.getId());List<OrderItemEntity> collect = orderItemVos.stream().map(item -> {OrderItemEntity orderItemEntity = new OrderItemEntity();//订单号orderItemEntity.setOrderSn(timeId);//spu信息SpuInfoEntity spuInfo = productFeignService.getSpuBySkuId(item.getSkuId());orderItemEntity.setSpuId(spuInfo.getId());orderItemEntity.setSpuName(spuInfo.getSpuName());orderItemEntity.setCategoryId(spuInfo.getCatalogId());//Sku信息orderItemEntity.setSkuId(item.getSkuId());orderItemEntity.setSkuName(item.getTitle());orderItemEntity.setSkuPic(item.getImage());orderItemEntity.setSkuPrice(item.getPrice());orderItemEntity.setSkuQuantity(item.getCount());//商品销售属性组合 List<String> -> String//集合 根据指定的分割符转换 为字符串String jsonString = JSON.toJSONString(item.getSkuAttrValues());orderItemEntity.setSkuAttrsVals(jsonString);//促销、优惠券、积分orderItemEntity.setPromotionAmount(BigDecimal.ZERO);orderItemEntity.setCouponAmount(BigDecimal.ZERO);orderItemEntity.setIntegrationAmount(BigDecimal.ZERO);//该商品经过优惠后的分解金额BigDecimal totalPrice = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));BigDecimal realPrice = totalPrice.subtract(orderItemEntity.getPromotionAmount()).subtract(orderItemEntity.getCouponAmount()).subtract(orderItemEntity.getIntegrationAmount());orderItemEntity.setRealAmount(realPrice);//赠送积分、成长值orderItemEntity.setGiftIntegration(totalPrice.intValue());orderItemEntity.setGiftGrowth(totalPrice.intValue());return orderItemEntity;}).collect(Collectors.toList());return collect;}

(3)锁定库存

    /*** 库存锁定** 1.查询该商品在那些仓库有库存* 2.锁定库存,遍历仓库去锁定库存,只要有一个仓库锁定代表成功,如果没有一个仓库能锁成功,抛异常,该sku商品库存不足**/
//    @Transactional(rollbackFor = NoWareStockException.class)@Transactional@Overridepublic boolean orderStock(OrderStockRequest orderStockRequest) {List<OrderItemVo> itemVos = orderStockRequest.getItemVos();List<SkuStockfromWare> collect = itemVos.stream().map(item -> {SkuStockfromWare skuStockfromWare = new SkuStockfromWare();skuStockfromWare.setSkuId(item.getSkuId());skuStockfromWare.setNum(item.getCount());//查询该商品在那些仓库有库存List<Long> wareId = wareSkuDao.skuStockfromWare(item.getSkuId());skuStockfromWare.setWareId(wareId);return skuStockfromWare;}).collect(Collectors.toList());//根据skuId遍历for (SkuStockfromWare skuStockfromWare : collect) {//判断是否锁定成功boolean flag = false;//判断该商品是否有仓库存在库存List<Long> wareIdList = skuStockfromWare.getWareId();if (wareIdList.size() < 0 || wareIdList == null){throw new NoWareStockException(skuStockfromWare.getSkuId());}for (Long wareId : wareIdList) {Long count = wareSkuDao.LockedStockFromWare(skuStockfromWare.getSkuId(),wareId,skuStockfromWare.getNum());if (count.equals(1L)){//锁定成功flag = true;//该商品锁定库存成功就执行下一个商品break;}}//如果没有一个仓库扣成功,代表此skuId的库存不足if (!flag){throw new SkuNoStockException(skuStockfromWare.getSkuId());}}return true;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/18472.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Scrap爬虫框架集成Selenium来解析动态网页

1、爬虫项目单独使用scrpay框架的不足 当前网站普遍采用了javascript 动态页面&#xff0c;特别是vue与react的普及&#xff0c;使用scrapy框架定位动态网页元素十分困难&#xff0c;而selenium是最流行的浏览器自动化工具&#xff0c;可以模拟浏览器来操作网页&#xff0c;解…

spring boot MySQL操作

极简spring boot MySQL测试 默认: spring boot环境已经搭好,可以跑最基本的hello world 有MySQL环境有部分测试数据表,并且有MySQL语法基础 配置 application.yml 如下配置,根据自己的数据库信息与个人需求配置 server: tomcat: uri-encoding: UTF-8 threads: …

使用Feign进行微服务之间的接口调用:Spring Cloud Alibaba中的声明式服务调用

一、Feign介绍 Feign是一个声明式的HTTP客户端框架&#xff0c;用于简化微服务架构中服务之间的通信。它是Spring Cloud框架的一部分&#xff0c;旨在提供一种优雅且易于使用的方式来定义和调用HTTP请求。 Feign的设计目标是让服务之间的通信变得更加简单和直观。通常情况下&am…

数据结构中队列的操作方式,一目了然

队列的概念 首先我们联想一下链表&#xff0c;在单链表中&#xff0c;我们只能对他的链表表尾进行插入&#xff0c;对链表的表头进行结点的删除&#xff0c;这样强限制性的链表&#xff0c;就是我们所说的队列。 也就是说&#xff0c;队列&#xff08;queue&#xff09;是限定…

代码随想录算法训练营第十二天 | 二叉树系列3

二叉树系列3 二叉树 看到二叉树就想到递归404 左叶子之和重点代码随想录的代码我的代码(当日晚上自己理解后写) 513 找树左下角的值重点代码随想录的代码我的代码(当日晚上自己理解后写)我去&#xff0c;我怎么能写出这样的代码&#xff0c;没有return的递归&#xff0c;大错特…

在Visual Studio Code里导出8266固件

1.编辑 .vscode目录下 arduion.json 添加 一个配置项output即输出目录.当然你不设置其它软固件一样会生成,只是就不知道你能不能找到了.我的配置如下 当然这个路径你想写什么 就是什么 . 2. 切换到 arduion的项目文件 xxxx.ino.点击vsc右上的验证 即可在上面设置的目录下找到…

Java面试题大全(2023牛客网最新版)大厂面试题附答案详解

很多 Java 工程师的技术不错&#xff0c;但是一面试就头疼&#xff0c;10 次面试 9 次都是被刷&#xff0c;过的那次还是去了家不知名的小公司。 问题就在于&#xff1a;面试有技巧&#xff0c;而你不会把自己的能力表达给面试官。 应届生&#xff1a;你该如何准备简历&#…

IntegrityError: FOREIGN KEY constraint failed解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

阿里云AliYun物联网平台使用-申请免费试用及完成初始配置

一、项目简介 本专栏文章将围绕阿里云物联网平台&#xff0c;实现其设备向云平台的数据上传&#xff0c;客户端获取云平台数据。设备通过NBIOT技术实现无线采集&#xff0c;定时上传。 二、阿里云平台申请 阿里云物联网平台试用申请地址 进入上述超链接网址&#xff1a; 由于是…

This application failed to start?

大家好&#xff0c;最近在搞一个定制的图像分割项目&#xff0c;其中需要自己构建数据集。 这里我用到了基于paddle开发高效智能的交互式分割标注软件 EISeg(Efficient Interactive Segmentation)。 它涵盖了通用、人像、遥感、医疗、视频等不同方向的高质量交互式分割模型。另…

Linux安装配置Oracle+plsql安装配置(详细)

如果觉得本文不够详细&#xff0c;没有效果图&#xff0c;可移步详细版&#xff1a; Linux安装配置Oracleplsql安装配置&#xff08;超详细&#xff09;_超爱慢的博客-CSDN博客 目录 1.安装虚拟机系统 1.安装虚拟机 2.配置虚拟机 1.设置机器名 2.修改域名映射 3.固定IP…

JavaEE——常见的锁策略、CAS、synchronized 原理(八股)

文章目录 一、常见的锁策略1.乐观锁 & 悲观锁2.轻量级锁 & 重量级锁3.自旋锁 & 挂起等待锁4.互斥锁 & 读写锁5. 公平锁 & 非公平锁 二、CAS1、什么是 CAS2. CAS 的应用场景2.实现自旋锁3. CAS 中的 ABA 问题 三、 Synchronized 原理 一、常见的锁策略 当前…