1. 程式人生 > 實用技巧 >穀粒商城訂單服務(四十)

穀粒商城訂單服務(四十)

261-284 訂單服務

程式碼提交到碼雲:https://gitee.com/dalianpai/gulimall

感覺訂單服務業務邏輯還是比較複雜的,改html改的頭皮發麻。

上個星期寫的,沒有時間簡單的記錄一下,讓人感覺有點意思的也就Feign的請求頭丟失的問題。

ThreadLocal可以檢視之前的部落格:https://www.cnblogs.com/dalianpai/p/12623044.html

/**
 * @author WGR
 * @create 2020/7/27 -- 21:52
 */
@Component
public class LoginUserInterceptor implements HandlerInterceptor {

    public static ThreadLocal<MemberRespVo> loginUser = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        MemberRespVo attribute = (MemberRespVo)request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);
        if(attribute !=null){
            loginUser.set(attribute);
            return true;
        }else{
            request.getSession().setAttribute("msg","請先進行登入");
            response.sendRedirect("http://auth.gulimall.com/login.html");
            return false;
        }
    }
}

Feign的請求頭丟失的問題:https://www.cnblogs.com/dalianpai/p/12426425.html

/**
 * @author WGR
 * @create 2020/7/28 -- 16:01
 */
@Configuration
public class GuliFeignConfig {

    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                if(attributes !=null){
                    HttpServletRequest request = attributes.getRequest();
                    if(request !=null){
                        requestTemplate.header("Cookie", request.getHeader("Cookie"));
                    }

                }
            }
        };
    }
}

業務邏輯:


/**
 * @author WGR
 * @create 2020/7/27 -- 22:05
 */
@Controller
public class OrderWenController {

    @Autowired
    OrderService orderService;

    @GetMapping("/toTrade")
    public String toTrade(Model model) throws ExecutionException, InterruptedException {

        OrderConfirmVo confirmVo =  orderService.confirmOrder();

        model.addAttribute("orderConfirmData",confirmVo);
        return "confirm";
    }

    @PostMapping("/submitOrder")
    public String submitOrder(OrderSubmitVo vo, Model model, RedirectAttributes redirectAttributes){
     System.out.println("訂單提交的資料。。。"+vo);
     SubmitOrderResponseVo responseVo = orderService.submitOrder(vo);
     if(responseVo.getCode() ==0){
         System.out.println(responseVo);
         model.addAttribute("submitOrderResp",responseVo);
         return "pay";
     }else{
         String msg="下單失敗:";
         switch (responseVo.getCode()){
             case 1: msg += "訂單資訊過期,請重新整理再次提交";break;
             case 2: msg += "訂單商品價格發生變化,請確認後再次提交";break;
             case 3: msg += "庫存鎖定失敗,商品庫存不足";break;
         }
         redirectAttributes.addFlashAttribute("msg",msg);
         return "redirect:http://order.gulimall.com/toTrade";
     }
    }
}

@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    private ThreadLocal<OrderSubmitVo> confirmVoThreadLocal = new ThreadLocal<>();
    @Autowired
    MemberFeignService memberFeignService;

    @Autowired
    OrderItemService orderItemService;

    @Autowired(required = false)
    OrderDao orderDao;

    @Autowired(required = false)
    OrderItemDao orderItemDao;

    @Autowired
    CartFeignService cartFeignService;

    @Autowired
    WmsFeignService wmsFeignService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    ProductFeignService productFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //1.遠端查詢所有的收貨地址列表
        CompletableFuture<Void> getCompletableFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberReceiveAddressEntity> address = memberFeignService.getAddress(memberRespVo.getId());
            System.out.println(address);
            orderConfirmVo.setAddress(address);
        }, executor);

        //2.遠端查詢購物車所有選中的購物項
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<CartItem> items = cartFeignService.getCurrentUserCartItems();
            orderConfirmVo.setItems(items);
        }, executor).thenRunAsync( () ->{
            List<CartItem> items = orderConfirmVo.getItems();
            List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());

            R hasStock = wmsFeignService.getSkusHasStock(collect);
            List<SkuHasStockVo> data = hasStock.getData(new TypeReference<List<SkuHasStockVo>>() {
            });
            if(data !=null){
                Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));
                orderConfirmVo.setStocks(map);
            }
        },executor);

        //3.查詢使用者積分
        Integer integration = memberRespVo.getIntegration();
        orderConfirmVo.setIntegration(integration);

        //5 防重令牌
        String token = UUID.randomUUID().toString().replace("-", "");
        System.out.println(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberRespVo.getId());
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberRespVo.getId(),token,30, TimeUnit.MINUTES);
        orderConfirmVo.setOrderToken(token);
        CompletableFuture.allOf(completableFuture,getCompletableFuture).get();
        return orderConfirmVo;
    }

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

        SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo();
        responseVo.setCode(0);
        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
        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<Long>(script,Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberRespVo.getId() ),orderToken);
        if(result ==0L){
            responseVo.setCode(1);
           return responseVo;
        }else{
            //令牌驗證成功
            //下單: 去建立訂單,驗證令牌,驗價格,鎖庫存
            OrderCreateTo order = createOrder();
            //2. 驗價
            BigDecimal payAmount = order.getOrder().getPayAmount();
            BigDecimal payPrice = vo.getPayPrice();
            if(Math.abs(payAmount.subtract(payPrice).doubleValue())<0.01){
                //3 儲存訂單
                saveOrder(order);
                //4 庫存鎖定
                WareSkuLockVo lockVo = new WareSkuLockVo();
                lockVo.setOrderSn(order.getOrder().getOrderSn());
                List<CartItem> locks = order.getOrderItems().stream().map(item -> {
                    CartItem itemVo = new CartItem();
                    itemVo.setSkuId(item.getSkuId());
                    itemVo.setCount(item.getSkuQuantity());
                    itemVo.setTitle(item.getSkuName());
                    return itemVo;
                }).collect(Collectors.toList());
                lockVo.setLocks(locks);
                R r = wmsFeignService.orderLockStock(lockVo);
                if(r.getCode() == 0){
                    //鎖定成功
                    responseVo.setOrder(order.getOrder());
                 //  int i = 1/0;
                    return responseVo;
                }else{
                    //鎖定失敗
                    responseVo.setCode(3);
                    return responseVo;
                }
            }else{
                responseVo.setCode(2);
                return responseVo;
            }
        }
    }

    @Transactional
    private void saveOrder(OrderCreateTo order) {
        OrderEntity orderEntity = order.getOrder();
        orderEntity.setModifyTime(new Date());
        this.save(orderEntity);

        List<OrderItemEntity> orderItems = order.getOrderItems();
        orderItems.forEach(l ->{
            orderItemService.save(l);
        });
     //   orderItemService.saveBatch(orderItems);
    }

    private OrderCreateTo createOrder(){
        OrderCreateTo ceateTo = new OrderCreateTo();
        //生成單號
        String orderSn = IdWorker.getTimeId();
        //建立訂單號
        OrderEntity entity = buildOrder(orderSn);
        //2 獲取到所有的訂單項
        List<OrderItemEntity> itemEntities = buildOrderItems();
        //3.計算價格相關
        computePrice(entity,itemEntities);

        ceateTo.setOrder(entity);
        ceateTo.setOrderItems(itemEntities);
        return ceateTo;

    }

    private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> itemEntities) {
        BigDecimal total = new BigDecimal("0.0");

        BigDecimal coupon = new BigDecimal("0.0");
        BigDecimal integration = new BigDecimal("0.0");
        BigDecimal promotion = new BigDecimal("0.0");

        BigDecimal gift = new BigDecimal("0.0");
        BigDecimal growth = new BigDecimal("0.0");
        for(OrderItemEntity entity:itemEntities){
            coupon = coupon.add(entity.getCouponAmount());
            integration = integration.add(entity.getIntegrationAmount());
            promotion = promotion.add(entity.getPromotionAmount());
            total = total.add(entity.getRealAmount());
            gift = gift.add(new BigDecimal(entity.getGiftIntegration().toString()));
            growth = growth.add(new BigDecimal(entity.getGiftGrowth().toString()));
        }
        orderEntity.setTotalAmount(total);
        orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));
        orderEntity.setIntegrationAmount(integration);
        orderEntity.setCouponAmount(coupon);

        //設定積分等資訊
        orderEntity.setIntegration(gift.intValue());
        orderEntity.setGrowth(growth.intValue());
        orderEntity.setDeleteStatus(0);
    }

    private OrderEntity buildOrder(String orderSn) {
        MemberRespVo respVo = LoginUserInterceptor.loginUser.get();
        OrderEntity entity = new OrderEntity();
        entity.setOrderSn(orderSn);
        entity.setMemberId(respVo.getId());

        OrderSubmitVo submitVo = confirmVoThreadLocal.get();
        //獲取地址的收貨資訊
        R fare = wmsFeignService.getFare(submitVo.getAddrId());
        FareVo fareResp = fare.getData(new TypeReference<FareVo>() {
        });
        entity.setFreightAmount(fareResp.getFare());
        entity.setReceiverCity(fareResp.getAddress().getCity());
        entity.setReceiverDetailAddress(fareResp.getAddress().getDetailAddress());
        entity.setReceiverName(fareResp.getAddress().getName());
        entity.setReceiverPhone(fareResp.getAddress().getPhone());
        entity.setReceiverPostCode(fareResp.getAddress().getPostCode());
        entity.setReceiverProvince(fareResp.getAddress().getProvince());
        entity.setReceiverRegion(fareResp.getAddress().getRegion());

        //設定訂單的相關狀態資訊
        entity.setStatus(1);
        entity.setAutoConfirmDay(7);
        return entity;
    }

    /**
     * 構建所有訂單項資料
     */
    private List<OrderItemEntity> buildOrderItems(){
        List<CartItem> currentUserCartItems = cartFeignService.getCurrentUserCartItems();
        if(currentUserCartItems !=null && currentUserCartItems.size()>0){
            List<OrderItemEntity> itemEntities = currentUserCartItems.stream().map(cartItem ->{
                OrderItemEntity itemEntity =buildOrderItem(cartItem);
                return itemEntity;
            }).collect(Collectors.toList());
            return itemEntities;
        }
        return null ;
    }

    private OrderItemEntity buildOrderItem(CartItem cartItem) {
        OrderItemEntity itemEntity = new OrderItemEntity();
        Long skuId = cartItem.getSkuId();

        R r = productFeignService.getSpuInfoBySkuId(skuId);
        SpuInfoVo data = r.getData(new TypeReference<SpuInfoVo>() {
        });
        itemEntity.setSpuId(data.getId());
        itemEntity.setSpuBrand(data.getBrandId().toString());
        itemEntity.setSpuName(data.getSpuName());
        itemEntity.setCategoryId(data.getCatalogId());
        //3.商品的sku資訊
        itemEntity.setSkuId(cartItem.getSkuId());
        itemEntity.setSkuName(cartItem.getTitle());
        itemEntity.setSkuPic(cartItem.getImages());
        itemEntity.setSkuPrice(cartItem.getPrice());
        String skuAttr = StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";");
        itemEntity.setSkuAttrsVals(skuAttr);
        itemEntity.setSkuQuantity(cartItem.getCount());

        //5.積分資訊
        itemEntity.setGiftGrowth(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());
        itemEntity.setGiftIntegration(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());

        //6.訂單項的價格資訊
        itemEntity.setPromotionAmount(new BigDecimal("0"));
        itemEntity.setCouponAmount(new BigDecimal("0"));
        itemEntity.setIntegrationAmount(new BigDecimal("0"));
        //當前訂單項的實際金額。總額-各種優惠
        BigDecimal orign = itemEntity.getSkuPrice().multiply(new BigDecimal(itemEntity.getSkuQuantity().toString()));
        BigDecimal subtract = orign.subtract(itemEntity.getCouponAmount())
                .subtract(itemEntity.getPromotionAmount())
                .subtract(itemEntity.getIntegrationAmount());
        itemEntity.setRealAmount(subtract);
        return itemEntity;
    }

}

鎖庫存的服務:

    @Override
    public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
        List<SkuHasStockVo> hasStockVoList = skuIds.stream().map(id -> {
            SkuHasStockVo vo = new SkuHasStockVo();
            Long count = baseMapper.getSkuStock(id);
            vo.setSkuId(id);
            vo.setHasStock(count ==null ?false:count > 0);
            return vo;
        }).collect(Collectors.toList());
        return hasStockVoList;
    }

    @Transactional
    @Override
    public Boolean orderLockStock(WareSkuLockVo vo) {
        //1. 找到每個商品在哪個倉庫都有庫存
        List<CartItem> locks = vo.getLocks();
        List<SkuWareHasStock> collect = locks.stream().map(item -> {
            SkuWareHasStock stock = new SkuWareHasStock();
            Long skuId = item.getSkuId();
            stock.setSkuId(skuId);
            stock.setNum(item.getCount());
            //查詢這個商品在哪裡有庫存
            List<Long> 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<Long> wareIds = hasStock.getWareId();
            if(wareIds == null || wareIds.size() ==0){
                throw new RRException(skuId+"不存在");
            }
            for (Long wareId : wareIds){
                //成功就是1,失敗就是0
                Long count = wareSkuDao.lockSkuStock(skuId, wareId, hasStock.getNum());
                if(count == 1){
                    skuStocked = true;
                    break;
                }else{
                    //當前倉庫鎖失敗,重試下一個倉庫
                }

            }

            if(skuStocked == false){
                throw new RRException(skuId+"不存在");
            }
        }

        return true;
    }

    @Data
    class SkuWareHasStock{
        private Long skuId;
        private List<Long> wareId;
        private Integer num;
    }

注意演示的時候把feign的超時時間都改大點,不然老是報錯,就很煩

feign:
  client:
    config:
      gulimall-ware:
        readTimeout: 12000
        connectTimeout: 12000
        loggerLevel: FULL
      gulimall-member:
        readTimeout: 12000
        connectTimeout: 12000
        loggerLevel: FULL
      gulimall-product:
        readTimeout: 12000
        connectTimeout: 12000
        loggerLevel: FULL
      gulimall-cart:
        readTimeout: 12000
        connectTimeout: 12000
        loggerLevel: FULL

演示如下: