# SA实战 ·《SpringCloud Alibaba实战》第06章-快速搭建三大微服务并完成交互开发与测试

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)

大家好,我是冰河~~

在《SpringCloud Alibaba实战 (opens new window)》专栏中前面的文章,我们为开发用户微服务、商品微服务和订单微服务做了充分的准备。今天,我们就一起正式开撸三大微服务的核心业务。

# 文章结构

本文有点长,文章的总体结构如下图所示。

sa-2022-04-21-008

# 项目概述

为了便于大家理解和跟上开发的进度,今天我们先使用SpringBoot快速搭建三大微服务,并基于SpringBoot完成三大微服务之间的交互。在后续的文章中,我们会陆续加入SpringCloud Alibaba的各种组件进行优化。

# 整体交互流程

在正式开撸源码之前,我们还是先来看下用户微服务、商品微服务和订单微服务的交互流程吧,也好让小伙伴们能够大体上了解系统的交互流程。

sa-2022-04-21-001

上图中比较清晰的描述了三大微服务的交互流程,这里,我就不再赘述了。如果有个别小伙伴对服务的交互流程存在疑问或者问题,可以到【冰河技术】知识星球中向我提问,文末有星球优惠券。

# 服务规划

对于用户微服务、商品微服务和订单微服务来说,每个服务占用的端口和访问的基础路径是不同的,这里就将每个服务占用的端口和访问的基础路径整理成下表所示。

服务名称 项目名称 占用端口 访问的基础路径 备注
用户微服务 shop-user 8060 /user 提供用户信息的增删改查服务
商品微服务 shop-product 8070 /product 提供商品信息的增删改查服务
订单微服务 shop-order 8080 /order 提供订单信息的增删改查服务

好了,接下来,我们就基于SpringBoot正式开撸三大微服务。

# 用户微服务

用户微服务主要用来提供对于用户基础信息的增删改查操作,用户执行下单操作时,会从用户微服务中查询用户的基本信息。用户微服务的总体结构如下图所示。

sa-2022-04-21-002

# 搭建项目

创建名称为shop-user的Maven项目,由于我们在前面的文章中,已经完成了对项目整体结构的搭建,所以,在shop-user的pom.xml文件里添加如下依赖即可。

<dependencies>
    <dependency>
        <groupId>io.binghe.shop</groupId>
        <artifactId>shop-bean</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </dependency>
</dependencies>
1
2
3
4
5
6
7

接下来,在项目的resources目录下创建application.yml文件,并在application.yml文件中添加如下配置。

server:
  port: 8060
  servlet:
    context-path: /user
spring:
  application:
    name: server-user
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: root
    platform: mysql
    type: com.alibaba.druid.pool.DruidDataSource
    # 下面为连接池的补充设置,应用到上面所有数据源中
    # 初始化大小,最小,最大
    initialSize: 10
    minIdle: 5
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 3600000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 3600000
    validationQuery: select 1 from dual
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 打开PSCache,并且指定每个连接上PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    maxOpenPreparedStatements: 20
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat

  http:
    encoding:
      enabled: true
      charset: UTF-8
      force: true

  mybatis-plus:
    global-config:
      db-config:
        id-type: auto
        field-strategy: not-empty
        table-underline: true
        db-type: oracle
        logic-delete-value: 1
        logic-not-delete-value: 0
    mapper-locations: classpath:/mapper/*.xml
    configuration:
      jdbc-type-for-null: 'null'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

从配置文件中,我们也可以看出,用户微服务启动后监听的端口为8060,基础的访问路径为/user,应用的名称为server-user,使用的数据库为MySQL,使用的数据库连接池为阿里开源的Druid,项目的交互编码采用UTF-8,持久化框架采用的是MyBatis-Plus。

这里注意一点的是,我们在application.yml文件中的mybatis-plus节点下定义了Mapper的xml文件的存放位置,如下所示

spring:
  mybatis-plus:
    mapper-locations: classpath:/mapper/*.xml
1
2
3

Mapper的xml文件位置在当前项目的classpath目录下的mapper目录下。也就是说,我们在使用MyBatis-Plus框架时,如果涉及到自己写SQL语句时,会将SQL语句写到XML文件中,而不是通过注解的方式写到接口的方法上。

# 开发持久层

用户持久层主要是提供对于用户数据表的基本增删改查操作。持久化框架采用的是MyBatis-Plus,在项目的io.binghe.shop.user.mapper包下创建UserMapper接口,主要用作使用MyBatis-Plus框架操作用户数据表的基础Mapper接口,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 用户Mapper
 */
public interface UserMapper extends BaseMapper<User> {
    
}
1
2
3
4
5
6
7
8

可以看到,在UserMapper接口中没有定义任何方法,这是由于MyBatis-Plus框架中已经封装好了针对实体类的基础增删改查操作,而我们目前快速搭建三大微服务的过程中,使用MyBatis-Plus框架封装的基础增删改查操作就能够满足需求了。所以,在UserMapper接口中,未定义任何方法。

接下来,在resources目录下创建mapper目录里创建UserMapper.xml文件,文件的内容如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.binghe.shop.user.mapper.UserMapper">
	
</mapper>
1
2
3
4
5

UserMapper.xml文件与UserMapper接口是一一对应的,UserMapper中定义的接口方法,都会在UserMapper.xml文件中写对应的SQL语句。由于UserMapper接口中位定义任何方法,所以在UserMapper.xml文件中,也就未声明任何SQL语句了。

# 开发业务逻辑层

用户微服务的业务逻辑层主要提供针对用户信息的业务逻辑开发,在io.binghe.shop.user.service包下创建UserService接口,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 用户业务接口
 */
public interface UserService {

    /**
     * 根据id获取用户信息
     */
    User getUserById(Long userId);
}
1
2
3
4
5
6
7
8
9
10
11
12

可以看到,在UserService接口中,定义了一个通过用户id获取用户信息的方法getUserById(),提供这个方法传入用户的id编号就可以获取到用户的信息。

接下来,在io.binghe.shop.user.service.impl包下创建UserServiceImpl类,实现UserService接口,并实现UserService接口定义的方法getUserById(),UserServiceImpl类的源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 用户业务实现类
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserById(Long userId) {
        return userMapper.selectById(userId);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

由于项目中使用了SpringBoot,SpringBoot又是基于Spring开发的,所以我们在项目开发过程中,都是使用Spring来管理Java对象的生命周期,也就是bean的生命周期。所以在UserServiceImpl类上标注了@Service注解,使用@Autowired注解注入了UserMapper对象,并在getUserById()中调用UserMapper对象的selectById()方法获取用户的基本信息。

# 开发接口层

用户微服务的接口层主要对外提供用户微服务的接口,在io.binghe.shop.user.controller包下创建UserController类,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 用户接口
 */
@Slf4j
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping(value = "/get/{uid}")
    public User getUser(@PathVariable("uid") Long uid){
        User user = userService.getUserById(uid);
        log.info("获取到的用户信息为:{}", JSONObject.toJSONString(user));
        return user;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在UserController上标注了@RestController注解,表明UserController能够提供Restful风格的接口,同时在UserController类中使用@Autowired注解注入了UserService对象,在getUser()方法中调用UserService对象的getUserById()方法获取用户的信息,并返回用户的信息,同时在getUserById()方法上标注了@GetMapping注解表示这个接口采用HTTP GET方式访问,并定义了这个接口的访问路径。

用户调用订单微服务的提交订单接口下单时,订单微服务会调用用户微服务的接口获取用户的基本信息。

# 开发服务启动类

在用户微服务的io.binghe.shop包下创建UserStarter类,作为用户微服务的启动类,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 启动用户服的类
 */
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan(value = { "io.binghe.shop.user.mapper" })
public class UserStarter {

    public static void main(String[] args){
        SpringApplication.run(UserStarter.class, args);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

至此,用户微服务开发完成。

# 商品微服务

商品微服务主要用来提供对于商品基础信息的增删改查操作,用户执行下单操作时,会从商品微服务中查询商品的基本信息,并完成商品的库存扣减操作。商品微服务的总体结构如下图所示。

sa-2022-04-21-003

# 项目搭建

商品微服务的搭建过程和用户微服务的搭建过程类似,只是在application.yml文件中的部分配置不同,在商品微服务的application.yml文件中,需要将端口修改为8070,基础访问路径修改为/product,应用名称修改为server-product,具体的源码可以到【冰河技术】星球获取,文末有优惠券。

# 开发持久层

商品持久层主要提供对于商品数据表的增删改查操作,在io.binghe.shop.product.mapper包下创建ProductMapper接口,用于MyBatis-Plus操作商品数据表的基础Mapper接口,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 商品服务Mapper接口
 */
public interface ProductMapper extends BaseMapper<Product> {

    /**
     * 扣减商品库存
     */
    int updateProductStockById(@Param("count") Integer count, @Param("id") Long id);
}
1
2
3
4
5
6
7
8
9
10
11
12

可以看到,在ProductMapper接口中除了可以使用MyBatis-Plus框架提供的基础增删改查外,还提供了一个单独的扣减商品库存的方法updateProductStockById(),传入需要扣减的商品数量和商品的id就可以实现商品库存的扣减操作。

接下来,在resources的mapper目录下创建ProductMapper.xml文件,在ProductMapper.xml文件中实现updateProductStockById对应的SQL语句,如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.binghe.shop.product.mapper.ProductMapper">
    <update id="updateProductStockById">
        update t_product set t_pro_stock = t_pro_stock - #{count} where id = #{id}
    </update>
</mapper>
1
2
3
4
5
6
7

# 开发业务逻辑层

商品微服务的业务逻辑层主要提供针对商品信息的业务逻辑操作,在io.binghe.shop.product.service包下创建ProductService接口,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 商品Service接口
 */
public interface ProductService {

    /**
     * 根据商品id获取商品信息
     */
    Product getProductById(Long pid);


    /**
     * 扣减商品库存
     */
    int updateProductStockById(Integer count, Long id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

可以看到,在ProductService接口中定义了获取商品信息的方法getProductById()和扣减商品库存的方法updateProductStockById()。

接下来,在io.binghe.shop.product.service.impl包下创建ProductServiceImpl类,实现ProductService接口,并实现ProductService接口中定义的getProductById()方法和updateProductStockById()方法,如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 商品业务实现类
 */
@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    private ProductMapper productMapper;
    @Override
    public Product getProductById(Long pid) {
        return productMapper.selectById(pid);
    }

    @Override
    public int updateProductStockById(Integer count, Long id) {
        return productMapper.updateProductStockById(count, id);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在ProductServiceImpl类中,getProductById()方法使用的是MyBatis-Plus框架中提供的selectById()方法获取商品的信息,updateProductStockById()方法中使用的是ProductMapper接口中定义的updateProductStockById()方法扣减商品的库存。

# 开发接口层

商品微服务的接口层主要是商品对外提供的接口,在io.binghe.shop.product.controller包下创建ProductController类,并在类上标注@RestController注解表示ProductController类提供的是Restful风格的接口。ProductController类的源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 商品api
 */
@RestController
@Slf4j
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping(value = "/get/{pid}")
    public Product getProduct(@PathVariable("pid") Long pid){
        Product product = productService.getProductById(pid);
        log.info("获取到的商品信息为:{}", JSONObject.toJSONString(product));
        return product;
    }

    @GetMapping(value = "/update_count/{pid}/{count}")
    public Result<Integer> updateCount(@PathVariable("pid") Long pid, @PathVariable("count") Integer count){
        log.info("更新商品库存传递的参数为: 商品id:{}, 购买数量:{} ", pid, count);
        int updateCount = productService.updateProductStockById(count, pid);
        Result<Integer> result = new Result<>(HttpCode.SUCCESS, "执行成功", updateCount);
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

可以看到,在ProductController类中,提供了一个获取商品信息的接口和扣减商品库存的接口,用户调用订单微服务的提交订单接口下单时,订单微服务会调用商品微服务的接口获取商品的基本信息并扣减商品的库存。

# 开发服务启动类

在商品微服务的io.binghe.shop包下创建ProductStarter类,作为商品微服务的启动类,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 商品服务启动类
 */
@SpringBootApplication
@MapperScan(value = { "io.binghe.shop.product.mapper" })
@EnableTransactionManagement(proxyTargetClass = true)
public class ProductStarter {

    public static void main(String[] args){
        SpringApplication.run(ProductStarter.class, args);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

至此,商品微服务开发完毕。

# 订单微服务

订单微服务主要用来提供用户下单操作的业务逻辑,用户执行下单操作时,订单微服务会调用用户微服务的接口获取用户的基本信息,会调用商品微服务的接口获取商品的基本信息。在订单微服务中校验用户的合法性和校验商品库存是否充足,如果用户合法并且商品库存充足,就会向订单数据表中记录订单信息并调用商品微服务的接口来扣减商品的库存。

订单微服务的总体结构如下图所示。

sa-2022-04-21-004

# 项目搭建

订单微服务的项目搭建过程与用户微服务和商品微服务的项目搭建过程类似,只是在application.yml文件中的部分配置不同,在订单微服务的application.yml文件中,需要将端口修改为8080,基础访问路径修改为/order,应用名称修改为server-order,具体的源码可以到【冰河技术】星球获取,文末有优惠券。

# 开发持久层

订单微服务的持久层主要提供对订单数据表的增删改查操作,订单服务会涉及到对t_order订单数据表和t_order_item订单条目数据表的操作,所以,在io.binghe.shop.order.mapper包下会创建OrderMapper和OrderItemMapper两个接口,如下所示。

  • OrderMapper接口
/**
 * @author binghe
 * @version 1.0.0
 * @description 订单Mapper
 */
public interface OrderMapper extends BaseMapper<Order> {
}
1
2
3
4
5
6
7
  • OrderItem接口
/**
 * @author binghe
 * @version 1.0.0
 * @description 订单条目Mapper
 */
public interface OrderItemMapper extends BaseMapper<OrderItem> {
}
1
2
3
4
5
6
7

由于在订单微服务中,对于订单数据表和订单条目数据表的操作,使用MyBatis-Plus框架提供的基本增删改查功能就能满足需求,所以在OrderMapper和OrderItemMapper接口中并没有定义任何方法。

# 开发业务逻辑层

订单微服务的业务逻辑层主要完成提交订单的业务逻辑,用户执行下单操作时,订单微服务会调用用户微服务的接口获取用户的基本信息,会调用商品微服务的接口获取商品的基本信息。在订单微服务中校验用户的合法性和校验商品库存是否充足,如果用户合法并且商品库存充足,就会向订单数据表中记录订单信息并调用商品微服务的接口来扣减商品的库存。

在io.binghe.shop.order.service包下创建OrderService接口,在接口中定义一个保存订单的接口saveOrder(),源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 订单业务接口
 */
public interface OrderService {
    /**
     * 保存订单
     */
    void saveOrder(OrderParams orderParams);
}
1
2
3
4
5
6
7
8
9
10
11

接下来,在io.binghe.shop.order.service.impl包下创建OrderServiceImpl类,实现OrderService接口,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description
 */
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderItemMapper orderItemMapper;
    @Autowired
    private RestTemplate restTemplate;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOrder(OrderParams orderParams) {
        if (orderParams.isEmpty()){
            throw new RuntimeException("参数异常: " + JSONObject.toJSONString(orderParams));
        }

        User user = restTemplate.getForObject("http://localhost:8060/user/get/" + orderParams.getUserId(), User.class);
        if (user == null){
            throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
        }
        Product product = restTemplate.getForObject("http://localhost:8070/product/get/" + orderParams.getProductId(), Product.class);
        if (product == null){
            throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
        }
        if (product.getProStock() < orderParams.getCount()){
            throw new RuntimeException("商品库存不足: " + JSONObject.toJSONString(orderParams));
        }
        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(orderParams.getCount())));
        orderMapper.insert(order);

        OrderItem orderItem = new OrderItem();
        orderItem.setNumber(orderParams.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/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
        if (result.getCode() != HttpCode.SUCCESS){
            throw new RuntimeException("库存扣减失败");
        }
        log.info("库存扣减成功");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

可以看到,在OrderServiceImpl类的实现中,使用了OrderMapper、OrderItemMapper和RestTemplate,RestTemplate主要用来实现远程调用。

在saveOrder()方法的实现中,实现的主要逻辑如下。

(1)判断orderParams封装的参数是否为空,如果参数为空,则抛出参数异常。

(2)通过RestTemplate调用用户微服务获取用户的基本信息,如果获取的用户信息为空,则抛出未获取到用户信息的异常。

(3)通过RestTemplate调用商品微服务获取商品的基本信息,如果获取的商品信息为空,则抛出未获取到商品信息的异常。

(4)判断商品的库存是否小于待扣减的商品数量,如果商品的库存小于待扣减的商品数量,则抛出商品库存不足的异常。

(5)如果orderParams封装的参数不为空,并且获取的用户信息和商品信息不为空,同时商品的库存充足,则创建订单对象保存订单信息,创建订单条目对象,保存订单条目信息。

(6)调用商品微服务的接口扣减商品库存。

# 开发接口层

订单微服务的接口层主要是订单微服务对外提供相应的接口,在io.binghe.shop.order.controller包下创建OrderController类,并在OrderController类上添加@RestController注解,表示OrderController类提供的接口是Restful风格的接口,OrderController类的源码如下所示。

@Slf4j
@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;

    @GetMapping(value = "/submit_order")
    public String submitOrder(OrderParams orderParams){
        log.info("提交订单时传递的参数:{}", JSONObject.toJSONString(orderParams));
        orderService.saveOrder(orderParams);
        return "success";
    }
1
2
3
4
5
6
7
8
9
10
11
12

可以看到,OrderController类提供的接口就比较简单了,通过传入相应的参数,调用OrderService的saveOrder方法完成下单操作。

# 开发服务启动类

在订单微服务的io.binghe.shop包下创建OrderStarter类,作为订单微服务的启动类,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 订单服务启动类
 */
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan(value = { "io.binghe.shop.order.mapper" })
public class OrderStarter {
    public static void main(String[] args){
        SpringApplication.run(OrderStarter.class, args);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 开发辅助类

在订单微服务中,使用了RestTemplate来完成远程服务的调用,关于RestTemplate的配置,在io.binghe.shop.order.config包下新建LoadBalanceConfig类,并在LoadBalanceConfig类上标注@Configuration注解,表示LoadBalanceConfig类是一个配置类,在LoadBalanceConfig类中使用@Bean注解将RestTemplate对象交由Spring管理,LoadBalanceConfig类的源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 配置类
 */
@Configuration
public class LoadBalanceConfig {

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

至此,订单微服务开发完成,接下来,我们进行测试。

# 测试项目

开发完成后,我们对快速搭建并开发完成的三大微服务进行简单的测试,在测试之前我们需要先在数据表中添加一些测试数据。

# 添加测试数据

(1)在用户表中添加一条id为1001的记录,如下所示。

INSERT INTO `shop`.`t_user`(`id`, `t_username`, `t_password`, `t_phone`, `t_address`) VALUES (1001, 'binghe', 'c26be8aaf53b15054896983b43eb6a65', '13212345678', '北京');
1

(2)在商品数据表中添加几条商品记录,如下所示。

INSERT INTO `shop`.`t_product`(`id`, `t_pro_name`, `t_pro_price`, `t_pro_stock`) VALUES (1001, '华为', 2399.00, 100);
INSERT INTO `shop`.`t_product`(`id`, `t_pro_name`, `t_pro_price`, `t_pro_stock`) VALUES (1002, '小米', 1999.00, 100);
INSERT INTO `shop`.`t_product`(`id`, `t_pro_name`, `t_pro_price`, `t_pro_stock`) VALUES (1003, 'iphone', 4999.00, 100);
1
2
3

# 测试库存不足的情况

(1)分别启动用户微服务、商品微服务和订单微服务。

(2)查询id为1001的商品信息,如下所示。

mysql> select * from t_product where id = 1001;
+------+------------+-------------+-------------+
| id   | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 华为       |     2399.00 |         100 |
+------+------------+-------------+-------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,id为1001的商品的库存为100。

(3)查询订单表和订单条目表中的数据,如下所示。

  • 查询订单表
mysql> select * from t_order;
Empty set (0.00 sec)
1
2

可以看到,订单数据表的数据为空。

  • 查询订单条目表
mysql> select * from t_order_item;
Empty set (0.00 sec)
1
2

可以看到,订单条目数据表的数据为空。

(4)在浏览器中调用订单微服务的下单接口,传入的商品数量为1001,如下所示。

sa-2022-04-21-005

可以看到,返回的信息中,code为500,codeMsg输出的信息为执行失败,data返回的结果为商品库存不足,并且输出了提交的参数信息。

(5)再次查询id为1001的商品信息,如下所示。

mysql> select * from t_product where id = 1001;
+------+------------+-------------+-------------+
| id   | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 华为       |     2399.00 |         100 |
+------+------------+-------------+-------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,商品id为1001的商品库存仍为100,并没有减少。

(6)再次查询订单表和订单条目表中的数据,如下所示。

  • 查询订单表
mysql> select * from t_order;
Empty set (0.00 sec)
1
2

可以看到,订单数据表的数据为空。

  • 查询订单条目表
mysql> select * from t_order_item;
Empty set (0.00 sec)
1
2

可以看到,订单条目数据表的数据为空。

综上,当提交订单时传入的商品数量大于商品的库存数量时,系统会抛出异常,并不会执行提交订单和扣减库存的操作。

# 测试正常下单的情况

(1)在测试库存不足的情况的基础上,我们将调用提交订单的接口时传入的商品数量修改为10,如下所示。

sa-2022-04-21-006

可以看到,当商品库存充足时,调用订单微服务的下单接口,返回的数据为success表示下单成功。

(2)再次查询id为1001的商品信息,如下所示。

mysql> select * from t_product where id = 1001;
+------+------------+-------------+-------------+
| id   | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 华为       |     2399.00 |          90 |
+------+------------+-------------+-------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,id为1001的商品库存由原来的100变更为90,减少了10个库存。

(3)再次查询订单表和订单条目表中的数据,如下所示。

  • 查询订单表
mysql> select * from t_order;
+------------------+-----------+-------------+-------------+-----------+---------------+
| id               | t_user_id | t_user_name | t_phone     | t_address | t_total_price |
+------------------+-----------+-------------+-------------+-----------+---------------+
| 3270016896208896 |      1001 | binghe      | 13212345678 | 北京      |      23990.00 |
+------------------+-----------+-------------+-------------+-----------+---------------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,订单数据表中成功记录了订单的信息

  • 查询订单条目表
mysql> select * from t_order_item;
+------------------+------------------+----------+------------+-------------+----------+
| id               | t_order_id       | t_pro_id | t_pro_name | t_pro_price | t_number |
+------------------+------------------+----------+------------+-------------+----------+
| 3270017277890560 | 3270016896208896 |     1001 | 华为       |     2399.00 |       10 |
+------------------+------------------+----------+------------+-------------+----------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7

可以看到,订单条目数据表中成功记录了订单条目的信息。

至此,项目的测试完毕。

另外,小伙伴们在【冰河技术】知识星球获取到源码后,可以自行测试其他异常情况,比如商品不存在的异常和用户不存在的异常,或者自行验证几个其他的异常情况,看提交是否提交,商品库存是否扣减了。

这次,我们只是使用SpringBoot快速搭建了三个微服务项目,从下一篇开始,我们就要逐渐接入SpringCloud Alibaba技术了,小伙伴们,你们准备好了吗?加入【冰河技术】知识星球,一起搞定《SpringCloud Alibaba实战 (opens new window)》吧,加油!!

# 星球服务

加入星球,你将获得:

1.项目学习:微服务入门必备的SpringCloud Alibaba实战项目、手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】、Seckill秒杀系统项目—进大厂必备高并发、高性能和高可用技能。

2.框架源码:手写RPC项目—所有大厂都需要的项目【含上百个经典面试题】、深度解析Spring6核心技术—只要学习Java就必须深度掌握的框架【含数十个经典思考题】。

3.硬核技术:深入理解高并发系列(全册)、深入理解JVM系列(全册)、深入浅出Java设计模式(全册)、MySQL核心知识(全册)。

4.技术小册:深入理解高并发编程(第1版)、深入理解高并发编程(第2版)、从零开始手写RPC框架、SpringCloud Alibaba实战、冰河的渗透实战笔记、MySQL核心知识手册、Spring IOC核心技术、Nginx核心技术、面经手册等。

5.技术与就业指导:提供相关就业辅导和未来发展指引,冰河从初级程序员不断沉淀,成长,突破,一路成长为互联网资深技术专家,相信我的经历和经验对你有所帮助。

冰河的知识星球是一个简单、干净、纯粹交流技术的星球,不吹水,目前加入享5折优惠,价值远超门票。加入星球的用户,记得添加冰河微信:hacker_binghe,冰河拉你进星球专属VIP交流群。

# 星球重磅福利

跟冰河一起从根本上提升自己的技术能力,架构思维和设计思路,以及突破自身职场瓶颈,冰河特推出重大优惠活动,扫码领券进行星球,直接立减149元,相当于5折, 这已经是星球最大优惠力度!


领券加入星球,跟冰河一起学习《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》,更有已经上新的《大规模分布式Seckill秒杀系统》,从零开始介绍原理、设计架构、手撸代码。后续更有硬核中间件项目和业务项目,而这些都是你升职加薪必备的基础技能。

100多元就能学这么多硬核技术、中间件项目和大厂秒杀系统,如果是我,我会买他个终身会员!

# 其他方式加入星球

特别提醒: 苹果用户进圈或续费,请加微信 hacker_binghe 扫二维码,或者去公众号 冰河技术 回复 星球 扫二维码加入星球。

# 星球规划

后续冰河还会在星球更新大规模中间件项目和深度剖析核心技术的专栏,目前已经规划的专栏如下所示。

# 中间件项目

  • 《大规模分布式定时调度中间件项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式IM(即时通讯)项目实战(非Demo)》:全程手撸代码。
  • 《大规模分布式网关项目实战(非Demo)》:全程手撸代码。
  • 《手写Redis》:全程手撸代码。
  • 《手写JVM》全程手撸代码。

# 超硬核项目

  • 《从零落地秒杀系统项目》:全程手撸代码,在阿里云实现压测(已上新)。
  • 《大规模电商系统商品详情页项目》:全程手撸代码,在阿里云实现压测。
  • 其他待规划的实战项目,小伙伴们也可以提一些自己想学的,想一起手撸的实战项目。。。

既然星球规划了这么多内容,那么肯定就会有小伙伴们提出疑问:这么多内容,能更新完吗?我的回答就是:一个个攻破呗,咱这星球干就干真实中间件项目,剖析硬核技术和项目,不做Demo。初衷就是能够让小伙伴们学到真正的核心技术,不再只是简单的做CRUD开发。所以,每个专栏都会是硬核内容,像《SpringCloud Alibaba实战》、《手撸RPC专栏》和《Spring6核心技术》就是很好的示例。后续的专栏只会比这些更加硬核,杜绝Demo开发。

小伙伴们跟着冰河认真学习,多动手,多思考,多分析,多总结,有问题及时在星球提问,相信在技术层面,都会有所提高。将学到的知识和技术及时运用到实际的工作当中,学以致用。星球中不少小伙伴都成为了公司的核心技术骨干,实现了升职加薪的目标。

# 联系冰河

# 加群交流

本群的宗旨是给大家提供一个良好的技术学习交流平台,所以杜绝一切广告!由于微信群人满 100 之后无法加入,请扫描下方二维码先添加作者 “冰河” 微信(hacker_binghe),备注:星球编号

冰河微信

# 公众号

分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。内容在 冰河技术 微信公众号首发,强烈建议大家关注。

公众号:冰河技术

# 视频号

定期分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。

视频号:冰河技术

# 星球

加入星球 冰河技术 (opens new window),可以获得本站点所有学习内容的指导与帮助。如果你遇到不能独立解决的问题,也可以添加冰河的微信:hacker_binghe, 我们一起沟通交流。另外,在星球中不只能学到实用的硬核技术,还能学习实战项目

关注 冰河技术 (opens new window)公众号,回复 星球 可以获取入场优惠券。

知识星球:冰河技术