一、Spring Cloud
1.1 Spring Cloud 简述
1.1.1 Spring Cloud 版本推荐
在讲解 Spring Cloud 之前,首先推荐一下各个依赖的版本,以免出现版本错误
版本推荐
必须根据以上版本,否则可能会出现一些不必要的错误
1.1.2 Spring Cloud 能做什么
- 它是一种微服务架构的工具包
- 可以帮助服务找到其他服务并互相连接
- 能集中管理服务的设置信息
- 有熔断和限流的功能,让系统更稳定
- 可以把请求分摊到不同的服务实例上
- 处理多个服务间的事务问题
- 能监控服务的状态和性能
1.2.3 Spring Cloud 不同功能对应的不同的组件
Spring Cloud 可以用于实现很多功能,方便我们开发
这些功能都对应着Spring Cloud 中的不同的组件
以下是不同服务分别对应的组件
1.2 微服务架构编码 Base 工程模块构建
这边我们直接使用项目来了解微服务
通过 下订单,做支付这个案例
先做 Base 工程,再依次添加各种模块组件
先简单的做一个通用的 boot微服务
然后逐步挨个引入 cloud 组件纳入微服务支撑体系
1.2.1 微服务 cloud 整体聚合 maven 父工程 Project
1、New Project
新建项目,请按照我一下的配置进行创建
将这些多余的包删除,这个仅仅是作为我们 maven 的父工程
2、聚合总父工程的名字
- 这个就是父工程的名字
3、字符编码
设置字符编码为 UTF-8 ,以防出现乱码
4、注解激活生效
当引入一些新的组件时,可能会激活不了,所以需要设置
5、java 编译版本选择 17
1.2.2 Maven 父工程 pom 文件内容
在 maven 中添加
<packaging>pom</packaging>
表示该项目作为一个父工程,用于组织和管理其他子项目的依赖关系、版本控制等
将以下依赖和版本复制进 pom文件中
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <hutool.version>5.8.22</hutool.version> <lombok.version>1.18.26</lombok.version> <druid.version>1.1.20</druid.version> <mybatis.springboot.version>3.0.3</mybatis.springboot.version> <mysql.version>8.0.11</mysql.version> <swagger3.version>2.2.0</swagger3.version> <mapper.version>4.2.3</mapper.version> <fastjson2.version>2.0.40</fastjson2.version> <persistence-api.version>1.0.2</persistence-api.version> <spring.boot.test.version>3.1.5</spring.boot.test.version> <spring.boot.version>3.2.0</spring.boot.version> <spring.cloud.version>2023.0.0</spring.cloud.version> <spring.cloud.alibaba.version>2022.0.0.0-RC2</spring.cloud.alibaba.version> </properties> <dependencyManagement> <dependencies> <!--springboot 3.2.0--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--springcloud 2023.0.0--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--springcloud alibaba 2022.0.0.0-RC2--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--SpringBoot集成mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.springboot.version}</version> </dependency> <!--Mysql数据库驱动8 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!--SpringBoot集成druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> </dependency> <!--通用Mapper4之tk.mybatis--> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>${mapper.version}</version> </dependency> <!--persistence--> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> <version>${persistence-api.version}</version> </dependency> <!-- fastjson2 --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>${fastjson2.version}</version> </dependency> <!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${swagger3.version}</version> </dependency> <!--hutool--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool.version}</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <optional>true</optional> </dependency> <!-- spring-boot-starter-test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring.boot.test.version}</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement>
<properties>
标签内的是依赖的版本号- 这样分开写版本号,是为了在子工程中添加依赖时,如果依赖没有添加版本号,则会直接使用父工程的版本
- 更好的实现版本的统一
<dependencyManagement>
标签来提供一种管理依赖版本号的方式- 一般都会在 pom 父工程中使用
- 它能够让所有子工程中不用显示的列出版本号
- 因为 maven 会沿着父子层向上走,直接找到拥有
<dependencyManagement>
元素的项目 - 然后子工程就会使用
<dependencyManagement>
元素中指定的版本号 - 可以避免每个子项目中都要引入一个版本号
- 如果子项目需要另外一个版本,只需要声明
<version>
即可
<dependencyManagement>
只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖如果不在子项目中声明依赖,是不会从父项目中继承下来的
只有在子项目中写了该依赖,并没没有指定版本号,才会从父项目中该项
1.2.3 Mysql 驱动说明
1、如果是 Mysql5
JDBC 配置
# mysql5.7---JDBC四件套 jdbc.driverClass = com.mysql.jdbc.Driver jdbc.url= jdbc:mysql://localhost:3306/db2024?useUnicode=true&characterEncoding=UTF-8&useSSL=false jdbc.user = root jdbc.password =123456
pom 文件依赖
# Maven的POM文件处理 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
2、如果是 Mysql8
JDBC 配置
# mysql8.0---JDBC四件套 jdbc.driverClass = com.mysql.cj.jdbc.Driver jdbc.url= jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true jdbc.user = root jdbc.password =123456
pom 文件依赖
# Maven的POM <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency>
1.3 Mapper4 一键生成增删改查
1.3.1 数据库搭建
生成增删改查代码之前,首先需要我们的 支付订单的数据库
首先创建数据库,数据库名为
db2024
然后创建数据表,将以下代码导入即可
DROP TABLE IF EXISTS `t_pay`; CREATE TABLE `t_pay` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `pay_no` VARCHAR(50) NOT NULL COMMENT '支付流水号', `order_no` VARCHAR(50) NOT NULL COMMENT '订单流水号', `user_id` INT(10) DEFAULT '1' COMMENT '用户账号ID', `amount` DECIMAL(8,2) NOT NULL DEFAULT '9.9' COMMENT '交易金额', `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除', `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='支付交易表'; INSERT INTO t_pay(pay_no,order_no) VALUES('pay17203699','6544bafb424a'); SELECT * FROM t_pay;
1.3.2 在项目中创建第一个子工程
创建一个子工程名为
mybatis_generator2024
它与业务无关,就是一个普通的 maven 工程
有他专门生成数据库的增删改查
在子工程中导入这个模块所需的依赖
复制替换子工程的 pom 文件即可
<?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> <parent> <groupId>com.atguigu.cloud</groupId> <artifactId>cloud2024</artifactId> <version>1.0-SNAPSHOT</version> </parent> <!--我自己独一份,只是一个普通Maven工程,与boot和cloud无关--> <artifactId>mybatis_generator2024</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!--Mybatis 通用mapper tk单独使用,自己独有+自带版本号--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <!-- Mybatis Generator 自己独有+自带版本号--> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.4.2</version> </dependency> <!--通用Mapper--> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> </dependency> <!--mysql8.0--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--persistence--> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> </dependency> <!--hutool--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <resources> <resource> <directory>${basedir}/src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>${basedir}/src/main/resources</directory> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.4.2</version> <configuration> <configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile> <overwrite>true</overwrite> <verbose>true</verbose> </configuration> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>4.2.3</version> </dependency> </dependencies> </plugin> </plugins> </build> </project>
1.3.3 设置各种配置
这里需要设置数据库配置和生成增删改查的配置
创建
config.properties
数据库配置文件#User表包名 package.name=com.atguigu.cloud # mysql5.7 jdbc.driverClass = com.mysql.jdbc.Driver jdbc.url= jdbc:mysql://localhost:3306/db2024?useUnicode=true&characterEncoding=UTF-8&useSSL=false jdbc.user = root jdbc.password =123456
创建自动生成配置文件
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <properties resource="config.properties"/> <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat"> <property name="beginningDelimiter" value="`"/> <property name="endingDelimiter" value="`"/> <plugin type="tk.mybatis.mapper.generator.MapperPlugin"> <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/> <property name="caseSensitive" value="true"/> </plugin> <jdbcConnection driverClass="${jdbc.driverClass}" connectionURL="${jdbc.url}" userId="${jdbc.user}" password="${jdbc.password}"> </jdbcConnection> <javaModelGenerator targetPackage="${package.name}.entities" targetProject="src/main/java"/> <sqlMapGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java"/> <javaClientGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java" type="XMLMAPPER"/> <table tableName="t_pay" domainObjectName="Pay"> <generatedKey column="id" sqlStatement="JDBC"/> </table> </context> </generatorConfiguration>
1.3.4 一键生成增删改查
打开 pom 管理,找到
mybatis_generator2024
的模块,点击mybatis-genertor:generate
即可一键生成
1.4 标准构建微服务工程
完整构建微服务的步骤
1、建 module 模块
2、改写 pom 文件
3、写 yml,启动类配置
4、创建主启动类
5、创建业务类
首先构建一个微服务的提供者,支付 Module 模块
1.4.1 构建提供者微服务模块
提供者模块名为
cloud-provider-payment8001
1.4.2 改写 pom 文件
改写 pom 文件,将需要的依赖导入其中
<?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> <parent> <groupId>org.atguigu.com</groupId> <artifactId>cloud2024</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>cloud-provider-payment8001</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!--SpringBoot通用依赖模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--SpringBoot集成druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> </dependency> <!--mybatis和springboot整合--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--Mysql数据库驱动8 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--persistence--> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> </dependency> <!--通用Mapper4--> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> </dependency> <!--hutool--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> <!-- fastjson2 --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.28</version> <scope>provided</scope> </dependency> <!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
1.4.3 编写 yml 文件,启动类配置
编写 yml 文件,启动类的配置
server: port: 8001 # ==========applicationName + druid-mysql8 driver=================== spring: application: name: cloud-payment-service datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true username: root password: 123456 # ========================mybatis=================== mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.atguigu.cloud.entities configuration: map-underscore-to-camel-case: true
并且创建一个 mapper 包
1.4.4 创建主启动类
创建主启动类,命名为
Main8001
添加
@MapperScan
注解注意不要导错包
1.4.5 创建业务类
1、entities(实体类)
创建两个实体 Pay 和 PayDTO
- Pay 是主实体类
- PayDTO 是给前端传递数据用的实体类
- PayDTO 内的属性是前端传给后端的数据
Pay 实体类直接复制
mybatis-generator2024
模块内自动生成的实体类即可然后创建 PayDTO 传递数据实体类
package com.atguigu.cloud.entities; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; @Data @AllArgsConstructor @NoArgsConstructor public class PayDTO { private Integer id; //支付流水号 private String payNo; //订单流水号 private String orderNo; //用户账号ID private Integer userId; //交易金额 private BigDecimal amount; }
2、mapper
分别在 cloud 包下和 resources 包下创建 mapper 包
cloud 包下的 mapper 包存放接口
resources 包下的 mapper 存放 xml 文件
也是同样复制
mybatis-generator2024
模块内自动生成的 mapper即可
3、Service
创建服务接口
PayService
,定义增删改查方法package com.atguigu.cloud.service; import com.atguigu.cloud.entities.Pay; import java.util.List; public interface PayService { //增 public int add(Pay pay); //根据id删 public int delete(Integer id); //改 public int update(Pay pay); //根据id查 public Pay getById(Integer id); //查询所有 public List<Pay> getAll(); }
- 创建服务实现类
PayServiceImpl
,实现增删改查方法
- 创建服务实现类
package com.atguigu.cloud.service.impl; import com.atguigu.cloud.entities.Pay; import com.atguigu.cloud.mapper.PayMapper; import com.atguigu.cloud.service.PayService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import java.util.List; @Service public class PayServiceImpl implements PayService { @Resource PayMapper payMapper; @Override public int add(Pay pay) { return payMapper.insertSelective(pay); } @Override public int delete(Integer id) { //按照主键删除 return payMapper.deleteByPrimaryKey(id); } @Override public int update(Pay pay) { //按照主键,主键不为空则更新 return payMapper.updateByPrimaryKeySelective(pay); } @Override public Pay getById(Integer id) { //根据主键查 return payMapper.selectByPrimaryKey(id); } @Override public List<Pay> getAll() { return payMapper.selectAll(); } }
4、controller
创建
PayController
类PayController 代码部分
package com.atguigu.cloud.controller; import cn.hutool.core.bean.BeanUtil; import com.atguigu.cloud.entities.Pay; import com.atguigu.cloud.entities.PayDTO; import com.atguigu.cloud.service.PayService; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController public class PayController { @Resource private PayService payService; @PostMapping("/pay/add") public String addPay(@RequestBody Pay pay){ int add = payService.add(pay); return "添加成功,返回值"+add; } @DeleteMapping("/pay/delete/{id}") public Integer removePay(@PathVariable("id") Integer id){ return payService.delete(id); } @PutMapping("/pay/update") public String updatePay(@RequestBody PayDTO payDTO){ Pay pay=new Pay(); BeanUtil.copyProperties(payDTO,pay); int i=payService.update(pay); return "修改成功,返回值"+i; } @GetMapping("pay/get/{id}") public Pay getById(@PathVariable("id") Integer id){ return payService.getById(id); } @GetMapping("pay/getAll") public List<Pay> getAll(){ return payService.getAll(); } }
1.5 Swagger3 代码测试
Swagger3 是一个用于测试接口的框架
使用 Swagger3 之前需要导入一个依赖
<!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
1.5.1 Swagger3 常用注解
使用 Swagger3 需要在代码中嵌入注解
以下是常用注解
-
注解 标注位置 作用 @Tag
controller 类 表示 controller 的作用 @Parmeter
参数 标识参数作用 @Parmeters
参数 参数多重说明 @Schema
model 层的 JavaBena 描述模型作用及每个属性 @Operation
方法 描述方法的作用 @ApiResponse
方法 描述响应状态码等 @Schema
实体类或实体类字段 描述实体类或实体类字段的作用 在我们编写的 PayController 中加入注解
package com.atguigu.cloud.controller; import cn.hutool.core.bean.BeanUtil; import com.atguigu.cloud.entities.Pay; import com.atguigu.cloud.entities.PayDTO; import com.atguigu.cloud.service.PayService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import org.springframework.beans.BeanUtils; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @Tag(name = "支付微服务模块",description = "支付CRUD") public class PayController { @Resource PayService payService; @PostMapping(value = "/pay/add") @Operation(summary = "新增", description = "新增支付流水方法,json串做参数") public String addPay(@RequestBody Pay pay) { System.out.println(pay.toString()); int i = payService.add(pay); return "成功插入记录,返回值:" + i; } @DeleteMapping(value = "/pay/del/{id}") @Operation(summary = "删除", description = "删除支付流水方法") public Integer deletePay(@PathVariable("id") Integer id) { return payService.delete(id); } @PutMapping(value = "/pay/update") @Operation(summary = "修改", description = "修改支付流水方法") public String updatePay(@RequestBody PayDTO payDTO) { Pay pay = new Pay(); BeanUtils.copyProperties(payDTO, pay); int i = payService.update(pay); return "成功修改记录,返回值:" + i; } @GetMapping(value = "/pay/get/{id}") @Operation(summary = "按照ID查流水", description = "查询支付流水方法") public Pay getById(@PathVariable("id") Integer id) { return payService.getById(id); } }
在我们的实体类 Pay 中添加注解,描述实体类及字段的作用
package com.atguigu.cloud.entities; import io.swagger.v3.oas.annotations.media.Schema; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import java.math.BigDecimal; import java.util.Date; /** * 表名:t_pay */ @Table(name = "t_pay") @Schema(title = "支付交易表") public class Pay { @Id @GeneratedValue(generator = "JDBC") private Integer id; /** * 支付流水号 */ @Column(name = "pay_no") @Schema(title = "支付流水号") private String payNo; /** * 订单流水号 */ @Column(name = "order_no") @Schema(title = "订单流水号") private String orderNo; /** * 用户账号ID */ @Column(name = "user_id") @Schema(title = "用户账号ID") private Integer userId; /** * 交易金额 */ private BigDecimal amount; /** * 删除标志,默认0不删除,1删除 */ private Byte deleted; /** * 创建时间 */ @Column(name = "create_time") @Schema(title = "创建时间") private Date createTime; /** * 更新时间 */ @Column(name = "update_time") @Schema(title = "更新时间") private Date updateTime; /** * @return id */ public Integer getId() { return id; } /** * @param id */ public void setId(Integer id) { this.id = id; } /** * 获取支付流水号 * * @return payNo - 支付流水号 */ public String getPayNo() { return payNo; } /** * 设置支付流水号 * * @param payNo 支付流水号 */ public void setPayNo(String payNo) { this.payNo = payNo; } /** * 获取订单流水号 * * @return orderNo - 订单流水号 */ public String getOrderNo() { return orderNo; } /** * 设置订单流水号 * * @param orderNo 订单流水号 */ public void setOrderNo(String orderNo) { this.orderNo = orderNo; } /** * 获取用户账号ID * * @return userId - 用户账号ID */ public Integer getUserId() { return userId; } /** * 设置用户账号ID * * @param userId 用户账号ID */ public void setUserId(Integer userId) { this.userId = userId; } /** * 获取交易金额 * * @return amount - 交易金额 */ public BigDecimal getAmount() { return amount; } /** * 设置交易金额 * * @param amount 交易金额 */ public void setAmount(BigDecimal amount) { this.amount = amount; } /** * 获取删除标志,默认0不删除,1删除 * * @return deleted - 删除标志,默认0不删除,1删除 */ public Byte getDeleted() { return deleted; } /** * 设置删除标志,默认0不删除,1删除 * * @param deleted 删除标志,默认0不删除,1删除 */ public void setDeleted(Byte deleted) { this.deleted = deleted; } /** * 获取创建时间 * * @return createTime - 创建时间 */ public Date getCreateTime() { return createTime; } /** * 设置创建时间 * * @param createTime 创建时间 */ public void setCreateTime(Date createTime) { this.createTime = createTime; } /** * 获取更新时间 * * @return updateTime - 更新时间 */ public Date getUpdateTime() { return updateTime; } /** * 设置更新时间 * * @param updateTime 更新时间 */ public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } }
1.5.2 创建迭代的 config 配置类
使用 Swagger3 之前还需要创建含分组迭代的 config 配置类
因此我们需要创建一个 Swagger3 相关的配置类
编写相关代码
package com.atguigu.cloud.config; import io.swagger.v3.oas.models.ExternalDocumentation; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class Swagger3Config { @Bean public GroupedOpenApi PayApi() { return GroupedOpenApi.builder().group("支付微服务模块").pathsToMatch("/pay/**").build(); } @Bean public GroupedOpenApi OtherApi() { return GroupedOpenApi.builder().group("其它微服务模块").pathsToMatch("/other/**", "/others").build(); } @Bean public OpenAPI docsOpenApi() { return new OpenAPI() .info(new Info().title("cloud2024") .description("通用设计rest") .version("v1.0")) .externalDocs(new ExternalDocumentation() .description("www.atguigu.com") .url("https://yiyan.baidu.com/")); } }
我们微服务的接口会有很多,这个配置类就是专门进行了分组
例如
这两个方法分别对应两个不同的模块
PayApi()
对应支付模块OtherApi()
对应其他模块
PayApi()
方法中调用的PathsToMatch()
方法当中的参数/pay/**
就表示,只要开头为/pay
的都属于这个模块docsOpenApi()
方法中的内容则是对文档的一些说明
1.5.3 使用 Swagger3 进行测试
运行启动类
然后打开浏览器输入网址:
http://localhost:8001/swagger-ui/index.html
成功访问的界面
此时右上角就是你在配置类中定义的分组
这里进行测试即可
1.6 微服务工程项目改进
1.6.1 时间格式问题优化
测试代码时,返回给我们的时间格式不是我们常用的类型
这里我们可以使用两种方式来进行优化
1、在相应的类的属性上使用 @JsonFormat
注解
- 在实体类的时间格式的属性上添加
@JsonFormat
2、在 Spring boot 项目中还可以在 application.yml 文件中指定
在 application.yml 文件中指定如下
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone:GMT+8
这里才是返回了我们想要的时间格式
1.6.2 统一返回值格式
1、具体思路
我们需要封装一个对象,给前端返回一个 KV 键值对
定义返回的标准格式(三大标配)
- code 状态值:由后端统一定义各种返回结果的状态码
- message描述:本次接口调用的结果描述
- data数据:本次返回的数据
- code 和 message 就是我们的 key 值
- data 就是我们的 value 值
定义接口调用时间之类
- 用于排查错误
- 告诉程序员什么时候调用接口
- 第几次调用接口,什么时间段出的故障
- 这就会添加 timestamp :接口调用时间
2、主要步骤
新建枚举类
ReturnCodeEnum
让我们的状态码尽量与 HTTP 的相对应
HTTP 请求返回的状态码
定义
ReturnCodeEnum
枚举类具体代码
package com.atguigu.cloud.resp; import lombok.Getter; import java.util.Arrays; /** * @auther zzyy * @create 2023-11-04 11:51 */ @Getter public enum ReturnCodeEnum { /**1.举值**/ /**操作失败**/ RC999("999","操作XXX失败"), /**操作成功**/ RC200("200","success"), /**服务降级**/ RC201("201","服务开启降级保护,请稍后再试!"), /**热点参数限流**/ RC202("202","热点参数限流,请稍后再试!"), /**系统规则不满足**/ RC203("203","系统规则不满足要求,请稍后再试!"), /**授权规则不通过**/ RC204("204","授权规则不通过,请稍后再试!"), /**access_denied**/ RC403("403","无访问权限,请联系管理员授予权限"), /**access_denied**/ RC401("401","匿名用户访问无权限资源时的异常"), RC404("404","404页面找不到的异常"), /**服务异常**/ RC500("500","系统异常,请稍后重试"), RC375("375","数学运算异常,请稍后重试"), INVALID_TOKEN("2001","访问令牌不合法"), ACCESS_DENIED("2003","没有权限访问该资源"), CLIENT_AUTHENTICATION_FAILED("1001","客户端认证失败"), USERNAME_OR_PASSWORD_ERROR("1002","用户名或密码错误"), BUSINESS_ERROR("1004","业务逻辑异常"), UNSUPPORTED_GRANT_TYPE("1003", "不支持的认证模式"); /**构造**/ /**自定义状态码**/ private final String code; /**自定义描述**/ private final String message; ReturnCodeEnum(String code, String message){ this.code = code; this.message = message; } /**遍历**/ //遍历枚举V1 public static ReturnCodeEnum getReturnCodeEnum(String code) { for (ReturnCodeEnum element:ReturnCodeEnum.values()) { if (element.code.equals(code)){ return element; } } return null; } //遍历枚举V2 public static ReturnCodeEnum getReturnCodeEnumV2(String code) { return Arrays.stream(ReturnCodeEnum.values()).filter(x -> x.getCode().equalsIgnoreCase(code)).findFirst().orElse(null); } /*public static void main(String[] args) { System.out.println(getReturnCodeEnumV2("200")); System.out.println(getReturnCodeEnumV2("200").getCode()); System.out.println(getReturnCodeEnumV2("200").getMessage()); }*/ }
3、如何定义一个通用的枚举类
这里主要讲解一下我们定义枚举的三个小步骤
举值-构造-遍历
举值
罗列出你的枚举值每一个 code 对应的 message 分别是什么
例如我们上面的代码中
第一个举值
RC999
在这个枚举里面,第一个举值 999
对应的 message 就是 操作失败
构造
- 我们的枚举是有两个具体的值
- 所以在构造上面我们就要创建有两个参数的构造函数
- 这个就叫做构造
遍历
- 我们需要遍历我们的枚举
- 根据我们传过来的 code 状态码,来返回所需要的枚举
- 如果没有找到对应的枚举,就需要返回一个 null
- 我们这里遍历枚举用了两种方法
4、新建统一返回对象 ResultData
新建类
ResultData
添加四个变量
code
、message
、data
、timestamp
在构造函数中,给 timestamp 设置时间,表示方法的调用时间
创建两个方法,成功和失败,设置返回结果
package com.atguigu.cloud.resp; import lombok.Data; import lombok.experimental.Accessors; @Data @Accessors(chain = true) public class ResultData<T> { private String code; private String message; private T data; private long timestamp; public ResultData(){ this.timestamp=System.currentTimeMillis(); } //成功 public static<T> ResultData<T> success(T data){ ResultData resultData=new ResultData(); resultData.setCode(ReturnCodeEnum.RC200.getCode()); resultData.setMessage(ReturnCodeEnum.RC200.getMessage()); resultData.setData(data); return resultData; } //失败 public static <T> ResultData<T> fail(String code,String message){ ResultData resultData=new ResultData(); resultData.setCode(code); resultData.setMessage(message); resultData.setData(null); return resultData; } }
5、修改 PayController
类中的返回值
设置好同意的返回值后,我们需要修改先前的
PayController
类中的返回值将当中方法的返回值实现统一
修改后的代码
package com.atguigu.cloud.controller; import cn.hutool.core.bean.BeanUtil; import com.atguigu.cloud.entities.Pay; import com.atguigu.cloud.entities.PayDTO; import com.atguigu.cloud.resp.ResultData; import com.atguigu.cloud.service.PayService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import org.springframework.beans.BeanUtils; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @Tag(name = "支付微服务模块",description = "支付CRUD") public class PayController { @Resource PayService payService; @PostMapping(value = "/pay/add") @Operation(summary = "新增", description = "新增支付流水方法,json串做参数") public ResultData<String> addPay(@RequestBody Pay pay) { System.out.println(pay.toString()); int i = payService.add(pay); return ResultData.success("成功插入记录,返回值:" + i); } @DeleteMapping(value = "/pay/del/{id}") @Operation(summary = "删除", description = "删除支付流水方法") public ResultData<Integer> deletePay(@PathVariable("id") Integer id) { return ResultData.success(payService.delete(id)); } @PutMapping(value = "/pay/update") @Operation(summary = "修改", description = "修改支付流水方法") public ResultData<String> updatePay(@RequestBody PayDTO payDTO) { Pay pay = new Pay(); BeanUtils.copyProperties(payDTO, pay); int i = payService.update(pay); return ResultData.success("成功修改记录,返回值:" + i); } @GetMapping(value = "/pay/get/{id}") @Operation(summary = "按照ID查流水", description = "查询支付流水方法") public ResultData<Pay> getById(@PathVariable("id") Integer id) { return ResultData.success(payService.getById(id)); } }
然后进行测试即可
1.6.3 全局异常处理
定义全局异常处理后将不需要再使用
try-cath
来解决异常了首先给我们
Controller
类中抛出一个错误当传输的 id 为 -4 时,抛出错误
定义全局异常处理类
GlobalExceptionHandler
package com.atguigu.cloud.exp; import com.atguigu.cloud.resp.ResultData; import com.atguigu.cloud.resp.ReturnCodeEnum; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(RuntimeException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResultData<String> exception(Exception e){ System.out.println("#### come in GlobalExceptionHandler"); return ResultData.fail(ReturnCodeEnum.RC500.getCode(), ReturnCodeEnum.RC500.getMessage()); } }
@ExceptionHandler
注解是具体要捕获的异常类型@ResponseStatus
注解中的HttpStatus
枚举是定义异常和 BUG 的一个标准和规范@ResponseStatus
主要作用是为了改变 Http 响应的状态码测试一下即可,执行查询方法,传入 id 为 -4
1.7 引入微服务理念
这里主要讲解不同模块之间如何调用
订单微服务80如何才能调用到支付微服务8001
1.7.1 RestTemplate
在引入微服务之前,需要介绍一下 API
RestTemplate
RestTemplate
提供了多种远程范围 HTTP 服务的方法是一种简单便捷的 restful 服务类
是 Spring 提供的用于访问 Rest 服务的客户端模板工具集
官网访问地址:https://docs.spring.io/spring-framework/docs/6.0.11/javadoc-api/org/springframework/web/client/RestTemplate.html
看官网的方法便可得知,它提供了两个微服务之间调用的增删改查的方法
使用restTemplate访问restful接口非常的简单粗暴无脑
(url, requestMap, ResponseBean.class)这三个参数分别代表
REST请求地址、请求参数、HTTP响应转换被转换成的对象类型
HTTP响应转换被转换成的对象类型简单理解就是要返回的对象
常用方法
如果访问的是
getForEntity
方法,则返回是 RestTemplate 自己封装的一层对象- 主要包含了一些重要信息
- 例如响应头,响应状态码,响应体等
如果访问的是
getForObject
方法,则直接返回我们指定的返回的对象GET 请求方法
POST 请求方法
使用 RestTemplate 的两种方式
直接 new 出来
使用 配置类来返回 RestTemplate
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
1.7.2 RestTemplate 增删改查使用的方法
以下推荐一下我自己增删改查使用的方法
- 增:
postForObject(url,传递参数,返回的对象)
- 删:
delete(url,传递参数,返回的对象)
- 改:
put(url,传递参数,返回的对象)
- 查:getForObject(url,返回的对象,传递的参数)
1.7.3 新建模块 80
按照之前的步骤:建模块-改pom-写yml-创建主启动类-创建业务类,来创建模块
创建模块
cloud-consumer-order80
改写 pom 文件
<?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> <parent> <groupId>com.atguigu.cloud</groupId> <artifactId>cloud2024</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>cloud-consumer-order80</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!--web + actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--hutool-all--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> <!--fastjson2--> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> </dependency> <!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
写 yml 文件
server: port: 80
创建主启动类,名为
Main80
package com.atguigu.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Main80 { public static void main(String[] args) { SpringApplication.run(Main80.class,args); } }
创建业务类
创建实体类
PayDTO
与 8001 模块的 DTO 一致因为消费者 80 产生的数据只应该是 支付者 8001 暴露出来的东西
给消费者 80 尝试填写,对于其他敏感信息,其他信息是不应该暴露的
所以需要使用 DTO 来传输数据
将之前在 8001 模块内定义的 统一返回对象拷贝过来
创建 RestTemplat 配置类
创建 Controller
定义两个属性
- REST请求地址
- RestTemplate
package com.atguigu.cloud.controller; import jakarta.annotation.Resource; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class OrderController { public static final String PaymentSrv_URL ="https://localhost:8001"; @Resource private RestTemplate restTemplate; }
1.7.4 微服务调用,实现 80 模块调用 8001 模块中的增删改查方法
然后就可以编写我们的增删改查方法了
首先我们编写一个增加方法,用于测试
package com.atguigu.cloud.controller; import com.atguigu.cloud.entities.PayDTO; import com.atguigu.cloud.resp.ResultData; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @Tag(name = "微服务消费模块",description = "消费调用支付的CRUD") public class OrderController { public static final String PaymentSrv_URL ="http://localhost:8001"; @Resource private RestTemplate restTemplate; @PostMapping("/consumer/pay/add") @Operation(summary = "消费调用支付的新增", description = "新增支付流水方法,json串做参数") public ResultData addOrder(PayDTO payDTO){ return restTemplate.postForObject(PaymentSrv_URL+"/pay/add", payDTO, ResultData.class); } }
postForObject
方法传递的三个参数就是对应 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型(及返回的对象类型)然后启动两个程序 Main8001 和 Main80
一个个启动的话会比较麻烦,这边推荐设置到一起启动、
右上角编辑配置
选择 Spring boot,添加新的运行配置
命名,并且指定模块
选择模块内的主启动类
然后右键运行即可
然后进行测试即可
增删改查的所有代码如下
package com.atguigu.cloud.controller; import com.atguigu.cloud.entities.PayDTO; import com.atguigu.cloud.resp.ResultData; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; @RestController @Tag(name = "微服务消费模块",description = "消费调用支付的CRUD") public class OrderController { public static final String PaymentSrv_URL ="http://localhost:8001"; @Resource private RestTemplate restTemplate; //增 @PostMapping("/consumer/pay/add") @Operation(summary = "消费调用支付的新增", description = "新增支付流水方法,json串做参数") public ResultData addOrder(PayDTO payDTO){ return restTemplate.postForObject(PaymentSrv_URL+"/pay/add", payDTO, ResultData.class); } //删 @DeleteMapping( "/consumer/pay/del/{id}") @Operation(summary = "消费调用支付删除", description = "删除支付流水方法") public ResultData delByIdOrder(@PathVariable("id") Integer id){ restTemplate.delete(PaymentSrv_URL+"/pay/del/{id}",id,ResultData.class); return ResultData.success("okx"); } //改 @PutMapping("/consumer/pay/update") @Operation(summary = "消费调用支付的修改",description = "修改支付流水方法") public ResultData updatePay(@RequestBody PayDTO payDTO){ restTemplate.put(PaymentSrv_URL+"/pay/update",payDTO,ResultData.class); return ResultData.success("ok"); } //查 @GetMapping("/consumer/pay/get/{id}") @Operation(summary = "消费调用支付的按照ID查流水", description = "查询支付流水方法") public ResultData getById(@PathVariable("id") Integer id){ return restTemplate.getForObject(PaymentSrv_URL+"/pay/get/{id}",ResultData.class,id); } }
1.8 工程重复代码提取
1.8.1 观察问题
在我们的 8001 支付模块和 80 订单模块 两个模块当中,都存在一些相同的类,例如
可见系统中存在相同的部分
此时我们可以新建一个模块,当作项目的一个包
将这些重复的部分写入模块,在使用时即可直接调用
1.8.2 新建通用组件模块
新建模块
cloud-api-commons
用于对外暴露通用的组件/api/接口/工具类等
改写 pom 文件
<?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> <parent> <groupId>com.atguigu.cloud</groupId> <artifactId>cloud2024</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>cloud-api-commons</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!--SpringBoot通用依赖模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--hutool--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> </dependencies> </project>
将两个模块通用的部分添加进新模块
使用 maven,将
cloud-api-commons
模块打成一个公用的 jar 包
1.8.3 对模块 8001 和 80 进行改造
将模块
cloud-api-commons
打成 jar 包后,需要在 8001 和 80 两个模块之间进行引用在 pom 文件中添加此依赖进行引用
<!-- 引入自己定义的api通用包 --> <dependency> <groupId>com.atguigu.cloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
既然已经引入公用的部分了,那么两个模块之间公用的部分就可以删除了
然后自行测试即可
1.9 Consul 服务注册与发现
1.9.1 为什么需要服务注册中心
- 例如我们之前所写的 Rest请求地址
- 我们将微服务所在的 IP地址 和 端口号 ==硬编码(即直接写死)==到了 80微服务模块服务当中,会出现很多问题
- 如果 80模块服务 和 8001 模块服务的 IP地址 和 端口号 发生了变化,则 8001模块服务 将变得不可用
- 需要同步修改80微服务中调用8001微服务的 IP地址 和 端口号
- 如果系统中提供了多个 80微服务 和 8001 微服务,则无法实现微服务的负载均衡功能
- 如果系统需要支持更高的并发,需要部署更多的 80微服务和 8001微服务,硬编码会使后续的维护变得异常复杂
- 所以,在微服务开发的过程中,需要引入服务治理功能
- 实现微服务之间的动态注册与发现
1.9.2 Consul 简介
官网地址:https://www.consul.io/
Consul 是一套开源的分布式服务和配置管理系统
提供了微服务的服务治理,配置中心,控制总线等功能
Consul可以做什么
-
作用 说明 服务发现 提供 HTTP 和 DNS 两种发现方式 健康监测 支持多种方式,HTTP、TCP、Docker、Shell 脚本定制 KV存储 Key、Value的存储方式 多数据中心 Consul 支持多数据中心 可视化 Web 界面
1.9.3 安装并运行 Consul
下载地址:https://developer.hashicorp.com/consul/downloads
选择你的系统然后下载即可
然后解压运行即可
在你运行
consul.exe
的文件下打开终端测试是否下载成功然后输入
consul -version
,出现版本号即代表下载成功然后使用开发模式启动
还是在刚才的路径终端,输入
consul agent -dev
通过以下地址可以访问 Consule 的首页:
http://localhost:8500
如果可以成功看到页面,并且 Consul 出现绿色的对勾,则代表本机启动完成
1.9.4 将服务提供者 8001 入注进服务中心
将支付服务 8001 模块注册进 consul
需要先在 8001 模块中引入 consul 的依赖
<!--SpringCloud consul discovery --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
然后要在 yml 文件中添加以下配置
spring: cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
之后,在启动类上添加
@EnableDiscoveryClient
注解,表示开启服务发现然后 8001 模块并查看 consul 控制台
访问之前记得在终端使用
consul agent -dev
启动 consul访问 Consule 的首页:
http://localhost:8500
注入成功
1.9.5 将服务消费者 80 入注服务中心
步骤与
1.9.4
的步骤相同导入依赖
<!--SpringCloud consul discovery --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
yml 文件添加配置
spring: application: name: cloud-consumer-order ####Spring Cloud Consul for Service Discovery cloud: consul: host: localhost port: 8500 discovery: prefer-ip-address: true #优先使用服务ip进行注册 service-name: ${spring.application.name}
主启动类添加注解
@EnableDiscoveryClient
只是这里要修改一下 Controller 类当中 Rest请求地址的值
之前我们是写死的,这里我们只需要引用名称即可
我们之前写的 8001 地址是8001模块的
这里只需要将地址改成 8001 在 Consul 当中的名称即可
8001 在 Consul 当中的名称为
cloud-payment-service
修改 80 模块中 Controller 类当中的地址
然后测试
1.9.6 两个模块注入后测试运行结果
当我们访问 80 模块当中的 get获取数据时,会发现出现了错误
异常由 80 模块发出的全局异常
因为 Consul 天生支持负载均衡
所以我们需要在 RestTemplateConfig 中添加注解
以此支撑 RestTemplate 对于负载均衡微服务调用支撑
在 RestTemplateConfig类中添加
@LoadBalanced
注解然后重新进行测试就可以查出来了
1.10 Consul 服务配置与刷新
1.10.1 分布式系统面临的配置问题
- 微服务意味着要将单体应用中的业务拆分成一个个子服务
- 因此系统中会出现大量的服务,由于每个服务都需要必要的配置信息才能运行
- 比如某些配置文件中的内容大部分都是相同的,只有个别的配置项不同
- 例如数据库配置,如果有时候主机迁移了,当下我们每一个微服务都带着一个
application.yml
,每个都需要修改,特别麻烦 - 我们希望一次修改,处处生效
- 所有有一套集中的、动态的配置管理设施必不可少的
1.10.2 bootstrap.yml
配置文件
- 在进行服务配置之前,我们需要先了解一下
bootstrap.yml
文件 - 我们先有的配置文件
application.yml
是用户级的资源配置项 - 而
bootstrap.yml
是系统级的,优先级更高 - Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的
Application Context
的父上下文 - 假设我们的微服务要启动,那么 Consul 一定是成功启动的
- 因为我们使用了服务配置,会将一些全局信息注册进 Consul 服务器
- 所以在微服务还没启动时,我们就要先读取
bootstrap.yml
文件将信息发送过去 Bootstrap Context
负责从外部源加载配置属性并解析配置Bootstrap
属性有高优先级,默认情况下,它们不会被本地配置覆盖application.yml
文件改为bootstrap.yml
,这是很关键的或者两者共存
1.10.3 Consul 的 KV 存储
这里只是简单了解一下,并不做深入的学习
Consul还提供了一个易于使用的键/值存储。这可以用来保存动态配置
使用 KV 存储需要在 Consul 上创建特定的文件夹
创建文件夹的规则
需要先创建一个名为
config
的文件夹,以/
结尾然后在 config 文件夹下创建三个文件夹
cloud-payment-service
、cloud-payment-service-dev
、cloud-payment-service-prod
,以/
结尾这三个文件夹分别对应默认环境,开发环境,生产环境
然后分别在这三个文件夹下创建data文件,这次不需要再末尾添加
/
注意文件格式要选择 YAML
注意:每写完一个 key 值时,都要在冒号后面,打上一个空格
cloud-payment-service
默认环境文件夹下创建的data必须在
atguigu:
和info:
的后面打上一个空格cloud-payment-service-dev
开发环境文件夹下创建的data必须在
atguigu:
和info:
的后面打上一个空格cloud-payment-service-prod
生产环境文件夹下创建的data必须在
atguigu:
和info:
的后面打上一个空格成功存储
1.10.4 服务配置步骤
我们需要将一些通用的全局信息,直接注册进 Consul 服务器,从 Consul 获取
既然从 Consul 获取,就需要自然遵守 Consul 的配置规则要求
修改
cloud-provider-payment8001
模块在 pom 文件中引入两个依赖
<!--SpringCloud consul config--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>
新建
bootstrap.yml
文件在
application.yml
文件中抽出跟服务配置有关的数据抽出之后的
bootstrap.yml
文件spring: application: name: cloud-payment-service ####Spring Cloud Consul for Service Discovery cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name} config: profile-separator: '-' # default value is ",",we update '-' format: YAML # config/cloud-payment-service/data # /cloud-payment-service-dev/data # /cloud-payment-service-prod/data
抽出之后的
application.yml
文件server: port: 8001 # ==========applicationName + druid-mysql8 driver=================== spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true username: root password: root profiles: active: dev # 多环境配置加载内容dev/prod,不写就是默认default配置 # ========================mybatis=================== mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.atguigu.cloud.entities configuration: map-underscore-to-camel-case: true
然后在 Controller 中测试是否可以获取
bootstrap.yml
中的 port 值并且同时测试我们现在在 Consul 中的 KV存储是否可以获取
在 Controller 中添加变量
port
并且添加getPortAndInfo
方法@Value("${server.port}") private String port; @GetMapping(value="/pay/get/protandinfo") public String getPortAndInfo(@Value("${atguigu.info}") String info){ return "port:"+port+"\tinfo:"+info; }
这边使用
@Value
注解来获取bootstrap.yml
中的port 值 和 KV 中存储的值@Value 注解的包为
org.springframework.beans.factory.annotation.Value
注意不要导错注解中的值则对应在文件中写的 key,然后通过 key 值就可以获取后面的 value 值
然后我们在 KV存储中创建的三个文件
你在配置文件中定义的是什么环境,就会调用什么环境当中的 data 文件
例如我这边配置文件中定义的是 dev 开发环境,那么则是调用 Consul 中
cloud-payment-service-dev
文件下的 data测试是否可以成功获取
测试成功
1.10.5 Consule 之及时动态刷新
我们先运行 Controller 中的
getPortAndInfo
方法然后在 Consul 中修改 dev 配置分支的内容后保存
此时我们打开先去运行过的
getPortAndInfo
方法的页面点击刷新此时我们发现结果的值并没有随着 Consul 服务器上的值修改而改变
这是因为 Consul 服务器有一个监控的等待时间,默认的是 55 秒
如果你希望服务器更新后立马更新
那么首先要在启动类上添加一个注解
@@RefreshScope
表示动态刷新然后在
bootstrap.yml
中添加配置watch-time
就表示健康等待时间,值为 1 则代表等待1秒、然后就可以及时刷新了
这里只为教学展示,一般不建议修改
1.10.6 Consul 中现阶段存在的问题
截止到这里,服务配置和动态刷新全部通过
但是现在依然存在一个问题
如果我重启 Consul,之前所配置的文件都会消失
就比如我们配置的 KV 存储也都会消失
这里就需要用到 Consul 的配置持久化了
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: root
profiles:
active: dev # 多环境配置加载内容dev/prod,不写就是默认default配置======mybatis=
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.cloud.entities
configuration:
map-underscore-to-camel-case: true[外链图片转存中…(img-0xksmCCo-1711897684530)]
然后在 Controller 中测试是否可以获取
bootstrap.yml
中的 port 值并且同时测试我们现在在 Consul 中的 KV存储是否可以获取
在 Controller 中添加变量
port
并且添加getPortAndInfo
方法@Value("${server.port}") private String port; @GetMapping(value="/pay/get/protandinfo") public String getPortAndInfo(@Value("${atguigu.info}") String info){ return "port:"+port+"\tinfo:"+info; }
这边使用
@Value
注解来获取bootstrap.yml
中的port 值 和 KV 中存储的值@Value 注解的包为
org.springframework.beans.factory.annotation.Value
注意不要导错注解中的值则对应在文件中写的 key,然后通过 key 值就可以获取后面的 value 值
然后我们在 KV存储中创建的三个文件
你在配置文件中定义的是什么环境,就会调用什么环境当中的 data 文件
[外链图片转存中…(img-s6TfqfJn-1711897684531)]
例如我这边配置文件中定义的是 dev 开发环境,那么则是调用 Consul 中
cloud-payment-service-dev
文件下的 data测试是否可以成功获取
[外链图片转存中…(img-OKc3jkbe-1711897684531)]
测试成功
1.10.5 Consule 之及时动态刷新
我们先运行 Controller 中的
getPortAndInfo
方法然后在 Consul 中修改 dev 配置分支的内容后保存
[外链图片转存中…(img-Db8od70L-1711897684531)]
此时我们打开先去运行过的
getPortAndInfo
方法的页面点击刷新[外链图片转存中…(img-Uu5uSrLj-1711897684531)]
此时我们发现结果的值并没有随着 Consul 服务器上的值修改而改变
这是因为 Consul 服务器有一个监控的等待时间,默认的是 55 秒
如果你希望服务器更新后立马更新
那么首先要在启动类上添加一个注解
@@RefreshScope
表示动态刷新[外链图片转存中…(img-uW8CbrJw-1711897684532)]
然后在
bootstrap.yml
中添加配置[外链图片转存中…(img-Or62n1Pi-1711897684532)]
watch-time
就表示健康等待时间,值为 1 则代表等待1秒、然后就可以及时刷新了
这里只为教学展示,一般不建议修改
1.10.6 Consul 中现阶段存在的问题
- 截止到这里,服务配置和动态刷新全部通过
- 但是现在依然存在一个问题
- 如果我重启 Consul,之前所配置的文件都会消失
- 就比如我们配置的 KV 存储也都会消失
- 这里就需要用到 Consul 的配置持久化了
- 这里我们只是引导问题,并不会解决,Consul 的配置持久化在后面学习的时候会讲解到