电商项目 - 用户订单确认及下订单操作

article/2025/8/19 12:00:40

一、订单确认页功能流程图

在这里插入图片描述

1、进入登录确认页之前会,先进入登录拦截器

同样是使用ThreadLocal来存储用户的登录信息,从请求的request中获取登录信息

@Component
public class LoginUserInterceptor implements HandlerInterceptor {public static ThreadLocal<MemberRespVo> loginUser = new ThreadLocal<>();/*** 在目标请求之前执行该方法** @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String requestURI = request.getRequestURI();boolean match = new AntPathMatcher().match("/order/order/queryOrderBySn/**", requestURI);if (match) {return true;}// 获取登录的用户MemberRespVo attribute = (MemberRespVo) request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);if (attribute != null) {// 已登录,将登录用户信息存放到threadLocal中,并放行loginUser.set(attribute);return true;} else {// 未登录,重定向到用户登录页request.getSession().setAttribute("msg", "请先进行登录");response.sendRedirect("http://auth.gulimall.com/login.html");return false;}}
}

2、查询订单确认页需要的数据

/*** 查询订单确认页需要的数据** @return* @throws ExecutionException* @throws InterruptedException*/@Overridepublic OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {OrderConfirmVo confirmVo = new OrderConfirmVo();MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();log.info("主线程:{}", Thread.currentThread().getId());// 获取之前的请求RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 收货地址列表 异步任务一CompletableFuture<Void> getAddressTask = CompletableFuture.runAsync(() -> {log.info("getAddress线程:{}", Thread.currentThread().getId());// 每一个线程都来共享之前的请求数据RequestContextHolder.setRequestAttributes(requestAttributes);List<MemberAddressVo> address = memberFeginService.getAddress(memberRespVo.getId());confirmVo.setAddress(address);}, executor);// 远程查询购物车中所有选中的购物项 异步任务二CompletableFuture<Void> getCartItemsTask = CompletableFuture.runAsync(() -> {log.info("getUserCartItems线程:{}", Thread.currentThread().getId());// 每一个线程都来共享之前的请求数据,在Feign的拦截器中就不会丢失老请求的数据RequestContextHolder.setRequestAttributes(requestAttributes);List<OrderItemVo> userCartItems = cartFeginService.getUserCartItems();confirmVo.setItems(userCartItems);}, executor).thenRunAsync(() -> {// 查询商品的库存信息,判断该商品是否有库存量List<OrderItemVo> items = confirmVo.getItems();if (items != null && !items.isEmpty()) {List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());// 调用库存服务R hasStock = wareSkuFeginService.getSkuHasStock(collect);if (hasStock != null) {List<SkuHasStockVo> data = hasStock.getData(new TypeReference<List<SkuHasStockVo>>() {});if (CollectionUtils.isNotEmpty(data)) {Map<Long, Boolean> hasStockMap = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));confirmVo.setStocks(hasStockMap);}}}}, executor);// 用户积分Integer integration = memberRespVo.getIntegration();confirmVo.setIntegration(integration);// 防重令牌String token = UUID.randomUUID().toString().replace("-", "");confirmVo.setOrderToken(token);stringRedisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 30, TimeUnit.MINUTES);// 其他数据自动计算CompletableFuture.allOf(getAddressTask, getCartItemsTask).get();return confirmVo;}
2.1 获取收货地址列表信息
// 收货地址列表 异步任务一CompletableFuture<Void> getAddressTask = CompletableFuture.runAsync(() -> {log.info("getAddress线程:{}", Thread.currentThread().getId());// 每一个线程都来共享之前的请求数据RequestContextHolder.setRequestAttributes(requestAttributes);List<MemberAddressVo> address = memberFeginService.getAddress(memberRespVo.getId());confirmVo.setAddress(address);}, executor);

通过openFeign远程调用获取收货地址信息

@FeignClient("gulimall-member")
public interface MemberFeginService {/*** 返回会员的所有收货地址列表* @param memberId* @return*/@GetMapping("/member/memberreceiveaddress/{memberId}/address")List<MemberAddressVo> getAddress(@PathVariable("memberId") Long memberId);
}
2.2 设置防重令牌

防重令牌的作用在用户下订单的时候为了防止用户重复提交的情况

// 防重令牌String token = UUID.randomUUID().toString().replace("-", "");confirmVo.setOrderToken(token);

二、用户下订单

/*** 下单* @param vo* @return* @Transactional:本地事务,在分布式系统下,只能控制自己的回滚,控制不了其他服务的回滚,这里只能使用分布式事务处理* @GlobalTransactional:全局事务,使用seata解决分布式事务问题。由于seata处理的方式比较麻烦,后改为RabbitMQ的延迟队列的方式处理分布式事务的问题。*/
//    @GlobalTransactional@Transactional@Overridepublic SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {SubmitOrderResponseVo response = new SubmitOrderResponseVo();response.setCode(0);MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();orderSubmitLocal.set(vo);/*** 验证令牌的原因是保证接口的幂等性,即防止重复提交订单* 1、验证令牌【令牌的获取、令牌的对比和删除三个操作必须保证原子性】*     0:令牌失败*     1:删除成功*/// 前端传过来的tokenString orderToken = vo.getOrderToken();// 使用redis的lua脚本来保证原子性String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";// 原子验证令牌和删除令牌 【0 -》令牌验证失败,1-》删除成功】Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken);if(result == 0L){// 令牌验证失败response.setCode(1);return response;}else{// 令牌验证成功,准备下单操作// 1、创建订单,订单项等信息OrderCreateTo order = createOrder();// 2、验价BigDecimal payAmount = order.getOrder().getPayAmount();BigDecimal payPrice = vo.getPayPrice();if(Math.abs(payPrice.subtract(payAmount).doubleValue()) <= 0.01){// 金额对比成功// 3、保存订单saveOrder(order);// 4、库存锁定,只要有异常需要回滚订单数据WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();wareSkuLockVo.setOrderSn(order.getOrder().getOrderSn());List<OrderItemEntity> orderItems = order.getOrderItems();List<OrderItemVo> collect = orderItems.stream().map(item -> {OrderItemVo orderItemVo = new OrderItemVo();orderItemVo.setSkuId(item.getSkuId());orderItemVo.setTitle(item.getSkuName());orderItemVo.setCount(item.getSkuQuantity());return orderItemVo;}).collect(Collectors.toList());wareSkuLockVo.setLocks(collect);//TODO 远程锁库存 ,库存成功了,但是网络原因超时了,订单回滚,库存不滚R r = wareSkuFeginService.orderLockStock(wareSkuLockVo);if(r.getCode() == 0){response.setOrder(order.getOrder());// TODO: 远程扣减积分 出现异常 如果使用传统的方式,那么订单回滚,锁定库存不回滚// int i = 10/0;// 将订单消息存入到延迟队列里面,一分钟后(真实情况下,是30分钟未支付,会自动取消订单),如果状态为待支付,则执行关单操作rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",order.getOrder());return response;}else{// 商品库存不足response.setCode(3);return response;}}else{// 金额对比失败response.setCode(2);return response;}}}

http://chatgpt.dhexx.cn/article/iY7QPTo4.shtml

相关文章

解决支付订单,重复提交问题!

点击上方“朱小厮的博客”&#xff0c;选择“设为星标” 后台回复"书"&#xff0c;获取 后台回复“k8s”&#xff0c;可领取k8s资料 概述 如图是一个简化的下单流程&#xff0c;首先是提交订单&#xff0c;然后是支付。支付的话&#xff0c;一般是走支付网关&#xf…

SpringBoot解决用户重复提交订单(方式二:通过Redis实现)

文章目录 前言1、方案实践1.1、引入Redis依赖1.2、添加Redis环境配置1.3、编写获取请求唯一ID的接口&#xff0c;同时将唯一ID存入redis 1.4、编写服务验证逻辑&#xff0c;通过 aop 代理方式实现1.5、在相关的业务接口上&#xff0c;增加SubmitToken注解即可 2、小结 前言 在…

后端怎样防止重复提交订单?

点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 一般我们都是这样做的&#xff1a; 创建订单的时候&#xff0c;用订单信息计算一个哈希值&#xff0c;判断redis中是否有key&#xff0c;有则不允许重复提交&#xff0c;没有则生成一个新key&…

后端怎么防止重复提交订单

前言 接口幂等性问题&#xff0c;对于开发人员来说&#xff0c;是一个跟语言无关的公共问题。本文分享了一些解决这类问题非常实用的办法&#xff0c;绝大部分内容我在项目中实践过的&#xff0c;给有需要的小伙伴一个参考。 不知道你有没有遇到过这些场景&#xff1a; 有时…

bootstrap订单提交页面

下载地址 基于bootstrap实现的订单提交页面&#xff0c;常见的电商购物网站订单确认提交页面。 dd:

移动端-确认订单页面

项目准备 lib文件中存放外来的文件&#xff0c;就比如这个项目使用到字体图标&#xff0c;那存放的就是字体图标的文件&#xff0c;css 样式&#xff0c;images 重要的图片&#xff0c;uploads 页面随时更新的图片&#xff0c;其次就是html文件。 base.css *{margin: 0;pad…

confirm-order提交订单

目录 顶部导航条&#xff1a;复用head组件新增收货地址订单收货地址页面顶部导航条&#xff1a;复用head组件无地址地址列表新增地址 增加收货地址add_address顶部导航条&#xff1a;复用head组件地址信息表单其他组件 送达时间商店商品底部弹出消息&#xff1a;复用alertTip组…

订单。。。

一、库存扣减和订单表不一致 1、网络抖动—网速是好是坏&#xff0c;不稳定。最大延迟与最小延迟的时间差&#xff0c;如最大延迟是20毫秒&#xff0c;最小延迟为5毫秒&#xff0c;那么网络抖动就是15毫秒 2、库存数据不一致的原因&#xff1a; 1&#xff09;事务性的问题 – …

实现提交订单的功能

根据购物车中的商品名称和数量生成了结算信息&#xff0c;并可以填写收货人姓名、联系电话和收货地址&#xff0c;本任务将实现提交订单的功能。 一、创建订单页面order.jsp <% page language"java" import"java.util.*" pageEncoding"UTF-8"…

美多商城项目:结算订单与提交订单

一、结算订单 1. 结算订单逻辑分析 结算订单是从Redis购物车中查询出被勾选的商品信息进行结算并展示。 2. 结算订单接口设计和定义 1.请求方式 选项方案请求方法GET请求地址/orders/settlement/ 3. 结算订单后端逻辑实现 class OrderSettlementView(LoginRequiredMixin, Vie…

电商系统-提交订单并发处理

在多个用户同时发起对一个商品的下单请求时&#xff0c;先查询商品库存&#xff0c;再修改商品库存&#xff0c;会出现资源竞争问题&#xff0c;导致库存的最终结果出现异常。 1、并发下单问题演示 每个不同的用户在程序上&#xff0c;我们可以理解成不同的线程&#xff0c;每…

Android Studio_Toast消息提醒

Android Studio_Toast消息提醒 1、Toast是Android系统提供的一种非常简洁的消息提醒方式&#xff0c;程序中可以使用它实现将短小的消息通知给用户&#xff0c;一点时间后自动消失&#xff0c;且不占用屏幕的任何空间。 2、Toast用法其实非常简单&#xff0c;通过静态方法make…

Vue 消息提示通知的几种方式汇总

Vue 消息提示通知组件&#xff08;Message /Notification&#xff09;是我们日常开发中经常使用的组件&#xff0c;它可用作与用户交互的反馈提示&#xff0c;信息提交成功、错误、操作警告等场景使用。原生JavaScript 提供了 alert、prompt、confirm 等方法 提示框1>Messag…

java信息提醒怎么实现_jsp怎么实现消息提醒

如果你是平台级别的系统,可以考虑消息队列的中间件,例如:阿里巴巴的rocketmq,用这个来做消息订阅与分发。 如果你只是简简单单的需要提示到web(jsp)页面,可以用js定时ajax访问后台,后台来确定是否有数据更新,无论这个数据是哪来的。 推荐课程:Java教程。 这里就使用在JSP页面…

RabbitMQ真延时队列实现消息提醒功能

RabbitMQ真延时队列实现消息提醒功能 一、需求场景 用户可以制定多个计划&#xff0c;同时可给该计划设置是否需要到点提醒&#xff0c;且中途可以取消提醒或修改提醒时间。 二、需要解决的问题 学习过rabbitmq的同学们都知道&#xff0c;通过TTL死信队列可以实现延时队列的…

企业微信 消息 html,企业微信怎么设置消息提醒

企业微信是一款非常不错的办公软件&#xff0c;用户加入企业群就能实时了解企业的动态。而且大家只需设置消息提醒&#xff0c;软件就会在第一时间通知你&#xff0c;不会让你错过任何重要的消息&#xff0c;下面小编为大家带来相关的设置教程。 方法/步骤分享&#xff1a; 1、…

vue websocket 新消息提醒

概述&#xff1a; 不是当前聊天&#xff0c;有其他消息来就通过2种方式接受到提醒。在连接的上下文中判断&#xff0c;符合条件的弹框&#xff0c;显示红点&#xff0c;此处调用了element弹框组件列表点击事件&#xff0c;红点消失列表显示&#xff0c;属性中包含小红点 前提…

html5载入提示音,html5新消息提示声音

【实例简介】 【实例截图】 【核心代码】HTML5手机声音提示 #chatBox{width:400px;border:1px solid #d3d3d3;margin:50px auto;} #chat {max-height:220px;overflow-y:auto;max-width:400px;} #chat > ul > li{padding:3px;clear:both;padding:4px;margin:10px 0px 5px …

【Android】消息提示notification

notification 1、notification消息提示 由Android系统来管理和维护的&#xff0c;因此用户可以随时进入查看。某些信息不需要用户马上处理&#xff0c;可以利用通知&#xff0c;即延迟消息&#xff0c;比如软件的更新、短信、新闻等。 2、消息包含的内容 3、代码 <Button…

消息提醒系统:设计模式与实现方案 (公告(通告)、消息、提醒等基本功能数据库表设计与实现)

参考地址&#xff1a; 公告(通告),消息,提醒等基本功能数据库表设计_DamonREN的博客-CSDN博客 多种消息提醒系统的设计模式、实现方案&#xff08;附功能截图表结构&#xff09;_黑夜的风的博客-CSDN博客_消息提醒 设计一个百万级的消息推送系统 - crossoverJie - 博客园 案…