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

一、前言

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

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

二、项目结构和拆分原则

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

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

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

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

三、远程调用

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

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

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

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

java 复制代码
@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.解析响应

java 复制代码
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)在目标模块导入依赖

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

(3)写入配置文件

XML 复制代码
  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:

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

2.导入依赖

XML 复制代码
        <!--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.配置

XML 复制代码
feign:
  okhttp:
    enabled: true # 开启OKHttp功能

4.创建接口

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

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

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

5.远程调用

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

java 复制代码
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());
        }
    }

6.日志级别

在这之前,我们创建了一个新的模块:hm-api,这里面包含了许多需要复用的dto以及feignClient,接下来我们来看看怎么开启feign的日志输出:

(1)创建配置类

在api模块创建配置类,因为这个日志输出的配置是需要复用的。

java 复制代码
public class DefaultFeignConfig {

    /**
     * 配置日志级别
     * @return
     */
    @Bean
    public Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

(2)启动类

第一、需要扫描api的包(通过依赖导入的),第二,直接指定日志输出级别配置类的字节码,用于配置日志输出:

java 复制代码
@EnableFeignClients(basePackages = "com.hmall.api.client",defaultConfiguration = DefaultFeignConfig.class)
@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();
    }
}

测试后就可以看到输出的日志了:

相关推荐
嘟嘟MD4 小时前
程序员副业 | 2025年12月复盘
后端·创业
利刃大大5 小时前
【SpringBoot】Spring事务 && @Transactional详解 && Spring事务失效问题
spring boot·spring·事务
..过云雨6 小时前
17-2.【Linux系统编程】线程同步详解 - 条件变量的理解及应用
linux·c++·人工智能·后端
南山乐只6 小时前
【Spring AI 开发指南】ChatClient 基础、原理与实战案例
人工智能·后端·spring ai
㳺三才人子7 小时前
初探 Spring Framework OncePerRequestFilter
spring boot·spring·junit
这是程序猿7 小时前
基于java的ssm框架学生作业管理系统
java·开发语言·spring boot·spring·学生作业管理系统
努力的小雨8 小时前
从“Agent 元年”到 AI IDE 元年——2025 我与 Vibe Coding 的那些事儿
后端·程序员
源码获取_wx:Fegn08958 小时前
基于springboot + vue小区人脸识别门禁系统
java·开发语言·vue.js·spring boot·后端·spring
wuxuanok9 小时前
Go——Swagger API文档访问500
开发语言·后端·golang
用户21411832636029 小时前
白嫖Google Antigravity!Claude Opus 4.5免费用,告别token焦虑
后端