电商项目——订单服务——第十二章——中篇

电商项目——全文检索-ElasticSearch——第一章——中篇
电商项目——商城业务-商品上架——第二章——中篇
电商项目——商城业务-首页——第三章——中篇
电商项目——性能压测——第四章——中篇
电商项目——缓存——第五章——中篇
电商项目——商城业务-检索服务——第六章——中篇
电商项目——商城业务-异步——第七章——中篇
电商项目——商品详情——第八章——中篇
电商项目——认证服务——第九章——中篇
电商项目——购物车——第十章——中篇
电商项目——消息队列——第十一章——中篇
电商项目——订单服务——第十二章——中篇
电商项目——分布式事务——第十三章——中篇

1:订单基本概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2:订单登录拦截

mall-order
OrderInterceptor

@Controller
public class OrderInterceptor 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.mall.com/login.html");
            return false;

        }
    }
}

MallWebConfig

@Configuration
public class MallWebConfig implements WebMvcConfigurer{

    /**
     * 添加一个拦截器,拦截器才可以工作
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截所有的请求/**
        registry.addInterceptor(new OrderInterceptor()).addPathPatterns("/**");
    }

}

OrderWebController

@Controller
public class OrderWebController {

    @GetMapping("toTrade")
    public String toTrade(){
        return "confirm";
    }

}

3:订单确认页模型抽取

mall-order
MemberAddressVo

@Data
public class MemberAddressVo {
    private Long id;
    /**
     * member_id
     */
    private Long memberId;
    /**
     * 收货人姓名
     */
    private String name;
    /**
     * 电话
     */
    private String phone;
    /**
     * 邮政编码
     */
    private String postCode;
    /**
     * 省份/直辖市
     */
    private String province;
    /**
     * 城市
     */
    private String city;
    /**
     * 区
     */
    private String region;
    /**
     * 详细地址(街道)
     */
    private String detailAddress;
    /**
     * 省市区代码
     */
    private String areacode;
    /**
     * 是否默认
     */
    private Integer defaultStatus;
}

OrderConfirmVo

//订单确认页需要的数据

public class OrderConfirmVo {

    @Getter @Setter
    //收货地址 ums_member_receive_address表
    List<MemberAddressVo> address;
    @Getter @Setter

    //所有选中的购物项
    List<OrderItemVo> items;
    @Getter @Setter

    //发票信息
    //优惠劵信息
    Integer integer;

    BigDecimal total;//订单总额

    public BigDecimal getTotal() {

        BigDecimal sum = new BigDecimal(0);
        if (items!=null) {
            for (OrderItemVo item : items
                    ) {

                BigDecimal peritems = item.getPrice().multiply(new BigDecimal(item.getCount().toString()));
                sum= sum.add(peritems);
            }


        }
        return sum;
    }

    BigDecimal payPrice;//应付价格
    public BigDecimal getpayPrice() {

        BigDecimal sum = new BigDecimal(0);
        if (items!=null) {
            for (OrderItemVo item : items
                    ) {

                BigDecimal peritems = item.getPrice().multiply(new BigDecimal(item.getCount().toString()));
                sum= sum.add(peritems);
            }


        }
        return sum;
    }

    //防重令牌
    @Getter @Setter
    String orderToken;
}

OrderItemVo

@Data
public class OrderItemVo {
    private Long skuId;
    private Boolean check=true;
    private String title;
    private String image;
    private List<String> skuAttr;
    private BigDecimal price;
    private Integer count;
    private BigDecimal totalPrice;
}

4:订单确认页数据获取

完成如下模块的后端代码
在这里插入图片描述

//订单确认页需要的数据
//订单确认页需要的数据
public class OrderConfirmVo {
    @Getter @Setter
    //收货地址 ums_member_receive_address表
    List<MemberAddressVo> address;
    @Getter @Setter

    //所有选中的购物项
    List<OrderItemVo> items;
    @Getter @Setter

    //发票信息
    //优惠劵信息
    Integer integer;

    BigDecimal total;//订单总额

    public BigDecimal getTotal() {

        BigDecimal sum = new BigDecimal(0);
        if (items!=null) {
            for (OrderItemVo item : items
                    ) {

                BigDecimal peritems = item.getPrice().multiply(new BigDecimal(item.getCount().toString()));
                sum= sum.add(peritems);
            }


        }
        return sum;
    }
    BigDecimal payPrice;//应付价格
    public BigDecimal getpayPrice() {

        BigDecimal sum = new BigDecimal(0);
        if (items!=null) {
            for (OrderItemVo item : items
                    ) {

                BigDecimal peritems = item.getPrice().multiply(new BigDecimal(item.getCount().toString()));
                sum= sum.add(peritems);
            }


        }
        return sum;
    }

    //防重令牌
    @Getter @Setter
    String orderToken;
}

OrderWebController

@Controller
public class OrderWebController {

     /**
     * 给订单确认页返回需要的数据confirm
     * @return
     */
    @Override
    public OrderConfirmVo confirmOrder() {
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        //使用拦截器里面的threalocal
        MemberRespVo memberRespVo = OrderInterceptor.loginUser.get();
        //1:远程查询所有的收货地址列表。。。远程调用查询收货地址ums_member_receive_address表  mall-member
        List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());

        //2:远程查询购物车所有选中的购物项 mall-order
        List<OrderItemVo> currentUserCartItem = cartFeignService.getCurrentUserCartItem();
        //3:查询用户积分
        Integer integration = memberRespVo.getIntegration();


        confirmVo.setInteger(integration);
        confirmVo.setAddress(address);
        confirmVo.setItems(currentUserCartItem);

        //4:一些其他数据

        //5:todo 防重令牌
        return confirmVo;
    }

}

5:Feign远程调用丢失请求头问题和Feign异步调用丢失请求头问题

  • Feign远程调用丢失请求头问题

在这里插入图片描述

在mall-order中加入下代码解决Feign远程调用丢失请求头问题
MallFeignConfig

//通过feign调用的源码我们知道,feign远程调用的时候会丢失请求头
@Configuration
public class MallFeignConfig {

    @Bean("")
    public RequestInterceptor requestInterceptor(){

        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {

                //可以使用RequestContextHolder拿到刚进来的这个请求的数据(http://order.mall.com/toTrade)
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = requestAttributes.getRequest();
                System.out.println("feign远程之前先进行RequestInterceptor.apply");
                //同步请求头数据,cookie,为了发送到cart,让他知道order发送了cookie给cart,请求头不会丢失
                String cookie = request.getHeader("Cookie");
                //给新请求同步了老请求的cookie
                requestTemplate.header("Cookie",cookie);

            }
        };
    }
}

我们以后要使用异步的方式给订单确认页返回需要的数据confirm
如下代码
OrderServiceImpl

 /**
     * 给订单确认页返回需要的数据confirm
     * @return
     */
    @Override
    public OrderConfirmVo confirmOrder() {

        OrderConfirmVo confirmVo = new OrderConfirmVo();

        //使用拦截器里面的threalocal
        MemberRespVo memberRespVo = OrderInterceptor.loginUser.get();

        CompletableFuture<Void> getaddress = CompletableFuture.runAsync(() -> {
            //1:远程查询所有的收货地址列表。。。远程调用查询收货地址ums_member_receive_address表  mall-member
            List<MemberAddressVo> address = membersFeignService.getAddress(memberRespVo.getId());
            confirmVo.setAddress(address);

        }, executor);

        CompletableFuture<Void> getcurrentUserCartItem = CompletableFuture.runAsync(() -> {
            //2:远程查询购物车所有选中的购物项 mall-order
            List<OrderItemVo> currentUserCartItem = cartFeignService.getCurrentUserCartItem();
            confirmVo.setItems(currentUserCartItem);

        }, executor);

        CompletableFuture<Void> getintegration = CompletableFuture.runAsync(() -> {
            //3:查询用户积分
            Integer integration = memberRespVo.getIntegration();
            confirmVo.setInteger(integration);

        }, executor);

        try {
            CompletableFuture.allOf(getaddress,getintegration,getcurrentUserCartItem).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //4:一些其他数据

        //5:todo 防重令牌
        return confirmVo;
    }
  • Feign异步调用丢失请求头问题
    在这里插入图片描述
    解决方案:我们就要在service中共享它的请求头,代码如下
    (每一个父线程进来,我们还是使用RequestContextHolder进行子线程的共享)
    OrderServiceImpl
 /**
     * 给订单确认页返回需要的数据confirm
     * @return
     */
    @Override
    public OrderConfirmVo confirmOrder() {

        OrderConfirmVo confirmVo = new OrderConfirmVo();

        //使用拦截器里面的threalocal
        MemberRespVo memberRespVo = OrderInterceptor.loginUser.get();

        System.out.println("主线程。。。"+Thread.currentThread().getId());

        //得到/addtTocart的请求进来的头数据
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        CompletableFuture<Void> getaddress = CompletableFuture.runAsync(() -> {
            System.out.println("address线程。。。"+Thread.currentThread().getId());
            //每一个线程都来共享之前的数据

            RequestContextHolder.setRequestAttributes(requestAttributes);
            //1:远程查询所有的收货地址列表。。。远程调用查询收货地址ums_member_receive_address表  mall-member
            List<MemberAddressVo> address = membersFeignService.getAddress(memberRespVo.getId());
            confirmVo.setAddress(address);

        }, executor);

        CompletableFuture<Void> getcurrentUserCartItem = CompletableFuture.runAsync(() -> {
            System.out.println("currentUserCartItem线程。。。"+Thread.currentThread().getId());
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //每一个线程都来共享之前的数据

            //2:远程查询购物车所有选中的购物项 mall-order
            List<OrderItemVo> currentUserCartItem = cartFeignService.getCurrentUserCartItem();
            confirmVo.setItems(currentUserCartItem);

        }, executor);

        CompletableFuture<Void> getintegration = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //每一个线程都来共享之前的数据

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

        }, executor);

        try {
            //Returns a new CompletableFuture that is completed when all of
            //     * the given CompletableFutures complete.  If any of the given
            //     * CompletableFutures complete exceptionally, then the returned
            //     * CompletableFuture also does so, with a CompletionException
            //     * holding this exception as its cause.
            CompletableFuture.allOf(getaddress,getintegration,getcurrentUserCartItem).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //4:一些其他数据

        //5:todo 防重令牌
        return confirmVo;
    }

MallFeignConfig

//通过feign调用的源码我们知道,feign远程调用的时候会丢失请求头
@Configuration
public class MallFeignConfig {

    @Bean("RequestContextHolder")
    public RequestInterceptor requestInterceptor(){

        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                System.out.println("拦截器的线程。。。"+Thread.currentThread().getId());

                //可以使用RequestContextHolder拿到刚进来的这个请求的数据(http://order.mall.com/toTrade)
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                HttpServletRequest request = requestAttributes.getRequest();
                if (request!=null){
                    //同步请求头数据,cookie,为了发送到cart,让他知道order发送了cookie给cart,请求头不会丢失
                    String cookie = request.getHeader("Cookie");
                    //给新请求同步了老请求的cookie
                    requestTemplate.header("Cookie",cookie);
                    System.out.println("feign远程之前先进行RequestInterceptor.apply");

                }
              

            }
        };
    }
}

6:bug修改

在这里插入图片描述
原因就是:mall-cart中调用mall-product的如下方法,不可以解析BigDecimal
mall-cart
ProductFeignService

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

所以我们作如下修改
mall-cart
ProductFeignService

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

mall-cart
CartServiceImpl

@Override
    public List<CartItem> getUserCartItems() {
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();

        if (userInfoTo.getUserId()==null){
            return null;
        }else {
            String s = CART_PREFIX + userInfoTo.getUserId();
            List<CartItem> cartItems = getCartItems(s);
            //从redis(getCartItems(s);)里面获取所有被选中的购物项
            List<CartItem> collect = cartItems.stream().
                    filter(cartItem -> cartItem.getCheck() == true)
                    .map(item->{
                        //todo 更新为最新价格
                        R price = productFeignService.getPrice(item.getSkuId());
                        String data = (String) price.get("data");
                        item.setPrice(new BigDecimal(data));
                        return item;
                    })
                    .collect(Collectors.toList());
            return collect;

        }
    }

mall-product
SkuInfoController

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


    @GetMapping("/{skuId}/price")
    public R getPrice(@PathVariable("skuId") Long skuId){

        SkuInfoEntity byId = skuInfoService.getById(skuId);

        return R.ok().setData(byId.getPrice().toString());
    }

7:订单确认页渲染

在这里插入图片描述

8:订单确认页库存查询

远程调用mall-ware库存服务去查询是否有库存
mall-order

**
     * 给订单确认页返回需要的数据confirm
     * @return
     */
    @Override
    public OrderConfirmVo confirmOrder() {

        OrderConfirmVo confirmVo = new OrderConfirmVo();

        //使用拦截器里面的threalocal
        MemberRespVo memberRespVo = OrderInterceptor.loginUser.get();

        System.out.println("主线程。。。"+Thread.currentThread().getId());
        System.out.println("memberRespVo的id。。。"+memberRespVo.getId());

        //得到/addtTocart的请求进来的头数据(获取之前的请求)
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        CompletableFuture<Void> getaddress = CompletableFuture.runAsync(() -> {
            System.out.println("address线程。。。"+Thread.currentThread().getId());

            //每一个线程都来共享之前的数据
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //1:远程查询所有的收货地址列表。。。远程调用查询收货地址ums_member_receive_address表  mall-member
            System.out.println("memberRespVo的id。。。"+memberRespVo.getId());

            List<MemberAddressVo> address = membersFeignService.getAddress(memberRespVo.getId());
            confirmVo.setAddress(address);

        }, executor);

        CompletableFuture<Void> getcurrentUserCartItem = CompletableFuture.runAsync(() -> {
            //每一个线程都来共享之前的数据

            System.out.println("currentUserCartItem线程。。。"+Thread.currentThread().getId());
            RequestContextHolder.setRequestAttributes(requestAttributes);

            //2:远程查询购物车所有选中的购物项 mall-order
            List<OrderItemVo> currentUserCartItem = cartFeignService.getCurrentUserCartItem();
            confirmVo.setItems(currentUserCartItem);

        }, executor).thenRunAsync(()->{

            List<OrderItemVo> items = confirmVo.getItems();
            List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
            R hasStock = wareFeignService.getSkusHasStock(collect);
            List<SkuHasStockVo> data = hasStock.getData("data", new TypeReference<  List<SkuHasStockVo> >() {
            });
            if (data!=null){
                Map<Long, Boolean> collect1 = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));
                confirmVo.setStocks(collect1);

            }

        },executor);

        CompletableFuture<Void> getintegration = CompletableFuture.runAsync(() -> {
            //每一个线程都来共享之前的数据

            RequestContextHolder.setRequestAttributes(requestAttributes);

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

        }, executor);

        try {
            //Returns a new CompletableFuture that is completed when all of
            //     * the given CompletableFutures complete.  If any of the given
            //     * CompletableFutures complete exceptionally, then the returned
            //     * CompletableFuture also does so, with a CompletionException
            //     * holding this exception as its cause.
            CompletableFuture.allOf(getaddress,getintegration,getcurrentUserCartItem).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //4:一些其他数据

        //5:todo 防重令牌
        return confirmVo;
    }
						<p>[[${item.title}]] <span style="color: red;"> ¥[[${item.price}]]</span> <span> x[[${item.count}]] </span> <span >[[${orderConfirmData.stocks[item.skuId]?"有货":"无货"}]]</span></p>

测试:
在这里插入图片描述
在这里插入图片描述

9:订单确认页模拟运费效果

mall-order
confirm.html

<!--地址-->
			<div class="top-3 addr-item" th:each="addr:${orderConfirmData.address}">
				<p th:attr="def=${addr.defaultStatus}">[[${addr.name}]]</p><span>[[${addr.name}]]  [[${addr.province}]]   [[${addr.detailAddress}]]  [[${addr.phone}]]  </span>
			</div>
							<p class="yfze_a"><span class="z">应付总额:</span><span class="hq"><b id="payPriceEle">[[${orderConfirmData.total}]]</b></span></p>

  highlight();
                var addid= $(".addr-item p[def='1']").attr("addrId")
                getFare(addid)
            })
			function highlight() {
				$(".addr-item p").css({"border":"2px sold gray"})
				$(".addr-item p[def='1']").css({"border":"2px sold red"})
            }

            $(".addr-item p").click(function () {
				$(".addr-item p").attr("def","0")
				$(this).attr("def","1")
				highlight()
				//获取当前的地址id
				var addrId=$(this).attr("addrId")
				getFare(addrId)

            })
			function getFare(addrId) {
                //发送ajax获取运费信息
                $.get("http://zlj.mall.com/api/ware/wareinfo/fare?addrId="+addrId,function (data) {
                    console.log(data)
                    $("#fareEle").text(data.data)
                    var total=[[${orderConfirmData.total}]]
                    $("#payPriceEle").text(total*1+data.data*1);
                })
            }
{msg: "success", code: 0, data: 2}
 {msg: "success", code: 0, data: 3}

在这里插入图片描述

10:订单确认页细节显示

mall-ware

@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);
    }

FareVo

@Data
public class FareVo {

    private MemberReceiveAddressVo address;
    private BigDecimal fare;
}

测试接口

http://zlj.mall.com/api/ware/wareinfo/fare?addrId=1
{"msg":"success","code":0,"data":{"address":{"id":1,"memberId":1,"name":"郑霖俊","phone":"18058008512","postCode":"23456","province":"福建省","city":"福州市","region":null,"detailAddress":"闽侯县金溪大道凤翔胡滨世纪","areacode":null,"defaultStatus":1},"fare":2}}

mall-order
confirm.html

				<p class="yfze_b">寄送至:<span id="receiveAddressEle"></span>收货人:<span id="recieverEle"></span></p>
function getFare(addrId) {
                //发送ajax获取运费信息 mall-ware WareInfoController中
                $.get("http://zlj.mall.com/api/ware/wareinfo/fare?addrId="+addrId,function (data) {
                    console.log(data)
                    $("#fareEle").text(data.data.fare)
                    var total=[[${orderConfirmData.total}]]
					//设置运费等信息
                    $("#payPriceEle").text(total*1+data.data.fare*1);

                    //设置收货人等信息
					$("#receiveAddressEle").text(data.data.address.province+" "+data.data.address.detailAddress)
					$("#recieverEle").text(data.data.address.name)
                })
            }

测试
mall-order
confirm.html
http://order.mall.com/toTrade

5 件商品,总商品金额: ¥29400.0000

返现: -¥0.00

运费:   ¥2

服务费:   ¥0.00

退换无忧:   ¥0.00

应付总额:¥29402

寄送至:福建省 闽侯县金溪大道凤翔胡滨世纪收货人:郑霖俊

11:接口幂等性讨论

接口幂等性介绍

12:订单确认页完成

我们可以使用令牌机制来解决接口幂等性问题
在这里插入图片描述
返回确认页的大致流程(confirm.html)
在这里插入图片描述
代码演示:在前端页面可以获取到后端产生的令牌
mall-order
confirm.html

<!--获取到    @GetMapping("/toTrade")请求返回给的前端页面的令牌数据-->
			<input name="orderToken" th:value="${orderConfirmData.orderToken}">
						<button class="tijiao">提交订单</button>

mall-order
OrderServiceImpl

    /**
     * 给订单确认页返回需要的数据confirm
     * @return
     */
    @Override
    public OrderConfirmVo confirmOrder() {
    //....
  //5:todo 防重令牌

        String token = UUID.randomUUID().toString().replace("-", "");
        //todo 给redis,confirmvo设置防重令牌
        confirmVo.setOrderToken(token);
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX+memberRespVo.getId(),token,30, TimeUnit.MINUTES);
            CompletableFuture.allOf(getaddress,getintegration,getcurrentUserCartItem).get();

测试:
在这里插入图片描述
我们现在就来开始完成提交订单的功能
mall-order

@Data
public class OrderSubmitVo {
    private Long addrId;//收货地址
    private Integer payType;//支付方式
    //无需提交需要购买的商品,去购物车在获取一遍即可(redis)
    //优惠,发票
    private String orderToken;//前端提交的防重令牌
    private BigDecimal payPrice;//应付价格 验价 可以不做
    //用户的所有信息,直接去session取出登录用户
    private String note;//订单备注 可以不做
}

在下单之前,我们必须要确保前端页面(confirm.html)的数据可以顺利提交给后端
mall-order
OrderSubmitVo

//封装订单提交的数据
@Data
public class OrderSubmitVo {

    private Long addrId;//收货地址
    private Integer payType;//支付方式
    //无需提交需要购买的商品,去购物车在获取一遍即可(redis)
    //优惠,发票
    private String orderToken;//前端提交的防重令牌
    private BigDecimal payPrice;//应付价格 验价 可以不做
    //用户的所有信息,直接去session取出登录用户
    private String note;//订单备注 可以不做
}

confirm.html

<form action="http://order.mall.com/submitOrder" method="post">
				<!--底下的三个数据已经可以成功提交了,但是attrid和payprice我们必须还要成功获取到数据才可以提交,不然是空值,我们在加上一个id-->
				<input id="addIdInput" type="hidden" name="addrId">
				<input id="payPriceInput" type="hidden" name="payPrice">

				<!--获取到    @GetMapping("/toTrade")请求返回给的前端页面的令牌数据-->
				<input type="hidden" name="orderToken" th:value="${orderConfirmData.orderToken}">
				<button class="tijiao" type="submit">提交订单</button>
			</form>
function getFare(addrId) {
			    //给表单回填选择的地址
				$("#addIdInput").val(addrId)
				//...
				//设置运费等信息
					var payPrice=total*1+resp.data.fare*1
//
                    $("#payPriceInput").val(payPrice)

OrderWebController

/**
     * 提交订单功能
     * @param vo
     * @return
     */
    @PostMapping("/submitOrder")
    public String submitOrder(OrderSubmitVo vo){

        //todo 下单:去创建订单,验证令牌,验证价格,锁库存。。。。。
        //下单成功来到支付选择页
        //下单失败回到订单确认页重新确认订单信息
        System.out.println("订单提交的数据。。"+vo);

        return null;
    }

测试前端发送的action="http://order.mall.com/submitOrder"数据是否会被后端成功接收

订单提交的数据。。OrderSubmitVo(addrId=1, payType=null, orderToken=f2765938350c45eb8767aa4697a8660d, payPrice=17402, note=null)

成功收到

13:原子验令牌

我们接下来按照图解流程去完成提交订单的流程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

提交订单以后跳转的页面有两种可呢,一种是下单成功返回订单信息,还有一种是下单失败,返回到confirm.html的页面,显示是什么原因失败(重复提交,还是验价,库存不足等等原因)
mall-order
OrderWebController

 /**
     * 提交订单功能
     * @param vo
     * @return
     */
    @PostMapping("/submitOrder")
    public String submitOrder(OrderSubmitVo vo){
        //todo 下单:去创建订单,验证令牌,验证价格,锁库存。。。。。就是去submitOrder方法中执行

        SubmitOrderResponseVo submitOrderResponseVo=submitOrderResponseVo=orderService.submitOrder(vo);
        System.out.println("订单提交的数据。。"+vo);

        if (submitOrderResponseVo.getCode()==0){
            //下单成功来到支付选择页

            return "pay";
        }else {
            //下单失败回到订单确认页重新确认订单信息
            return "redirect:http://order.mall.com/toTrade";

        }
    }

OrderServiceImpl

 @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {

        SubmitOrderResponseVo submitOrderResponseVo=new SubmitOrderResponseVo();
        //使用拦截器里面的threalocal
        MemberRespVo memberRespVo = OrderInterceptor.loginUser.get();
        //1:验证令牌【令牌的对比和删除,获取令牌必须保证原子性】
        //如果redis调用get方法获取KEYS[1]值,然后它就会返回令牌删除,然后返回0
        //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<Long>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken);
        if (result==0){
            //令牌验证不通过

            return null;
        }else {
            //令牌验证通过
//        下单:去创建订单,验证令牌,验证价格,锁库存

            return null;
        }
//        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());
//            return null;
//        }else {
//            //不通过
//            return null;
//        }
    }

14:构造订单数据

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
mall-order
OrderCreateTo

@Data
public class OrderCreateTo {

    private OrderEntity order;
    private List<OrderItemEntity> orderItems;
    private BigDecimal payPrice;//订单计算的应付价格
    private BigDecimal fare;//运费
}

OrderServiceImpl

 private OrderCreateTo createOrder( ){

        OrderCreateTo createTo = new OrderCreateTo();
        //1:生成一个订单号
        String orderSn = IdWorker.getTimeId();
        OrderEntity orderEntity = buildOrder(orderSn);
        //2:获取到所有的订单项
        List<OrderItemEntity> itemEntities = builderOrderItems();

        //3:验价

        return createTo;
    }

    private OrderEntity buildOrder(String orderSn) {
        OrderEntity orderEntity = new OrderEntity();

        orderEntity.setOrderSn(orderSn);
        //获取收货地址的信息
        //使用ThreadLocal皆可以使用一条线程获取数据
        OrderSubmitVo submitVo = confirmVoThreadLocal.get();
        R fare = wareFeignService.getFare(submitVo.getAddrId());
        FareVo fareresp = fare.getData("data", new TypeReference<FareVo>() {
        });

        //设置运费信息
        orderEntity.setFreightAmount(fareresp.getFare());

        //设置收货人信息
        orderEntity.setReceiverCity(fareresp.getAddress().getCity());
        orderEntity.setReceiverDetailAddress(fareresp.getAddress().getDetailAddress());
        orderEntity.setReceiverName(fareresp.getAddress().getName());
        orderEntity.setReceiverPhone(fareresp.getAddress().getPhone());
        orderEntity.setReceiverPostCode(fareresp.getAddress().getPostCode());
        orderEntity.setReceiverProvince(fareresp.getAddress().getProvince());
        orderEntity.setReceiverRegion(fareresp.getAddress().getRegion());

        return orderEntity;
    }

    /**
     * 构建所有订单项数据
     * @return
     */
    private List<OrderItemEntity> builderOrderItems(){
        //2:获取到所有的订单项(创建订单项)
        List<OrderItemVo> currentUserCartItem = cartFeignService.getCurrentUserCartItem();
        if (currentUserCartItem!=null&&currentUserCartItem.size()>0){
            List<OrderItemEntity> itemEntities = currentUserCartItem.stream().map(cartItems -> {
                OrderItemEntity itemEntity = builderOrderItem(cartItems);

                return itemEntity;
            }).collect(Collectors.toList());
            return itemEntities;
        }
        return null;
    }

    /**
     * 构建所有订单项中的每一个订单项
     * @param cartItem
     * @return
     */
    private OrderItemEntity builderOrderItem(OrderItemVo cartItem) {
        return null;
    }

15:构造订单项数据

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
mall-order
OrderServiceImpl

 /**
     * 构建所有订单项数据
     * @return
     * @param orderSn
     */
    private List<OrderItemEntity> builderOrderItems(String orderSn){
        //2:获取到所有的订单项(创建订单项)
        List<OrderItemVo> currentUserCartItem = cartFeignService.getCurrentUserCartItem();
        if (currentUserCartItem!=null&&currentUserCartItem.size()>0){
            List<OrderItemEntity> itemEntities = currentUserCartItem.stream().map(cartItems -> {
                OrderItemEntity itemEntity = builderOrderItem(cartItems);

                itemEntity.setOrderSn(orderSn);

                return itemEntity;
            }).collect(Collectors.toList());
            return itemEntities;
        }
        return null;
    }

    /**
     * 构建所有订单项中的每一个订单项
     * @param cartItem
     * @return
     */
    private OrderItemEntity builderOrderItem(OrderItemVo cartItem) {
        OrderItemEntity orderItemEntity = new OrderItemEntity();
        //1:订单信息,订单号
        
        //2:商品的spu信息
        Long skuId = cartItem.getSkuId();
        R spuInfoBySkuId = productFeignService.getSpuInfoBySkuId(skuId);
        SpuInfoVo data = spuInfoBySkuId.getData("data", new TypeReference<SpuInfoVo>() {
        });
        orderItemEntity.setSpuBrand(data.getBrandId().toString());
        orderItemEntity.setSpuId(data.getId());
        orderItemEntity.setSpuName(data.getSpuName());
        orderItemEntity.setCategoryId(data.getCatalogId());

        //3:商品的sku信息
        orderItemEntity.setSkuId(cartItem.getSkuId());
        orderItemEntity.setSkuName(cartItem.getTitle());
        orderItemEntity.setSkuPic(cartItem.getImage());
        orderItemEntity.setSkuPrice(cartItem.getPrice());
        String skuAttr = org.springframework.util.StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";");
        orderItemEntity.setSkuAttrsVals(skuAttr);
        orderItemEntity.setSkuQuantity(cartItem.getCount());

        //4:优惠信息(不做)
        
        //5:积分信息
        orderItemEntity.setGiftGrowth(cartItem.getPrice().intValue());
        orderItemEntity.setGiftIntegration(cartItem.getPrice().intValue());
        ret
    }

16:订单验价

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
OrderServiceImpl

@Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {

        confirmVoThreadLocal.set(vo);
        SubmitOrderResponseVo submitOrderResponseVo=new SubmitOrderResponseVo();
        //使用拦截器里面的threalocal
        MemberRespVo memberRespVo = OrderInterceptor.loginUser.get();
        //1:验证令牌【令牌的对比和删除,获取令牌必须保证原子性】
        //如果redis调用get方法获取KEYS[1]值,然后它就会返回令牌删除,然后返回0
        //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<Long>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken);
        if (result==0){
            //令牌验证不通过
            submitOrderResponseVo.setCode(1);

            return submitOrderResponseVo;
        }else {
            //令牌验证通过
//        下单:去创建订单,验证令牌,验证价格,锁库存

            //第一步:创建订单,订单项等信息
            OrderCreateTo order=createOrder();
            //第二步:验证价格
            BigDecimal payAmount = order.getOrder().getPayAmount();
            BigDecimal payPrice = vo.getPayPrice();
            //前端传来的页面的值和后端我们计算的价格这两个值相差不可以大于0.1;;就是会有误差很正常
            if (Math.abs(payAmount.subtract(payPrice).doubleValue())<0.1){
                //金额对比..
            }else {
                submitOrderResponseVo.setCode(2);
                return submitOrderResponseVo;
            }

        }
//        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());
//            return null;
//        }else {
//            //不通过
//            return null;
//        }
        return submitOrderResponseVo;

    }

    private OrderCreateTo createOrder( ){

        OrderCreateTo createTo = new OrderCreateTo();
        //1:生成一个订单号
        String orderSn = IdWorker.getTimeId();
        OrderEntity orderEntity = buildOrder(orderSn);
        //2:获取到所有的订单项
        List<OrderItemEntity> itemEntities = builderOrderItems(orderSn);

        //3:验价(计算价格相关)
        computePrice(orderEntity,itemEntities);

        return createTo;
    }

    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 growth=new BigDecimal("0.0");
        BigDecimal gift=new BigDecimal("0.0");

        //订单的总额,叠加每一个订单的总额信息
        for (OrderItemEntity entity:itemEntities
             ) {
            BigDecimal realAmount = entity.getRealAmount();
            coupon=coupon.add(entity.getCouponAmount());
            integration=integration.add(entity.getIntegrationAmount());
            promotion=promotion.add(entity.getPromotionAmount());
            total=total.add(realAmount);
            //设置积分,成长值
            gift.add(new BigDecimal(entity.getGiftIntegration().toString()));
            growth.add(new BigDecimal(entity.getGiftGrowth().toString()));

        }
        //1:订单价格相关
        orderEntity.setTotalAmount(total);
        //应付总额
        orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));

        orderEntity.setPromotionAmount(promotion);
        orderEntity.setIntegrationAmount(integration);
        orderEntity.setCouponAmount(coupon);

        //设置积分等信息
        orderEntity.setIntegration(gift.intValue());
        orderEntity.setGrowth(growth.intValue());
        orderEntity.setDeleteStatus(0);//未删除
    }

    private OrderEntity buildOrder(String orderSn) {
        OrderEntity orderEntity = new OrderEntity();

        orderEntity.setOrderSn(orderSn);
        //获取收货地址的信息
        //使用ThreadLocal皆可以使用一条线程获取数据
        OrderSubmitVo submitVo = confirmVoThreadLocal.get();
        R fare = wareFeignService.getFare(submitVo.getAddrId());
        FareVo fareresp = fare.getData("data", new TypeReference<FareVo>() {
        });

        //设置运费信息
        orderEntity.setFreightAmount(fareresp.getFare());

        //设置收货人信息
        orderEntity.setReceiverCity(fareresp.getAddress().getCity());
        orderEntity.setReceiverDetailAddress(fareresp.getAddress().getDetailAddress());
        orderEntity.setReceiverName(fareresp.getAddress().getName());
        orderEntity.setReceiverPhone(fareresp.getAddress().getPhone());
        orderEntity.setReceiverPostCode(fareresp.getAddress().getPostCode());
        orderEntity.setReceiverProvince(fareresp.getAddress().getProvince());
        orderEntity.setReceiverRegion(fareresp.getAddress().getRegion());

        //设置订单的相关状态信息
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        orderEntity.setAutoConfirmDay(7);
        return orderEntity;
    }

    /**
     * 构建所有订单项数据
     * @return
     * @param orderSn
     */
    private List<OrderItemEntity> builderOrderItems(String orderSn){
        //2:获取到所有的订单项(创建订单项)
        List<OrderItemVo> currentUserCartItem = cartFeignService.getCurrentUserCartItem();
        if (currentUserCartItem!=null&&currentUserCartItem.size()>0){
            List<OrderItemEntity> itemEntities = currentUserCartItem.stream().map(cartItems -> {
                OrderItemEntity itemEntity = builderOrderItem(cartItems);

                itemEntity.setOrderSn(orderSn);

                return itemEntity;
            }).collect(Collectors.toList());
            return itemEntities;
        }
        return null;
    }

    /**
     * 构建所有订单项中的每一个订单项
     * @param cartItem
     * @return
     */
    private OrderItemEntity builderOrderItem(OrderItemVo cartItem) {
        OrderItemEntity orderItemEntity = new OrderItemEntity();
        //1:订单信息,订单号
        
        //2:商品的spu信息
        Long skuId = cartItem.getSkuId();
        R spuInfoBySkuId = productFeignService.getSpuInfoBySkuId(skuId);
        SpuInfoVo data = spuInfoBySkuId.getData("data", new TypeReference<SpuInfoVo>() {
        });
        orderItemEntity.setSpuBrand(data.getBrandId().toString());
        orderItemEntity.setSpuId(data.getId());
        orderItemEntity.setSpuName(data.getSpuName());
        orderItemEntity.setCategoryId(data.getCatalogId());

        //3:商品的sku信息
        orderItemEntity.setSkuId(cartItem.getSkuId());
        orderItemEntity.setSkuName(cartItem.getTitle());
        orderItemEntity.setSkuPic(cartItem.getImage());
        orderItemEntity.setSkuPrice(cartItem.getPrice());
        String skuAttr = org.springframework.util.StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";");
        orderItemEntity.setSkuAttrsVals(skuAttr);
        orderItemEntity.setSkuQuantity(cartItem.getCount());

        //4:优惠信息(不做)
        
        //5:积分信息
        orderItemEntity.setGiftGrowth(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());
        orderItemEntity.setGiftIntegration(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());
        //6:订单项的价格信息
        orderItemEntity.setPromotionAmount(new BigDecimal("0"));
        orderItemEntity.setCouponAmount(new BigDecimal(("0")));
        orderItemEntity.setIntegrationAmount(new BigDecimal("0"));
        //当前订单项的实际金额=总额减去各种优惠
        BigDecimal orign = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));
        BigDecimal subtract = orign.subtract(orderItemEntity.getCouponAmount())
                .subtract(orderItemEntity.getPromotionAmount())
                .subtract(orderItemEntity.getIntegrationAmount());
        orderItemEntity.setRealAmount(subtract);



        return orderItemEntity;
    }

17:保存订单数据

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
mall-ware
WareSkuLockVo

@Data
public class WareSkuLockVo {

    private String orderSn;//订单号
    private List<OrderItemVo> locks;//需要锁住的所有库存信息

}

LockStockResult

@Data
public class LockStockResult {
    private Long skuId;
    private Integer num;
    private Boolean locked;
}

OrderItemVo

@Data
public class OrderItemVo {
    private Long skuId;
    private Boolean check=true;
    private String title;
    private String image;
    private List<String> skuAttr;
    private BigDecimal price;
    private Integer count;
    private BigDecimal totalPrice;
    //todo 查询库存状态
    private boolean hasStock=true;
}

WareSkuController

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

    @PostMapping("/lock/order")
    public R orderLockStock(@RequestBody WareSkuLockVo vo){
        //返回每一个商品锁定成功的信息,谁锁定成功了,谁没有锁定成功
       List<LockStockResult> lockStockResult= wareSkuService.orderLockStock(vo);
        return R.ok().setData(lockStockResult);
    }

mall-order
WareFeignService

@FeignClient("mall-ware")
public interface WareFeignService {
    @PostMapping("/lock/order")
    public R orderLockStock(@RequestBody WareSkuLockVo vo);
}

OrderServiceImpl

@Transactional
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {

        confirmVoThreadLocal.set(vo);
        SubmitOrderResponseVo submitOrderResponseVo=new SubmitOrderResponseVo();
        //使用拦截器里面的threalocal
        MemberRespVo memberRespVo = OrderInterceptor.loginUser.get();
        //1:验证令牌【令牌的对比和删除,获取令牌必须保证原子性】
        //如果redis调用get方法获取KEYS[1]值,然后它就会返回令牌删除,然后返回0
        //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<Long>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken);
        if (result==0){
            //令牌验证不通过
            submitOrderResponseVo.setCode(1);

            return submitOrderResponseVo;
        }else {
            //令牌验证通过
//        下单:去创建订单,验证令牌,验证价格,锁库存

            //第一步:创建订单,订单项等信息
            OrderCreateTo order=createOrder();
            //第二步:验证价格
            BigDecimal payAmount = order.getOrder().getPayAmount();
            BigDecimal payPrice = vo.getPayPrice();
            //前端传来的页面的值和后端我们计算的价格这两个值相差不可以大于0.1;;就是会有误差很正常
            if (Math.abs(payAmount.subtract(payPrice).doubleValue())<0.1){
                //金额对比..

                //第三步:保存订单
                saveOrder(order);
                //第四步:库存锁定  我们现在要加一个事务(只要有异常回滚订单数据) 只要订单一保存成功,我们马上就要锁住库存,如果库存没锁住就会回调返回失败
                //订单号,所有订单项(skuId,skuName,num)
                WareSkuLockVo lockVo=new WareSkuLockVo();
                lockVo.setOrderSn(order.getOrder().getOrderSn());
                List<OrderItemVo> 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);
                //去(mall-ware)库存服务锁住我们在mall-order中传递过来的lockVo
                R r = wareFeignService.orderLockStock(lockVo);
                if (r.getCode()==0){
                    //锁成功
                }else {
                    //锁失败
                }

            }else {
                submitOrderResponseVo.setCode(2);
                return submitOrderResponseVo;
            }

        }
//        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());
//            return null;
//        }else {
//            //不通过
//            return null;
//        }
        return submitOrderResponseVo;

    }

18:锁定库存

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们所有买的商品都要锁库存,比如1,2,3订单,所以每一个商品都要尝试锁库存,只有全部锁定就可以成功,修改锁定状态,只要有一个没锁库存就会撤销
mall-ware
WareSkuController

@RestController
@RequestMapping("ware/waresku")
public class WareSkuController {
@PostMapping("/lock/order")
    public R orderLockStock(@RequestBody WareSkuLockVo vo){
        //返回每一个商品锁定成功的信息,谁锁定成功了,谁没有锁定成功
        try {
            Boolean lockStockResult= wareSkuService.orderLockStock(vo);
            return R.ok();
        }catch (NoStockException e){
            return R.error(BigCodeEnume.NO_STOCK_EXCEPTION.getCode(),BigCodeEnume.NO_STOCK_EXCEPTION.getMsg());
        }

    }

mall-ware
WareSkuServiceImpl

 /**
     * 为某一个订单锁定库存
     * @param vo
     * @return
     */
    //默认是锁库存异常都会回滚
    @Transactional(rollbackFor = NoStockException.class)
    @Override
    public Boolean orderLockStock(WareSkuLockVo vo) {

        //1:按照下单的收货地址,找到一个就近的仓库,锁定库存:太麻烦不这么做
        //1:找到每个商品在那个仓库都有库存
        List<OrderItemVo> locks = vo.getLocks();

        List<SkuWareHasStock> collect = locks.stream().map(item -> {
            SkuWareHasStock skuWareHasStock = new SkuWareHasStock();
            Long skuId = item.getSkuId();
            skuWareHasStock.setSkuId(skuId);
            //查询这个商品在哪里有库存
            List<Long> wareIds= wareSkuDao.listWareIdHasSkuStock(skuId);

            skuWareHasStock.setNum(item.getCount());
            skuWareHasStock.setWareId(wareIds);
            return skuWareHasStock;
        }).collect(Collectors.toList());

        Boolean allLock=false;
        //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 NoStockException(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 NoStockException(skuId);
            }
        }

        //3:肯定都是全部锁定成功的

        return true;
    }

WareSkuDao

    <update id="lockSkuStock">
        UPDATE  wms_ware_sku SET stock_locked=stock_locked+#{num} where sku_id=#{skuId} and ware_id=#{wareId} 
        and stock-stock_locked>=#{num}
    </update>

19:提交订单的问题

mall-order
OrderServiceImpl

 @Transactional
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {

        confirmVoThreadLocal.set(vo);
        SubmitOrderResponseVo submitOrderResponseVo=new SubmitOrderResponseVo();
        //使用拦截器里面的threalocal
        MemberRespVo memberRespVo = OrderInterceptor.loginUser.get();
        //1:验证令牌【令牌的对比和删除,获取令牌必须保证原子性】
        //如果redis调用get方法获取KEYS[1]值,然后它就会返回令牌删除,然后返回0
        //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<Long>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()), orderToken);
        if (result==0){
            //令牌验证不通过
            submitOrderResponseVo.setCode(1);

            return submitOrderResponseVo;
        }else {
            //令牌验证通过
//        下单:去创建订单,验证令牌,验证价格,锁库存

            //第一步:创建订单,订单项等信息
            OrderCreateTo order=createOrder();
            //第二步:验证价格
            BigDecimal payAmount = order.getOrder().getPayAmount();
            BigDecimal payPrice = vo.getPayPrice();
            //前端传来的页面的值和后端我们计算的价格这两个值相差不可以大于0.1;;就是会有误差很正常
            if (Math.abs(payAmount.subtract(payPrice).doubleValue())<0.1){
                //金额对比..

                //第三步:保存订单
                saveOrder(order);
                //todo 第四步:远程库存锁定  我们现在要加一个事务(只要有异常回滚订单数据) 只要订单一保存成功,我们马上就要锁住库存,如果库存没锁住就会回调返回失败
                //订单号,所有订单项(skuId,skuName,num)
                WareSkuLockVo lockVo=new WareSkuLockVo();
                lockVo.setOrderSn(order.getOrder().getOrderSn());
                List<OrderItemVo> 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);
                //去(mall-ware)库存服务锁住我们在mall-order中传递过来的lockVo
                R r = wareFeignService.orderLockStock(lockVo);
                if (r.getCode()==0){
                    //锁成功
                    submitOrderResponseVo.setOrderEntity(order.getOrder());
                    return submitOrderResponseVo;
                }else {
                    //锁失败
                    submitOrderResponseVo.setCode(3);
                    return submitOrderResponseVo;
                }

            }else {
                submitOrderResponseVo.setCode(2);
                return submitOrderResponseVo;
            }

        }
//        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());
//            return null;
//        }else {
//            //不通过
//            return null;
//        }
//        return submitOrderResponseVo;

    }

OrderWebController

 /**
     * 提交订单功能
     * @param vo
     * @return
     */
    @PostMapping("/submitOrder")
    public String submitOrder(OrderSubmitVo vo){
        //todo 下单:去创建订单,验证令牌,验证价格,锁库存。。。。。就是去submitOrder方法中执行

        SubmitOrderResponseVo submitOrderResponseVo=submitOrderResponseVo=orderService.submitOrder(vo);
        System.out.println("订单提交的数据。。"+vo);

        if (submitOrderResponseVo.getCode()==0){
            //下单成功来到支付选择页

            return "pay";
        }else {
            //下单失败回到订单确认页重新确认订单信息
            return "redirect:http://order.mall.com/toTrade";

        }
    }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值