先安装好 JDK 和 MySQL。
一、案例介绍
以电商平台为例,有很多业务:用户、订单、秒杀、搜索......该服务巨大,微服务是更好的选择。
1、服务拆分原则
并不是越细越好,越细带来的管理困难也就越高。
- 单一职责,效率更高。
- 服务自治,互相影响更小。每个服务都能独立开发、测试、构建、部署、运行。
- 单向依赖,禁止循环、双向依赖。无法避免时,可用其他技术如消息队列解耦。
2、服务拆分示例
对于订单列表:
① 单一职责:拆分为订单服务、商品服务(实际项目划分更细)。
② 服务自治:每个服务创建独立数据库。
订单表:
sql
-- 建库
create database if not exists cloud_order charset utf8mb4;
-- 订单表
DROP TABLE IF EXISTS order_detail;
CREATE TABLE order_detail (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '订单id',
`user_id` BIGINT ( 20 ) NOT NULL COMMENT '⽤⼾ID',
`product_id` BIGINT ( 20 ) NULL COMMENT '产品id',
`num` INT ( 10 ) NULL DEFAULT 0 COMMENT '下单数量',
`price` BIGINT ( 20 ) NOT NULL COMMENT '实付款',
`delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now(),
PRIMARY KEY ( id )) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '订单表';
-- 数据初始化
insert into order_detail (user_id,product_id,num,price)
values
(2001, 1001,1,99), (2002, 1002,1,30), (2001, 1003,1,40),
(2003, 1004,3,58), (2004, 1005,7,85), (2005, 1006,7,94);
商品表:
sql
create database if not exists cloud_product charset utf8mb4;
-- 产品表
DROP TABLE IF EXISTS product_detail;
CREATE TABLE product_detail (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '产品id',
`product_name` varchar ( 128 ) NULL COMMENT '产品名称',
`product_price` BIGINT ( 20 ) NOT NULL COMMENT '产品价格',
`state` TINYINT ( 4 ) NULL DEFAULT 0 COMMENT '产品状态 0-有效 1-下架',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now(),
PRIMARY KEY ( id )) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '产品表';
-- 数据初始化
insert into product_detail (id, product_name,product_price,state)
values
(1001,"T恤", 101, 0), (1002, "短袖",30, 0), (1003, "短裤",44, 0),
(1004, "卫⾐",58, 0), (1005, "⻢甲",98, 0),(1006,"⽻绒服", 101, 0),
(1007, "冲锋⾐",30, 0), (1008, "袜⼦",44, 0), (1009, "鞋⼦",58, 0),
(10010, "⽑⾐",98, 0)
可能会出现错误:

这是客户端编码跟表编码不一致的锅,在建库前加一句:
sql
SET NAMES utf8mb4;
二、工程搭建
1、构建父子工程
(1)构建父项目
- 父工程负责管理子模块依赖,不负责编译生成可执行文件,因此只需保留 pom.xml 文件。
创建空 maven 项目,删除所有源代码,只保留 pom.xml 文件。


完善 pom 文件:
- 父工程的打包方式是 pom 而不是 jar,因此需要手动声明以 pom 方式打包。
- 父工程依赖 Sring Boot 父工程,需要 <parent> 声明父工程。
- <properties> 管理依赖的版本号,注意 spring cloud 版本与 spring boot 版本匹配。
- <dependencies> jar 包直接放到项目中,子项目直接继承。
- <dependencyManagement> 声明依赖 ,没有引入 jar 包,子项目想引入 jar 需要显示声明 。若子项目指定版本号,按指定版本引入;若子项目没有指定版本 ,按父工程管理的版本号来。
XML
<!-- 声明当前工程包含哪些子工程,创建子工程时会自动添加到当前工程中-->
<modules>
<module>order-service</module>
<module>product-service</module>
</modules>
<!-- 声明以 pom 方式打包 -->
<packaging>pom</packaging>
<!-- 继承官方的 Spring Boot 父工程,引入 Spring Boot 常用依赖无需手动指定版本号-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.6</version>
<relativePath/>
</parent>
<!-- 声明管理依赖的版本号-->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
<mybatis.version>3.0.3</mybatis.version>
<mysql.version>8.0.33</mysql.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<!-- 引入依赖,子工程继承父工程的依赖,无需重复引入-->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<!-- 声明依赖,子工程引入依赖时,可无需指定版本号,由父工程统一管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>${mybatis.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
因为**<dependencyManagement>** 只是声明,没有真正引入 jar 包,所以爆红了很正常,不用管。上面的依赖没有爆红,是因为我之前下载到本地仓库过:

(2)构建子项目:订单、商品服务
创建新模块:

引入项目依赖和项目构建插件,这里就不用指定版本号,直接继承父工程的依赖版本号管理:
XML
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2、完善子项目服务

- 启动类
java
package com.pygymi.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
- 配置文件
java
#应用端口号
server:
port: 8080
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: "123456"
driver-class-name: com.mysql.jdbc.Driver
# mybatis配置
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印日志
map-underscore-to-camel-case: true # 配置驼峰⾃动转换
- 完善订单服务业务代码:
实体类:
java
@Data
public class OrderInfo {
private Integer id;
private Integer userId;
private Integer productId;
private Integer num;
private Integer price;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
controller:
java
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource(name = "orderServiceImpl")
private OrderService orderService;
@GetMapping("/{orderId}")
public OrderInfo getOrder(@PathVariable("orderId") String orderId) {
return orderService.getOrder(orderId);
}
}
service:
java
public class OrderServiceImpl implements OrderService {
@Resource
private OrderMapper orderMapper;
@Override
public OrderInfo getOrder(String orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
return orderInfo;
}
}
mapper:
java
public interface OrderMapper {
@Select("SELECT * FROM order_info WHERE id = #{orderId}")
OrderInfo selectOrderById(String orderId);
}
- 完善商品服务业务代码:同理。
- 接口测试:

商品查询:127.0.0.1:9090/product/1001

3、远程调用
查询订单信息时,需要查询商品的详细信息,解决办法是:订单服务通过 HTTP 访问商品服务,获取到商品详情放到订单返回结果中。
实现方式使用 Spring 提供的 RestTemplate。
定义 RestTemplate,因为五大注入 bean 的注解修饰的是类,RestTemplate 是别人写的类我们无法修改,所以使用方法注入 @Bean:
java
@Configuration
public class BeanConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在订单服务中,通过 HTTP 访问商品服务:
java
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private OrderMapper orderMapper;
@Resource
private RestTemplate restTemplate;
@Override
public OrderInfo getOrder(String orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
// 构造访问商品服务的 url
String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
// 使用 restTemplate 访问商品服务
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
// 把商品详情设置到 orderInfo 中
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}

三、RestTemplate
1、REST
REST(表现层资源状态转移)是一种设计风格,指网络资源以某种表现形式进行状态转移。
- 资源:网络上的所有数据,如图像、视频、文本等。
- 表现层:资源的表现形式,如 jpg、txt、json 等。
- 状态转移:通过网络访问资源,对资源进行修改(增删改查),都会引起资源状态转移。
2、RESTful API
满足 REST 设计风格的接口都叫做 RESTful API。其特性:
- 统一接口:对资源的操作,对应 HTTP 协议提供的 GET、POST、PUT、DELETE。
如:GET /blog/{blogId}:查询博客
使用 REST 风格实现的接口,我们只能从 URL 定位其资源,并不能直接知晓其是什么操作,想要知道还需要用抓包工具查看请求,得知 GET、POST......
3、RestTemplate
就是 Spring 实现的,强制使用 RESTsul 风格的 HTTP 调用模板。我们只需要提供资源地址和参数类型。
4、RESTful 风格缺点
- 想知道该接口对资源执行了什么操作,还要先抓包,不方便团队理解和交流。
- 一些旧浏览器对 GET、POST 以外的请求支持不太友好。
- 复杂的业务强行使用 RESTful 风格反而增加开发难度,因为不能仅仅使用增删改查实现。
四、项目存在的问题
- 远程调用时,被调用方的 IP 是写死的,IP 容易写错,也容易在源代码基础上修改。我们希望不依赖提供方的 IP。
- 所有服务都可以调用该接口,具有风险。
- 多机部署,没有实现压力分摊。
...... 后续继续学习 spring cloud 的各种组件,来解决这些问题。