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

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月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)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
10天前
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
|
1月前
|
搜索推荐 NoSQL Java
微服务架构设计与实践:用Spring Cloud实现抖音的推荐系统
本文基于Spring Cloud实现了一个简化的抖音推荐系统,涵盖用户行为管理、视频资源管理、个性化推荐和实时数据处理四大核心功能。通过Eureka进行服务注册与发现,使用Feign实现服务间调用,并借助Redis缓存用户画像,Kafka传递用户行为数据。文章详细介绍了项目搭建、服务创建及配置过程,包括用户服务、视频服务、推荐服务和数据处理服务的开发步骤。最后,通过业务测试验证了系统的功能,并引入Resilience4j实现服务降级,确保系统在部分服务故障时仍能正常运行。此示例旨在帮助读者理解微服务架构的设计思路与实践方法。
103 17
|
2月前
|
JSON Java API
利用Spring Cloud Gateway Predicate优化微服务路由策略
Spring Cloud Gateway 的路由配置中,`predicates`​(断言)用于定义哪些请求应该匹配特定的路由规则。 断言是Gateway在进行路由时,根据具体的请求信息如请求路径、请求方法、请求参数等进行匹配的规则。当一个请求的信息符合断言设置的条件时,Gateway就会将该请求路由到对应的服务上。
185 69
利用Spring Cloud Gateway Predicate优化微服务路由策略
|
2月前
|
Java Nacos Sentinel
Spring Cloud Alibaba:一站式微服务解决方案
Spring Cloud Alibaba(简称SCA) 是一个基于 Spring Cloud 构建的开源微服务框架,专为解决分布式系统中的服务治理、配置管理、服务发现、消息总线等问题而设计。
463 13
Spring Cloud Alibaba:一站式微服务解决方案
|
2月前
|
消息中间件 监控 Java
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
48 6
|
2月前
|
Java 关系型数据库 MySQL
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
81 5
|
2月前
|
缓存 监控 Java
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
60 5
|
2月前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
210 5
|
1月前
|
人工智能 安全 Java
微服务引擎 MSE:打造通用的企业级微服务架构
微服务引擎MSE致力于打造通用的企业级微服务架构,涵盖四大核心内容:微服务技术趋势与挑战、MSE应对方案、拥抱开源及最佳实践。MSE通过流量入口、内部流量管理、服务治理等模块,提供高可用、跨语言支持和性能优化。此外,MSE坚持开放,推动云原生与AI融合,助力企业实现无缝迁移和高效运维。
|
2月前
|
运维 监控 持续交付
微服务架构解析:跨越传统架构的技术革命
微服务架构(Microservices Architecture)是一种软件架构风格,它将一个大型的单体应用拆分为多个小而独立的服务,每个服务都可以独立开发、部署和扩展。
560 36
微服务架构解析:跨越传统架构的技术革命