国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

這篇具有很好參考價(jià)值的文章主要介紹了谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

導(dǎo)航:

谷粒商城筆記+踩坑匯總篇

目錄

1、訂單確認(rèn)頁

1.1、vo類抽取

1.2、獲取訂單詳情頁數(shù)據(jù),完整代碼

1.2.1、Controller編寫跳轉(zhuǎn)訂單確認(rèn)頁方法

1.2.2、Service獲取訂單詳情頁數(shù)據(jù)

1.3、【會(huì)員模塊】獲取會(huì)員所有收貨地址

1.3.1、controller

1.3.2、service?

1.4、訂單服務(wù)遠(yuǎn)程調(diào)用用戶服務(wù)

1.5、【購物車模塊】 獲取用戶選擇的所有CartItem

1.5.1、業(yè)務(wù)流程?

1.5.2、編寫Controller層接口

1.5.3、Service層實(shí)現(xiàn)類

1.5.4、【商品模塊】獲取指定商品的價(jià)格

1.5.5、購物車服務(wù)遠(yuǎn)程調(diào)用商品服務(wù)?

1.5.6、訂單服務(wù)遠(yuǎn)程調(diào)用購物車服務(wù)

1.6、Feign遠(yuǎn)程調(diào)用丟失請求頭問題

1.6.1、問題分析?

1.6.2、【訂單模塊】解決:配置類添加請求攔截器

1.7、異步線程丟失主線程請求頭問題

1.8、前端,訂單確認(rèn)頁渲染

1.9、訂單確認(rèn)頁里,商品的庫存查詢

1.10、根據(jù)用戶地址ID,返回詳細(xì)地址并計(jì)算物流費(fèi)

1.10.1、需求?

1.10.2、前端,選擇收貨地址頁面效果

1.10.3、?模型類抽取?

1.10.4、controller

1.10.5、倉庫模塊遠(yuǎn)程調(diào)用用戶模塊,查地址信息

1.10.6、service,根據(jù)地址id獲取地址信息和費(fèi)用

1.11、保證接口冪等性,防重復(fù)提交表單

1.11.1、冪等性概述

1.11.2、任務(wù)冪等性的三種保證方法

1.11.3、業(yè)務(wù)流程

1.11.4、代碼實(shí)現(xiàn),防重復(fù)提交表單,唯一序列號方式保證冪等性

11.1.5 測試


1、訂單確認(rèn)頁


1.1、vo類抽取

訂單確認(rèn)頁需要用的數(shù)據(jù)

  • 因?yàn)榇嬖诰W(wǎng)路延遲等問題,若一直點(diǎn)下單會(huì)下許多。所以我們需要防重令牌

com.atguigu.gulimall.order.vo

/**
 * Description: 訂單確認(rèn)頁需要用的數(shù)據(jù)
 */
public class OrderConfirmVo {

    /**
     * 收貨地址,ums_member_receive_address 表
     */
    @Setter@Getter
    List<MemberAddressVo> addressVos;

    /**
     * 所有選中的購物車項(xiàng)
     */
    @Setter@Getter
    List<OrderItemVo> items;

    // 發(fā)票記錄。。。

    /**
     * 優(yōu)惠券信息
     */
    @Setter@Getter
    Integer integration;
    /**
     * 是否有庫存
     */
    @Setter@Getter
    Map<Long,Boolean> stocks;

    /**
     * 防重令牌
     */
    @Setter@Getter
    String OrderToken;

    /**
     * @return  訂單總額
     * 所有選中商品項(xiàng)的價(jià)格 * 其數(shù)量
     */
    public BigDecimal getTotal() {
        BigDecimal sum =  new BigDecimal("0");
        if (items != null) {
            for (OrderItemVo item : items) {
                BigDecimal multiply = item.getPrice().multiply(new BigDecimal(item.getCount().toString()));
                sum = sum.add(multiply);
            }
        }
        return sum;
    }

    /**
     * 應(yīng)付價(jià)格
     */
    //BigDecimal pryPrice;
    public BigDecimal getPryPrice() {
        return getTotal();
    }


    public Integer getCount(){
        Integer i =0;
        if (items!=null){
            for (OrderItemVo item : items) {
               i+=item.getCount();
            }
        }
        return i;
    }
}

收貨地址,ums_member_receive_address 表

package com.atguigu.gulimall.order.vo;

@Data
public class OrderConfirmVo {

    /**
     * 收貨地址,ums_member_receive_address 表
     */
    List<MemberAddressVo> addressVos;

    /**
     * 所有選中的購物車項(xiàng)
     */
    List<OrderItemVo> items;

    // 發(fā)票記錄。。。

    /**
     * 優(yōu)惠券信息
     */
    Integer integration;

    /**
     * 訂單總額
     */
    BigDecimal total;

    /**
     * 應(yīng)付價(jià)格
     */
    BigDecimal pryPrice;
}

商品項(xiàng)信息

package com.atguigu.gulimall.order.vo;
@Data
public class OrderItemVo {
    /**
     * 商品Id
     */
    private Long skuId;
    /**
     * 商品標(biāo)題
     */
    private String title;
    /**
     * 商品圖片
     */
    private String image;
    /**
     * 商品套餐信
     */
    private List<String> skuAttr;
    /**
     * 商品價(jià)格
     */
    private BigDecimal price;
    /**
     * 數(shù)量
     */
    private Integer count;
    /**
     * 小計(jì)價(jià)格
     */
    private BigDecimal totalPrice;

}

1.2、獲取訂單詳情頁數(shù)據(jù),完整代碼


1.2.1、Controller編寫跳轉(zhuǎn)訂單確認(rèn)頁方法


com.atguigu.gulimall.order.web

@Controller
public class OrderWebController {

    @Autowired
    OrderService orderService;

//去結(jié)算確認(rèn)頁
    @GetMapping("/toTrade")
    public String toTrade(Model model){
        OrderConfirmVo confirmVo = orderService.confirmOrder();
        model.addAttribute("OrderConfirmData",confirmVo);
        return "confirm";
    }
}

1.2.2、Service獲取訂單詳情頁數(shù)據(jù)

業(yè)務(wù)流程:

  • 1、遠(yuǎn)程查詢所有的地址列表
  • 2、遠(yuǎn)程查詢購物車所有選中的購物項(xiàng)
  • 3、查詢用戶積分
  • 4、其他數(shù)據(jù)自動(dòng)計(jì)算
  • 5、防重令牌

?com.atguigu.gulimall.order.service.impl?

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

    @Autowired
    MemberFeignService memberFeignService;

    @Autowired
    CartFeignService cartFeignService;

    /**
     * 訂單確認(rèn)頁返回需要用的數(shù)據(jù)
     * @return
     */
    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {

        //構(gòu)建響應(yīng)模型類OrderConfirmVo
        OrderConfirmVo confirmVo = new OrderConfirmVo();

        //從攔截器ThreadLocal獲取當(dāng)前用戶登錄的信息
        MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();

        //TODO :獲取當(dāng)前線程請求頭信息(解決Feign異步調(diào)用丟失請求頭問題)
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        //開啟第一個(gè)異步任務(wù)
        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {

            //每一個(gè)線程都來共享之前的請求數(shù)據(jù)
            RequestContextHolder.setRequestAttributes(requestAttributes);

            //1、遠(yuǎn)程查詢所有的收獲地址列表
            List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
            confirmVo.setMemberAddressVos(address);
        }, threadPoolExecutor);

        //開啟第二個(gè)異步任務(wù)
        CompletableFuture<Void> cartInfoFuture = CompletableFuture.runAsync(() -> {

            //每一個(gè)線程都來共享之前的請求數(shù)據(jù)
            RequestContextHolder.setRequestAttributes(requestAttributes);

            //2、遠(yuǎn)程查詢購物車所有選中的購物項(xiàng)
            List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
            confirmVo.setItems(currentCartItems);
            //feign在遠(yuǎn)程調(diào)用之前要構(gòu)造請求,調(diào)用很多的攔截器
        }, threadPoolExecutor).thenRunAsync(() -> {
            List<OrderItemVo> items = confirmVo.getItems();
            //獲取全部商品的id
            List<Long> skuIds = items.stream()
                    .map((itemVo -> itemVo.getSkuId()))
                    .collect(Collectors.toList());

            //遠(yuǎn)程查詢商品庫存信息
            R skuHasStock = wmsFeignService.getSkuHasStock(skuIds);
            List<SkuStockVo> skuStockVos = skuHasStock.getData("data", new TypeReference<List<SkuStockVo>>() {});

            if (skuStockVos != null && skuStockVos.size() > 0) {
                //將skuStockVos集合轉(zhuǎn)換為map
                Map<Long, Boolean> skuHasStockMap = skuStockVos.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                confirmVo.setStocks(skuHasStockMap);
            }
        },threadPoolExecutor);

        //3、查詢用戶積分
        Integer integration = memberResponseVo.getIntegration();
        confirmVo.setIntegration(integration);

        //4、價(jià)格數(shù)據(jù)自動(dòng)計(jì)算

        //TODO 5、防重令牌(防止表單重復(fù)提交)
        //為用戶設(shè)置一個(gè)token,三十分鐘過期時(shí)間(存在redis)
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().set(USER_ORDER_TOKEN_PREFIX+memberResponseVo.getId(),token,30, TimeUnit.MINUTES);
        confirmVo.setOrderToken(token);


        CompletableFuture.allOf(addressFuture,cartInfoFuture).get();

        return confirmVo;
    }


}

1.3、【會(huì)員模塊】獲取會(huì)員所有收貨地址

1.3.1、controller

package com.atguigu.gulimall.member.controller;

@RestController
@RequestMapping("member/memberreceiveaddress")
public class MemberReceiveAddressController {
    @Autowired
    private MemberReceiveAddressService memberReceiveAddressService;

    @GetMapping("/{memberId}/address")
    public List<MemberReceiveAddressEntity> getAddress(@PathVariable("memberId") Long memberId) {
        return memberReceiveAddressService.getAddress(memberId);
    }

1.3.2、service?

com.atguigu.gulimall.member.service.impl

@Override
public List<MemberReceiveAddressEntity> getAddress(Long memberId) {
  return this.list(new QueryWrapper<MemberReceiveAddressEntity>().eq("member_id", memberId));
}

1.4、訂單服務(wù)遠(yuǎn)程調(diào)用用戶服務(wù)

package com.atguigu.gulimall.order.feign;

@FeignClient("gulimall-member")
public interface MemberFeignService {

    /**
     * 返回會(huì)員所有的收貨地址列表
     * @param memberId 會(huì)員ID
     * @return
     */
    @GetMapping("/member/memberreceiveaddress/{memberId}/address")
    List<MemberAddressVo> getAddress(@PathVariable("memberId") Long memberId);

}

1.5、【購物車模塊】 獲取用戶選擇的所有CartItem


1.5.1、業(yè)務(wù)流程?

  1. 首先通過用戶ID在Redis中查詢到購物車中的所有的購物項(xiàng)
  2. 通過 filter 過濾 用戶購物車中被選擇的購物項(xiàng)
  3. 查詢數(shù)據(jù)庫中當(dāng)前購物項(xiàng)的價(jià)格,不能使用之前加入購物車的價(jià)格
  4. 編寫遠(yuǎn)程 gulimall-product 服務(wù)中的 查詢sku價(jià)格接口

1.5.2、編寫Controller層接口

編寫 gulimall-cart 服務(wù)中 package com.atguigu.cart.controller; 路徑下的 CartController 類:

package com.atguigu.cart.controller;

@Controller
public class CartController {

    @Autowired
    CartService cartService;

    @GetMapping("/currentUserCartItems")
    @ResponseBody
    public List<CartItem> getCurrentUserCartItems(){
        return cartService.getUserCartItems();
    }
  
  	//....
}

1.5.3、Service層實(shí)現(xiàn)類

編寫 gulimall-cart 服務(wù)中 com.atguigu.cart.service.impl 路徑中 CartServiceImpl 類

@Autowired
ProductFeignService productFeignService;

/**
* 獲取用戶選擇的所有購物項(xiàng)
* @return
*/
@Override
public List<CartItem> getUserCartItems() {
  UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
  if (userInfoTo.getUserId() == null) {
    return null;
  } else {
    String cartKey = CART_PREFIX + userInfoTo.getUserId();
    // 獲取所有用戶選擇的購物項(xiàng)
    List<CartItem> collect = getCartItems(cartKey).stream()
      .filter(item -> item.getCheck())
      .map(item->{
        // TODO 1、更新為最新價(jià)格
        R price = productFeignService.getPrice(item.getSkuId());
        String data = (String) price.get("data");
        item.setPrice(new BigDecimal(data));
        return item;
      })
      .collect(Collectors.toList());
    return collect;
  }
}

1.5.4、【商品模塊】獲取指定商品的價(jià)格

Gulimall-product 服務(wù)中 com.atguigu.gulimall.product.app 路徑下的 SkuInfoController

package com.atguigu.gulimall.product.app;

@RestController
@RequestMapping("product/skuinfo")
public class SkuInfoController {
    @Autowired
    private SkuInfoService skuInfoService;

    /**
     * 獲取指定商品的價(jià)格
     * @param skuId
     * @return
     */
    @GetMapping("/{skuId}/price")
    public R getPrice(@PathVariable("skuId") Long skuId){
        SkuInfoEntity skuInfoEntity = skuInfoService.getById(skuId);
        return R.ok().setData(skuInfoEntity.getPrice().toString());
    }

1.5.5、購物車服務(wù)遠(yuǎn)程調(diào)用商品服務(wù)?

package com.atguigu.cart.feign;
@FeignClient("gulimall-product")
public interface ProductFeignService {

    //.....

    @GetMapping("/product/skuinfo/{skuId}/price")
    R getPrice(@PathVariable("skuId") Long skuId);
}

1.5.6、訂單服務(wù)遠(yuǎn)程調(diào)用購物車服務(wù)

package com.atguigu.gulimall.order.feign;

@FeignClient("gulimall-cart")
public interface CartFeignService {

    @GetMapping("/currentUserCartItems")
    List<OrderItemVo> getCurrentUserCartItems();
}


?

1.6、Feign遠(yuǎn)程調(diào)用丟失請求頭問題

1.6.1、問題分析?


問題 :Feign遠(yuǎn)程調(diào)用的時(shí)候會(huì)丟失請求頭

原因:遠(yuǎn)程調(diào)用是一個(gè)新的請求,不攜帶之前請求的cookie,導(dǎo)致購物車服務(wù)得不到請求頭cookie里的登錄信息。

解決:加上feign遠(yuǎn)程調(diào)用的請求攔截器。(RequestInterceptor)

因?yàn)閒eign在遠(yuǎn)程調(diào)用之前會(huì)執(zhí)行所有的RequestInterceptor攔截器

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性
谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

1.6.2、【訂單模塊】解決:配置類添加請求攔截器

新請求同步cookie到請求頭里

package com.atguigu.gulimall.order.config;

@Configuration
public class GulimallFeignConfig {

    /**
     * feign在遠(yuǎn)程調(diào)用之前會(huì)執(zhí)行所有的RequestInterceptor攔截器
     * @return
     */
    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor(){
            @Override
            public void apply(RequestTemplate requestTemplate) {
                // 1、使用 RequestContextHolder 拿到請求數(shù)據(jù),RequestContextHolder底層使用過線程共享數(shù)據(jù) ThreadLocal<RequestAttributes>
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                if (attributes!=null){
                    HttpServletRequest request = attributes.getRequest();
                    // 2、同步請求頭數(shù)據(jù),Cookie
                    String cookie = request.getHeader("Cookie");
                    // 給新請求同步了老請求的cookie
                    requestTemplate.header("Cookie",cookie);
                }
            }
        };
    }
}

1.7、異步線程丟失主線程請求頭問題


問題演示,刪除紅框代碼:

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

上面完整代碼里service里,已經(jīng)解決了異步編排請求頭丟失問題,我們可以刪除再調(diào)試:

發(fā)現(xiàn)報(bào)錯(cuò),報(bào)錯(cuò)原因是沒有登錄(因?yàn)?strong>遠(yuǎn)程調(diào)用線程丟失了請求頭,ThreadLocal里也就獲取不到登錄信息)。

問題
由于 RequestContextHolder底層使用的是線程共享數(shù)據(jù) ThreadLocal<RequestAttributes>,我們知道線程共享數(shù)據(jù)的域是 當(dāng)前線程下,線程之間是不共享的。所以在開啟異步后,異步線程獲取不到主線程請求的信息,自然也就無法共享cookie了。

解決
向異步 RequestContextHolder 線程域中放主線程的域。

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

修改 gulimall-order 服務(wù)中 com.atguigu.gulimall.order.service.impl 目錄下的 OrderServiceImpl 類

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

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

    // 獲取主線程的域
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    // 1、遠(yuǎn)程查詢所有的地址列表
    CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
        RequestContextHolder.setRequestAttributes(requestAttributes);
        // 將主線程的域放在該線程的域中
        List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
        confirmVo.setAddressVos(address);
    }, executor);

    // 2、遠(yuǎn)程查詢購物車所有選中的購物項(xiàng)
    CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
        // 將老請求的域放在該線程的域中
        RequestContextHolder.setRequestAttributes(requestAttributes);
        List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
        confirmVo.setItems(items);
    }, executor);


    // feign在遠(yuǎn)程調(diào)用請求之前要構(gòu)造

    // 3、查詢用戶積分
    Integer integration = memberRespVo.getIntegration();
    confirmVo.setIntegration(integration);

    // 4、其他數(shù)據(jù)自動(dòng)計(jì)算

    // TODO 5、防重令牌
    CompletableFuture.allOf(getAddressFuture,cartFuture).get();
    return confirmVo;
}

1.8、前端,訂單確認(rèn)頁渲染


谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

修改 gulimall-order 服務(wù)中,src/main/resources/templates/路徑下的 confirm.html

<!--主體部分-->
<p class="p1">填寫并核對訂單信息</p>
<div class="section">
   <!--收貨人信息-->
   <div class="top-2">
      <span>收貨人信息</span>
      <span>新增收貨地址</span>
   </div>

   <!--地址-->
   <div class="top-3" th:each="addr:${orderConfirmData.addressVos}">
      <p>[[${addr.name}]]</p><span>[[${addr.name}]]  [[${addr.province}]]  [[${addr.city}]] [[${addr.detailAddress}]] [[${addr.phone}]]</span>
   </div>
   <p class="p2">更多地址︾</p>
   <div class="hh1"/></div>
<div class="xia">
   <div class="qian">
      <p class="qian_y">
         <span>[[${orderConfirmData.count}]]</span>
         <span>件商品,總商品金額:</span>
         <span class="rmb">¥[[${#numbers.formatDecimal(orderConfirmData.total,1,2)}]]</span>
      </p>
      <p class="qian_y">
         <span>返現(xiàn):</span>
         <span class="rmb">  -¥0.00</span>
      </p>
      <p class="qian_y">
         <span>運(yùn)費(fèi): </span>
         <span class="rmb"> ? ¥0.00</span>
      </p>
      <p class="qian_y">
         <span>服務(wù)費(fèi): </span>
         <span class="rmb"> ? ¥0.00</span>
      </p>
      <p class="qian_y">
         <span>退換無憂: </span>
         <span class="rmb"> ? ¥0.00</span>
      </p>

   </div>

   <div class="yfze">
      <p class="yfze_a"><span class="z">應(yīng)付總額:</span><span class="hq">¥[[${#numbers.formatDecimal(orderConfirmData.pryPrice,1,2)}]]</span></p>
      <p class="yfze_b">寄送至:  IT-中心研發(fā)二部 收貨人:</p>
   </div>
   <button class="tijiao">提交訂單</button>
</div>

1.9、訂單確認(rèn)頁里,商品的庫存查詢


需求:

在遠(yuǎn)程查詢購物車所有選中的購物項(xiàng)之后進(jìn)行 批量查詢庫存

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

1)、在訂單確認(rèn)頁數(shù)據(jù)獲取 Service層實(shí)現(xiàn)類 OrderServiceImpl 方法中進(jìn)行批量查詢庫存

Gulimall-order 服務(wù)中 com.atguigu.gulimall.order.service.impl 路徑下的 OrderServiceImpl 類

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

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

        // 獲取主線程的請求域
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 1、遠(yuǎn)程查詢所有的地址列表
        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            // 將主線程的請求域放在該線程請求域中
            List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
            confirmVo.setAddressVos(address);
        }, executor);

        // 2、遠(yuǎn)程查詢購物車所有選中的購物項(xiàng)
        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            // 將主線程的請求域放在該線程請求域中
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
            confirmVo.setItems(items);
        }, executor).thenRunAsync(()->{
            // 批量查詢商品項(xiàng)庫存
            List<OrderItemVo> items = confirmVo.getItems();
            List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
            R hasStock = wareFeignService.getSkusHasStock(collect);
            List<SkuStockVo> data = hasStock.getData(new TypeReference<List<SkuStockVo>>() {
            });
            if (data != null) {
                Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                confirmVo.setStocks(map);
            }
        }, executor);

        // feign在遠(yuǎn)程調(diào)用請求之前要構(gòu)造

        // 3、查詢用戶積分
        Integer integration = memberRespVo.getIntegration();
        confirmVo.setIntegration(integration);

        // 4、其他數(shù)據(jù)自動(dòng)計(jì)算

        // TODO 5、防重令牌
        CompletableFuture.allOf(getAddressFuture,cartFuture).get();
        return confirmVo;
    }

2)、在gulimall-order 服務(wù)中創(chuàng)建商品是否有庫存的VO類

在 Gulimall-order 服務(wù)中 package com.atguigu.gulimall.order.vo 路徑下創(chuàng)建 SkuStockVo 類

package com.atguigu.gulimall.order.vo;
@Data
public class SkuStockVo {
    private Long skuId;
    private Boolean hasStock;
}

3)、gulimall-ware 庫存服務(wù)中提供 查詢庫存的接口

gulimall-ware 服務(wù)中 com.atguigu.gulimall.ware.controller 路徑下的 WareSkuController 類,之前編寫過。

package com.atguigu.gulimall.ware.controller;

@RestController
@RequestMapping("ware/waresku")
public class WareSkuController {
    @Autowired
    private WareSkuService wareSkuService;


    // 查詢sku是否有庫存
    @PostMapping("/hasstock")
    public R getSkusHasStock(@RequestBody List<Long> skuIds){
        // sku_id,stock
        List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds);

        return R.ok().setData(vos);
    }
  //....
}

gulimall-order 服務(wù)中編寫遠(yuǎn)程調(diào)用 gulimall-ware 庫存服務(wù)中 查詢庫存 feign接口
gulimall-order 服務(wù)下 com.atguigu.gulimall.order.feign 路徑下:WareFeignService

package com.atguigu.gulimall.order.feign;

@FeignClient("gulimall-ware")
public interface WareFeignService {

    @PostMapping("/ware/waresku/hasstock")
    R getSkusHasStock(@RequestBody List<Long> skuIds);
}

4)、頁面效果

[[${orderConfirmData.stocks[item.skuId]?"有貨":"無貨"}]]

<div class="mi">
   <p>[[${item.title}]]<span style="color: red;"> ¥ [[${#numbers.formatDecimal(item.price,1,2)}]]</span> <span> x[[${item.count}]]</span> <span>[[${orderConfirmData.stocks[item.skuId]?"有貨":"無貨"}]]</span></p>
   <p><span>0.095kg</span></p>
   <p class="tui-1"><img src="/static/order/confirm/img/i_07.png" />支持7天無理由退貨</p>
</div>

1.10、根據(jù)用戶地址ID,返回詳細(xì)地址并計(jì)算物流費(fèi)

1.10.1、需求?


需求:選擇收貨地址,計(jì)算物流費(fèi)

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

1.10.2、前端,選擇收貨地址頁面效果

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

function highlight(){
   $(".addr-item p").css({"border": "2px solid gray"});
   $(".addr-item p[def='1']").css({"border": "2px solid red"});
}
$(".addr-item p").click(function () {
   $(".addr-item p").attr("def","0");
   $(this).attr("def","1");
   highlight();
   // 獲取當(dāng)前地址id
   var addrId = $(this).attr("addrId");
   // 發(fā)送ajax獲取運(yùn)費(fèi)信息
   getFare(addrId);
});
function getFare(addrId) {
   $.get("http://gulimall.cn/api/ware/wareinfo/fare?addrId="+addrId,function (resp) {
      console.log(resp);
      $("#fareEle").text(resp.data.fare);
      var total = [[${orderConfirmData.total}]]
      // 設(shè)置運(yùn)費(fèi)信息
      $("#payPriceEle").text(total*1 + resp.data.fare*1);
      // 設(shè)置收貨人信息
      $("#reciveAddressEle").text(resp.data.address.province+" " + resp.data.address.region+ "" + resp.data.address.detailAddress);
      $("#reveiverEle").text(resp.data.address.name);
   })
}

1.10.3、?模型類抽取?

gulimall-ware 服務(wù)中 com.atguigu.gulimall.ware.vo路徑下的 Vo

@Data
public class FareVo {
    private MemberAddressVo addressVo;
    private BigDecimal fare;
}

1.10.4、controller


gulimall-ware倉儲服務(wù)編寫 根據(jù)用戶地址,返回詳細(xì)地址并計(jì)算物流費(fèi)h

package com.atguigu.gulimall.ware.controller;

@RestController
@RequestMapping("ware/wareinfo")
public class WareInfoController {
    @Autowired
    private WareInfoService wareInfoService;

    @GetMapping("/fare")
    public R getFare(@RequestParam("addrId") Long addrId){
        FareVo fare = wareInfoService.getFare(addrId);
        return R.ok().setData(fare);
    }
  //...
}

1.10.5、倉庫模塊遠(yuǎn)程調(diào)用用戶模塊,查地址信息

package com.atguigu.gulimall.ware.feign;

@FeignClient("gulimall-member")
public interface MemberFeignService {

    /**
     * 根據(jù)地址id查詢地址的詳細(xì)信息
     * @param id
     * @return
     */
    @RequestMapping("/member/memberreceiveaddress/info/{id}")
    R addrInfo(@PathVariable("id") Long id);
}

1.10.6、service,根據(jù)地址id獲取地址信息和費(fèi)用

gulimall-ware 服務(wù)中 com.atguigu.gulimall.ware.service.impl路徑下 WareInfoServiceImpl 類

@Override
public FareVo getFare(Long addrId) {

  FareVo fareVo = new FareVo();
  R r = memberFeignService.addrInfo(addrId);
  MemberAddressVo data = r.getData("memberReceiveAddress",new TypeReference<MemberAddressVo>() {
  });
  if (data!=null) {
    // 簡單處理:截取手機(jī)號最后一位作為郵費(fèi)
    String phone = data.getPhone();
    String substring = phone.substring(phone.length() - 1, phone.length());
    BigDecimal bigDecimal = new BigDecimal(substring);
    fareVo.setAddressVo(data);
    fareVo.setFare(bigDecimal);
    return fareVo;
  }
  return null;
}


?

1.11、保證接口冪等性,防重復(fù)提交表單

1.11.1、冪等性概述

接口冪等性就是用戶對于同一操作發(fā)起的一次請求或者多次請求的結(jié)果是一致的。

  • 接口冪等性
    接口冪等性就是用戶對于同一操作發(fā)起的一次請求或者多次請求的結(jié)果是一致的,不會(huì)因?yàn)槎啻吸c(diǎn)擊而產(chǎn)生了副作用,比如說支付場景,用戶購買了商品支付扣款成功,但是返回結(jié)果的時(shí)候網(wǎng)絡(luò)異常,此時(shí)錢已經(jīng)扣了,用戶再次點(diǎn)擊按鈕,此時(shí)會(huì)進(jìn)行第二次扣款,返回結(jié)果成功,用戶查詢余額返發(fā)現(xiàn)多扣錢了,流水記錄也交成了兩條這就沒有保證接口的冪等性。
  • 哪些情況需要防止:
    • 用戶多次點(diǎn)擊按鈕
    • 用戶頁面回退再次提交
    • 微服務(wù)互相調(diào)用,由于網(wǎng)絡(luò)問題,導(dǎo)致請求失敗。feign 觸發(fā)重試機(jī)制
      其他業(yè)務(wù)情況
  • 冪等性解決方案
    • 1、token機(jī)制(令牌機(jī)制)本項(xiàng)目采用令牌機(jī)制
    • 2、各種鎖機(jī)制
    • 3、各種唯一性約束
    • 4、防重表
    • 5、全球請求唯一id

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

1.11.2、任務(wù)冪等性的三種保證方法

  • 數(shù)據(jù)庫約束:比如唯一約束,主鍵。同一個(gè)主鍵不可能兩次都插入成功。不推薦因?yàn)檫m用范圍太窄,只適用于保存數(shù)據(jù)庫前就已經(jīng)設(shè)置好主鍵并且每次主鍵一樣的情況下。

  • 樂觀鎖:數(shù)據(jù)庫表中增加一個(gè)版本字段,更新時(shí)判斷是否等于某個(gè)版本。例如重復(fù)提交時(shí)判斷數(shù)據(jù)庫發(fā)現(xiàn)版本已被改變就不提交了。不推薦,因?yàn)橐閿?shù)據(jù)庫,給數(shù)據(jù)庫壓力,臨時(shí)的操作我們盡量在緩存庫里操作,降低數(shù)據(jù)庫壓力。

  • Redis唯一序列號(推薦):Redis鍵為任務(wù)id,值為隨機(jī)序列化uuid。請求前生成唯一的序列號,攜帶序列號去請求,請求時(shí)在redis記錄該序列號表示以該序列號的請求執(zhí)行過了,如果相同的序列號再次來執(zhí)行說明是重復(fù)執(zhí)行。也可以通過讓用戶每次提交時(shí)輸入驗(yàn)證碼,提交后校驗(yàn)前后端驗(yàn)證碼實(shí)現(xiàn)冪等性。

1.11.3、業(yè)務(wù)流程

需求:用戶進(jìn)入訂單確認(rèn)頁,在不刷新、不重進(jìn)的情況下,重復(fù)點(diǎn)擊“提交訂單”,只有一次能提交成功。

確認(rèn)訂單:?生成令牌:redis添加數(shù)據(jù),key為"order:token"+用戶id,value為防重復(fù)提交表單的uuid作為token,并設(shè)置30min過期時(shí)間。?

提交訂單(下一篇文章詳細(xì)講):

驗(yàn)令牌:先獲取前端傳來的token,再根據(jù)用戶id查詢Redis里的token,比較兩個(gè)token是否相等,相等則代表是同一個(gè)的訂單。因?yàn)閡uid能保證唯一性,它是根據(jù)時(shí)間戳和mac地址生成的。

原子性驗(yàn)刪令牌:驗(yàn)令牌和刪除令牌寫成一個(gè)lua腳本,Redis傳參鍵值對并執(zhí)行l(wèi)ua腳本,執(zhí)行成功代表驗(yàn)證成功,執(zhí)行失敗代表驗(yàn)證失敗。

1.11.4、代碼實(shí)現(xiàn),防重復(fù)提交表單,唯一序列號方式保證冪等性

gulimall-order服務(wù) com.atguigu.gulimall.order.service.impl路徑下的 OrderServiceImpl

簡潔版:

    /**
     * 確認(rèn)訂單、訂單確認(rèn)頁返回需要用的數(shù)據(jù)
     * @return
     */
    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        //構(gòu)建OrderConfirmVo,查登錄、查詢庫存、購物車、商品id
        //5、防重令牌(防止表單重復(fù)提交)
        //為用戶設(shè)置一個(gè)token,三十分鐘過期時(shí)間(存在redis)
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().set(USER_ORDER_TOKEN_PREFIX+memberResponseVo.getId(),token,30, TimeUnit.MINUTES);
        confirmVo.setOrderToken(token);
        return confirmVo;
    }


/**
 * 下單操作:驗(yàn)令牌、創(chuàng)建訂單、驗(yàn)價(jià)格、驗(yàn)庫存
 * @param vo
 * @return
 */
@Override
public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {
    SubmitOrderResponseVo response = new SubmitOrderResponseVo();
 
    // 從攔截器中拿到當(dāng)前的用戶
    MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
    // 1、驗(yàn)證令牌【令牌的對比和刪除必須保證原子性】,通過使用腳本來完成(0:令牌校驗(yàn)失敗; 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();
    // 原子驗(yàn)證令牌和刪除令牌
    Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(),
            OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), orderToken);
    if (result == 0L) {
        // 令牌驗(yàn)證失敗
        response.setCode(1);
        return response;
    } else {
        // 令牌驗(yàn)證成功
        return response;
    }
}

前端會(huì)顯示令牌:

    <form action="http://order.gulimall.com/submitOrder" method="post">
		<input id="addrInput" type="hidden" name="addrId" />
		<input id="payPriceInput" type="hidden" name="payPrice">
        <input name="orderToken" th:value="${confirmOrderData.orderToken}" type="hidden"/>
        <button class="tijiao" type="submit">提交訂單</button>
    </form>

11.1.5 測試

重啟服務(wù),進(jìn)入訂單確認(rèn)頁,暫時(shí)刪去前端<input>里的type="hidden",可以看見令牌:

谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性

測試發(fā)現(xiàn),刷新頁面,令牌會(huì)更改?;赝撕笾匦逻M(jìn)入確認(rèn)頁,令牌會(huì)更改。

我(id是66)在不刷新的情況下,連續(xù)點(diǎn)擊2次提交,傳到后端的token都是一樣的,記為token1,Redis存"order:token66"--->token1。

  • 線程A:“提交訂單”的controller接收到token1,原子性對比redis里的token1和刪除,校驗(yàn)通過;
  • 線程B:“提交訂單”的controller接收到token1,原子性對比redis里的token1和刪除,因?yàn)榫€程A已經(jīng)刪除成功,所以現(xiàn)在校驗(yàn)失敗或者刪除失敗,所以校驗(yàn)失敗。

我刷新一下,再次點(diǎn)擊2次提交,傳到后端的token都是一樣的,記為token2,Redis存"order:token66"--->token2。文章來源地址http://www.zghlxwxcb.cn/news/detail-405826.html

  • 線程C:“提交訂單”的controller接收到token2,原子性對比redis里的token2和刪除,校驗(yàn)通過;
  • 線程D:“提交訂單”的controller接收到token2,原子性對比redis里的token2和刪除,因?yàn)榫€程C已經(jīng)刪除成功,所以現(xiàn)在校驗(yàn)失敗或者刪除失敗,所以校驗(yàn)失敗。

到了這里,關(guān)于谷粒商城筆記+踩坑(20)——訂單確認(rèn)頁。feign、異步請求頭丟失問題+令牌保證冪等性的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包