目录
一、SpringBoot和SpringCloud版本选型
1.Springcloud版本选择
2.Springcloud版本选择
3.Springcloud Alibaba版本选择
4.SpringCloud VS SpringBoot VS SpringCloud Alibaba版本三者制约对应关系
二、SpringCloud介绍
1.单体架构
2.微服务架构
3.springcloud
4.SpringCloud相关组件介绍
三、微服务项目搭建
1.项目构建
2.MyBatis逆向工程
3.微服务提供者支付module
4.待优化的问题
四、项目改进
1.优化时间格式
(1)方式一:
(2)方式二:
2.统一返回结果Result
(1)步骤一:定义一个枚举类,用于状态码的返回
(2)步骤二:定义统一返回类Result
(3)步骤三:修改原来接口的返回值
3.全局异常处理
五、微服务调用者订单module
1.项目构建
2.服务调用RestTemplate
(1) RestTemplate介绍
(2)项目配置 RestTemplate
3.重复代码抽取
4.存在问题
一、SpringBoot和SpringCloud版本选型
技术 | 版本 |
---|---|
Java | jdk17+ |
boot | 3.2.0 |
cloud | 2023.0.0 |
cloud alibaba | 2023.0.0.0-RC1 |
Maven | 3.9+ |
MySQL | 8.0+ |
1.Springcloud版本选择
通过上面官网发现,Boot官方强烈建议你使用Java17+升级到3.X以上版本。
2.Springcloud版本选择
与 SpringBoot 3.2.0 相对应的 SpringCloud 版本为 2023.0.x。
3.Springcloud Alibaba版本选择
官网显示版本为 2021.0.4.0,但官网有延后情况,非最新版(不推荐)。
Github说明:GitHub - alibaba/spring-cloud-alibaba: Spring Cloud Alibaba provides a one-stop solution for application development for the distributed solutions of Alibaba middleware.
因此,我们选择 SpringCloudAlibaba 版本为 2023.0.0.0-RC1。
4.SpringCloud VS SpringBoot VS SpringCloud Alibaba版本三者制约对应关系
若同时用 boot 和 cloud,由话事人 cloud 决定 boot 版本。
同时,cloud 也决定了使用哪种版本的 SpringCloudAlibaba。
二、SpringCloud介绍
1.单体架构
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署
优点:
- 架构简单
- 部署成本低
当随着业务越来越多,代码量将上升至几万行甚至几十万行,开发人员也会需要越来越多!
各业务之前存在耦合性,编写、提交代码时,可能会产生冲突。
同时,代码量过高也会造成编译,打包时间过长。
某些业务存在高并发,就会很容易将服务器资源耗尽,其他业务就只能等待,访问时就会卡顿。
缺点:
- 团队协作成本高
- 系统发布效率低
- 系统可用性差
总结:单体架构适合开发功能相对简单,规模较小的项目
2.微服务架构
微服务架构:服务化,就是把单体架构中的模块拆分为多个独立的项目。它以专注于单一职责的很多小型项目为基础,组合出复杂度大型应用。
微服务通常需要:
- 粒度小
- 团队自治
- 服务自治
3.springcloud
实现微服务架构,会面临多种问题,需要通过以下各种技术得以解决:
大体流程如下:
这就会导致程序员除了编写业务逻辑代码,还需要为了保证项目正常运行,做各种其他的事情。
SpringCloud 就集成了各种微服务功能组件,并基于 SpringBoot 实现了这些组件的自动装配,从而提供了良好的开箱即用体验,帮助程序员减负!
4.SpringCloud相关组件介绍
在2019年之前,使用的大部分技术都是Netflix提供的,但是由于开发SpringCloud的相关技术不挣钱,因此Netflix就暂停开发相关技术了,虽然他提供的那些技术依旧可以使用,但是已经不推荐了。
对于老的技术栈,可能老项目中会用到,这里只介绍一些推荐的新组件。
注册与发现
- Eureka【Netflix最后的火种,不推荐】
- Consul【推荐使用】
- Etcd【可以使用】
- Nacos【推荐使用,阿里巴巴提供的】
服务调用和负载均衡
- Ribbon【Netflix提供的,建议直接弃用】
- OpenFeign
- LoadBalancer
分布式事务
- Seata【推荐使用,阿里巴巴的】
- LCN
- Hmily
服务熔断和降级
- Hystrix【已经停更了,不推荐】
- Circuit Breaker【这只一套规范,使用的是它的实现类】
- Resilience4J【CircuitBreaker的实现类,可以使用】
- Sentinel【阿里巴巴的,推荐使用】
服务链路追踪
- Sleuth+Zipkin【逐渐被替代了,不推荐】
- Micrometer Tracing【推荐使用】
服务网关
- Zuul【不推荐使用】
- Gate Way
分布式配置管理
- Config+Bus【不推荐了】
- Consul
- Nacos
三、微服务项目搭建
1.项目构建
需求:
① 先做一个通用boot微服务
② 逐步引入cloud组件纳入微服务支撑体系
步骤:
① 新建一个 maven 父工程,该项目只留下 pom.xml 文件,
② 检查项目的编码格式,统一为UTF-8
③ 检查注解支撑是否打开
④ 检查 java 编译版本
⑤ 父工程的 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>
<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>2023.0.0.0-RC1</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 2023.0.0.0-RC1-->
<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>
<!-- 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>
注意:dependencyManagement里只是声明依赖,*并不实现引入*,因此子项目需要显示的声明需要用的依赖。
2.MyBatis逆向工程
本次使用 Mapper4,可以一键生成,不用写单表操作了
步骤:
① 建库建表,表名 t_pay
create database db2024;
use 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;
② 在父工程下面创建一个子模块,给子模块导入依赖
说明:这个工程只是为了暂时存储生成的代码,等到业务工程使用的时候,会将对应的类复制过去
<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>
③ 在子模块的resources下新建文件 config.properties,将内容改成自己的
#t_pay表包名
package.name=com.mihoyo.cloud
# mysql8.0
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
③ 在子模块的resources下新建文件 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>
④ 双击运行 Maven 中的插件,一键生成
3.微服务提供者支付module
步骤:
① 创建一个业务逻辑模块(cloud-provider-payment8001)
② 给模块导入依赖
<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>
③ 编写yaml配置文件
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.mihoyo.cloud.entities
configuration:
map-underscore-to-camel-case: true
④ 创建启动类
@SpringBootApplication
@MapperScan("com.mihoyo.cloud.mapper") //import tk.mybatis.spring.annotation.MapperScan;
public class Main8001 {
public static void main(String[] args) {
SpringApplication.run(Main8001.class, args);
}
}
⑤ 将逆向工程生成的文件拷贝到业务工程中
⑥ 建立传递数值实体类PayDTO
说明:将数据返回给前端时,有些字段不想暴露给前端,将可以暴露的放在PayDTO类中,提高安全性
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayDTO implements Serializable
{
private Integer id;
//支付流水号
private String payNo;
//订单流水号
private String orderNo;
//用户账号ID
private Integer userId;
//交易金额
private BigDecimal amount;
}
⑦ 编写 Service 层的增删改查方法
public interface PayService {
public int add(Pay pay);
public int delete(Integer id);
public int udpate(Pay pay);
public Pay getById(Integer id);
public List<Pay> getAll();
}
@Service
public class PayServiceImpl implements PayService {
@Resource
private 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 udpate(Pay pay) {
return payMapper.updateByPrimaryKeySelective(pay);
}
@Override
public Pay getById(Integer id) {
return payMapper.selectByPrimaryKey(id);
}
@Override
public List<Pay> getAll() {
return payMapper.selectAll();
}
}
⑧ 编写 Controller 层的增删改查方法
@RestController
@Slf4j
@RequestMapping("pay")
public class PayController {
@Resource
private PayService payService;
@PostMapping("add")
public String addPay(@RequestBody Pay pay){
int i = payService.add(pay);
return "成功插入记录,返回值:"+i;
}
@DeleteMapping("del/{id}")
public Integer deletePay(@PathVariable("id") Integer id){
return payService.delete(id);
}
@PutMapping("update")
public String updatePay(@RequestBody PayDTO payDTO){
Pay pay = new Pay();
BeanUtils.copyProperties(payDTO,pay);
int i = payService.udpate(pay);
return "成功修改记录,返回值:"+i;
}
@GetMapping("get/{id}")
public Pay getById(@PathVariable("id") Integer id){
return payService.getById(id);
}
@GetMapping("get")
public List<Pay> getAll(){
return payService.getAll();
}
}
⑨ 整合 Swagger3
注解 | 标注位置 |
---|---|
@Tag | Controller类 |
@Operation | 方法上 |
@Schema | model层的bean和bean的方法上 |
注意:pom 文件中必须要导入以下依赖,才能使用 Swagger3
Ⅰ. 添加依赖
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${swagger3.version}</version>
</dependency>
Ⅱ. controller:
@RestController
@Slf4j
@RequestMapping("pay")
@Tag(name = "支付微服务模块",description = "支付CRUD")
public class PayController {
@Resource
private PayService payService;
@PostMapping("add")
@Operation(summary = "新增",description = "新增支付流水方法,json串做参数")
public String addPay(@RequestBody Pay pay){
int i = payService.add(pay);
return "成功插入记录,返回值:"+i;
}
@DeleteMapping("del/{id}")
@Operation(summary = "删除",description = "删除支付流水方法")
public Integer deletePay(@PathVariable("id") Integer id){
return payService.delete(id);
}
@PutMapping("update")
@Operation(summary = "修改",description = "修改支付流水方法")
public String updatePay(@RequestBody PayDTO payDTO){
Pay pay = new Pay();
BeanUtils.copyProperties(payDTO,pay);
int i = payService.udpate(pay);
return "成功修改记录,返回值:"+i;
}
@GetMapping("get/{id}")
@Operation(summary = "按照ID查流水",description = "查询支付流水方法")
public Pay getById(@PathVariable("id") Integer id){
return payService.getById(id);
}
@GetMapping("get")
@Operation(summary = "查询全部流水",description = "查询支付流水方法")
public List<Pay> getAll(){
return payService.getAll();
}
}
Ⅲ. entity:
/**
* 表名:t_pay
* 表注释:支付交易表
*/
@Table(name = "t_pay")
@Schema(title = "支付交易表实体类")
public class Pay {
@Id
@GeneratedValue(generator = "JDBC")
@Schema(title = "主键")
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;
/**
* 交易金额
*/
@Schema(title = "交易金额")
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;
}
}
Ⅳ. 编写配置类,配置Swagger
@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 GroupedOpenApi CustomerApi()
{
return GroupedOpenApi.builder().group("客户微服务模块").pathsToMatch("/customer/**", "/customers").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/"));
}
}
Ⅴ. 启动项目,访问swagger的地址,调试接口
地址:localhost:8001/swagger-ui/index.html
4.待优化的问题
① 时间日志格式的统一和定制情况?
② Java如何设计API接口实现统一格式返回?
目前的返回值类型有多种:数值,string,对象 .....
这会影响前端、小程序、app等交互体验和开发
③ 如果出现异常如何处理?
全局异常接入返回的标准格式:需要全局统一异常,并有统一返回值。
四、项目改进
1.优化时间格式
(1)方式一:
在实体类的时间属性上加 @JsonFormat 注解
@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss" ,timezone = "GMT+8")
private Date createTime;
(2)方式二:
SpringBoot 项目在 yml 中进行配置
spring:
jackson:
date-format: yyyy-MM-dd HH-mm-ss
time-zone: GMT+8
选择:在后续过程中,yml 中的配置内容会越来越多,所以我们选择方式一,采用注解方式
/**
* 创建时间
*/
@Column(name = "create_time")
@Schema(title = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 更新时间
*/
@Column(name = "update_time")
@Schema(title = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
2.统一返回结果Result
(1)步骤一:定义一个枚举类,用于状态码的返回
HTTP 请求返回的状态码:
枚举类的书写方法:
① 举值
② 构造
③ 遍历
package com.mihoyo.cloud.resp;
import lombok.Getter;
import java.util.Arrays;
@Getter
public enum ReturnCodeEnum {
//1.举值
RC999("999", "操作XXX失败"),
RC200("200", "success"),
RC201("201", "服务开启降级保护,请稍后再试!"),
RC202("202", "热点参数限流,请稍后再试!"),
RC203("203", "系统规则不满足要求,请稍后再试!"),
RC204("204", "授权规则不通过,请稍后再试!"),
RC403("403", "无访问权限,请联系管理员授予权限"),
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", "不支持的认证模式");
//2.构造
private final String code;//自定义状态码,对应枚举的第一个参数
private final String message;//自定义信息,对应枚举的第二个参数
ReturnCodeEnum(String code, String message) {
this.code = code;
this.message = message;
}
//3.遍历
public static ReturnCodeEnum getReturnCodeEnum(String code) {
//传入一个状态码,如果有,就返回整个枚举信息,如果没有就返回空
/* 方式一:传统循环遍历 */
/*
for (ReturnCodeEnum element : ReturnCodeEnum.values()) {
if (element.getCode().equalsIgnoreCase(code)) {
return element;
}
}
return null;
*/
/* 方式二:Stream流 */
return Arrays.stream(ReturnCodeEnum.values()).filter(x -> x.getCode().equalsIgnoreCase(code)).findFirst().orElse(null);
}
}
(2)步骤二:定义统一返回类Result
返回标准格式:
code状态值 | 由后端统一定义各种返回结果的状态码 |
message描述 | 本次接口调用的结果描述 |
data数据 | 本次返回的数据 |
timestamp | 接口调用时间(对后期改bug十分重要) |
@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<T> 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<T> resultData = new ResultData<>();
resultData.setCode(code);
resultData.setMessage(message);
return resultData;
}
}
(3)步骤三:修改原来接口的返回值
@RestController
@Slf4j
@RequestMapping("pay")
@Tag(name = "支付微服务模块",description = "支付CRUD")
public class PayController {
@Resource
private PayService payService;
@PostMapping("add")
@Operation(summary = "新增",description = "新增支付流水方法,json串做参数")
public ResultData<String> addPay(@RequestBody Pay pay){
int i = payService.add(pay);
return ResultData.success("成功插入记录,返回值:"+i);
}
@DeleteMapping("del/{id}")
@Operation(summary = "删除",description = "删除支付流水方法")
public ResultData<Integer> deletePay(@PathVariable("id") Integer id){
int i = payService.delete(id);
return ResultData.success(i);
}
@PutMapping("update")
@Operation(summary = "修改",description = "修改支付流水方法")
public ResultData<String> updatePay(@RequestBody PayDTO payDTO){
Pay pay = new Pay();
BeanUtils.copyProperties(payDTO,pay);
int i = payService.udpate(pay);
return ResultData.success("成功修改记录,返回值:"+i);
}
@GetMapping("get/{id}")
@Operation(summary = "按照ID查流水",description = "查询支付流水方法")
public ResultData<Pay> getById(@PathVariable("id") Integer id){
Pay pay = payService.getById(id);
return ResultData.success(pay);
}
@GetMapping("get")
@Operation(summary = "查询全部流水",description = "查询支付流水方法")
public ResultData<List<Pay>>getAll(){
List<Pay> pays = payService.getAll();
return ResultData.success(pays);
}
}
3.全局异常处理
首先在查询方法中,加入一个异常:
@GetMapping("get/{id}")
@Operation(summary = "按照ID查流水",description = "查询支付流水方法")
public ResultData<Pay> getById(@PathVariable("id") Integer id){
if(id == -4) throw new RuntimeException("id不能为负数");
Pay pay = payService.getById(id);
return ResultData.success(pay);
}
运行结果:
加入全局异常处理机制:
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//触发该异常Http响应状态码为500
public ResultData<String> exception(Exception e) {
log.error("全局异常信息:{}", e.getMessage(), e);
return ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
}
}
运行结果:
五、微服务调用者订单module
1.项目构建
步骤:
① 创建一个业务逻辑模块(cloud-consumer-order80)
② 给模块导入依赖
<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>
③ 编写 yaml 配置文件
server:
port: 80
④ 创建启动类
@SpringBootApplication
public class Main80
{
public static void main(String[] args)
{
SpringApplication.run(Main80.class,args);
}
}
⑤ 建立传递数值实体类PayDTO
说明:一般而言,调用者不应该获悉服务提供者的 entity 资源并知道表结构关系,所以服务提供方给出的接口文档都都应成为DTO
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayDTO implements Serializable
{
private Integer id;
//支付流水号
private String payNo;
//订单流水号
private String orderNo;
//用户账号ID
private Integer userId;
//交易金额
private BigDecimal amount;
}
2.服务调用RestTemplate
Question:订单微服务 80如何才能调用到支付微服务 8001?
(1) 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响应转换被转换成的对象类型。
getForObject 方法 与 getForEntity 方法的区别:
getForObject:返回对象为响应体中数据转化成的对象,基本上可以理解为 Json
getForEntity:返回对象为 ResponseEntity 对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
GET请求方法 | POST请求方法 |
---|---|
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables); | <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables); |
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables); | <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables); |
<T> T getForObject(URI url, Class<T> responseType); | <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType); |
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables); | <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables); |
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables); | <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables); |
<T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType); | <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType); |
(2)项目配置 RestTemplate
步骤:
① 编写 config 配置类
@Configuration
public class RestTemplateConfig
{
@Bean
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}
② 编写 controller 层
@RestController
public class OrderController {
public static final String PaymentSrv_URL = "http://localhost:8001";//先写死,硬编码
@Autowired
private RestTemplate restTemplate;
/**
* 一般情况下,通过浏览器的地址栏输入url,发送的只能是get请求
* 我们底层调用的是post方法,模拟消费者发送get请求
*/
@GetMapping("/consumer/pay/add")
public ResultData addOrder(@RequestBody PayDTO payDTO) {
return restTemplate.postForObject(PaymentSrv_URL + "/pay/add", payDTO, ResultData.class);
}
@GetMapping("/consumer/pay/get/{id}")
public ResultData getPayInfo(@PathVariable("id") Integer id) {
return restTemplate.getForObject(PaymentSrv_URL + "/pay/get/" + id, ResultData.class, id);
}
}
3.重复代码抽取
存在问题:两个模块中有很多重复的代码。例如实体类、返回结果、异常处理类等。
解决方法:将公共代码抽取到一个模块中,其他模块引用公共模块
步骤:
① 新建模块(cloud-api-commons)
② 导入依赖
<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>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
</dependencies>
③ 将前面两个模块中的公共代码抽取出来放到这个新的模块中(如:entities包、utils包、exception包)
④ maven运行命令clean、install,把模块打成 jar 包,放到本地仓库中。
⑤ 支付和订单模块在 pom 文件中引入公共模块的 jar 包
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
⑥ 删除原有模块的重复代码文件,启动项目
4.存在问题
微服务所在的IP地址和端口号硬编码到订单微服务中,会存在非常多的问题
(1)如果订单微服务和支付微服务的IP地址或者端口号发生了变化,则支付微服务将变得不可用,需要同步修改订单微服务中调用支付微服务的IP地址和端口号。
(2)如果系统中提供了多个订单微服务和支付微服务,则无法实现微服务的负载均衡功能。
(3)如果系统需要支持更高的并发,需要部署更多的订单微服务和支付微服务,硬编码订单微服务则后续的维护会变得异常复杂。
所以,在微服务开发的过程中,需要引入服务治理功能,实现微服务之间的动态注册与发现,从此刻开始我们正式进入SpringCloud实战。