• Feign的使用


    Fegin介绍

    Fegin是一个非常好用的HTTP客户端

    Feign很大程度上简化了HTTP调用方式

    Fegin能做什么

    Feign包含了多种HTTP的调用形式

    (1 、Spring MVC: @RequestMapping  @RequestParam @Pathvariable, @RequestHeader, @RequestBody)

    Feign可以整合Ribbon和Hystrix

    Feign提供了多种HTTP底层支持(底层支持Apache HttpClient,OKHttp,RestTemplate等)

    Feign特性

    Feign实现了可插拔注解支持,包括Feign和JAX-RS注解

    Feign支持可插拔的HTTP编码器和解码器

    Feign支持HTTP请求和响应的压缩

    传统服务间的调用方式: 比如服务A调用服务B,服务A要跟服务B建立网络连接,然后构造一个复杂的请求,最后对返回的响应结果再写一大堆代码来处理。

    Feign为我们提供了优雅的解决方案: 用注解定义一个FeignClient接口。没有底层建立连接,构造请求,解析响应的代码。Fegin底层会根据注解,和你指定的服务建立连接,构造请求,获取响应,解析响应等等。

    Feign原理: Fegin的一个关键机制是使用了动态代理,如下图: 参考 https://blog.csdn.net/Anbang713/article/details/85370080

     首先,如果你对某个接口定义了 @FeignClient注解,Fegin就会针对这个接口创建一个动态代理

    接着你要调用哪个接口,本质就是调用Fegi创建的动态代理,这是核心的核心

    Fegin的动态代理会根据你在接口上的 @RequestMmapping等注解,来动态构造你要请求的服务地址

    最后针对这个地址,发起请求、解析响应

    Feign实践

    通过Order工程调用Product工程的接口

    一、Feign实现应用间的通信

    声明式REST客户端(伪RPC),采用基于接口的注解。本质上是Http客户端,Http远程调用。

    1、 在Order工程中的pom文件增加

    		<dependency>
    			<groupId>org.springframework.cloud</groupId>
    			<artifactId>spring-cloud-starter-openfeign</artifactId>
    		</dependency>
    

      

    2、增加注解@EnableFeignClients

     

    3、声明要调用的接口

    package com.example.demo.client;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    
    /**
     * 需要在Product服务中要调的接口
     */
    @FeignClient(name = "product") //product代表访问product应用下的msg接口
    public interface ProductClient {
    
        @GetMapping("/msg") //
        String productMsg();
    }
    

      

    4、在Order应用调用

    二、Order查询调用Product来查询商品信息

    第一步:Product工程

    1、增加  List<ProductInfo> findByProductIdIn(List<String> productIdList);方法

    package com.example.product.repository;
    
    import com.example.product.dataobject.ProductInfo;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import java.util.List;
    
    
    public interface ProductInfoRepository extends JpaRepository<ProductInfo, String> {
        //查询所有在架的商品
        List<ProductInfo> findByProductStatus(Integer productStatus);
    
        List<ProductInfo> findByProductIdIn(List<String> productIdList);
    }
    

      

    2、Server层

    @Service
    public class ProductServiceImpl  implements ProductService{
    
        @Autowired
        private ProductInfoRepository productInfoRepository;
    
    
        /**
         * 查询商品列表
         *
         * @param productIdList
         */
        @Override
        public List<ProductInfo> findList(List<String> productIdList) {
            return productInfoRepository.findByProductIdIn(productIdList);
        }
    }

    接口

    public interface ProductService {
    
    
        /**
         * 查询商品列表
         * @param productIdList
         */
        List<ProductInfo> findList(List<String> productIdList);
    }
    

      

    3、Controller层

    @RestController
    @RequestMapping("/product")
    @Slf4j
    public class ProductController {
    
        @Autowired
        private ProductService productService;
    
       
        /**
         * 获取商品列表(给订单服务用)
         * @param productIdList
         * @return
         */
        @PostMapping("/listForOrder")
        public List<ProductInfo> listForOrder(@RequestBody List<String> productIdList){
            return  productService.findList(productIdList);
        }
    }
    

      

    第二步、Order工程

    1.在接口中定义product中的方法

    /**
     * 需要在Product服务中要调的接口
     */
    @FeignClient(name = "product") //product代表访问product应用下的msg接口
    public interface ProductClient {
    
        @PostMapping("/product/listForOrder")
        List<ProductInfo> listForOrder(@RequestBody  List<String> productIdList);
    }
    

      

    2、Controller层调用上一步定义的方法。

    @RestController
    @Slf4j
    public class ClientController {
    
      
        @Autowired
        private ProductClient productClient;
    
        @GetMapping("/getProductList")
        public String getProductList(){
           List<ProductInfo> productInfoList = productClient.listForOrder(Arrays.asList("164103465734242707"));
            log.info("response={}",productInfoList);
            return  "ok";
        }
    }
    

      

      

    三、扣库存的实现

    第一步:Product工程

     1、创建ResultEnum 信息提示

    @Getter
    public enum ResultEnum {
        PRODUCT_NOT_EXIST(1,"商品不存在"),
    
        PRODUCT_STOCK_ERROR(2,"库存有误"),
        ;
    
        private  Integer code;
        private String message;
    
        ResultEnum(Integer code, String message){
            this.code = code;
            this.message = message;
        }
    }
    

     

    2、创建购物车类CartDTO 

    /**
     * 购物车
     */
    @Data
    public class CartDTO {
        /**
         * 商品id
         */
        private String productId;
    
        /**
         * 商品数量
         */
        private Integer productQuantity;
    
        public CartDTO() {
    
        }
    
    
        public CartDTO(String productId, Integer productQuantity) {
            this.productId = productId;
            this.productQuantity = productQuantity;
        }
    }
    

      

      3、异常类

    public class ProductException extends  RuntimeException {
        private Integer code;
    
        public ProductException(Integer code, String message){
            super(message);
            this.code = code;
        }
    
        public ProductException(ResultEnum resultEnum){
            super(resultEnum.getMessage());
            this.code = resultEnum.getCode();
        }
    }
    

      

    4、Service层扣库存

       /**
         * 扣库存
         *
         * @param cartDTOList
         */
        @Override
        @Transactional  //由于扣库存是list操作,所以需要事务操作
        public void decreaseStock(List<CartDTO> cartDTOList) {
            for(CartDTO cartDTO : cartDTOList){
               Optional<ProductInfo> productInfoOptional =  productInfoRepository.findById(cartDTO.getProductId());
               //判断商品是否存在
               if(!productInfoOptional.isPresent()){
                    throw new ProductException(ResultEnum.PRODUCT_NOT_EXIST);
               }
               ProductInfo productInfo = productInfoOptional.get();
               //库存是否足够  数据库里的库存-购物车中的数量
              Integer result = productInfo.getProductStock() - cartDTO.getProductQuantity();
              if(result <= 0){
                    throw new ProductException(ResultEnum.PRODUCT_STOCK_ERROR);
              }
              productInfo.setProductStock(result);
              productInfoRepository.save(productInfo);
            }
        }
    

      

    5、Controller层

       @PostMapping("/decreaseStock")
        public void decreaseStock(@RequestBody List<CartDTO> cartDTOList){
              productService.decreaseStock(cartDTOList);
        }
    

      

    第二步 Order工程

    1、增加方法定义

    /**
     * 需要在Product服务中要调的接口
     */
    @FeignClient(name = "product") //product代表访问product应用下的msg接口
    public interface ProductClient {
    
    
        @PostMapping("/product/decreaseStock")
        void decreaseStock(@RequestBody List<CartDTO> cartDTOList);
    }
    

      

    2、增加测试Controller

      @GetMapping("/productDecreaseStock")
        public String productDecreaseStock(){
            productClient.decreaseStock(Arrays.asList(new CartDTO("164103465734242707",3)));
            return  "ok";
        }
    

     

    3、测试调用

    然后查看数据库是否扣库存成功。

     

    四、完善下单接口

     1、服务层代码

     @Autowired
        private ProductClient productClient;
    
        /**
         * 创建订单
         *
         * @param orderDTO
         * @return
         */
        @Override
        public OrderDTO create(OrderDTO orderDTO) {
            String orderId= KeyUtil.genUniqueKey();
            // 查询商品信息(调用商品服务)
            List<String> productIdList = orderDTO.getOrderDetailList().stream()
                    .map(OrderDetail::getProductId)
                    .collect(Collectors.toList());
           List<ProductInfo> productInfoList = productClient.listForOrder(productIdList);
           Date date = new Date();
           //计算总价
            BigDecimal orderAmount = new BigDecimal(0);
            for(OrderDetail orderDetail: orderDTO.getOrderDetailList()){
                for(ProductInfo productInfo : productInfoList){
                    if (productInfo.getProductId().equals(orderDetail.getProductId())){
                        //单价 * 数量
                        orderAmount =  productInfo.getProductPrice()
                                .multiply( new BigDecimal(orderDetail.getProductQuantity()))
                                .add(orderAmount);
                        //这种方式copy,值为null也会copy过去
                        BeanUtils.copyProperties(productInfo, orderDetail);
                        orderDetail.setOrderId(orderId);
                        orderDetail.setDetailId(KeyUtil.genUniqueKey());
                        orderDetail.setCreateTime(date);
                        orderDetail.setUpdateTime(date);
                        //订单详情入口
                        orderDetailRepository.save(orderDetail);
                    }
                }
    
            }
    
            //扣库存(调用商品服务)
            List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream()
                    .map(e -> new CartDTO(e.getProductId(), e.getProductQuantity()))
                    .collect(Collectors.toList());
            productClient.decreaseStock(cartDTOList);
    
    
            // 5、订单入口
            OrderMaster orderMaster = new OrderMaster();
            orderDTO.setOrderId(orderId);
            BeanUtils.copyProperties(orderDTO, orderMaster);
            orderMaster.setOrderAmount(orderAmount);
            orderMaster.setOrderStatus(OrderStatusEnum.New.getCode());
            orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
            orderMaster.setCreateTime(date);
            orderMaster.setUpdateTime(date);
            orderMasterResponsibility.save(orderMaster);
    
            return orderDTO;
        }
    

      

    2、Controller层代码

        @RequestMapping("/create")
        public ResultVO<Map<String, String>> create(@Valid OrderForm orderForm, BindingResult bindingResult){
            if(bindingResult.hasErrors()){
                log.error("【创建订单】参数不正确, orderForm={}", orderForm);
                throw new OrderException(ResultEnum.PARAM_ERROR.getCode(),
                        bindingResult.getFieldError().getDefaultMessage());
            }
            // orderForm -> orderDTO
            OrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);
            if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {
                log.error("【创建订单】购物车信息为空");
                throw new OrderException(ResultEnum.CART_EMPTY);
            }
    
            OrderDTO result = orderService.create(orderDTO);
    
            Map<String, String> map = new HashMap<>();
            map.put("orderId", result.getOrderId());
            return ResultVOUtil.success(map);
        }
    

      

    3、验证下单接口,使用postman工具

    Feign实践缺点:

    里面介绍到Order工程调用Product工程里的方法,定义了FeignClient的/product/listForOrder方法,如果还有一个工程要调用/product/listForOrder这个方法,又要重复定义一次。

    /

     解决方法:

    可以把Feign的接口定义统一到一个工程中,其它接口如果想调用,继承这个接口即可。

    举例:

    定义了一个Backend_API工程

    然后在要使用这个接口的地方继承该接口

  • 相关阅读:
    C++函数参数传参的本质解析
    C#值类型和引用类型详解
    C#学习笔记(转换)
    C#学习笔记(泛型)
    # Java反射2——获取实体所有属性和方法,并对属性赋值
    Java反射1——扫描某个包下的所有类
    JSR教程2——Spring MVC数据校验与国际化
    JSR教程1——JSR 303
    Github如何撤销提交并清除痕迹
    论文第5章:Android绘图平台的实现
  • 原文地址:https://www.cnblogs.com/linlf03/p/10224236.html
Copyright © 2020-2023  润新知