Spring Cloud【Finchley】实战-03订单微服务与商品微服务之间的调用
作者:互联网
文章目录
- Spring Cloud【Finchley】专栏
- 概述
- HTTP方式之RestTemplate
- Fegin 的使用
- 商品微服务获取商品列表功能开发
- 调用商品微服务扣库存功能开发
- 整合
- 知识点小结
- Github
如果还没有系统的学过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中的数据
artisan_order
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
标签:03,product,Spring,springframework,annotation,Finchley,org,import,order 来源: https://blog.51cto.com/u_15239532/2836499