SpringCloud基础环境
1、基本环境版本选择
Java: Java17+;
spring cloud:2023.0.0;
spring boot:3.2.0;
cloud alibaba:2022.0.0.0-RC2;
Maven:3.9+;
Mysql:8.0+;
2、SpringCloud各组件
最新版的SpringCloud各服务组件与过去的版本变化很大,但是现在成熟的系统还得是原来的版本,所以过去版本的服务组件还是要会的。
3、微服务架构编码Base工程模块构建
3.1、整体业务需求说明
3.2、IDE新建Project和Maven父工程
3.2.1、创建的父工程结构:
3.2.2、父工程依赖项
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu.cloud</groupId>
<artifactId>mscloudV5</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<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.2</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>
</project>
3.2.3、数据库
新建一个数据库db2024,执行以下sql创建表和插入一条数据。
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;
3.2.4、创建子工程mybatis_generator
在父工程下新建一个maven项目模块,创建完成后的目录结构如下:
给mybatis_generator模块添加依赖,这个模块就是使用mybatis的快速生成工具mapper4一键生成DAO层的。
下面导入依赖:
<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>
配置文件config.properties,就是数据库配置文件,数据库驱动,我的MySQL8的端口是3307,MySQL5是3306,同时运行了。
#t_pay表包名
package.name=com.zm.cloud
# mysql8.0
jdbc.driverClass = com.mysql.cj.jdbc.Driver
jdbc.url= jdbc:mysql://localhost:3307/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
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>
准备工作完成之后就可以使用mybatis的插件进行自动生成了
执行之后DAO层的东西就有了
3.3、Rest通用Base工程构建
3.3.1、新增module
我们再建一个模块cloud-provider-payment8001支付模块是微服务的提供者。
微服务小口诀:
建module;
改POM;
写YML;
主启动类;
业务;
改POM文件
<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>
在resources下新建application.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:3307/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.zm.cloud.entities
configuration:
map-underscore-to-camel-case: true
顺便把resources下的mapper文件夹建了,然后把它自动生成的主启动类给改名为Main8001,然后把它的内容改成spring的启动类。
@SpringBootApplication
@MapperScan("com.zm.cloud.mapper")//这里导的是tk.mybatis.spring.annotation.MapperScan
public class Main8001 {
public static void main(String[] args) {
SpringApplication.run(Main8001.class,args);
}
}
把自动生成的实体类和mapper接口及配置文件复制到支付模块中的cloud目录下,就可以把原来mybatis_generator里面自动生成的删除了。
我们对外暴露的不能是数据库表中的全部数据,像密码等敏感数据不能直接提供给前端,所以在实体类中添加一个PayDTO实体类只写部分数据。在自己写测试项目情况下,使用简单的实体对象 Pay可能更加直观和简洁。DTO 的使用通常是为了解耦不同层之间的依赖,同时可以控制数据的传输和保护隐私信息。但如果情境简单且没有太多层次的依赖关系,直接使用实体对象也是完全合理的做法。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PayDTO implements Serializable {
private Integer id;
//支付流水号
private String payNo;
//订单流水号
private String orderNo;
//用户账号ID
private Integer userId;
//交易金额
private BigDecimal amount;
}
3.3.2、service层
然后创建server层,创建service接口PayService
public interface PayService {
//添加
public int add(Pay pay);
public int delete(Integer id);
public int update(Pay pay);
public Pay getById(Integer id);
public List<Pay> getAll();
}
有了接口就要有实现类,PayServiceImp
@Service
public class PayServiceImp 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 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();
}
}
3.3.3、controller层
新增controller层,PayController
@RestController
public class PayController {
@Resource
private PayServiceImp payServiceImp;
//增加数据@RequestBody包含请求体
@PostMapping(value = "/pay/add")
public String addPay(@RequestBody Pay pay){
System.out.println(pay.toString());
int i = payServiceImp.add(pay);
return "新增一个记录返回值:"+i;
}
//删除
@DeleteMapping(value = "/pay/del/{id}")
public String delByID(@PathVariable Integer id){
return "成功删除:"+payServiceImp.delete(id);
}
//修改,但不能修改全部
@PutMapping(value = "/pay/update")
public String update(@RequestBody PayDTO payDTO){
//创建真实体类对象pay,然后把payDTO的json串复制给它
Pay pay = new Pay();
BeanUtil.copyProperties(payDTO,pay);
return "修改成功返回值:" + payServiceImp.update(pay);
}
//通过id查询
@GetMapping("/pay/get/{id}")
public Pay payById(@PathVariable Integer id){
return payServiceImp.getById(id);
}
//查询所有消息
@GetMapping("/pay/getall")
public List<PayDTO> getAll() {
List<PayDTO> list = new ArrayList<>();
for (Pay pay : payServiceImp.getAll()) {
PayDTO payDTO = new PayDTO(); // 在循环内创建新的 PayDTO 对象
BeanUtil.copyProperties(pay, payDTO);
list.add(payDTO);
System.out.println("查询到的消息:" + pay.toString());
}
return list;
}
}
写完controller我们来测试一下,把服务启动,然后在idea中就可以直接测试
交给前端不给那么多信息就行,根据id查询我们就做测试,给出所有数据。
3.3.4、PostMan测试
下面测试其他的请求,由于普通的浏览器不支持post方法,我们就使用PostMan或者SwaggerAPI测试工具来测试剩下的请求。
发送请求,查看结果
再通过id查询出来
然后再修改它
修改成功后再使用查询全部,全查询出来,只不过是部分信息。
查询出来的内容没有问题,我们把第二条记录通过id删除掉
再查询所有,看是否删除。
3.3.5、使用Swagger测试
依赖在之前已经导过了就不再重复,我们需要写一个swagger的配置类,Swagger3Config
常用的swagger注解说明:
注解 | 标注位置 | 作用 |
---|---|---|
@Tag | controller类 | 标识controller作用 |
@Parameter | 参数 | 标识参数作用 |
@Parameters | 参数 | 参数多重说明 |
@Schema | model层的javaBean | 描述模型作用及每个属性 |
@Operation | 方法 | 描述方法作用 |
@ApiResponse | 方法 | 描述响应状态码 |
Swagger3Config
@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.zm.com")
.url("https://yiyan.baidu.com/"));
}
}
修改controller,打上swagger的说明注解
@RestController
@Tag(name = "支付订单模块",description = "支付的CRUD")
public class PayController {
@Resource
private PayServiceImp payServiceImp;
//增加数据@RequestBody包含请求体
//summary大概说明,description具体描述
@PostMapping(value = "/pay/add")
@Operation(summary = "新增",description = "新增支付流水方法,json串做参数")
public String addPay(@RequestBody Pay pay){
System.out.println(pay.toString());
int i = payServiceImp.add(pay);
return "新增一个记录返回值:"+i;
}
//删除
@DeleteMapping(value = "/pay/del/{id}")
@Operation(summary = "删除",description = "删除支付流水方法")
public String delByID(@PathVariable("id") Integer id){
return "成功删除:"+payServiceImp.delete(id);
}
//修改,但不能修改全部
@PutMapping(value = "/pay/update")
@Operation(summary = "修改",description = "修改支付流水方法")
public String update(@RequestBody PayDTO payDTO){
//创建真实体类对象pay,然后把payDTO的json串复制给它
Pay pay = new Pay();
BeanUtil.copyProperties(payDTO,pay);
return "修改成功返回值:" + payServiceImp.update(pay);
}
//通过id查询
@GetMapping("/pay/get/{id}")
@Operation(summary = "查询",description = "通过ID查询")
public Pay payById(@PathVariable("id") Integer id){
return payServiceImp.getById(id);
}
//查询所有消息
@GetMapping("/pay/getall")
@Operation(summary = "查询所有",description = "查到所有简要信息")
public List<PayDTO> getAll(){
List<PayDTO> list = new ArrayList<>();
for (Pay pay : payServiceImp.getAll()) {
PayDTO payDTO = new PayDTO();
BeanUtil.copyProperties(pay,payDTO);
list.add(payDTO);
System.out.println("查询到的消息:"+ pay.toString());
}
return list;
}
}
给实体类打上@Schema(title = "Pay实体类"),每一个属性上也打上对应的说明,打上完所有的说明之后就可以启动服务,访问http://localhost:8001/swagger-ui/index.html
由于我们在配置类中写了俩模块,选择支付订单模块。
下面就开始测试,新增一条数据。
{
"payNo": "1321testadd",
"orderNo": "53845u389testadd",
"userId": 2,
"amount": 1999,
"deleted": "0",
"createTime": "2024-03-07T12:06:56.493Z",
"updateTime": "2024-03-07T12:06:56.493Z"
}
查询所有
修改
通过id查询
删除掉
潜在问题
时间格式,不太符合我们正常看时间的显示方式;
一般我们有两种解决方法
可以在相应的时间属性上加上注解@JsonFormat注解,然后写上格式。
@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;
在Spring Boot项目中可以在application.yml文件中指定格式。
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8
Java如何设计API接口实现统一格式返回,我们现在设置的controller的返回值有string、有int、还有实体、还有list;
全局异常接入返回的标准格式,假如我们程序出错了,爆出的错误要有统一返回值和全局统一异常;
3.3.6、API接口实现统一格式返回
定义返回标准格式的三个基本
code状态值:后端定义统一定义各种返回结果的状态码;
message描述:本次接口调用的结果描述;
data数据:本次返回的数据;
一般还得加上接口调用时间,timestamp,在调试过程中得结果可能是由于缓存造成的结果不变,还可以调查错误出现的时间,每次返回的时间戳不一样就代表不是缓存。
HTTP请求返回的状态码
新建一个枚举类ReturnCodeEnum
//@Getter获取全部值的get方法
@Getter
public enum ReturnCodeEnum {
/**操作失败**/
//这里都是两个参数的构造方法
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;
//2、构造
ReturnCodeEnum(String code, String message) {
this.code = code;
this.message = message;
}
//3、遍历
//3.1、遍历第一种方式,传统的
public static ReturnCodeEnum getReturnCodeEnumV1(String code){
//循环遍历,如果传进来的code值和遍历出来的有一样的就说明是我们定义的,存在就返回。
for (ReturnCodeEnum element : ReturnCodeEnum.values()) {
if (element.getCode().equalsIgnoreCase(code)){
return element;
}
}
return null;
}
//3.2、stream流式计算
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(ReturnCodeEnum.getReturnCodeEnumV1("200"));
System.out.println(ReturnCodeEnum.getReturnCodeEnumV1("200").getCode());
System.out.println(ReturnCodeEnum.getReturnCodeEnumV1("200").getMessage());
System.out.println("---------下面是v2版本--------");
System.out.println(ReturnCodeEnum.getReturnCodeEnumV1("404"));
System.out.println(ReturnCodeEnum.getReturnCodeEnumV1("404").getCode());
System.out.println(ReturnCodeEnum.getReturnCodeEnumV1("404").getMessage());
}*/
}
下面是测试结果:
新建统一的返回对象ResultData<T>
使用这个对象就可以统一给前端返回ResultData,前端只用收这一个对象就行了,后端想传的数据类型以泛型的方式返回,再包上一层。
@Data
@Accessors(chain = true)//@Accessors(chain = true)推荐使用链式编程
public class ResultData<T> {
private String code;//结果状态 ,具体状态码参见枚举类ReturnCodeEnum.java
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;
}
}
3.3.7、修改PayController
@RestController
@Tag(name = "支付订单模块",description = "支付的CRUD")
public class PayController {
@Resource
private PayServiceImp payServiceImp;
//增加数据@RequestBody包含请求体
//summary大概说明,description具体描述
@PostMapping(value = "/pay/add")
@Operation(summary = "新增",description = "新增支付流水方法,json串做参数")
public ResultData<String> addPay(@RequestBody Pay pay){
System.out.println(pay.toString());
int i = payServiceImp.add(pay);
return ResultData.success("新增一个记录返回值:"+i);
}
//删除
@DeleteMapping(value = "/pay/del/{id}")
@Operation(summary = "删除",description = "删除支付流水方法")
public ResultData<String> delByID(@PathVariable("id") Integer id){
int i = payServiceImp.delete(id);
return ResultData.success("成功删除:"+i);
}
//修改,但不能修改全部
@PutMapping(value = "/pay/update")
@Operation(summary = "修改",description = "修改支付流水方法")
public ResultData<String> update(@RequestBody PayDTO payDTO){
//创建真实体类对象pay,然后把payDTO的json串复制给它
Pay pay = new Pay();
BeanUtil.copyProperties(payDTO,pay);
return ResultData.success("修改成功返回值:"+payServiceImp.update(pay));
}
//通过id查询
@GetMapping("/pay/get/{id}")
@Operation(summary = "查询",description = "通过ID查询")
public ResultData<Pay> payById(@PathVariable("id") Integer id){
return ResultData.success(payServiceImp.getById(id));
}
//查询所有消息
@GetMapping("/pay/getall")
@Operation(summary = "查询所有",description = "查到所有简要信息")
public ResultData<List<PayDTO>> getAll(){
List<PayDTO> list = new ArrayList<>();
for (Pay pay : payServiceImp.getAll()) {
PayDTO payDTO = new PayDTO();
BeanUtil.copyProperties(pay,payDTO);
list.add(payDTO);
System.out.println("查询到的消息:"+ pay.toString());
}
return ResultData.success(list);
}
}
再进行测试,新增一条记录
修改
通过id查询,4号
查询所有
通过id删除
如果我们在查询的时候查的是错误的,比如-4这个时候前端显示的就是错误页面,很不美观,还破环了我们的统一封装格式。
3.3.8、全局异常接入返回的标准格式
定义了全局异常处理就不需要自己try catch了,那样麻烦,新建一个处理异常的类,GlobalExceptionHandler
//默认全局异常处理
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResultData<String> exception(Exception e){
System.out.println("---come in GlobalExceptionHandler---");
log.error("全局异常信息:{}",e.getMessage(),e);
return ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
}
}
这个时候我们启动服务,访问正常的和错误的页面
3.4、cloud-consumer-order80微服务调用者订单Module模块
3.4.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>
新建application.yml文件
server:
port: 80
订单微服务80如何才能调用到支付微服务8001?
3.4.2、RestTemplate
RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集。
使用restTemplate访问restful接口非常的简单粗暴无脑。(url, requestMap, ResponseBean.class)这三个参数分别代表 :REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
config配置类,RestTemplateConfig
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
新建OrderController
@RestController
public class OrderController {
//先写死,支付模块的地址
private static final String PAY_MAN_SERVER = "http://localhost:8001/";
@Autowired
private RestTemplate restTemplate;
//添加订单
//一般情况下,通过浏览器的地址栏输入url,发送的只能是get请求
//我们底层调用的是post方法,模拟消费者发送get请求,客户端消费者
//参数可以不添加@RequestBody
@GetMapping("/consumer/pay/add")
public ResultData addOrder(PayDTO payDTO){
return restTemplate.postForObject(PAY_MAN_SERVER + "/pay/add",payDTO,ResultData.class);
}
//通过id查询
@GetMapping("/consumer/pay/get/{id}")
public ResultData getOrderById(@PathVariable("id") Integer id){
return restTemplate.getForObject(PAY_MAN_SERVER + "/pay/get/"+id,ResultData.class,id);
}
//修改
@GetMapping("/consumer/pay/update")
public ResultData updateOrder(PayDTO payDTO){
return restTemplate.postForObject(PAY_MAN_SERVER + "/pay/update",payDTO,ResultData.class);
}
//删除
@GetMapping("/consumer/pay/del/{id}")
public ResultData delById(@PathVariable("id") Integer id){
return restTemplate.postForObject(PAY_MAN_SERVER + "/pay/del/"+id,id,ResultData.class);
}
//查询所有
@GetMapping("/consumer/pay/getall")
public ResultData getAll(){
return restTemplate.getForObject(PAY_MAN_SERVER + "/pay/getall",ResultData.class);
}
}
启动postman测试,新增
查询所有
3.5、重构
系统中重复的内容太多了,我们希望提取出公共的类,每一个模块都可以共享,就不要再复制了,新建一个module,cloud-api-commons,它对外暴露通用的组件/api/接口/工具类等等。
导入一点点依赖
<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>
把公共使用的实体类PayDTO和返回信息工具类resp目录中的ResultData和ReturnCodeEnum拷贝到模块下,还有统一异常处理。
然后我们使用maven的install命令,自定义依赖
然后把80客户端服务和8001支付服务的公共部分删除掉,在pom中添加这个自定义的依赖。
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.zm.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
然后再分别启动这两个服务测试一下能不能使用。
再添加一个记录
通过id查询
其他操作就不再一一演示,下面看一下当前工作进展
我们目前只不过是最基础的内容,SpringCloud的内容还没提及呢,当前有很多问题。
//先写死,支付模块的地址
private static final String PAY_MAN_SERVER = "http://localhost:8001/";
这个先写死的URL,微服务所在的IP地址和端口号硬编码到订单微服务中,会存在非常多的问题
如果订单微服务和支付微服务的IP地址或者端口号发生了变化,则支付微服务将变得不可用,需要同步修改订单微服务中调用支付微服务的IP地址和端口号,就很麻烦了。
如果系统中提供了多个订单微服务和支付微服务,则无法实现微服务的负载均衡功能,这都写死了,怎么做负载。
如果系统需要支持高并发,需要部署更多的订单微服务和支付微服务,硬编码订单微服务则后续的维护会变得异常复杂。
所以,在微服务的开发过程中,需要引入服务治理功能,实现微服务之间的动态注册与发现,从此刻开始我们正式进入SpringCloud实战。