目录
- 一、环境搭建
- 二、工程搭建
-
- [2.1 案例](#2.1 案例)
- [2.2 服务拆分原则](#2.2 服务拆分原则)
- [2.3 父子工程搭建](#2.3 父子工程搭建)
-
- [2.3.1 数据准备](#2.3.1 数据准备)
- [2.3.2 创建⽗⼯程](#2.3.2 创建⽗⼯程)
- [2.3.3 创建子工程](#2.3.3 创建子工程)
- [2.3.4 订单服务](#2.3.4 订单服务)
- [2.3.5 商品服务](#2.3.5 商品服务)
- 三、远程调用
- 四、RestTemplate
-
- [4.1 REST](#4.1 REST)
- [4.2 RESTful](#4.2 RESTful)

一、环境搭建
在服务器和本地都安装JDK17和MySQL。
Ubuntu安装JDK:
yml
#更新软件包
sudo apt update
#安装JDK
sudo apt install openjdk-17-jdk
Ubuntu安装mysql:
java
#查找安装包
apt list |grep "mysql-server"
#安装mysql
sudo apt install mysql-server
#安装安全设置
sudo mysql_secure_installation
#连接mysql服务器
sudo mysql
--密码强度为2时, 密码要包含⼤⼩写字符, 特殊符号
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY
'密码';
二、工程搭建
2.1 案例
我们以一个电商平台的案例来解析一下,微服务的工程搭建。

2.2 服务拆分原则
微服务的拆分主要遵循以下原则(具体的实现还是根据最适合当前架构实现):
坚持 "合适优于业界领先", 避免"过度设计"。
- 单一职责原则
⼀个微服务也应该只负责⼀个功能或业务领域,每个服务应该有清晰的定义和边界,只关注⾃⼰的特定业务领域。就像写代码时一个类就专注一个功能。
电商系统拆分不同的服务
- 服务自治原则
每个服务要能做到独⽴开发,独⽴测试,独⽴构建,独⽴部署,独⽴运⾏。
电商系统不同的服务,独立自治
- 单向依赖原则
微服务之间需要做到单向依赖,严禁循环依赖,双向依赖。
2.3 父子工程搭建
我们使用两个服务先了解微服务: 订单服务,商品服务
订单服务: 提供订单ID,获取订单详细信息
商品服务: 根据商品ID,返回商品详细信息
2.3.1 数据准备
订单表:
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)
2.3.2 创建⽗⼯程
创建⼀个空的Maven项⽬:

完善pom文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-cloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<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-test-autoconfigure</artifactId>
<version>3.0.4</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
DependencyManagement 和 Dependencies
- dependencies :将所依赖的jar直接加到项⽬中.⼦项⽬也会继承该依赖.
- dependencyManagement :只是声明依赖, 并不实现Jar包引⼊。如果⼦项⽬需要⽤到相关依赖,需要显式声明。如果⼦项⽬没有指定具体版本,会从⽗项⽬中读取version。如果⼦项⽬中指定了版本号,就会使⽤⼦项⽬中指定的jar版本。此外⽗⼯程的打包⽅式应该是pom,不是jar,这⾥需要⼿动使⽤ packaging 来声明。
2.3.3 创建子工程

订单服务:

商品服务:

2.3.4 订单服务
根据订单id,获取订单详情。
pom文件加上:
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>
启动类:
java
package com.cloud.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);
}
}
配置文件:
yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #配置驼峰⾃动转换
实体类:
java
package com.cloud.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;
}
mapper:
java
package com.cloud.order.mapper;
import com.cloud.order.model.OrderInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface OrderMapper {
@Select("select * from order_detail where id=#{orderId}")
OrderInfo selectOrderById(Integer orderId);
}
service:
java
package com.cloud.order.service;
import com.cloud.order.mapper.OrderMapper;
import com.cloud.order.model.OrderInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
return orderInfo;
}
}
controller:
java
package com.cloud.order.controller;
import com.cloud.order.model.OrderInfo;
import com.cloud.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;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/{orderId}")
public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId){
return orderService.selectOrderById(orderId);
}
}
接口测试:

2.3.5 商品服务
根据商品id,获取商品详情。
pom文件加上:
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>
配置文件:
yml
server:
port: 9090
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #配置驼峰⾃动转换
完善启动类:
java
package com.cloud.product;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
实体类
java
package com.cloud.product.model;
import lombok.Data;
import java.util.Date;
@Data
public class ProductInfo {
private Integer id;
private String productName;
private Integer productPrice;
private Integer state;
private Date createTime;
private Date updateTime;
}
mapper:
java
package com.cloud.product.mapper;
import com.cloud.product.model.ProductInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface ProductMapper {
@Select("select * from product_detail where id=#{id}")
ProductInfo selectProductById(Integer id);
}
service:
java
package com.cloud.product.service;
import com.cloud.product.mapper.ProductMapper;
import com.cloud.product.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
public ProductInfo selectProductById(Integer id){
return productMapper.selectProductById(id);
}
}
controller:
java
package com.cloud.product.controller;
import com.cloud.product.model.ProductInfo;
import com.cloud.product.service.ProductService;
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;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/{productId}")
public ProductInfo getProductById(@PathVariable("productId") Integer productId){
System.out.println("收到请求,Id:"+productId);
return productService.selectProductById(productId);
}
}
接口测试:

三、远程调用
根据订单查询订单信息时,根据订单⾥产品ID,获取产品的详细信息。

实体类:将ProductInfo 类拷贝一份进com.cloud.order.model,在OrderInfo 添加ProductInfo 作为成员。
java
package com.cloud.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;
private ProductInfo productInfo;
}
order-service 服务向product-service 服务发送⼀个http请求,把得到的返回结果,和订单结果融合在⼀起,返回给调⽤⽅。
实现⽅式:采⽤Spring 提供的RestTemplate
实现RestTemplate
java
package com.cloud.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeanConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
OrderService修改:
java
package com.cloud.order.service;
import com.cloud.order.mapper.OrderMapper;
import com.cloud.order.model.OrderInfo;
import com.cloud.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 {
@Autowired
private OrderMapper orderMapper;
@Autowired
private 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;
}
}
接口测试:

四、RestTemplate
RestTemplate 是从 Spring3.0 开始⽀持的⼀个 HTTP 请求⼯具, 它是⼀个同步的 REST API 客⼾端,提供了常⻅的REST请求⽅案的模版。
4.1 REST
REST(Representational State Transfer),表现层资源状态转移
主要有三个概念:
- 资源: ⽹络上的所有事物都可以抽象为资源 每个资源都有⼀个唯⼀的资源标识符(URI)
- 表现层: 资源的表现形式, ⽐如⽂本作为资源, 可以⽤txt格式表现, 也可以通过HTML, XML, JSON等格式来表现, 甚⾄以⼆进制的格式表现.
- 状态转移: 访问URI, 也就是客⼾端和服务器的交互过程. 客⼾端⽤到的⼿段,只能是HTTP协议.这个过程中, 可能会涉及到数据状态的变化. ⽐如对数据的增删改查, 都是状态的转移.
REST描述的是在⽹络中Client和Server的⼀种交互形式, REST本⾝不实⽤,实⽤的是如何设计 RESTful API(REST⻛格的⽹络接⼝)
4.2 RESTful
RESTful就是满⾜REST架构⻛格的接⼝。
RESTful ⻛格⼤致有以下⼏个主要特征:
- 资源: 资源可以是⼀个图⽚,⾳频,视频或者JSON格式等⽹络上的⼀个实体 除了⼀些⼆进制的资源外普通的⽂本资源更多以JSON为载体、⾯向⽤⼾的⼀组数据(通常从数据库中查询⽽得到)
- 统⼀接⼝:对资源的操作. ⽐如获取, 创建, 修改和删除. 这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE⽅法. 换⾔⽽知,如果使⽤RESTful⻛格的接⼝,从接⼝上你可能只能定位其资源,但是⽆法知晓它具体进⾏了什么操作,需要具体了解其发⽣了什么操作动作要从其HTTP请求⽅法类型上进⾏判断
RESTful API 缺点:
- 操作⽅式繁琐, RESTful API通常根据GET, POST, PUT, DELETE 来区分对资源的操作动作. 但是 HTTP Method 并不可直接⻅到, 需要通过抓包等⼯具才能观察. 如果把动作放在URL上反⽽更加直观, 更利于团队的理解和交流.
- ⼀些浏览器对GET, POST之外的请求⽀持不太友好, 需要额外处理.
- 过分强调资源. ⽽实际业务需求可能⽐较复杂, 并不能单纯使⽤增删改查就能满⾜需求, 强⾏使⽤RESTful API会增加开发难度和成本.


