news 2026/2/4 20:27:15

SpringCloud —— 黑马商城的项目拆分和Nacos

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringCloud —— 黑马商城的项目拆分和Nacos

一、前言

我们已经将黑马商城的前置知识学完(mp、docker),接下来就是要开始黑马商城项目了,这个项目本身是一个已经做好的项目,但是是单体架构,和苍穹外卖是一样的,现在我们将利用黑马商城来学习微服务,所以首先就需要拆分项目了。

在这之前,记得一定要创建一个git仓库,并且虚拟机要定期保存快照,本项目经多人实测,中途可能会出现问题(比如无法访问nacos网站等问题),而本人也经历了虚拟机崩溃的问题,所以在将来可能需要使用git回溯,如果虚拟机出问题了也需要恢复快照。

二、项目结构和拆分原则

黑马商城的项目结构是比较简单的,每一个板块都是分得比较清楚的,比如有商品模块、购物车模块、订单模块、支付模块等等......

这里我们选择将这些较为独立集中的模块拆分成项目结构中的模块(也可以直接拆成一个独立的项目,但是在学习期间不易管理),如下图所示(部分拆分)

同时需要导入不同模块所需要的表:

我们的拆分原则是:拆分出功能相对独立的模块,每一个拆分后的模块需要有自己单独的表。

三、远程调用

在拆分时我们会发现一些问题,比如购物车模块需要依赖商品模块,但是跨模块是不允许直接调用的,而且也不能直接调用,因为我们拆分项目的初衷是解耦合,使项目更好管理,如果模块之间相互依赖调用,这样和单体架构就没什么两样了,所以也就完全体现不出微服务的优势了。

所以这里我们绝对不能直接调用,这个时候就可以联想到之前苍穹外卖使使用过的HttpClient了,当时我们是使用这个工具来让客户端获取店铺信息,本质上是通过向管理端发起了一个请求,然后获取管理端传回来的响应,解析后拿到店铺状态。

这里我们暂时就不使用httpclient了,我们使用restTemplate:

首先需要注册到Bean中去,所以要一个配置类,但是由于这个配置很简单,所以我们直接写在启动类中:

@MapperScan("com.hmall.cart.mapper") @SpringBootApplication public class CartApplication { public static void main(String[] args) { SpringApplication.run(CartApplication.class, args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }

使用restTemplate主要也就分为两步:

1.发送请求并接收响应

2.解析响应

private void handleCartItems(List<CartVO> vos) { // TODO 1.获取商品id Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet()); // 2.查询商品 // List<ItemDTO> items = itemService.queryItemByIds(itemIds); // 2.1.利用RestTemplate发起http请求,得到http的响应 ResponseEntity<List<ItemDTO>> response = restTemplate.exchange( "http://localhost:8081/items?ids={ids}", HttpMethod.GET, null, new ParameterizedTypeReference<List<ItemDTO>>() { }, Map.of("ids", CollUtil.join(itemIds, ",")) ); // 2.2.解析响应 if(!response.getStatusCode().is2xxSuccessful()){ // 查询失败,直接结束 return; } List<ItemDTO> items = response.getBody(); if (CollUtils.isEmpty(items)) { return; } // 3.转为 id 到 item的map Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity())); // 4.写入vo for (CartVO v : vos) { ItemDTO item = itemMap.get(v.getItemId()); if (item == null) { continue; } v.setNewPrice(item.getPrice()); v.setStatus(item.getStatus()); v.setStock(item.getStock()); } }

虽然逻辑简单,但是代码确实不少,太繁琐了,所以后期我们会使用一个新技术来高度封装这些步骤,这里学习RestTemplate有助于了解封装的底层原理,所以还是需要看看的。

四、Nacos注册中心

1.简介

首先我们要知道,目前我们拆分了项目,但是其实是没有进行统一管理的,尤其是一个模块如果出现多个实例,我们根本无法达到负载均衡。

先前我们在nginx中提到了负载均衡的,大致意思就是如果一个模块是一个服务器集群,那么当多个请求访问这个服务器时,可以将请求压力分摊到每个服务器上,也就是不会一直只访问一个服务器,这就是负载均衡

为了实现这个目的,我们要引入一个管理中心——Nacos,我们可以将每个模块注册进这个管理中心,由Nacos管理,有远程调用的需求时,Nacos会自动将请求分配给目标服务器(告诉请求发起者应该请求给哪个服务器)

2.具体实现步骤

(1)创建并启动容器

首先我们要在虚拟机的Docker中用Nacos的镜像创建一个Nacos的容器:

(2)在目标模块导入依赖

<!--nacos 服务注册发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>

(3)写入配置文件

cloud: nacos: server-addr: 192.168.xxx.xxx:8848

这里就是指定Nacos容器所在的IP地址和Nacos的端口号,这样就能让SpringCloud将项目注册到Nacos中了(导入依赖的目的就是为了让这个配置生效)。

(4)测试

这里我将启动三个模块,其中有两个实例是源于一个模块,相当于模拟一个服务器集群(集群中有两台服务器)

这是就可以访问nacos的网站了,在这里就可以统一管理服务器了:

这里我们发送五次请求,看看是否实现了负载均衡:

从结果来看是实现了的,8081和8083都是被请求到了的。

五、OpenFeign

刚刚在远程调用的时候就提到了,使用restTemplate是及其不方便的,也预告了后面会封装,这个组件就是OpenFeign,它通过和SpringBoot相似的语法,减少了学习成本,让远程调用更加规范,耦合度更低,最重要的是,代码量大幅减少。

OpenFeign的底层是可以选择的,比如刚刚提到的httpclient,除此之外还要okhttp等等。

接下来讲解如何使用OpenFeign:

1.启动类

首先启动类需要开启OpenFeign:

@EnableFeignClients @MapperScan("com.hmall.cart.mapper") @SpringBootApplication public class CartApplication { public static void main(String[] args) { SpringApplication.run(CartApplication.class, args); } }

2.导入依赖

<!--openFeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependency> <!--OK http 的依赖 --> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>

3.配置

feign: okhttp: enabled: true # 开启OKHttp功能

4.创建接口

这里注意,这个接口是不需要被实现的,和Mapper接口一样,Spring会通过动态代理自动实现。

@FeignClient("item-service") public interface ItemClient { @GetMapping("/items") List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids); }

可以看到这个接口的写法和SpringBoot的语法几乎一样,所以也不再赘述,就是需要注意要在注解写明远程调用的项目名

5.远程调用

修改代码,一行代码就可以获取到item-service响应回来的数据了。

private void handleCartItems(List<CartVO> vos) { //1.获取商品id Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet()); //openfeign List<ItemDTO> items = itemClient.queryItemByIds(itemIds); if (CollUtils.isEmpty(items)) { //如果查询失败 return; } // 3.转为 id 到 item的map Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity())); // 4.写入vo for (CartVO v : vos) { ItemDTO item = itemMap.get(v.getItemId()); if (item == null) { continue; } v.setNewPrice(item.getPrice()); v.setStatus(item.getStatus()); v.setStock(item.getStock()); } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!