前言:本课程是在慕课网上学习Spring Cloud微服务实战 第五章 应用通信 时所做的笔记,供本人复习之用.
主要讲述了 应用间进行通信的方法与实践以及项目多模块的拆分.
代码地址https://github.com/springcloud-demo
目录
第一章 HTTP与PRC
第二章 RestTemplate的三种使用方法
2.1 第一种使用方式
2.2 第二种使用方式
2.3 第三种使用方式
第三章 负载均衡器Ribbon
3.1 Ribbon概要介绍
3.2 Ribbon源码追踪
第四章 Feign
第五章 使用Feign做服务间的通信
5.1 查询商品详情
5.1.1 商品服务
5.1.2 订单服务
5.2 扣库存
5.2.1 商品服务
5.2.2 订单服务
5.3 完善整个下单流程
5.3.1 订单服务
第六章 项目拆分成多模块的原因
6.1 封装对象的问题
6.2 对象的重复问题
6.3 接口的问题
6.4 项目划分为多模块
6.4.1 模块间的依赖关系
第七章 项目拆分成多模块
7.1 商品服务拆分成多模块
7.2 订单服务拆分成多模块
应用间的通信方式主要有两种HTTP与RPC,SpringCloud与Dubbo正好是这两种方式的代表.
Dubbo本身的定位就是一个RPC框架,基于Dubbo开发的应用还是要依赖周边的平台存在,相比其它RPC框架,Dubbo在服务治理功能上非常完善,不仅提供了服务注册发现,负载均衡,路由以及面向分布式集群的技术能力,还涉及了面向开发测试阶段的mocker泛化调用等机制,同时也提供了服务治理和监控的可视化平台,SpringCloud在没有出来之前Dubbo在国内应用的相当广泛,Dubbo的定位始终是一款RPC框架,而SpringCloud的目标是微服务架构下的一站式解决方案,在重启维护后,Dubbo官方表示,要积极寻求适配到SpringCloud的方式,比如作为SpringCloud的二进制通信方案来发挥Dubbo的性能优势,或者Dubbo通过模块化或者对http的支持适配到SpringCloud,不过当前两者还是不兼容.
SpringCloud微服务架构下,微服务之间使用的是http restful通信方式,http restful本身轻量易用,适用性强可以很容易的跨语言,跨平台,或者与已有的系统交互,我们前端举的例子node.js的Eureka Client也印证了这一点.
SpringCloud通过以下两种restful调用方式.
RestTemplate与Feign.
RestTemplate是一个http客户端,功能与java中的httpClient类似,用法上更加简单,下面我们将用订单服务调用商品服务的接口.我们可以将订单服务理解成客户端,商品服务理解成服务端,
服务端配置application.properties,引入的依赖在上一篇中:
spring.application.name=product
#datasource
spring.datasource.url=jdbc:mysql://localhost:3306/SpringCloud_Sell?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#Jpa
spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
eureka.client.service-url.defaultZone = http://localhost:8761/eureka
server.port=8083
客户端配置:
spring.application.name=order
#datasource
spring.datasource.url=jdbc:mysql://localhost:3306/SpringCloud_Sell?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#Jpa
spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
eureka.client.service-url.defaultZone = http://localhost:8761/eureka
server.port=8081
服务中心配置:
eureka.client.service-url.defaultZone = http://localhost:8761/eureka/
eureka.client.register-with-eureka=false
eureka.server.enable-self-preservation=false
spring.application.name=eureka
server.port=8761
我们先调用几个简单的方法.
product服务端的controller(我们要在客户端中进行调用的)
@RestController
public class ServerController {
@GetMapping("/msg")
public String msg(){
return "this is product' msg2";
}
}
order客户端中的controller:
直接指定地址和返回值.
@RequestMapping("/getProductMsg")
public String getProductMsg(){
//第一种方式,到那时当有多个地址的时候就有问题了
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("http://localhost:8083/msg",String.class);
log.info("response={}",response);
return response;
}
启动应用,访问地址
访问成功
这样的弊端是地址写死,如果不知道对方的地址,或者对方有两个地址就会无从下手.
现在我们有两个服务器
product服务端配置不变.
order客户端的服务方式变为:
@RestController
@Slf4j
public class ClientController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@RequestMapping("/getProductMsg")
public String getProductMsg(){
ServiceInstance serviceInstance = loadBalancerClient.choose("Product");
String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort())+"/msg";
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url,String.class);
log.info("response={}",response);
return response;
}
}
也访问到了数据
我们这样通过应用名就访问到了要调用的服务,但是这种方式还有一些繁琐.
第三种方式主要是使用了@LoadBalanced注解
新建Config类.
@Component
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
product服务端配置不变.
order客户端的服务方式变为:
@RestController
@Slf4j
public class ClientController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/getProductMsg")
public String getProductMsg(){
String response = restTemplate.getForObject("http://PRODUCT/msg",String.class);
log.info("response={}",response);
return response;
}
}
继续访问可以获得信息.
这三种方式都可以归结为使用restTemplate的方式调用应用的接口,值得注意的是这里有一个负载均衡的问题,它会帮我们去选择ip地址,至于负载均衡的策略,可以随机,轮询等等.
在讲解Eureka的时候,我们也说过它是客户端发现,Eureka属于客户端发现的方式,它的负载均衡是软负载,也就是客户端会向服务器拉取已注册的可用服务信息,然后根据负载均衡策略,直接命中每台服务器发送请求,整个过程都是在客户端完成的,并不需要服务器的参与,SpringCloud客户端的负载均衡就是Ribbon组件,它是基于netflix Ribbon实现的,通过SpringCloud的封装,可以轻松的面向服务的Rest模板请求,自动转化成客户端负载均衡服务调用.
RestTemplate Feign Zuul都使用到了Ribbon,SpirngCloud在结合了Ribbon的负载均衡实现中,封装增加了HttpClient和OkHttp两种请求端实现,默认使用ribbon对eureka服务发现的负载均衡Client,第二章中介绍了RestTemplate三种实现方式,其中通过添加@LoadBalanced注解或者直接写代码的时候使用loadBalanceClient,其实用到的就是Ribbon的组件,添加@LoadBalanced注解后,Ribbon会通过loadBalanceClient自动帮助你基于某种规则比如简单的轮询,随机连接等去连接目标服务,从而很容易使用Ribbnon实现自定义负载均衡算法.
Ribbon实现软负载均衡核心有三点
1.服务发现,也就是发现依赖服务的列表.也就是依据服务的名字,找出服务中的所有实例.
2.服务选择规则,依据规则策略,如何从多个服务中选择一个有效的服务.
3.服务监听,也就是检测失效的服务,做到高效剔除.
Ribbon的主要组件
ServerList,IRule,ServerListFilter等
主要流程是这样的,通过ServerList获得所有的可用服务列表,然后通过ServerListFilter过滤掉一部分地址,最后剩下的地址中通过IRule选择一个实例作为最终目标结果.
我们用2.2的方式来查看源码.
ServiceInstance serviceInstance = loadBalancerClient.choose("Product");
idea:鼠标放在choose上,在按alt+ctrl+b找到其实现.
找到choose源码,getServer就是要将服务列表找出来
public class RibbonLoadBalancerClient implements LoadBalancerClient {
//...省略
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, null);
}
public ServiceInstance choose(String serviceId, Object hint) {
Server server = getServer(getLoadBalancer(serviceId), hint);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
//...省略
}
我们点进getServer中,看到其用ILoadBalancer在找.
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
我们点进chooseServer中,选择BaseLoadBalancer类,如图1所示,其中的getAllServers就是获取所有的服务列表.
@Override
public List getAllServers() {
return Collections.unmodifiableList(allServerList);
}
再在BaseLoadBalancer类中找到chooseServer方法,rule.choose表示用规则选择服务,我们点进rule
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
我们发现rule使用的是默认的规则RoundRobinRule,这是一个轮询的方式.
private final static IRule DEFAULT_RULE = new RoundRobinRule();
private final static SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy();
private static final String DEFAULT_NAME = "default";
private static final String PREFIX = "LoadBalancer_";
protected IRule rule = DEFAULT_RULE;
我们在DynamicServerListLoadBalancer打上断点也能发现其用的是轮询的规则.
通过如下可以切换规,比如图2就表示换成随机规则.在springcloud的官方文档中搜索Ribbon关键字找到Customizing default for all Ribbon Clients可以查看更多相关信息.
Feign是一个声明式REST客户端,采用了接口加注解的方式,Feign内部也使用了Ribbon.
前面使用了RestTemplate,这章使用Feign进行应用间的通信.订单服务调用商品服务.
在订单服务中增加Feign的依赖.
org.springframework.cloud
spring-cloud-starter-openfeign
在启动主类上加注解@EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
在订单服务中新建接口,name说明的是要应用哪个应用,getmapping指明要用应用中的哪个controller.方法名只是一个标识.
@FeignClient(name="product")
public interface ProductClient {
@GetMapping("msg")
String productMsg();
}
在订单中直接注入,然后当作方法调用即可.
@RestController
@Slf4j
public class ClientController {
@Autowired
private ProductClient productClient;
@GetMapping("/getProductMsg")
public String getProductMsg(){
String response = productClient.productMsg();
log.info("response={}",response);
return response;
}
}
我们要用订单服务去调用商品服务,从订单服务中给出商品的id,然后商品服务根据id查询商品信息,并将查询到的商品信息返回给订单服务.
写之前有两点需要注意:@ResquestBody注解必须要用PostMapping的形式来映射,无参,单个参数@RequestParam,或者@PathVariable注解都可以用get.
主要是传入productId,返回product详细信息集合.
controller:
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
/*获取商品列表(给订单服务用的)*/
@PostMapping("/listForOrder")
public List listForOrder(@RequestBody List productList){
return productService.findByProductIdIn(productList);
}
}
service:
@Override
public List findByProductIdIn(List productIdList) {
return productInfoRepository.findByProductIdIn(productIdList);
}
dao:
public interface ProductInfoRepository extends JpaRepository {
List findByProductIdIn(List productId);
}
指明要使用的服务
@FeignClient(name="product")
public interface ProductClient {
@PostMapping("/product/listForOrder")
List listForOrder(@RequestBody List productList);
}
在controller中进行调用
@GetMapping("/getProductList")
public String getProductList(){
List productInfoList = productClient.listForOrder(Arrays.asList("164103465734242707"));
log.info("response:{}",productInfoList);
return "ok";
}
访问地址http://localhost:8081/getProductList,获得信息成功
订单服务将要扣库存的商品id与数量传入商品服务,由商品服务进行商品数量的更改.
数据对象:
@Data
public class CartDTO {
private String productId;
private Integer productQuantity;
public CartDTO(String productId, Integer productQuantity) {
this.productId = productId;
this.productQuantity = productQuantity;
}
}
service层:
@Override
@Transactional
public void decreaseStock(List cartDTOList) {
for(CartDTO cartDTO:cartDTOList){
//从数据库中查询处商品信息
Optional productInfoOptional = productInfoRepository.findById(cartDTO.getProductId());
//商品不存在抛出异常
if(!productInfoOptional.isPresent()){
throw new ProductException(ResultEnum.PRODUCT_NOT_EXIST);
}
ProductInfo productInfo = productInfoOptional.get();
//商品数量不足抛出异常
Integer result = productInfo.getProductStock() - cartDTO.getProductQuantity();
if(result<0){
throw new ProductException(ResultEnum.PRODUCT_STOCK_ERROR);
}
//将更改数量的商品存入数据库
productInfo.setProductStock(result);
productInfoRepository.save(productInfo);
}
}
controller层:
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping("/decreaseStock")
public void decreaseStock(@RequestBody List cartDTOList){
productService.decreaseStock(cartDTOList);
}
}
指明商品服务配置:
@FeignClient(name="product")
public interface ProductClient {
@PostMapping("/product/decreaseStock")
void decreaseStock(@RequestBody List cartDTOList);
}
controller进行调用:
@GetMapping("productDecreaseStock")
public String productDecreaseStock(){
productClient.decreaseStock(Arrays.asList(new CartDTO("164103465734242707",3)));
return "ok";
}
运行,商品库存减少.
前台的请求参数就是用户信息以及购买的各种商品数量,类似下图所示
下单流程: 查询商品信息->计算总价->扣库存->订单入库
controller层:
@Autowired
private OrderService orderService;
@PostMapping("/create")
public ResultVO
service层:
@Override
public OrderDTO create(OrderDTO orderDTO) {
String orderId = KeyUtil.getUniqueKey();
//查询商品信息(调用商品服务)
List productIdList = orderDTO.getOrderDetailList().stream()
.map(OrderDetail::getProductId)
.collect(Collectors.toList());
log.info("查询商品信息(调用商品服务)");
List productInfoList = productClient.listForOrder(productIdList);
//计算总价
BigDecimal orderAmout = new BigDecimal(BigInteger.ZERO);
for (OrderDetail orderDetail: orderDTO.getOrderDetailList()) {
for (ProductInfo productInfo: productInfoList) {
if (productInfo.getProductId().equals(orderDetail.getProductId())) {
//单价*数量
orderAmout = productInfo.getProductPrice()
.multiply(new BigDecimal(orderDetail.getProductQuantity()))
.add(orderAmout);
BeanUtils.copyProperties(productInfo, orderDetail);
orderDetail.setOrderId(orderId);
orderDetail.setDetailId(KeyUtil.getUniqueKey());
//订单详情入库
orderDetailRepository.save(orderDetail);
}
}
}
//扣库存(调用商品服务)
List decreaseStockInputList = orderDTO.getOrderDetailList().stream()
.map(e -> new CartDTO(e.getProductId(), e.getProductQuantity()))
.collect(Collectors.toList());
productClient.decreaseStock(decreaseStockInputList);
//订单入库
OrderMaster orderMaster = new OrderMaster();
orderDTO.setOrderId(orderId);
BeanUtils.copyProperties(orderDTO, orderMaster);
orderMaster.setOrderAmount(orderAmout);
orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode());
orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
orderMasterRepository.save(orderMaster);
return orderDTO;
}
之前我们用订单服务操作商品服务完成了下单,但是有的地方还做的不够好.
商品服务返回了ProductInfo对象,基本上这个对象是不应该暴露给外部的,肯定要封装另外一个对象,它们之间的内容可能会有一些细微的差别.如果把这个暴露出去都给别人看到了会有问题.
@Entity
public class ProductInfo {
@Id
private String productId;
/** 名字. */
private String productName;
/** 单价. */
private BigDecimal productPrice;
/** 库存. */
private Integer productStock;
/** 描述. */
private String productDescription;
/** 小图. */
private String productIcon;
/** 状态, 0正常1下架. */
private Integer productStatus;
/** 类目编号. */
private Integer categoryType;
private Date createTime;
private Date updateTime;
}
我们在商品服务于订单服务中都有ProductInfo对象,这样维护起来比较麻烦.
我们现在把商品服务的接口定义在了订单服务中,如下代码所示就是定义在订单服务中的商品服务的接口.一个公司中,订单服务和商品服务可能是两组人负责开发,把对方的服务定义在了自己的服务中,这显然是不符合逻辑的,应该自己定义向外暴露的接口.即下面的接口应该定义在商品服务中.
@FeignClient(name="product")
public interface ProductClient {
@GetMapping("msg")
String productMsg();
@PostMapping("/product/listForOrder")
List listForOrder(@RequestBody List productList);
@PostMapping("/product/decreaseStock")
void decreaseStock(@RequestBody List cartDTOList);
}
把项目划分成三个模块
product-server模块,存放所有的业务逻辑,就是之前的controller,service中的代码.
product-client模块,存放对外暴露的接口,比如商品模块对外暴露了两个接口,商品列表和扣库存.
product-common模块,放的是公用的对象,即会被外部服务调用,也会被内部其它模块使用.
业务逻辑中会使用公用对象,接口中的参数与返回值会使用公用对象
1.在product项目中的pom.xml中加入下面的代码,并将packaging改成pom,在module的提示中选择Create Module With Parent,因为server封装的是业务代码,所以将product中的src移动到server中.同时将product的pom.xml中的内容移动到server的pom.xml中.
common
client
server
pom
之后项目的结构如下图所示
2.为了避免名称冲突,将client,server,common三者的项目名改成product-client,product-server,product-common.
client与server都依赖于common,修改client与server的pom.xml继承于common.
修改完后三者的pom.xml如下:
common:
com.imooc
product
0.0.1-SNAPSHOT
product-common
org.projectlombok
lombok
client(因为要提供对外接口所以引入了spring-cloud-openfeign-core):
com.imooc
product
0.0.1-SNAPSHOT
product-client
org.springframework
spring-web
com.imooc
product-common
org.springframework.cloud
spring-cloud-openfeign-core
server:
com.imooc
product
0.0.1-SNAPSHOT
product-server
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
runtime
com.imooc
product-common
org.springframework.boot
spring-boot-maven-plugin
3.在common与client中建立对应的文件夹结构如下图所示
common:
client:
4. 在common中建立ProductInfoOutput对象,代替之前的ProductInfo对象.建立DecreaseStockInput对象代替之前的CartDTO对象.
对应的server中一些涉及到的业务代码也要更换,这个自行更换.
@Data
public class ProductInfoOutput {
private String productId;
/** 名字. */
private String productName;
/** 单价. */
private BigDecimal productPrice;
/** 库存. */
private Integer productStock;
/** 描述. */
private String productDescription;
/** 小图. */
private String productIcon;
/** 状态, 0正常1下架. */
private Integer productStatus;
/** 类目编号. */
private Integer categoryType;
}
@Data
public class DecreaseStockInput {
private String productId;
private Integer productQuantity;
public DecreaseStockInput(String productId, Integer productQuantity) {
this.productId = productId;
this.productQuantity = productQuantity;
}
public DecreaseStockInput() {
}
}
5.对应接口的设置
在client中设置对应的接口
@FeignClient(name = "product")
public interface ProductClient {
/*获取商品列表(给订单服务用的)*/
@PostMapping("/listForOrder")
public List listForOrder(@RequestBody List productList);
@PostMapping("/decreaseStock")
public void decreaseStock(@RequestBody List decreaseStockInputList);
}
具体流程和上面的,这里就主要列以下代码.
还是client与server依赖于common.
项目的结构:
order的pom.xml,我们这里因为需要product服务中的接口与对象,所以我们引入了product-client依赖.
org.springframework.boot
spring-boot-starter-parent
2.1.4.RELEASE
com.imooc
order
0.0.1-SNAPSHOT
common
client
server
pom
order
Demo project for Spring Boot
1.8
Greenwich.SR1
0.0.1-SNAPSHOT
0.0.1-SNAPSHOT
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
com.imooc
product-client
${product-client.version}
com.imooc
order-common
${order-common.version}
order-common的pom.xml,因为没有公共对象,所以order-common里并没有实际的代码.
com.imooc
order
0.0.1-SNAPSHOT
order-common
org.projectlombok
lombok
order-client的pom.xml,因为没有对外提供接口,所以order-client里并没有实际的代码.
com.imooc
order
0.0.1-SNAPSHOT
com.imooc
order-client
0.0.1-SNAPSHOT
com.imooc
order-common
order-client的pom.xml,因为没有对外提供接口,所以order-client里并没有实际的代码.
com.imooc
order
0.0.1-SNAPSHOT
com.imooc
order-client
0.0.1-SNAPSHOT
com.imooc
order-common
order-server的pom.xml,这里封装了业务逻辑,订单相关的代码都在其中.
com.imooc
order
0.0.1-SNAPSHOT
order-server
com.imooc
order-common
com.imooc
product-client
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
runtime
com.google.code.gson
gson
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.boot
spring-boot-maven-plugin
因为这里引入了product的代码,所以我们要改变原有的一些代码,这里仅以OrderServiceImpl举例.其它的也类似.
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMasterRepository orderMasterRepository;
@Autowired
private OrderDetailRepository orderDetailRepository;
@Autowired
private ProductClient productClient;
@Transactional
@Override
public OrderDTO create(OrderDTO orderDTO) {
log.info("进入了函数");
String orderId = KeyUtil.getUniqueKey();
//查询商品信息(调用商品服务)
List productIdList = orderDTO.getOrderDetailList().stream()
.map(OrderDetail::getProductId)
.collect(Collectors.toList());
log.info("查询商品信息(调用商品服务)");
log.info("商品id长度为{}",productIdList.size());
log.info("值为:{}",productIdList.toString());
List productInfoList = productClient.listForOrder(productIdList);
//计算总价
BigDecimal orderAmout = new BigDecimal(BigInteger.ZERO);
for (OrderDetail orderDetail: orderDTO.getOrderDetailList()) {
for (ProductInfoOutput productInfoOutput: productInfoList) {
if (productInfoOutput.getProductId().equals(orderDetail.getProductId())) {
//单价*数量
orderAmout = productInfoOutput.getProductPrice()
.multiply(new BigDecimal(orderDetail.getProductQuantity()))
.add(orderAmout);
BeanUtils.copyProperties(productInfoOutput, orderDetail);
orderDetail.setOrderId(orderId);
orderDetail.setDetailId(KeyUtil.getUniqueKey());
//订单详情入库
orderDetailRepository.save(orderDetail);
}
}
}
//扣库存(调用商品服务)
List decreaseStockInputList = orderDTO.getOrderDetailList().stream()
.map(e -> new DecreaseStockInput(e.getProductId(), e.getProductQuantity()))
.collect(Collectors.toList());
productClient.decreaseStock(decreaseStockInputList);
//订单入库
OrderMaster orderMaster = new OrderMaster();
orderDTO.setOrderId(orderId);
BeanUtils.copyProperties(orderDTO, orderMaster);
orderMaster.setOrderAmount(orderAmout);
orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode());
orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
log.info("buyerPhone为{}",orderMaster.getBuyerPhone());
orderMasterRepository.save(orderMaster);
return orderDTO;
}
}
最后启动eureka服务中心,product服务,order服务,测试下单功能,成功.