SA实战 ·《SpringCloud Alibaba实战》快速搭建三大微服务(二)
接下来,在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);
}
}
在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;
}
}
可以看到,在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);
}
}
至此,商品微服务开发完毕。
订单微服务
订单微服务主要用来提供用户下单操作的业务逻辑,用户执行下单操作时,订单微服务会调用用户微服务的接口获取用户的基本信息,会调用商品微服务的接口获取商品的基本信息。在订单微服务中校验用户的合法性和校验商品库存是否充足,如果用户合法并且商品库存充足,就会向订单数据表中记录订单信息并调用商品微服务的接口来扣减商品的库存。
订单微服务的总体结构如下图所示。
项目搭建
订单微服务的项目搭建过程与用户微服务和商品微服务的项目搭建过程类似,只是在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> {
}
OrderItem接口
/**
* @author binghe
* @version 1.0.0
* @description 订单条目Mapper
*/
public interface OrderItemMapper extends BaseMapper<OrderItem> {
}
由于在订单微服务中,对于订单数据表和订单条目数据表的操作,使用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);
}
接下来,在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("库存扣减成功");
}
}
可以看到,在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";
}
可以看到,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);
}
}
开发辅助类
在订单微服务中,使用了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)在用户表中添加一条id为1001的记录,如下所示。
INSERT INTO `shop`.`t_user`(`id`, `t_username`, `t_password`, `t_phone`, `t_address`) VALUES (1001, 'binghe', 'c26be8aaf53b15054896983b43eb6a65', '13212345678', '北京');
(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)查询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)
可以看到,id为1001的商品的库存为100。
(3)查询订单表和订单条目表中的数据,如下所示。
查询订单表
mysql> select * from t_order;
Empty set (0.00 sec)
可以看到,订单数据表的数据为空。
查询订单条目表
mysql> select * from t_order_item;
Empty set (0.00 sec)
可以看到,订单条目数据表的数据为空。
(4)在浏览器中调用订单微服务的下单接口,传入的商品数量为1001,如下所示。
可以看到,返回的信息中,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)
可以看到,商品id为1001的商品库存仍为100,并没有减少。
(6)再次查询订单表和订单条目表中的数据,如下所示。
查询订单表
mysql> select * from t_order;
Empty set (0.00 sec)
可以看到,订单数据表的数据为空。
查询订单条目表
mysql> select * from t_order_item;
Empty set (0.00 sec)
可以看到,订单条目数据表的数据为空。
综上,当提交订单时传入的商品数量大于商品的库存数量时,系统会抛出异常,并不会执行提交订单和扣减库存的操作。
测试正常下单的情况
(1)在测试库存不足的情况的基础上,我们将调用提交订单的接口时传入的商品数量修改为10,如下所示。
可以看到,当商品库存充足时,调用订单微服务的下单接口,返回的数据为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)
可以看到,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)
可以看到,订单数据表中成功记录了订单的信息
查询订单条目表
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)
可以看到,订单条目数据表中成功记录了订单条目的信息。
至此,项目的测试完毕。
另外,小伙伴们在【冰河技术】知识星球获取到源码后,可以自行测试其他异常情况,比如商品不存在的异常和用户不存在的异常,或者自行验证几个其他的异常情况,看提交是否提交,商品库存是否扣减了。
这次,我们只是使用SpringBoot快速搭建了三个微服务项目,从下一篇开始,我们就要逐渐接入SpringCloud Alibaba技术了,小伙伴们,你们准备好了吗?加入【冰河技术】知识星球,一起搞定《SpringCloud Alibaba实战》吧,加油!!
关于星球
最近,冰河创建了【冰河技术】知识星球,《SpringCloud Alibaba实战》专栏的源码获取方式会放到知识星期中,同时在微信上会创建专门的知识星球群,冰河会在知识星球上和星球群里解答球友的提问。
文章转自公众号:冰河技术