其他分享
首页 > 其他分享> > Spring Cloud【Finchley】实战-03订单微服务与商品微服务之间的调用

Spring Cloud【Finchley】实战-03订单微服务与商品微服务之间的调用

作者:互联网

文章目录

Spring Cloud【Finchley】专栏

如果还没有系统的学过Spring Cloud ,先到我的专栏去逛逛吧

Spring Cloud 【Finchley】手札


概述

还记得上篇博文的TODO吧

在这里插入图片描述

这里我们先循序渐进的了解下,微服务之间调用的几种方式

先了解下应用之间的通行的主要两种方式


HTTP方式之RestTemplate

我们在order微服务调用product微服务。

product作为服务端,先对外暴露个测试接口
在这里插入图片描述

order作为客户端调用该接口

在这里插入图片描述

方式一 (直接使用restTemplate访问URL,url写死)

在这里插入图片描述

访问 http://localhost:8081/order/getServerInfoFromClient

在这里插入图片描述

写死的地址,并且只能请求一个,如果有多个地址就比较麻烦了。而且还是IP地址。


方式二 (使用LoadBalancerClient通过应用名获取url,拼装请求地址,然后再使用restTemplate)

package com.artisan.order.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
@RequestMapping("/order")
public class ClientController {


    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/getServerInfoFromClient")
    public String requestServer(){


        // 方式二 (使用LoadBalancerClient通过应用名获取url,拼装请求地址,然后再使用restTemplate)
        RestTemplate restTemplate2 = new RestTemplate();
        ServiceInstance serviceInstance = loadBalancerClient.choose("ARTISAN-PRODUCT");
        // 获取ip port 组装url
        String url = String.format("http://%s:%s",
                serviceInstance.getHost(),serviceInstance.getPort() + "/product/serverMsg");
        log.info("url:{}",url);

        String msg = restTemplate2.getForObject(url,String.class);
        log.info("msg from server : {}", msg);
        return msg;
    }
}

loadBalancerClient.choose("ARTISAN-PRODUCT"); 通过loadBalancerClient 选择 注册到Eurek Server上的ip . 需要填写注册到注册中心的名字ARTISAN-PRODUCT。

访问 http://localhost:8081/order/getServerInfoFromClient

在这里插入图片描述


方式三 (使用@LoadBalanced注解)

先初始化RestTemplate , 标注 @LoadBalanced 注解

package com.artisan.order.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
package com.artisan.order.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
@RequestMapping("/order")
public class ClientController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/getServerInfoFromClient")
    public String requestServer(){

        // 方式三  (使用@LoadBalanced注解)

        String msg = restTemplate.getForObject("http://ARTISAN-PRODUCT/product/serverMsg",String.class);

        log.info("msg from server : {}", msg);
        return msg;
    }
}

请求的地址 http://ARTISAN-PRODUCT/product/serverMsg 注册到服务中心上的服务

访问 http://localhost:8081/order/getServerInfoFromClient

在这里插入图片描述


Fegin 的使用

Spring Cloud【Finchley】-06服务消费者整合Feign

总体来说,在作为客户端的order微服务中, 步骤如下

  1. 添加依赖
  2. 添加注解@EnableFeignClients
  3. 开发接口
  4. 使用

pom.xml 添加依赖

在这里插入图片描述


添加注解@EnableFeignClients
在这里插入图片描述


编写client接口

package com.artisan.order.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

// name为注册在注册中心上的名称
@FeignClient(name="ARTISAN-PRODUCT")
public interface ProductClient {

    // ARTISAN-PRODUCT微服务接口的访问路径
    @GetMapping("/product/serverMsg")
    String getServerInfo();
}

调用

package com.artisan.order.controller;

import com.artisan.order.client.ProductClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequestMapping("/order")
public class FeginClientController {

    @Autowired
    private ProductClient productClient;

    @GetMapping("/getServerInfoByFeign")
    public String requestServer(){

        String msg = productClient.getServerInfo();

        log.info("msg from server : {}", msg);
        return msg;
    }
}

访问 http://localhost:8081/order/getServerInfoByFeign

在这里插入图片描述

OK。


商品微服务获取商品列表功能开发

熟悉了基本使用后,刚开始说了,我们有几个TODO要做,那开始吧


Product微服务查询商品列表功能开发

我们看下前台会传递什么给我们
在这里插入图片描述

关于商品的信息,productId是个集合,那么我们就需要提供一个根据传入的productId列表来返回product集合的功能。

DAO层

老规矩,DAO层先

在这里插入图片描述
单元测试下,

在这里插入图片描述

   @Test
    public void findByProductIdIn() {
        List<Product> list =  productRepository.findByProductIdIn(Arrays.asList("1","2"));

        Assert.assertEquals(2,list.size());

    }

结合库表中的数据
在这里插入图片描述
在这里插入图片描述
单元测试通过


Service层

紧接着Service层

在这里插入图片描述

实现类
在这里插入图片描述

单元测试

在这里插入图片描述
单元测试通过

在这里插入图片描述


Controller层

  /**
     * 根据productIdList 查询商品列表
     * 提供给Order微服务用
     * @param productIdList
     * @return
     */
    @PostMapping("/productListForOrder")
    private List<Product> getProductForOrder(@RequestBody  List<String> productIdList){
        return productService.getProductList(productIdList);
    }

Order微服务调用接口查询商品列表

增加接口方法

在这里插入图片描述

返回的类型是个Product集合,我们先从product微服务那边将Product copy一份过来。 后续会优化这些地方

我们写个方法来测试下这个功能, 那就在刚才的用作测试的FeginClientController类中写个方法吧

在这里插入图片描述

当参数中标注了@RequestBody , 则必须使用POST方法

启动服务,测试下 http://localhost:8081/order/getProductList

在这里插入图片描述
可见功能是OK的。


调用商品微服务扣库存功能开发

Product微服务减库存功能开发

减库存的参数 DTO封装

我们看下前台会传递什么给我们

在这里插入图片描述

肯定是 某个产品 扣除多少个数量。 []可以传递多个,对于后台来讲是个集合 。

Product微服务需要两个参数 productId 和 productQuantity

在这里插入图片描述


Service

分析下,扣减库存,直接使用JPA内置的方法即可,DAO层可以省略了。

直接来Service吧 ,直接写实现类中的方法你把

@Override
    // 因为是对List操作,所以加个事务控制
    @Transactional
    public void decreaseProduct(List<CartDTO> cartDTOList) {

        // 遍历CartDTO
        for (CartDTO cartDTO : cartDTOList) {
            // 根据productId查询Product
            Optional<Product> productOptional = productRepository.findById(cartDTO.getProductId());

            // 商品是否存在
            if (!productOptional.isPresent()) {
                throw new ProductException(ResultEnum.PRODUCT_NOT_EXIST);
            }
            // 是否库存充足

            Product product = productOptional.get();
            int leftStock = product.getProductStock() - cartDTO.getProductQuantity();

            if (leftStock < 0 ){
                throw new ProductException(ResultEnum.PRODUCT_STOCK_ERROR);
            }
            // 将剩余库存设置到product,并更新数据库
            product.setProductStock(leftStock);
            productRepository.save(product);
        }

    }

因为是对List操作,所以加个事务控制 @Transactional

单元测试

@Test
    public void decreaseProductTest() {

        CartDTO cartDTO = new CartDTO();
        cartDTO.setProductId("3");
        cartDTO.setProductQuantity(2);

        productService.decreaseProduct(Arrays.asList(cartDTO));
    }

测试前数据

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


Controller层

在这里插入图片描述


Order微服务调用接口扣减库存

增加接口方法

ProductClient接口新增方法

在这里插入图片描述

测试下 ,在 FeginClientController 新增个方法 (这个Controller和工程无关哈,仅仅是用来测试用的)

访问 http://localhost:8081/order/decreseProduct

在这里插入图片描述

在这里插入图片描述


整合

Product微服务要提供的功能及Order微服务调用都开发完了,那整合到业务逻辑中吧

@Override
    public OrderDTO createOrder(OrderDTO orderDTO) {

        String orderId = KeyUtil.genUniqueKey();

        //  查询商品信息(调用商品微服务)
        List<String> productIdList = orderDTO.getOrderDetailList().stream()
                .map(OrderDetail::getProductId)
                .collect(Collectors.toList());
        List<Product> productList = productClient.getProductForOrder(productIdList);


        //   计算订单总价

        BigDecimal orderAmout = new BigDecimal(BigInteger.ZERO);
        for (OrderDetail orderDetail: orderDTO.getOrderDetailList()) {
            for (Product product: productList) {
                if (product.getProductId().equals(orderDetail.getProductId())) {
                    //单价*数量
                    orderAmout = product.getProductPrice()
                            .multiply(new BigDecimal(orderDetail.getProductQuantity()))
                            .add(orderAmout);
                    BeanUtils.copyProperties(product, orderDetail);
                    orderDetail.setOrderId(orderId);
                    orderDetail.setDetailId(KeyUtil.genUniqueKey());
                    //订单详情入库
                    orderDetailRepository.save(orderDetail);
                }
            }
        }

        // 扣减库存(调用商品微服务)

        List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream()
                .map(e -> new CartDTO(e.getProductId(), e.getProductQuantity()))
                .collect(Collectors.toList());
        productClient.decreseProduct(cartDTOList);

        //订单入库
        Order order = new Order();
        orderDTO.setOrderId(orderId);
        // 复制属性
        BeanUtils.copyProperties(orderDTO, order);
        // 设置其他属性
        order.setOrderAmount(orderAmout);
        order.setOrderStatus(OrderStatusEnum.NEW.getCode());
        order.setPayStatus(PayStatusEnum.WAIT.getCode());
        orderRepository.save(order);

        return orderDTO;
    }

测试

在这里插入图片描述

  [{
      "productId": "1",
       "productQuantity": 2  
},
{
      "productId": "2",
       "productQuantity": 5  
},
{
      "productId": "3",
       "productQuantity": 10  
}]

买 1号商品 2个 ,金额 20.99乘以2 = 41.98
买 2号商品 5个 ,金额 7.5乘以5 = 37.5
买 3号商品 10个 ,金额 15乘以10 = 150
总金额 229.48

原始库存:

在这里插入图片描述

使用POSTMAN测试一把
在这里插入图片描述

检查下总金额,库存扣减,及order_detail中的数据

artisan_order
在这里插入图片描述

order_detail 3条记录
在这里插入图片描述

库存:

在这里插入图片描述

OK


知识点小结

点1

Spring MVC在接收集合请求参数时,需要在Controller方法的集合参数里前添加@RequestBody

List<Product> getProductForOrder(@RequestBody  List<String> productIdList)

点2

当参数中标注了@RequestBody , 则必须使用POST方法
在这里插入图片描述


Github

artisan-product: https://github.com/yangshangwei/springcloud-o2o/tree/master/artisan_order

artisan_order: https://github.com/yangshangwei/springcloud-o2o/tree/master/artisan-product

标签:03,product,Spring,springframework,annotation,Finchley,org,import,order
来源: https://blog.51cto.com/u_15239532/2836499