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

本文涉及的产品
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
简介: Spring Cloud【Finchley】实战-03订单微服务与商品微服务之间的调用

Spring Cloud【Finchley】专栏


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

Spring Cloud 【Finchley】手札


概述

还记得上篇博文的TODO吧


20190324003702357.png


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

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

  • RPC – 代表 Dubbo (可以基于TCP协议,也可以基于HTTP协议)
  • HTTP —代表 Spring Cloud (基于HTTP协议)


HTTP方式之RestTemplate

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

product作为服务端,先对外暴露个测试接口


20190324203648863.png


order作为客户端调用该接口


20190324203841157.png


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


20190324204056349.png

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

20190324204229891.png

写死的地址,并且只能请求一个,如果有多个地址就比较麻烦了。而且还是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

20190324204229891.png

方式三 (使用@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

20190324204229891.png


Fegin 的使用


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

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

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

pom.xml 添加依赖


20190324214543898.png

添加注解@EnableFeignClients


20190324214605658.png


编写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


20190324221102935.png

OK。


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


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


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

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


20190324221300232.png

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


DAO层


老规矩,DAO层先


20190324221648654.png


单元测试下,

20190324221722570.png

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

结合库表中的数据

20190324221927130.png

20190324222004416.png


单元测试通过


Service层

紧接着Service层

20190324222429933.png


实现类


20190324222503133.png


单元测试


20190324222528546.png


单元测试通过

20190324222544848.png


Controller层

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


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

增加接口方法


20190324224712829.png


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

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


20190324225531692.png

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

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

20190324225846946.png

可见功能是OK的。


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

Product微服务减库存功能开发

减库存的参数 DTO封装

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

20190324221300232.png


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

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

20190325001328964.png

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


测试前数据


20190324233343803.png

20190324234559660.png

20190324234546310.png

Controller层


20190324235112950.png


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

增加接口方法

ProductClient接口新增方法


20190324235653852.png

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

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


20190324235938411.png

20190324235959496.png

整合

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


测试


20190325001822923.png

  [{
      "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

原始库存:

20190325002507975.png


使用POSTMAN测试一把


20190325002657358.png



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


20190325002349137.png


order_detail 3条记录


20190325002410382.png


库存:


2019032500253238.png


OK


知识点小结


点1

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

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


点2

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


20190324224511326.png

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


相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
2月前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
62 2
|
18天前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
52 3
|
26天前
|
运维 NoSQL Java
后端架构演进:微服务架构的优缺点与实战案例分析
【10月更文挑战第28天】本文探讨了微服务架构与单体架构的优缺点,并通过实战案例分析了微服务架构在实际应用中的表现。微服务架构具有高内聚、低耦合、独立部署等优势,但也面临分布式系统的复杂性和较高的运维成本。通过某电商平台的实际案例,展示了微服务架构在提升系统性能和团队协作效率方面的显著效果,同时也指出了其带来的挑战。
61 4
|
2月前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
2月前
|
JSON Java 数据格式
【微服务】SpringCloud之Feign远程调用
本文介绍了使用Feign作为HTTP客户端替代RestTemplate进行远程调用的优势及具体使用方法。Feign通过声明式接口简化了HTTP请求的发送,提高了代码的可读性和维护性。文章详细描述了Feign的搭建步骤,包括引入依赖、添加注解、编写FeignClient接口和调用代码,并提供了自定义配置的示例,如修改日志级别等。
99 1
|
2月前
|
人工智能 文字识别 Java
SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?
尼恩,一位拥有20年架构经验的老架构师,通过其深厚的架构功力,成功指导了一位9年经验的网易工程师转型为大模型架构师,薪资逆涨50%,年薪近80W。尼恩的指导不仅帮助这位工程师在一年内成为大模型架构师,还让他管理起了10人团队,产品成功应用于多家大中型企业。尼恩因此决定编写《LLM大模型学习圣经》系列,帮助更多人掌握大模型架构,实现职业跃迁。该系列包括《从0到1吃透Transformer技术底座》、《从0到1精通RAG架构》等,旨在系统化、体系化地讲解大模型技术,助力读者实现“offer直提”。此外,尼恩还分享了多个技术圣经,如《NIO圣经》、《Docker圣经》等,帮助读者深入理解核心技术。
SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?
|
20天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
78 6
|
20天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
31 1
|
3月前
|
安全 应用服务中间件 API
微服务分布式系统架构之zookeeper与dubbo-2
微服务分布式系统架构之zookeeper与dubbo-2
|
3月前
|
负载均衡 Java 应用服务中间件
微服务分布式系统架构之zookeeper与dubbor-1
微服务分布式系统架构之zookeeper与dubbor-1