Spring Cloud【Finchley】专栏
如果还没有系统的学过Spring Cloud ,先到我的专栏去逛逛吧
概述
还记得上篇博文的TODO吧
这里我们先循序渐进的了解下,微服务之间调用的几种方式
先了解下应用之间的通行的主要两种方式
- RPC – 代表 Dubbo (可以基于TCP协议,也可以基于HTTP协议)
- HTTP —代表 Spring Cloud (基于HTTP协议)
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微服务中, 步骤如下
- 添加依赖
- 添加注解@EnableFeignClients
- 开发接口
- 使用
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中的数据
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