一.前期准备
1.1开发环境安装
Mysql安装版本8.0即可
1.2分析需求

1.3服务拆分原则
1.3.1单⼀职责原则
组织团队也是, ⼀个⼈专注做⼀件事情的效率远高于同时关注多件事情
比如电商系统:
1.3.2服务自治

1.3.3单向依赖
- 循环依赖: A -> B -> C ->A
- 双向依赖: A -> B, B -> A
如果⼀些场景确实无法避免循环依赖或者双向依赖, 可以考虑使用消息队列等其他方式来实现
补充:
1.4设置需求
- 订单列表
- 商品信息
二.项目搭建
Spring Cloud 是基于SpringBoot搭建的, 所以Spring Cloud 版本与SpringBoot版本有关
该项⽬中使⽤的SpringBoot 版本为 3.1.6, 对应的Spring Cloud版本应该为2022.0.x, 选择任⼀就可以
2.1数据准备
根据服务自治原则, 每个服务都应有自己独立的数据库
-- 订单服务-- 建库
create database if not exists cloud_order charset utf8mb4;use cloud_order;
-- 订单表
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);
产品服务数据库信息:
-- 产品服务
create database if not exists cloud_product charset utf8mb4;-- 产品表
use cloud_product;
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);
2.2工程搭建
项目创建目前有两种:
- 使用JavaEE的方式,使用IDEA分别创建两个项目,一个项目一个窗口(麻烦)
- 采用父子工程的方式搭建(推荐)
1.创建父工程
2.删除src文件(因为不需要编写代码)
3.完善pom.xml文件信息
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.6</version><relativePath/> <!-- lookup parent from repository --></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>
补充:
pom文件DependencyManagement 和 Dependencies区别:
dependencies :将所依赖的jar直接加到项⽬中. ⼦项⽬也会继承该依赖
4.创建两个子项目(order-service , product-service)
此时父项目的pom文件就会多出modul
5.给order.pom文件和 product.pom文件添加必要的依赖
<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><resources><resource><directory>src/main/resources</directory><filtering>true</filtering><includes><include>**/**</include></includes></resource></resources>
</build>
2.3编写order服务
1.给order子项目创建order包 - 编写启动类
package 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);}
}
2.编写application.yml配置文件
开始编写业务代码
订单服务:
- 根据订单ID,返回订单详情
3.先写实体类
package order.model;import lombok.Data;import java.util.Date;@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;
}
4.编写mapper接口
package order.mapper;import order.model.OrderInfo;
import org.apache.ibatis.annotations.Select;public interface OrderMapper {@Select("select * from order_detail where id = #{orderId}")OrderInfo selectOrderById(Integer orderId);//先声明方法
}
5,编写service Service主要也是从mapper接口调用订单的信息
package order.service;import order.mapper.OrderMapper;
import order.model.OrderInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;//注入这个接口public OrderInfo selectOrderById(Integer orderId){return orderMapper.selectOrderById(orderId);}
}
6.编写controller 接口
package order.controller;import order.model.OrderInfo;
import order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RequestMapping("order")
@RestController
public class OrderController {@Autowiredprivate OrderService orderService;@RequestMapping("/{orderId}")public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId){return orderService.selectOrderById(orderId);}
}
启动项目失败,
原因:忘记在mapper接口添加@Mapper注解
7.项目启动成功耶耶
2.4编写product服务
给product子项目也跟上述操作类似(代码就不粘贴了)
1.编写启动类
2.配置yml文件(注意修改端口号成9090,数据库名也要修改)
开始编写业务代码:
- 根据商品ID,返回商品信息
1.编写实体类信息
2.编写返回商品信息接口
3.编写service层
4.编写controller控制层
5.启动项目成功
2.5远程调用
实现两个子项目进行交互,因为订单信息里面肯定需要商品信息嘛
根据订单查询订单信息时,根据订单里产品ID,获取产品的详细信息
实现思路: order-service服务向product-service服务发送⼀个http请求, 把得到的返回结果, 和订单结果融合在⼀起, 返回给调用方.
实现方式: 采用Spring 提供的RestTemplate
1.在将product的实体类复制到order的实体类,并在order实体类添加product类信息
2.创建config包 - 创建HTTP对象 - 定义RestTemplate
3.在servicec层使用http对象调用
package order.service;import order.mapper.OrderMapper;
import order.model.OrderInfo;
import order.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;//注入这个接口@Autowiredprivate RestTemplate restTemplate;public OrderInfo selectOrderById(Integer orderId){OrderInfo orderInfo = orderMapper.selectOrderById(orderId);String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();ProductInfo productInfo = restTemplate.getForObject(url,ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}
}
4.两个项目都要启动,然后调用order路径就可以返回product的信息了
三.RestTemplate介绍
3.1什么是REST?
REST(Representational State Transfer), 表现层资源状态转移.
可以把 REST 想象成一个大家都遵循的规则手册,让不同的软件、系统之间能够顺畅地 “交流” 和 “合作”,就像人们说同一种语言能更好地沟通一样。
这里面主要有三个概念:
- 资源: ⽹络上的所有事物(文字,图片,视频等等)都可以抽象为资源, 每个资源都有⼀个唯⼀的资源标识符(URI)
- 表现层: 资源的表现形式, ⽐如⽂本作为资源, 可以⽤txt格式表现, 也可以通过HTML, XML, JSON等格式来表现, 甚⾄以⼆进制的格式表现.
- 状态转移: 访问URI, 也就是客⼾端和服务器的交互过程. 客⼾端⽤到的⼿段,只能是HTTP协议. 这个过程中, 可能会涉及到数据状态的变化. ⽐如对数据的增删改查, 都是状态的转移
REST 是⼀种设计⻛格, 指资源在⽹络中以某种表现形式进⾏状态转移
3.2什么是RESTful?
RESTful 风格大致有以下⼏个主要特征:
- 资源: 资源可以是⼀个图⽚, ⾳频, 视频或者JSON格式等⽹络上的⼀个实体, 除了⼀些⼆进制的资源外普通的⽂本资源更多以JSON为载体、⾯向⽤⼾的⼀组数据(通常从数据库中查询⽽得到)
- 统⼀接⼝: 对资源的操作. ⽐如获取, 创建, 修改和删除. 这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE⽅法. 换⾔⽽知,如果使⽤RESTful⻛格的接⼝, 从接⼝上你可能只能定位其资源,但是⽆法知晓它具体进⾏了什么操作,需要具体了解其发⽣了什么操作动作要从其HTTP请求⽅法类型上进⾏判断
这些内容都是通过HTTP协议来呈现的. 所以RESTful是基于HTTP协议的,RestTemplate 是Spring提供, 封装HTTP调用, 并强制使用RESTful风格. 它会处理HTTP连接和关闭,只需要使用者提供资源的地址和参数即可。
3.3RESTful实践
RESTful API 缺点:
- 操作⽅式繁琐, RESTful API通常根据GET, POST, PUT, DELETE 来区分对资源的操作动作. 但是HTTP Method 并不可直接⻅到, 需要通过抓包等⼯具才能观察. 如果把动作放在URL上反⽽更加直观, 更利于团队的理解和交流.
- ⼀些浏览器对GET, POST之外的请求⽀持不太友好, 需要额外处理.
- 过分强调资源. ⽽实际业务需求可能⽐较复杂, 并不能单纯使⽤增删改查就能满⾜需求, 强⾏使⽤RESTful API会增加开发难度和成本
3.4总结
REST:
想象有一个超级大的 “信息超市”,里面有各种各样的 “商品”,这些 “商品” 就是资源。比如说有水果类的资源(像苹果、香蕉),电器类的资源(像电视、冰箱),REST 就是这个 “信息超市” 里大家都遵守的一套规则,有了它,不管是谁来超市(不管是哪种客户端),也不管超市是谁开的(不管是哪个服务器),大家都能按照统一的方式顺畅地交易 “商品”(交换信息)。
RESTful:
如果说 REST 是规则,那 RESTful 就是严格遵守这个规则的 “好超市”。这个 “好超市” 里的每一个 “商品” 都有清晰准确的标签(唯一的 URI),工作人员和顾客交流时也完全按照规定的方式来(使用标准 HTTP 方法)。
RESTful 实践:
资源标识:
给每一本书都分配一个独一无二的编号,在网络里就是 URI。比如/books/1
代表编号为 1 的书,这就像给书贴上了专属标签,方便大家查找。
HTTP 方法使用:
- GET:你在网页上输入
/books/1
并发送请求,就像你在书店里跟工作人员说 “我想看看编号为 1 的书的信息”,服务器会返回这本书的详细信息,如书名、作者、价格等。 - POST:你在网页上填写新书的信息并提交,就像你要把一本新书放到书店里卖。服务器接收到这个请求后,会创建一个新的图书资源。
- PUT:你发现编号为 1 的书价格写错了,于是修改价格后再次提交,这就像你在书店里把书的价格标签换了。服务器会根据你提供的新信息更新这本书的资源。
- DELETE:你觉得编号为 1 的书不再需要了,发送一个删除请求,就像你把这本书从书店的货架上拿走了。服务器会把对应的图书资源删除。
现在假设有一个在线书店,我们把它当作一个遵守 REST 规则的 “信息超市” 来实践。
通过这些方式,在线书店就能高效地管理图书信息,并且能和不同的用户(客户端)进行良好的交互,这就是 RESTful 实践.
四.项目存在问题
- 远程调⽤时, URL的IP和端⼝号是写死的(http://127.0.0.1:9090/product/), 如果更换IP, 需要修改代码
- 调⽤⽅如何可以不依赖服务提供⽅的IP? 如果多机部署, 如何分摊压⼒?
- 远程调⽤时, URL非常容易写错, ⽽且复⽤性不⾼, 如何优雅的实现远程调⽤
- 所有的服务都可以调用该接⼝, 是否有风险?
微服务架构还面临很多问题, 接下来我们学习如何使用Spring Cloud 来解决这些问题