SpringCloud框架学习(第一部分:初始项目搭建)

目录

一、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版本选型

技术版本
Javajdk17+
boot3.2.0
cloud2023.0.0
cloud alibaba2023.0.0.0-RC1
Maven3.9+
MySQL8.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 generatorConfigurationPUBLIC "-//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-servicedatasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: 123456# ========================mybatis===================
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.mihoyo.cloud.entitiesconfiguration: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;//用户账号IDprivate 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 {@Resourceprivate PayMapper payMapper;@Overridepublic int add(Pay pay) {return payMapper.insertSelective(pay);}@Overridepublic int delete(Integer id) {return payMapper.deleteByPrimaryKey(id);}@Overridepublic int udpate(Pay pay) {return payMapper.updateByPrimaryKeySelective(pay);}@Overridepublic Pay getById(Integer id) {return payMapper.selectByPrimaryKey(id);}@Overridepublic List<Pay> getAll() {return payMapper.selectAll();}
}

⑧ 编写 Controller 层的增删改查方法

@RestController
@Slf4j
@RequestMapping("pay")
public class PayController {@Resourceprivate 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

注解标注位置
@TagController类
@Operation方法上
@Schemamodel层的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 {@Resourceprivate 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
{@Beanpublic GroupedOpenApi PayApi(){return GroupedOpenApi.builder().group("支付微服务模块").pathsToMatch("/pay/**").build();}@Beanpublic GroupedOpenApi OtherApi(){return GroupedOpenApi.builder().group("其它微服务模块").pathsToMatch("/other/**", "/others").build();}/*@Beanpublic GroupedOpenApi CustomerApi(){return GroupedOpenApi.builder().group("客户微服务模块").pathsToMatch("/customer/**", "/customers").build();}*/@Beanpublic 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-sstime-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 {@Resourceprivate 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响应状态码为500public 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;//用户账号IDprivate 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
{@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
}

② 编写 controller 层

@RestController
public class OrderController {public static final String PaymentSrv_URL = "http://localhost:8001";//先写死,硬编码@Autowiredprivate 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实战。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/59948.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uni-app打包后报错云服务空间未关联

使用uni-app打包到h5 项目里面用到了uni-app的云端一体城市选择组件&#xff0c;这个组件数据用到了uniCloud云服务空间&#xff0c;在本地运行没问题&#xff0c;打包之后测试环境报错&#xff1a; 一顿查&#xff0c;查到了官网是这样说的&#xff1a; cli publish --platfo…

解决Mac M芯片 Wireshark 运行rvictl -s 后,出现Starting device failed

前言 mac os big sur 之后&#xff0c;苹果系统的安全性能提升&#xff0c;导致 rvictl -s 创建虚拟网卡失败。 $ rvictl -s 000348120-001621w21184C01E bootstrap_look_up(): 1102Starting device 000348120-001621w21184C01E [FAILED] 这是由于 rvictl 需要开启系统扩展才能…

浙江酒店WIFI广告路由实现——酒店经营

像这样的链接WIFI&#xff0c;后自动弹出连接认证&#xff0c;或者广告&#xff0c;可以展示酒店介绍&#xff0c;住房信息 智能wifi优点&#xff1a; 对于酒店体验而言&#xff1a; 1. 便捷性&#xff1a;客人无需繁琐的认证步骤就能自动连接 WiFi&#xff0c;提升了入住的便…

音视频入门基础:H.264专题(22)——通过FFprobe显示H.264裸流每个packet的信息

音视频入门基础&#xff1a;H.264专题系列文章&#xff1a; 音视频入门基础&#xff1a;H.264专题&#xff08;1&#xff09;——H.264官方文档下载 音视频入门基础&#xff1a;H.264专题&#xff08;2&#xff09;——使用FFmpeg命令生成H.264裸流文件 音视频入门基础&…

JavaScript中执行上下文和执行栈是什么?

一、执行上下文 简单的来说&#xff0c;执行上下文是一种对Javascript代码执行环境的抽象概念&#xff0c;也就是说只要有Javascript代码运行&#xff0c;那么它就一定是运行在执行上下文中 执行上下文的类型分为三种&#xff1a; 全局执行上下文&#xff1a;只有一个&#…

达梦8-达梦数据实时同步软件(DMHS)配置-Oracle-DM8

1、安装环境 源端目的端IP地址192.168.6.111192.168.6.110系统版本Red Hat 6.4Kylin v10数据库版本Oracle11g达梦 v8系统用户Oracledmdba字符集MERICAN_AMERICA.AL32UTF8UTF-8端口15215236实例名PRODDMSERVER数据库软件目录/u01/app/oracle/opt/dmdbmsDMHS安装目录/u01/dmhs/o…

【优选算法 — 滑动窗口】滑动窗口小专题(一)

长度最小的子数组 长度最小的子数组 题目解析&#xff1a; 对于示例一 对于剩下两种示例&#xff1a; 解法一&#xff1a;暴力枚举 把所有的子数组全部枚举出来&#xff0c;并且枚举出的每一个子数组求和判断&#xff0c;返回长度最小的子数组&#xff1b; 时间复杂度 &…

【数据集】【YOLO】【目标检测】安全帽识别数据集 22789 张,YOLO安全帽佩戴目标检测实战训练教程!

数据集介绍 【数据集】安全帽识别数据集 22789 张&#xff0c;目标检测&#xff0c;包含YOLO/VOC格式标注。数据集中包含2种分类&#xff1a;{0: head, 1: helmet}&#xff0c;分别是无安全帽和佩戴安全帽。数据集来自国内外图片网站和视频截图。检测场景为施工地工人安全帽佩…

Linux 系统结构

Linux系统一般有4个主要部分&#xff1a;内核、shell、文件系统和应用程序。内核、shell和文件系统一起形成了基本的操作系统结构&#xff0c;它们使得用户可以运行程序、管理文件并使用系统。 1. linux内核 内核是操作系统的核心&#xff0c;具有很多最基本功能&#xff0c;它…

飞书 富文本(Markdown)

飞书机器人webhook支持Markdown格式&#xff0c;包括表格 表格 |Syntax | Description |\n|-------- | -------- |\n|Header | Title |\n|Paragraph | Text |参考 富文本&#xff08;Markdown&#xff09;

R语言实战——一些批量对地理数据进行操作的方法

各位朋友在进行数据处理时&#xff0c;当有多张栅格影像时&#xff0c;如果我们都要进行同一操作时&#xff0c;一张一张做很繁琐&#xff0c;用ArcGIS模型构建器是一种比较好的方法。当然&#xff0c;今天小编新学了R语言上面进行批量裁剪&#xff0c;一起来学习一下吧&#x…

【时间之外】IT人求职和创业应知【31】

目录 新闻一&#xff1a;2024年“秦创原沣东杯”陕西省科技工作者创新创业大赛颁奖仪式暨沣东新城机器人产业发展大会盛大启幕 新闻二&#xff1a;声网CEO赵斌&#xff1a;RTE将成为生成式AI时代AI Infra的关键部分 新闻三&#xff1a;“5G工业互联网”融合应用试点城市名单…

使用ThorUi

摘要&#xff1a; 官网 今天遇到一个老项目&#xff0c;使用的是ThorUi组件库&#xff01;之前没有用过这组件库&#xff0c;所以记录一下不同框架是使用情况&#xff01; ThorUI 是一个基于 Thorium 的 UI 框架&#xff0c;用于构建跨平台的桌面应用程序。如果你打算使用 Thor…

科研绘图系列:R语言文章组合图形(barplot scatterplot)

文章目录 介绍加载R包数据下载清理环境设置计算资源数据处理图1图2图3图4图5图6图7图8图9系统信息介绍 R语言组合图形 加载R包 library(xlsx) library(reshape2) library(ggplot2) library(ggh4x) library(wbstats

内网部署web项目,外网访问不了?只有局域网能访问!怎样解决?

相关技术 要实现“内网部署&#xff0c;外网访问”&#xff0c;可以使用内网穿透、VPN技术、DMZ主机、端口映射等方法。以下是对这些方法的详细解释&#xff1a; 一、内网穿透 内网穿透是一种技术&#xff0c;它通过将内网设备映射到公网上的方式&#xff0c;实现外网访问内…

从零开始训练一个大语言模型需要多少天?

一&#xff0c;前言 在AI领域&#xff0c;训练一个大型语言模型&#xff08;LLM&#xff09;是一个耗时且复杂的过程。几乎每个做大型语言模型&#xff08;LLM&#xff09;训练的人都会被问到&#xff1a;“从零开始&#xff0c;训练大语言模型需要多久和花多少钱&#xff1f;”…

Halcon resistor.hedv 使用多个对焦级别提取深度

depth_from_focus * Extract depth using multiple focus levels * 使用多个对焦级别提取深度 Names : [] * 初始化一个空数组&#xff0c;用于存储图像名称 dev_close_window () * 关闭当前打开的图像窗口 for i : 1 to 10 by 1 * 循环开始&#xff0c;从1到10 …

C语言 | Leetcode C语言题解之第546题移除盒子

题目&#xff1a; 题解&#xff1a; int dp[100][100][100];int calculatePoints(int* boxes, int l, int r, int k) {if (l > r) {return 0;}if (dp[l][r][k] 0) {int r1 r, k1 k;while (r1 > l && boxes[r1] boxes[r1 - 1]) {r1--;k1;}dp[l][r][k] calcu…

玩的花,云产品也能拼团了!!!

说起拼单大家都不陌生&#xff0c;电商一贯的营销手段&#xff0c;不过确实可以给消费者省下一笔钱。双11到了&#xff0c;腾讯云产品也玩起了拼团&#xff0c;这明显是对开发人员和各企业的福利。 对于有云产品需求的个人或企业&#xff0c;这次绝对是难得的一次薅羊毛机会。…

win10@win10 配置openssh服务

1.下载离线包&#xff1a;https://github.com/PowerShell/Win32-OpenSSH/releases 2.然后管理员打开powershell&#xff0c;cd到这个安装包放置的目录中来&#xff0c;输入以下命令&#xff1a;powershell.exe -ExecutionPolicy Bypass -File install-sshd.ps1 此时要注意pow…