黑马程序员SpringCloud微服务开发与实战-微服务-服务拆分02

news/2025/11/28 0:18:49/文章来源:https://www.cnblogs.com/xinmomoyan/p/19279829

黑马程序员SpringCloud微服务开发与实战-微服务-服务拆分02

Posted on 2025-11-28 00:17  心默默言  阅读(0)  评论(0)    收藏  举报

接下来,我们就一起将黑马商城这个单体项目拆分为微服务项目,并解决其中出现的各种问题。

1. 熟悉黑马商城

image

2. 拆分原则

image
image

3. 微服务项目工程结构

一般微服务项目有两种不同的工程结构:

  • 完全解耦:每一个微服务都创建为一个独立的工程,甚至可以使用不同的开发语言来开发,项目完全解耦。
    • 优点:服务之间耦合度低
    • 缺点:每个项目都有自己的独立仓库,管理起来比较麻烦
  • Maven聚合:整个项目为一个Project,然后每个微服务是其中的一个Module
    • 优点:项目代码集中,管理和运维方便(授课也方便)
    • 缺点:服务之间耦合,编译时间较长

注意:
为了授课方便,我们会采用Maven聚合工程,大家以后到了企业,可以根据需求自由选择工程结构。

在hmall父工程之中,我已经提前定义了SpringBoot、SpringCloud的依赖版本,所以为了方便期间,我们直接在这个项目中创建微服务module.

4. 商品服务

image
引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>hmall</artifactId><groupId>com.heima</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>item-service</artifactId><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><dependencies><!--common--><dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version></dependency><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--数据库--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--mybatis--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><!--单元测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

编写启动类:
image

package com.hmall.item;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;@MapperScan("com.hmall.item.mapper")
@SpringBootApplication
public class ItemApplication {public static void main(String[] args) {SpringApplication.run(ItemApplication.class, args);}
}

接下来是配置文件,可以从hm-service中拷贝:
image
application.yaml改动内容如下:
image
image

剩下的application-dev.yaml和application-local.yaml直接从hm-service拷贝即可。

然后拷贝hm-service中与商品管理有关的代码到item-service,如图:
image
注意:需要将代码中com.hmall改为com.hamll.item
image
这也是因为ItemMapper的所在包发生了变化,因此这里代码必须修改包路径。

最后,还要导入数据库表。默认的数据库连接的是虚拟机,在你docker数据库执行课前资料提供的SQL文件:
image
最终,会在数据库创建一个名为hm-item的database,将来的每一个微服务都会有自己的一个database:
image

注意:在企业开发的生产环境中,每一个微服务都应该有自己的独立数据库服务,而不仅仅是database,课堂我们用database来代替。

接下来,就可以启动测试了,在启动前我们要配置一下启动项,让默认激活的配置为local而不是dev:
image

接着,启动item-service,访问商品微服务的swagger接口文档:http://localhost:8081/doc.html
然后测试其中的根据id批量查询商品这个接口:
image
测试参数:100002672302,100002624500,100002533430 结果如下:
image
说明商品微服务抽取成功了。

5. 购物车服务

与商品服务类似,在hmall下创建一个新的module,起名为cart-service:
然后是依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.heima</groupId><artifactId>hmall</artifactId><version>1.0.0</version></parent><artifactId>cart-service</artifactId><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--common--><dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version></dependency><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--数据库--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--mybatis--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><!--单元测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

然后是启动类:

package com.hmall.cart;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
public class CartApplication {public static void main(String[] args) {SpringApplication.run(CartApplication.class, args);}
}

然后是配置文件,同样可以拷贝自item-service,不过其中的application.yaml需要修改:

server:port: 8082
spring:application:name: cart-serviceprofiles:active: devdatasource:url: jdbc:mysql://${hm.db.host}:3306/hm-cart?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: ${hm.db.pw}
mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandlerglobal-config:db-config:update-strategy: not_nullid-type: auto
logging:level:com.hmall: debugpattern:dateformat: HH:mm:ss:SSSfile:path: "logs/${spring.application.name}"
knife4j:enable: trueopenapi:title: 黑马商城购物车接口文档description: "黑马商城购物车接口文档"email: zhanghuyi@itcast.cnconcat: 虎哥url: https://www.itcast.cnversion: v1.0.0group:default:group-name: defaultapi-rule: packageapi-rule-resources:- com.hmall.cart.controller
hm:jwt:location: classpath:hmall.jksalias: hmallpassword: hmall123tokenTTL: 30mauth:excludePaths:- /search/**- /users/login- /items/**- /hi
# keytool -genkeypair -alias hmall -keyalg RSA -keypass hmall123 -keystore hmall.jks -storepass hmall123

最后,把hm-service中的与购物车有关功能拷贝过来,最终的项目结构如下:
image

特别注意的是com.hmall.cart.service.impl.CartServiceImpl,其中有两个地方需要处理:

  • 需要获取登录用户信息,但登录校验功能目前没有复制过来,先写死固定用户id
  • 查询购物车时需要查询商品信息,而商品信息不在当前服务,需要先将这部分代码注释
package com.hmall.cart.service.impl;import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.cart.domain.dto.CartFormDTO;
import com.hmall.cart.domain.po.Cart;
import com.hmall.cart.domain.vo.CartVO;
import com.hmall.cart.mapper.CartMapper;
import com.hmall.cart.service.ICartService;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.UserContext;
//import com.hmall.service.IItemService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;/*** <p>* 订单详情表 服务实现类* </p>** @author 虎哥* @since 2023-05-05*/
@Service
@RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {//    private final IItemService itemService;@Overridepublic void addItem2Cart(CartFormDTO cartFormDTO) {// 1.获取登录用户Long userId = UserContext.getUser();// 2.判断是否已经存在if(checkItemExists(cartFormDTO.getItemId(), userId)){// 2.1.存在,则更新数量baseMapper.updateNum(cartFormDTO.getItemId(), userId);return;}// 2.2.不存在,判断是否超过购物车数量checkCartsFull(userId);// 3.新增购物车条目// 3.1.转换POCart cart = BeanUtils.copyBean(cartFormDTO, Cart.class);// 3.2.保存当前用户cart.setUserId(userId);// 3.3.保存到数据库save(cart);}@Overridepublic List<CartVO> queryMyCarts() {// 1.查询我的购物车列表List<Cart> carts = lambdaQuery().eq(Cart::getUserId, 1L/* TODO UserContext.getUser()*/).list();if (CollUtils.isEmpty(carts)) {return CollUtils.emptyList();}// 2.转换VOList<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);// 3.处理VO中的商品信息handleCartItems(vos);// 4.返回return vos;}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);
//        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());
//        }}@Overridepublic void removeByItemIds(Collection<Long> itemIds) {// 1.构建删除条件,userId和itemIdQueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>();queryWrapper.lambda().eq(Cart::getUserId, UserContext.getUser()).in(Cart::getItemId, itemIds);// 2.删除remove(queryWrapper);}private void checkCartsFull(Long userId) {int count = lambdaQuery().eq(Cart::getUserId, userId).count();if (count >= 10) {throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", 10));}}private boolean checkItemExists(Long itemId, Long userId) {int count = lambdaQuery().eq(Cart::getUserId, userId).eq(Cart::getItemId, itemId).count();return count > 0;}
}

最后,还是要导入数据库表,在本地数据库直接执行课前资料对应的SQL文件:
image
在数据库中会出现名为hm-cart的database,以及其中的cart表,代表购物车:
image

接下来,就可以测试了。不过在启动前,同样要配置启动项的active profile为local:
image

然后启动CartApplication,访问swagger文档页面:http://localhost:8082/doc.html
我们测试其中的查询我的购物车列表接口:
image

无需填写参数,直接访问:
image

我们注意到,其中与商品有关的几个字段值都为空!这就是因为刚才我们注释掉了查询购物车时,查询商品信息的相关代码。
那么,我们该如何在cart-service服务中实现对item-service服务的查询呢?

6. 远程调用

拆分的时候,我们发现一个问题:就是购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了item-service服务,导致我们无法查询。
最终结果就是查询到的购物车数据不完整,因此要想解决这个问题,我们就必须改造其中的代码,把原本本地方法调用,改造成跨微服务的远程调用(RPC,即Remote Produce Call)。
image
因此,现在查询购物车列表的流程变成了这样:
image
代码中需要变化的就是这一步:
image
那么问题来了:我们该如何跨服务调用,准确的说,如何在cart-service中获取item-service服务中的提供的商品数据呢?
大家思考一下,我们以前有没有实现过类似的远程查询的功能呢?

答案是肯定的,我们前端向服务端查询数据,其实就是从浏览器远程查询服务端数据。比如我们刚才通过Swagger测试商品查询接口,就是向http://localhost:8081/items这个接口发起的请求
而这种查询就是通过http请求的方式来完成的,不仅仅可以实现远程查询,还可以实现新增、删除等各种远程请求。
假如我们在cart-service中能模拟浏览器,发送http请求到item-service,是不是就实现了跨微服务的远程调用了呢?
那么:我们该如何用Java代码发送Http的请求呢?

6.1 RestTemplate

image
其中提供了大量的方法,方便我们发送Http请求,例如:
image
可以看到常见的Get、Post、Put、Delete请求都支持,如果请求参数比较复杂,还可以使用exchange方法来构造请求。
我们在cart-service服务中定义一个配置类:
image

package com.hmall.cart.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class RemoteCallConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}

6.2 远程调用

接下来,我们修改cart-service中的com.hmall.cart.service.impl.CartServiceImpl的handleCartItems方法,发送http请求到item-service:
image

 private void handleCartItems(List<CartVO> vos) {// 1.获取商品idSet<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());// 2.查询商品
//        List<ItemDTO> items = itemService.queryItemByIds(itemIds);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, ",")));if(!response.getStatusCode().is2xxSuccessful()){//查询失败,直接返回return;}List<ItemDTO> items = response.getBody();if (CollUtils.isEmpty(items)) {return;}// 3.转为 id 到 item的mapMap<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));// 4.写入vofor (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());}}

好了,现在重启cart-service,并启动item-service,再次测试查询我的购物车列表接口:
image
可以发现,所有商品相关数据都已经查询到了。

在这个过程中,item-service提供了查询接口,cart-service利用Http请求调用该接口。因此item-service可以称为服务的提供者,而cart-service则称为服务的消费者或服务调用者。
image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/979098.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

API设计最佳实践 - 智慧园区

后端工程师的API设计与开发实战指南:从原则到部署作为一名后端开发,日常工作中一大部分时间都在和API打交道。它就像是整个应用的「服务员」,前端、移动端或者其他服务想要什么数据、执行什么操作,都得通过它。一个…

Python高阶知识点整理

数据类型 常用方法 (Method) 简要说明与应用场景字符串 str .strip() 去除首尾空格(常用于处理用户输入).split(sep) 按指定分隔符分割成列表(处理CSV数据常用).join(iter) 将列表元素合并成字符串(.split的逆操作…

第4单元检测卷

第4单元检测卷 - 题目、答案与解析 一、单项选择题题目:在Python中,用于创建一个新字典的语法是 ( )A. d = dict[] B. d = {} C. d = () D. d = new dict()答案:B 解析:在Python中,创建字典最常用和直接的方法是使…

javascript下载文件五种方式

javascript下载文件五种方式参考:https://blog.csdn.net/weixin_42705100/article/details/133125521 本文介绍了五种在JavaScript中下载文件的方法:通过`window.location.href`、`window.open()`、iframe、动态a标签…

ubunutu连接蓝牙键盘鼠标

​ 双系统ubunutu能连接上蓝牙耳机,但是win能连接上蓝牙键盘鼠标ubunutu却连接不上,百思不得其解。怀疑要么是驱动要没是配置的问题。试试了一下现有文章的方法,都是要让装bluez和blueman之类,装完之后还是搜索不到…

详细介绍:从 1.0 到 13.0:C# 十八年进化史,一部写给开发者的语言成长记

详细介绍:从 1.0 到 13.0:C# 十八年进化史,一部写给开发者的语言成长记2025-11-28 00:02 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: …

生研界:技术赋能,AI如何重塑医学科研生态?

在科技浪潮席卷全球的当下,人工智能(AI)正以前所未有的速度重塑医学科研生态。从靶点发现到药物设计,从疾病预测到精准诊疗,AI技术正逐步渗透至医学科研的每一个环节,推动着医学研究范式的深刻变革。在这场变革中…

2025ICPC区域赛成都站记——为者败之,执者失之

打银2025年的成都站是我有生以来第一次线下xcpc比赛。在先前的网络赛选拔中,我们队获得了两个icpc名额和1个ccpc名额。先前西安站我们学校打了两块金牌的情况下,我们队的三个人都非常希望在大学的第一场比赛就能打出…

quickfox windows 海外回国加速器 会导致部分国外网站不能使用

比如说 google.com可以,但是grok.com不能打开 关掉quickfox后,grok.com就能打开了

4433

用StegSolve分析图片看到一个二维码,在https://cli.im/deqr/other 扫描二维码猜测为摩斯密码...--.----...--..但没有分隔,根据题目提示4433,分隔为...-/-.--/--./..-/-..,在https://www.iamwawa.cn/morse.html 解…

在VMware Workstation设置虚拟机的VNC连接功能

在VMware Workstation设置虚拟机的VNC连接功能在VMware Workstation设置虚拟机的VNC连接功能。首先在workstation中设置好虚拟机的VNC连接参数,如图:使用VNC客户端开始连接虚拟机,配置如下: 这时候出现错误提示:…

rust基础第三篇:所有权

rust基础第三篇:所有权值被唯一的scope拥有,它们共存亡。 值可以从一个scope移动到另一个scope,新的scope会拥有这个值。 一个值可以有多个只读引用和单个可变引用,它们之间是互斥关系。 引用不能超越值的存活期。…

Houdini软件简介

Houdini软件简介Houdini(wiki,chs)是一款由加拿大Side Effects Software Inc.(简称SESI)公司开发的3D动画软件,可运行于Linux、Windows、Mac OS等操作系统 与其它的三维软件相比,其结构、操作方式等有很大的差异…

Windows系统磁盘管理——迁移“恢复分区”

方案一、将“恢复分区”迁移到“新分区”将“恢复分区(例如分区 4)”迁移到新分区(分区F)中。1.1 创建新分区 在磁盘 0 的最右侧分出一块新分区(分区 F),其容量需略大于当前的“恢复分区”。 1.2 给“恢复分区”…

WinFormedge 增加登录页面(自定义布局,非左右布局) 并作为启动页面 及两种布局切换全屏非全屏

WinFormedge 增加登录页面(自定义布局,非左右布局)并作为启动页面的实现方法 1、增加一个自定义布局(Components/Layout下新建EmptyLayout.razor布局文件) 代码如下:@inherits LayoutComponentBase@Body <Fl…

2025.11.27总结

完成儿童故事的项目 完成CS架构的项目 目前bs架构的方向还没定,不过我打算在设计上就搞好这个项目,我认为只要能设计好,从目的,数据库设计,方法接口设计。清晰的将模块拆分,功能罗列出来。 这样在编码阶段才能有…

第6单元检测卷

好的,这是根据您提供的第三份PDF文档(第6单元检测卷)内容提取的题目、答案和解析,已按题型分类,并以Markdown格式呈现。第6单元检测卷 - 题目、答案与解析 一、单项选择题题目:关于数据库存储描述正确的是 ( )A.…

第7单元检查卷

单元检测卷题目与答案解析 一、单项选择题题目:在批量重命名活动照片项目中,读取CSV报名表时需跳过标题行,应使用的函数是 ( )A. csv.skip() B. next(reader) C. reader.skip() D. csv.reader(skipheader=True)答案…

2025 美本申请 SAT 高分指南:全国 TOP6培训机构甄选,从课程到服务全维度测评

2025 年美本、加本及新加坡本科申请已进入白热化阶段,SAT 作为院校筛选的核心硬指标,直接决定学子能否在万级竞争者中突围。尤其 SAT 机考改革后,题型迭代、备考周期压缩,再加上市场上机构良莠不齐 —— 部分宣称 …

2025 年江苏有机农场推荐榜:德芳有机农场全品类覆盖、国家权威有机认证

随着消费者对食品安全和健康生活方式的日益重视,有机农产品市场需求持续井喷,尤其是在经济发达的长三角地区,对高品质、可追溯的有机食材的需求达到了新的高度。2025 年,江苏省内有机农场数量不断增加,但真正具备…