整个项目主要分为 用户微服务、商品微服务和订单微服务,整个过程模拟的是用户下单扣减库存的操作。这里,为了简化整个流程,将商品的库存信息保存到了商品数据表,同时,使用商品微服务来扣减库存。小伙伴们在实现时,也可以将商品库存信息单独开发一个微服务模块,主体逻辑和将库存信息放在商品微服务进行管理是一样的。各服务之间的调用流程如下:
用户微服务、商品微服务和订单微服务的整体流程为:用户通过客户端调用订单微服务的提交订单的接口后,订单微服务会分别调用用户微服务和商品微服务的接口来查询用户信息和商品信息,并校验商品库存是否充足,如果商品库存充足的话,就会保存订单。并且会调用商品微服务的扣减库存的接口来扣减库存
整个项目采用 SpringCloud Alibaba 技术栈实现,主要的技术选型如下所示:
为了方便开发和维护,同时为了模块的复用性,整体项目在搭建时,会将用户微服务、商品微服务和订单微服务放在同一个 Maven 父工程下,作为父工程的子模块,同时,将用户微服务、商品微服务和订单微服务都会使用的 JavaBean 单独作为一个 Maven 模块,以及各服务都会使用的工具类单独作为一个 Maven 模块
代码码云地址
其中,数据库文件位于 db
文件夹下。
代码已托管到码云,这里只贴部分代码。
在IDEA中创建 Maven 工程,名称为 shop-springcloud-alibaba,创建后在项目的 pom.xml 文件中添加
StringBoot 与 SpringCloud alibaba 相关的配置,如下所示:
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.6.RELEASEversion>
<relativePath/>
parent>
<packaging>pompackaging>
<groupId>com.zzcgroupId>
<artifactId>shop-springcloud-alibabaartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>shop-springcloud-alibabaname>
<description>shop-springcloud-alibabadescription>
<properties>
<java.version>1.8java.version>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<spring-cloud.version>Greenwich.RELEASEspring-cloud.version>
<spring-cloud-alibaba.version>2.1.0.RELEASEspring-cloud-alibaba.version>
<logback.version>1.1.7logback.version>
<slf4j.version>1.7.21slf4j.version>
<common.logging>1.2common.logging>
<fastjson.version>1.2.51fastjson.version>
<mybatis.version>3.4.6mybatis.version>
<mybatis.plus.version>3.4.1mybatis.plus.version>
<mysql.jdbc.version>8.0.19mysql.jdbc.version>
<druid.version>1.1.10druid.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
project>
在父工程下创建工具类模块 shop-utils,作为整个项目的通用工具类模块。工具类模块的总体结构如下所示:
代码结构说明:
HttpCode
:HTTP 状态码封装类RestCtrlExceptionHandler
:全局异常捕获类md5
包:通用 MD5 与密码加密类Result
:数据响应类id
包:使用雪花算法生成 Id在父工程下创建实体类模块 shop-bean,作为整个项目的通用实体类模块
shop-bean 模块的依赖相对来说就比较简单了,只需要依赖 shop-utils 模块即可。在 shop-bean 模块的 pom.xml 文件中添加如下配置:
<dependency>
<groupId>com.zzcgroupId>
<artifactId>shop-utilsartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
对于用户微服务、商品微服务和订单微服务来说,每个服务占用的端口和访问的基础路径是不同的,这里就将每个服务占用的端口和访问的基础路径整理成下表所示:
服务名称 | 项目名称 | 占用端口 | 访问的基础路径 | 备注 |
---|---|---|---|---|
用户微服务 | shop-user | 8060 | /user | 用户的增删改查 |
商品微服务 | shop-product | 8070 | /product | 商品的增删改查 |
订单微服务 | shop-order | 8080 | /order | 订单的增删改查 |
创建名称为 shop-user
的 Maven 项目,由于我们在前面的文章中,已经完成了对项目整体结构的搭建,所以,在 shop-user 的 pom.xml 文件里添加如下依赖即可:
<dependency>
<groupId>com.zzcgroupId>
<artifactId>shop-beanartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
shop-product
、shop-order
微服务与 shop-user
类似,只是配置文件 application.yml
配置端口、访问路径不同。
这里重点看看 shop-order
的下单接口:
@Override
@Transactional(rollbackFor = Exception.class)
public void saveOrder(OrderParamVo orderParamVo) {
if (orderParamVo.isEmpty()){
throw new RuntimeException("参数异常: " + JSONObject.toJSONString(orderParamVo));
}
User user = restTemplate.getForObject("http://localhost:8060/user/get/" + orderParamVo.getUserId(), User.class);
if (user == null){
throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParamVo));
}
Product product = restTemplate.getForObject("http://localhost:8070/product/get/" + orderParamVo.getProductId(), Product.class);
if (product == null){
throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParamVo));
}
if (product.getProStock() < orderParamVo.getCount()){
throw new RuntimeException("商品库存不足: " + JSONObject.toJSONString(orderParamVo));
}
Order order = new Order();
order.setAddress(user.getAddress());
order.setPhone(user.getPhone());
order.setUserId(user.getId());
order.setUsername(user.getUsername());
order.setTotalPrice(product.getProPrice().multiply(BigDecimal.valueOf(orderParamVo.getCount())));
baseMapper.insert(order);
OrderItem orderItem = new OrderItem();
orderItem.setNumber(orderParamVo.getCount());
orderItem.setOrderId(order.getId());
orderItem.setProId(product.getId());
orderItem.setProName(product.getProName());
orderItem.setProPrice(product.getProPrice());
orderItemMapper.insert(orderItem);
Result<Integer> result = restTemplate.getForObject("http://localhost:8070/product/update_count/" + orderParamVo.getProductId() + "/" + orderParamVo.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
throw new RuntimeException("库存扣减失败");
}
log.info("库存扣减成功");
}
在 saveOrder()
方法的实现中,实现的主要逻辑如下:
在上述实现的过程中,存在一个很明显的问题:那就是将用户微服务所在的IP和端口,以及商品微服务所在的IP和端口硬编码到订单微服务的代码中了。这样的做法存在着非常多的问题
如果将用户微服务和商品微服务所在的IP地址和端口号硬编码到订单微服务中,会存在非常多的问题,其中,最明显的问题有三个,如下所示。
所以,在微服务开发的过程中,需要引入服务治理功能,实现微服务之间的动态注册与发现。