订单服务以及相关问题的解决

订单服务以及相关问题的解决_第1张图片

订单中心主要包括物流信息,用户信息,支付信息,订单信息,促销信息,商品信息

订单服务以及相关问题的解决_第2张图片

订单服务以及相关问题的解决_第3张图片

订单服务以及相关问题的解决_第4张图片

订单服务以及相关问题的解决_第5张图片

订单服务以及相关问题的解决_第6张图片

订单服务-Feign远程调用丢失请求头问题:

订单服务以及相关问题的解决_第7张图片

解决:加一个feign远程调用的拦截器

订单服务以及相关问题的解决_第8张图片

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @description:
 * @author: wei-xhh
 * @create: 2020-07-26
 */
@Configuration
public class GreymallFeignConfig {

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor(){
            @Override
            public void apply(RequestTemplate requestTemplate) {
//                System.out.println("feign远程之前先进行RequestInterceptor.apply");

                //1、RequestContextHolder拿到刚进来的请求
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                if(attributes != null){
                    HttpServletRequest request = attributes.getRequest(); //老请求
                    if(request != null){
                        //同步请求头数据,Cookie
                        String cookie = request.getHeader("Cookie");
                        //给新请求同步了老请求的cookie
                        requestTemplate.header("Cookie",cookie);
                    }
                }
            }
        };
    }
}

订单服务-Feign远程调用丢失请求头问题:

订单服务以及相关问题的解决_第9张图片

解决方法:

在线程开始执行之前拿到共享数据,然后再每个线程里共享一下

@Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

        //解决Feign异步 ThreadLocal 问题
        //获取当前线程的数据
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        CompletableFuture getAddressFuture = CompletableFuture.runAsync(() -> {
            //1、远程查询所有收货地址列表
            //设置主线程中的数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List address =
                    memberFeignService.getAddress(memberRespVo.getId());
            confirmVo.setAddress(address);
        }, executor);

        CompletableFuture cartFuture = CompletableFuture.runAsync(() -> {
            //2、远程查询购物车所有选中的购物项
            //设置主线程中的数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List items =
                    cartFeignService.getCurrentUserCartItems();
            confirmVo.setItems(items);
            //feign在远程调用之前要构造请求,调用很多的拦截器
        }, executor).thenRunAsync(() -> {
            List items = confirmVo.getItems();
            List collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());

            R hasStock = wmsFeignService.getSkuHasStock(collect);
            List data = hasStock.getData(new TypeReference>() {
            });
            if (data != null) {
                Map map = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                confirmVo.setStocks(map);
            }
        }, executor);


        //3、查询用户积分
        Integer integration = memberRespVo.getIntegration();
        confirmVo.setIntegration(integration);

        //4、其他数据自动计算

        //TODO 5、防重令牌
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 30, TimeUnit.MINUTES);
        confirmVo.setOrderToken(token);

        CompletableFuture.allOf(getAddressFuture, cartFuture).get();

        return confirmVo;
    }

接口幂等性讨论:

订单服务以及相关问题的解决_第10张图片

订单服务以及相关问题的解决_第11张图片

在数据库级别可以给订单号添加唯一索引来确保幂等性

订单服务以及相关问题的解决_第12张图片

token机制也就是令牌机制,常见的实现方式有验证码

订单服务以及相关问题的解决_第13张图片

订单服务以及相关问题的解决_第14张图片

订单服务以及相关问题的解决_第15张图片

订单服务以及相关问题的解决_第16张图片

订单服务以及相关问题的解决_第17张图片

订单服务以及相关问题的解决_第18张图片

订单服务以及相关问题的解决_第19张图片

订单提交的幂等性解决:

 代码:

  @Transactional
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {
        submitVoThreadLocal.set(vo);

        SubmitOrderResponseVo response = new SubmitOrderResponseVo();
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
        response.setCode(0);

        //1、验证令牌【令牌的对比和删除必须保证原子性】
        //0令牌失败,1删除成功
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        String orderToken = vo.getOrderToken();

        //原子验证令牌和删除令牌
        Long result = redisTemplate.execute(new DefaultRedisScript(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();
            double abs = Math.abs(payAmount.subtract(payPrice).doubleValue());//44442

            if (Math.abs(payAmount.subtract(payPrice).doubleValue()) < 0.01) {
                //金额对比
                //3、保存订单
                saveOrder(order);
                //4、库存锁定,只要有异常回滚订单数据。
                // 订单号,所有订单项(skuId,skuName,num)
                WareSkuLockVo lockVo = new WareSkuLockVo();
                lockVo.setOrderSn(order.getOrder().getOrderSn());
                List locks = order.getOrderItems().stream().map(item -> {
                    OrderItemVo itemVo = new OrderItemVo();
                    itemVo.setSkuId(item.getSkuId());
                    itemVo.setCount(item.getSkuQuantity());
                    itemVo.setTitle(item.getSkuName());
                    return itemVo;
                }).collect(Collectors.toList());
                lockVo.setLocks(locks);

                //TODO 远程锁库存
                //问题:库存成功了,但是网络原因超时了,订单回滚,库存不回滚
                //为了保证高并发,库存服务自己回滚,可以发消息个库存服务;
                //库存服务本身也可以自动解锁模式 使用消息队列
                R r = wmsFeignService.orderLockStock(lockVo);
                if(r.getCode() == 0){
                    //锁成功了
                    response.setOrder(order.getOrder());

                    //TODO 模拟远程扣减积分,出异常
//                    int i = 10/0; //订单回滚,库存不回滚
                    //TODO 订单创建成功发送消息给MQ
                    rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",order.getOrder());
                    return response;
                } else {
                    //锁定失败
                    String msg = (String) r.get("msg");
                    throw new NoStockException(msg);
//                    response.setCode(3);
//                    return response;
                }

            } else {
                response.setCode(2);
                return response;
            }
        }
//        String redisToken = redisTemplate.opsForValue().get(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());
//        if(orderToken != null && orderToken.equals(redisToken)){
//            //令牌验证通过
//            redisTemplate.delete(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());
//
//        } else {
//            //不通过
//        }
    }

锁库存代码:

/**
     * 为某个订单锁定库存
     *默认只要是运行时异常都会回滚
     *
     *库存解锁的场景
     * 1、下订单成功,订单过期没有支付被系统自动取消,被用户手动取消,都要解锁库存
     * 2、下订单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚
     *    之前解锁的库存就要自动解锁
     * @param vo
     * @return
     */
    @Transactional(rollbackFor = NoStockException.class)
    @Override
    public boolean orderLockStock(WareSkuLockVo vo) {
        /**
         * 保存库存工作单的详情
         *
         */
        WareOrderTaskEntity taskEntity = new WareOrderTaskEntity();
        taskEntity.setOrderSn(vo.getOrderSn());
        wareOrderTaskService.save(taskEntity);

        //1、按照下单的收货地址,找到一个就近仓库,锁定库存
        //1、找到每个商品在哪个仓库都有库存
        List locks = vo.getLocks();
        List collect = locks.stream().map(item -> {
            SkuWareHasStock stock = new SkuWareHasStock();
            Long skuId = item.getSkuId();
            stock.setSkuId(skuId);
            stock.setNum(item.getCount());
            //查询这个商品在哪里有库存
            List wareIds = wareSkuDao.listWareIdHasSkuStock(skuId);
            stock.setWareId(wareIds);
            return stock;
        }).collect(Collectors.toList());

        //2、锁定库存
        for (SkuWareHasStock hasStock : collect) {
            boolean skuStocked = false;
            Long skuId = hasStock.getSkuId();
            List wareIds = hasStock.getWareId();
            if (wareIds == null || wareIds.size() == 0) {
                //没有任何仓库有这个商品的库存
                throw new NoStockException(skuId);
            }
            //1、如果每个商品都锁定成功,将当前商品锁定了几件的工作单记录发送给MQ
            //2、如果锁定失败,前面保存的工作的回滚,发送出去的消息,即使要解锁记录,由于去数据库查不到id,所以就不用解锁
            //
            for (Long wareId : wareIds) {
                //成功就返回1,否则为0
                Long count = wareSkuDao.lockSkuStock(skuId,wareId,hasStock.getNum());
                if(count == 1){
                    skuStocked = true;
                    // TODO 告诉MQ库存锁定成功
                    WareOrderTaskDetailEntity entity =
                            new WareOrderTaskDetailEntity(null, skuId, null, hasStock.getNum(), taskEntity.getId(), wareId, 1);
                    wareOrderTaskDetailService.save(entity);
                    StockLockedTo lockedTo = new StockLockedTo();
                    lockedTo.setId(taskEntity.getId());
                    StockDetailTo stockDetailTo = new StockDetailTo();
                    BeanUtils.copyProperties(entity,stockDetailTo);
                    //只发id不行,防止回滚以后找不到数据
                    lockedTo.setDetail(stockDetailTo);

                    rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo);
                    break;
                } else {
                    //当前仓库锁失败,重试下一个仓库
                }
            }
            if(skuStocked == false){
                //当前商品所有仓库都没有锁住
                throw new NoStockException(skuId);
            }
        }

        //3、肯定全部锁定成功

        return true;
    }

 

你可能感兴趣的:(订单服务以及相关问题的解决)