SpringCloud2️⃣项目搭建:服务拆分 & 远程调用

1、相关概念

  • 服务拆分:将传统的单体应用,根据业务功能拆分为多个服务。
    • 不重复造轮子:不同微服务,不要重复开发相同业务。
    • 独立:微服务数据独立,不要访问其它微服务的数据库
    • 面向服务:将微服务的业务暴露为接口,供其它微服务调用。
  • 远程调用
    • 单体应用:所有业务功能集中在一个项目。可直接通过 service 调用其它模块。
    • 微服务:每个服务作为独立的业务模块,独立部署。无法直接调用,需要通过 HTTP 请求调用。

2、微服务项目

示例:搭建一个简单的微服务项目,实现以下功能

  • 环境

    • Spring Cloud:Hoxton.SR10
    • Spring Boot:2.3.x
  • 要求

    • 搭建用户和订单 2 个微服务,使用各自的数据库。

    • 实现查询功能,对外暴露 RestFUL 接口。

    • 实现服务之间的调用。

      image-20220602161943933

2.1、数据库

建立 2 个数据库,模拟多台远程数据库。

  • 用户数据库cloud_user
  • 订单数据库cloud_order

在 2 台数据库下分别建表,自行插入数据。

  • tb_user:用户(id, name, password)

    CREATE TABLE `tb_user` (
      `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `name` varchar(36) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
      `password` varchar(18) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'
    
  • tb_order:订单(id, name, price, user_id

    CREATE TABLE `tb_order` (
      `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
      `name` VARCHAR(36) COLLATE utf8mb4_general_ci NOT NULL COMMENT '商品名',
      `price` DOUBLE NOT NULL COMMENT '价格',
      `user_id` BIGINT NOT NULL COMMENT '关联用户ID',
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='订单表'
    

2.2、Spring Boot 项目

导入 cloud-demo 项目,注意以下问题

  • Spring Boot 版本兼容问题。
  • 项目结构
    • cloud-demo:父项目,负责管理依赖和配置。
      • user-service:用户服务
      • order-service:订单服务
  • 配置:修改数据库配置(URL,账号密码)

2.2.1、entity

entity数据库表对应

  • User

    @Data
    public class User {
        private Long id;
        private String name;
        private String password;
    }
    
  • Order

    @Data
    public class Order {
        private Long id;
        private String name;
        private Long price;
        private Long userId;
    }
    

2.2.2、DAO

DAO 与数据库交互

Hint:为便于演示,以注解形式编写 SQL。

  • UserMapper:根据 ID 查询用户

    public interface UserMapper {
        @Select("SELECT * FROM tb_user WHERE id = #{id}")
        User getUserById(Long id);
    }
    
  • OrderMapper:根据 ID 查询用户

    public interface OrderMapper {
        @Select("SELECT * FROM tb_order WHERE id = #{id}")
        Order getOrderById(Long id);
    }
    

2.2.3、service

servcie 处理业务逻辑

  • UserService

    @Service
    public class UserService {
        @Resource
        private UserMapper userMapper;
    
        public User getUserById(Long id) {
            return userMapper.getUserById(id);
        }
    }
    
  • OrderService

    @Service
    public class OrderService {
        @Resource
        private OrderMapper orderMapper;
    
        public Order queryOrderById(Long id) {
            return orderMapper.getOrderById(id);
    }
    

2.2.4、接口

Controller 对外暴露接口

  • UserController

    @RestController
    @RequestMapping("/user")
    public class UserController {
        @Resource
        private UserService userService;
    
        @GetMapping("/{id}")
        public User queryUserById(@PathVariable("id") Long id) {
            return userService.getUserById(id);
        }
    }
    
  • OrderController

    @RestController
    @RequestMapping("order")
    public class OrderController {
        @Resource
        private OrderService orderService;
    
        @GetMapping("{id}")
        public Order queryOrderById(@PathVariable("id") Long id) {
            return orderService.queryOrderById(id);
        }
    }
    

2.2.5、测试

  • 用户

    image-20220605021802579

  • 订单:只有 userId,没有用户详情

    image-20220605021851274

3、远程调用

3.1、说明

3.1.1、跨模块调用

假设模块 A 要调用模块 B 的功能

  • 单体应用(本地调用):定义成员变量,调用 ServiceB 的方法。

    @Service
    public class ServiceA {
    	@Resource
        private ServiceB serviceB;
        
        public void test() {
            // 调用ServiceB的方法
        }
    }
    
  • 微服务应用远程调用):发起 HTTP 请求,调用 微服务 B 提供的接口。

    @Service
    public class ServiceA {
        
        public void test() {
            // 调用服务B的接口
        }
    }
    

3.1.2、服务调用关系

在一个业务中,服务调用关系有 2 个角色

  • 服务提供者:提供接口,供其它微服务调用。
  • 服务消费者:调用其它微服务提供的接口。

角色是相对业务而言的,即一个微服务既可以是提供者,也可以是消费者。

3.2、case

需求:根据 orderId 查询完整 order 信息,包括关联用户详情。

  1. 查询 order
  2. 向 user 服务发起 HTTP 请求(根据 id 查询用户)
  3. 将返回结果拼接到 orderVO 中,返回

3.2.1、RestTemplate

作用:在一个服务中执行 HTTP 请求,调用另一个服务的接口。

配置 RestTemplate

  1. 创建一个 @Configuration 配置类(或使用已有配置类)

  2. 使用 @Bean,向 Spring 容器中注册

    @Configuration
    public class RestTemplateConfig {
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    

3.2.2、OrderVO

VO 用于接口和页面之间的交互

  1. 定义 OrderVO

    @Data
    public class OrderVO extends Order {
        private User user;
    }
    
  2. 定义 entityToVo():将 entity 转换为 VO

    (通常定义在工具类中,此处为简化可定义在 VO 中)

    public void entityToVo(Order order) {
        this.setId(order.getId());
        this.setName(order.getName());
        this.setPrice(order.getPrice());
        this.setUserId(order.getUserId());
    }
    

3.2.3、OrderService

OrderVO 继承自 Order,

因此原先定义的返回 Order 的方法,可直接返回 OrderVO

  1. 订单:查询 order,转换为 VO

  2. 用户:向 user 服务发起 HTTP 请求,将返回结果拼接到 orderVO 中

  3. 返回 VO

    @Service
    public class OrderService {
        @Resource
        private OrderMapper orderMapper;
        @Resource
        private RestTemplate restTemplate;
    
        public Order queryOrderById(Long id) {
            // 1.1、订单entity
            Order order = orderMapper.getOrderById(id);
            if (order == null) {
                return null;
            }
            // 1.2、订单entity转VO
            OrderVO orderVO = new OrderVO();
            orderVO.entityToVo(order);
    
            // 2.1、关联用户
            String url = "http://localhost:8081/user/" + order.getUserId();
            User user = restTemplate.getForObject(url, User.class);
    
            // 2.2、拼接关联用户
            if (user != null) {
                orderVO.setUser(user);
            }
    
            // 3.返回
            return orderVO;
        }
    }
    

3.2.4、测试接口

查询结果中包含关联用户详情。

image-20220605021955097

posted @ 2022-06-02 17:38  Jaywee  阅读(95)  评论(0编辑  收藏  举报

👇