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

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月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)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
25天前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
48 2
|
22天前
|
自然语言处理 Java API
Spring Boot 接入大模型实战:通义千问赋能智能应用快速构建
【10月更文挑战第23天】在人工智能(AI)技术飞速发展的今天,大模型如通义千问(阿里云推出的生成式对话引擎)等已成为推动智能应用创新的重要力量。然而,对于许多开发者而言,如何高效、便捷地接入这些大模型并构建出功能丰富的智能应用仍是一个挑战。
89 6
|
25天前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
62 2
|
1月前
|
Java 数据库 数据安全/隐私保护
Spring 微服务提示:使用环境变量抽象数据库主机名
Spring 微服务提示:使用环境变量抽象数据库主机名
40 1
|
1月前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
26 1
|
1月前
|
监控 Java 对象存储
监控与追踪:如何利用Spring Cloud Sleuth和Netflix OSS工具进行微服务调试
监控与追踪:如何利用Spring Cloud Sleuth和Netflix OSS工具进行微服务调试
42 1
|
28天前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
62 0
|
1月前
|
JSON 前端开发 JavaScript
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
本文介绍如何使用 Spring Boot 3.3 实现职责链模式,优化电商订单处理流程。通过将订单处理的各个环节(如库存校验、优惠券核验、支付处理等)封装为独立的处理器,并通过职责链将这些处理器串联起来,实现了代码的解耦和灵活扩展。具体实现包括订单请求类 `OrderRequest`、抽象处理器类 `OrderHandler`、具体处理器实现(如 `OrderValidationHandler`、`VerifyCouponHandler` 等)、以及初始化职责链的配置类 `OrderChainConfig`。
|
1月前
|
XML Java 数据库连接
【2020Spring编程实战笔记】Spring开发分享~(上)
【2020Spring编程实战笔记】Spring开发分享~
53 0
|
6天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
31 6