前言:本课程是在慕课网上学习Spring Cloud微服务实战 第五章 应用通信 时所做的笔记,供本人复习之用.
主要讲述了 应用间进行通信的方法与实践以及项目多模块的拆分.
代码地址https://github.com/springcloud-demo
目录
第一章 HTTP与PRC
应用间的通信方式主要有两种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的三种使用方法
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
我们先调用几个简单的方法.
2.1 第一种使用方式
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;
}
启动应用,访问地址
访问成功
这样的弊端是地址写死,如果不知道对方的地址,或者对方有两个地址就会无从下手.
2.2 第二种使用方式
现在我们有两个服务器
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;
}
}
也访问到了数据
我们这样通过应用名就访问到了要调用的服务,但是这种方式还有一些繁琐.
2.3 第三种使用方式
第三种方式主要是使用了@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地址,至于负载均衡的策略,可以随机,轮询等等.
第三章 负载均衡器Ribbon
3.1 Ribbon概要介绍
在讲解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选择一个实例作为最终目标结果.
3.2 Ribbon源码追踪
我们用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<Server> 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
Feign是一个声明式REST客户端,采用了接口加注解的方式,Feign内部也使用了Ribbon.
前面使用了RestTemplate,这章使用Feign进行应用间的通信.订单服务调用商品服务.
在订单服务中增加Feign的依赖.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在启动主类上加注解@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;
}
}
第五章 使用Feign做服务间的通信
5.1 查询商品详情
我们要用订单服务去调用商品服务,从订单服务中给出商品的id,然后商品服务根据id查询商品信息,并将查询到的商品信息返回给订单服务.
写之前有两点需要注意:@ResquestBody注解必须要用PostMapping的形式来映射,无参,单个参数@RequestParam,或者@PathVariable注解都可以用get.
5.1.1 商品服务
主要是传入productId,返回product详细信息集合.
controller:
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
/*获取商品列表(给订单服务用的)*/
@PostMapping("/listForOrder")
public List<ProductInfo> listForOrder(@RequestBody List<String> productList){
return productService.findByProductIdIn(productList);
}
}
service:
@Override
public List<ProductInfo> findByProductIdIn(List<String> productIdList) {
return productInfoRepository.findByProductIdIn(productIdList);
}
dao:
public interface ProductInfoRepository extends JpaRepository<ProductInfo,String> {
List<ProductInfo> findByProductIdIn(List<String> productId);
}
5.1.2 订单服务
指明要使用的服务
@FeignClient(name="product")
public interface ProductClient {
@PostMapping("/product/listForOrder")
List<ProductInfo> listForOrder(@RequestBody List<String> productList);
}
在controller中进行调用
@GetMapping("/getProductList")
public String getProductList(){
List<ProductInfo> productInfoList = productClient.listForOrder(Arrays.asList("164103465734242707"));
log.info("response:{}",productInfoList);
return "ok";
}
访问地址http://localhost:8081/getProductList,获得信息成功
5.2 扣库存
订单服务将要扣库存的商品id与数量传入商品服务,由商品服务进行商品数量的更改.
5.2.1 商品服务
数据对象:
@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<CartDTO> cartDTOList) {
for(CartDTO cartDTO:cartDTOList){
//从数据库中查询处商品信息
Optional<ProductInfo> 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<CartDTO> cartDTOList){
productService.decreaseStock(cartDTOList);
}
}
5.2.2 订单服务
指明商品服务配置:
@FeignClient(name="product")
public interface ProductClient {
@PostMapping("/product/decreaseStock")
void decreaseStock(@RequestBody List<CartDTO> cartDTOList);
}
controller进行调用:
@GetMapping("productDecreaseStock")
public String productDecreaseStock(){
productClient.decreaseStock(Arrays.asList(new CartDTO("164103465734242707",3)));
return "ok";
}
运行,商品库存减少.
5.3 完善整个下单流程
前台的请求参数就是用户信息以及购买的各种商品数量,类似下图所示
下单流程: 查询商品信息->计算总价->扣库存->订单入库
5.3.1 订单服务
controller层:
@Autowired
private OrderService orderService;
@PostMapping("/create")
public ResultVO<Map<String,String>> create(@Valid OrderForm orderForm, BindingResult bindingResult){
if(bindingResult.hasErrors()){
log.error("创建订单参数不正确 ={}",orderForm);
throw new OrderException(1,bindingResult.getFieldError().getDefaultMessage());
}
OrderDTO orderDTO = OrderForm2OrderDTO.convert(orderForm);
//判断购物车是否为空
if(CollectionUtils.isEmpty(orderDTO.getOrderDetailList())){
log.error("创建订单信息失败,购物车为空");
throw new OrderException(-1,"购物车为空");
}
OrderDTO result = orderService.create(orderDTO);
Map<String,String> map = new HashMap<>();
map.put("orderId",result.getOrderId());
return ResultVOUtil.success(map);
}
service层:
@Override
public OrderDTO create(OrderDTO orderDTO) {
String orderId = KeyUtil.getUniqueKey();
//查询商品信息(调用商品服务)
List<String> productIdList = orderDTO.getOrderDetailList().stream()
.map(OrderDetail::getProductId)
.collect(Collectors.toList());
log.info("查询商品信息(调用商品服务)");
List<ProductInfo> 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<CartDTO> 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;
}
第六章 项目拆分成多模块的原因
之前我们用订单服务操作商品服务完成了下单,但是有的地方还做的不够好.
6.1 封装对象的问题
商品服务返回了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;
}
6.2 对象的重复问题
我们在商品服务于订单服务中都有ProductInfo对象,这样维护起来比较麻烦.
6.3 接口的问题
我们现在把商品服务的接口定义在了订单服务中,如下代码所示就是定义在订单服务中的商品服务的接口.一个公司中,订单服务和商品服务可能是两组人负责开发,把对方的服务定义在了自己的服务中,这显然是不符合逻辑的,应该自己定义向外暴露的接口.即下面的接口应该定义在商品服务中.
@FeignClient(name="product")
public interface ProductClient {
@GetMapping("msg")
String productMsg();
@PostMapping("/product/listForOrder")
List<ProductInfo> listForOrder(@RequestBody List<String> productList);
@PostMapping("/product/decreaseStock")
void decreaseStock(@RequestBody List<CartDTO> cartDTOList);
}
6.4 项目划分为多模块
把项目划分成三个模块
product-server模块,存放所有的业务逻辑,就是之前的controller,service中的代码.
product-client模块,存放对外暴露的接口,比如商品模块对外暴露了两个接口,商品列表和扣库存.
product-common模块,放的是公用的对象,即会被外部服务调用,也会被内部其它模块使用.
6.4.1 模块间的依赖关系
业务逻辑中会使用公用对象,接口中的参数与返回值会使用公用对象
第七章 项目拆分成多模块
7.1 商品服务拆分成多模块
1.在product项目中的pom.xml中加入下面的代码,并将packaging改成pom,在module的提示中选择Create Module With Parent,因为server封装的是业务代码,所以将product中的src移动到server中.同时将product的pom.xml中的内容移动到server的pom.xml中.
<modules>
<module>common</module>
<module>client</module>
<module>server</module>
</modules>
<packaging>pom</packaging>
之后项目的结构如下图所示
2.为了避免名称冲突,将client,server,common三者的项目名改成product-client,product-server,product-common.
client与server都依赖于common,修改client与server的pom.xml继承于common.
修改完后三者的pom.xml如下:
common:
<parent>
<groupId>com.imooc</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>product-common</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
client(因为要提供对外接口所以引入了spring-cloud-openfeign-core):
<parent>
<groupId>com.imooc</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>product-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>product-common</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
</dependency>
</dependencies>
server:
<parent>
<groupId>com.imooc</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>product-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>product-common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
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<ProductInfoOutput> listForOrder(@RequestBody List<String> productList);
@PostMapping("/decreaseStock")
public void decreaseStock(@RequestBody List<DecreaseStockInput> decreaseStockInputList);
}
7.2 订单服务拆分成多模块
具体流程和上面的,这里就主要列以下代码.
还是client与server依赖于common.
项目的结构:
order的pom.xml,我们这里因为需要product服务中的接口与对象,所以我们引入了product-client依赖.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.imooc</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<modules>
<module>common</module>
<module>client</module>
<module>server</module>
</modules>
<packaging>pom</packaging>
<name>order</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
<product-client.version>0.0.1-SNAPSHOT</product-client.version>
<order-common.version>0.0.1-SNAPSHOT</order-common.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>product-client</artifactId>
<version>${product-client.version}</version>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>order-common</artifactId>
<version>${order-common.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
order-common的pom.xml,因为没有公共对象,所以order-common里并没有实际的代码.
<parent>
<groupId>com.imooc</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>order-common</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
order-client的pom.xml,因为没有对外提供接口,所以order-client里并没有实际的代码.
<parent>
<groupId>com.imooc</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.imooc</groupId>
<artifactId>order-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>order-common</artifactId>
</dependency>
</dependencies>
order-client的pom.xml,因为没有对外提供接口,所以order-client里并没有实际的代码.
<parent>
<groupId>com.imooc</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.imooc</groupId>
<artifactId>order-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>order-common</artifactId>
</dependency>
</dependencies>
order-server的pom.xml,这里封装了业务逻辑,订单相关的代码都在其中.
<parent>
<groupId>com.imooc</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>order-server</artifactId>
<dependencies>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>order-common</artifactId>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>product-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
因为这里引入了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<String> productIdList = orderDTO.getOrderDetailList().stream()
.map(OrderDetail::getProductId)
.collect(Collectors.toList());
log.info("查询商品信息(调用商品服务)");
log.info("商品id长度为{}",productIdList.size());
log.info("值为:{}",productIdList.toString());
List<ProductInfoOutput> 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<DecreaseStockInput> 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服务,测试下单功能,成功.