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

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

在这里插入图片描述

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
     */
    @Override
    public 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
     */
    @Override
    public 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
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {
        SubmitOrderResponseVo response = new SubmitOrderResponseVo();
        response.setCode(0);
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
        orderSubmitLocal.set(vo);
        /**
         * 验证令牌的原因是保证接口的幂等性,即防止重复提交订单
         * 1、验证令牌【令牌的获取、令牌的对比和删除三个操作必须保证原子性】
         *     0:令牌失败
         *     1:删除成功
         */
        // 前端传过来的token
        String 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;
            }
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值