通用型产品发布解决方案(基于分布式微服务技术栈:SpringBoot+SpringCloud+Spring CloudAlibaba+Vue+ElementUI+MyBatis-Plus+MySQL+Git+Maven+Linux+Docker+Nginx – 《02》
项目的源码地址:
- GitHub:https://github.com/China-Rainbow-sea/RainbowSealiving
- Gitee:https://gitee.com/Rainbow–Sea/rainbow-sealiving
创建 GateWay 服务 和配置
通过 GateWay 进行对各个服务的转发,重定向进行配置信息。从而就简化配置,同时注意存在一个“同源策略” 的问题——》就是一个跨域问题。 CORS 的问题。
GateWay 不是一个 Web 服务,所以千万不可以在 GateWay 引入 Web 相关的依赖模块,不然会发生冲突
重点:注意:我们这里的 GateWay 不是一个 Web 服务,所以所以所以,我们千万千万千万不可以将 Web 相关的依赖的模块导入到 GateWay 当中,不然是会报错的,发生一个冲突。
gateway本身不是一个做 web服务器的,因此不要引入 spring-boot-starter-web,
否则会报告冲突
注意是: spring-boot-starter-web
重点
server:
port: 5050 #gateway监听端口
spring:
cloud:
#配置网关
#http://localhost:5050/api/commodity/brand/list
#http://www.hspliving.com/api/commodity/brand/list
gateway:
routes: #配置路由,可以有多个路由
- id: rainbowsealiving_renren_fast_route # 路由id, 由程序员指定,保证唯一
# 当前配置完成的需求说明:
# 如果到网关的请求时 http://localhost:5050/commodity/brand/list ,gateway 通过断言。
# 最终将请求路由转发到 http://localhost:9090/commodity/brand/list = >url=uri+path
uri: lb://renren-fast
predicates:
- Path=/api/** #断言,路径相匹配的进行路由
# - id: member_routh01 # 路由id, 由程序员指定,保证唯一
# # 当前配置完成的需求说明:
# # 如果到网关的请求时 http://localhost:5050/commodity/brand/list ,gateway 通过断言。
# # 最终将请求路由转发到 http://localhost:9090/commodity/brand/list = >url=uri+path
# uri: http://localhost:9090
# predicates:
# - Path=/commodity/brand/list #断言,路径相匹配的进行路由
nacos:
discovery:
server-addr: 127.0.0.1:8848 #配置nacos地址
application:
name: rainbowsealiving-gateway
server:
port: 5050 #gateway监听端口
spring:
cloud:
#配置网关
#http://localhost:5050/api/commodity/brand/list
#http://www.hspliving.com/api/commodity/brand/list
gateway:
routes: #配置路由,可以有多个路由
- id: rainbowsealiving_renren_fast_route # 路由id, 由程序员指定,保证唯一
# 当前配置完成的需求说明:
# 如果到网关的请求时 http://localhost:5050/api/???/??? ,gateway 通过断言。
# 最终将请求路由转发到 http://renren-fast [注册到 nacosd renren-fast 服务ip+端口] = >url=uri+path
uri: lb://renren-fast
predicates:
- Path=/api/** #断言,路径相匹配的进行路由
server:
port: 5050 #gateway监听端口
spring:
cloud:
#配置网关
#http://localhost:5050/api/commodity/brand/list
#http://www.hspliving.com/api/commodity/brand/list
gateway:
routes: #配置路由,可以有多个路由
- id: rainbowsealiving_renren_fast_route # 路由id, 由程序员指定,保证唯一
# 当前配置完成的需求说明:
# 如果到网关的请求时 http://localhost:5050/api/???/??? ,gateway 通过断言。
# 最终将请求路由转发到 http://renren-fast [注册到 nacosd renren-fast 服务ip+端口] = >url=uri+path
uri: lb://renren-fast
predicates:
- Path=/api/** #断言,路径相匹配的进行路由
# - id: member_routh01 # 路由id, 由程序员指定,保证唯一
# # 当前配置完成的需求说明:
# # 如果到网关的请求时 http://localhost:5050/commodity/brand/list ,gateway 通过断言。
# # 最终将请求路由转发到 http://localhost:9090/commodity/brand/list = >url=uri+path
# uri: http://localhost:9090
# predicates:
# - Path=/commodity/brand/list #断言,路径相匹配的进行路由
nacos:
discovery:
server-addr: 127.0.0.1:8848 #配置nacos地址
application:
name: rainbowsealiving-gateway
路径重定向,替换重改- segment
server:
port: 5050 #gateway监听端口
spring:
cloud:
#配置网关
#http://localhost:5050/api/commodity/brand/list
#http://www.hspliving.com/api/commodity/brand/list
gateway:
routes: #配置路由,可以有多个路由
- id: rainbowsealiving_renren_fast_route # 路由id, 由程序员指定,保证唯一
# 当前配置完成的需求说明:
# 如果到网关的请求时 http://localhost:5050/api/???/??? ,gateway 通过断言。
# 最终将请求路由转发到 http://renren-fast [注册到 nacosd renren-fast 服务ip+端口]/????? = >url=uri+path
# 因为我们要去掉断言到 Path的/api ,所以这里我们需要使用上路径重写。
uri: lb://renren-fast
predicates:
- Path=/api/** #断言,路径相匹配的进行路由
filters:
- RewritePath=/api/(?<segment>.*), /renren-fast/$\{segment}
# - id: member_routh01 # 路由id, 由程序员指定,保证唯一
# # 当前配置完成的需求说明:
# # 如果到网关的请求时 http://localhost:5050/commodity/brand/list ,gateway 通过断言。
# # 最终将请求路由转发到 http://localhost:9090/commodity/brand/list = >url=uri+path
# uri: http://localhost:9090
# predicates:
# - Path=/commodity/brand/list #断言,路径相匹配的进行路由
nacos:
discovery:
server-addr: 127.0.0.1:8848 #配置nacos地址
application:
name: rainbowsealiving-gateway
server:
port: 5050 #gateway监听端口
spring:
cloud:
#配置网关
#http://localhost:5050/api/commodity/brand/list
#http://www.hspliving.com/api/commodity/brand/list
gateway:
routes: #配置路由,可以有多个路由
- id: rainbowsealiving_renren_fast_route # 路由id, 由程序员指定,保证唯一
# 当前配置完成的需求说明:
# 如果到网关的请求时 http://localhost:5050/api/???/??? ,gateway 通过断言。
# 最终将请求路由转发到 http://renren-fast [注册到 nacosd renren-fast 服务ip+端口]/????? = >url=uri+path
# 因为我们要去掉断言到 Path的/api ,所以这里我们需要使用上路径重写。
uri: lb://renren-fast
predicates:
- Path=/api/** #断言,路径相匹配的进行路由
filters:
- RewritePath=/api/(?<segment>.*), /renren-fast/$\{segment}
# - id: member_routh01 # 路由id, 由程序员指定,保证唯一
# # 当前配置完成的需求说明:
# # 如果到网关的请求时 http://localhost:5050/commodity/brand/list ,gateway 通过断言。
# # 最终将请求路由转发到 http://localhost:9090/commodity/brand/list = >url=uri+path
# uri: http://localhost:9090
# predicates:
# - Path=/commodity/brand/list #断言,路径相匹配的进行路由
- id: rainbowSealiving_commodity_route # 路由id, 由程序员指定,保证唯一
# 当前配置完成的需求说明:
# 如果到网关的请求时 http://localhost:5050/api/commodity/???/??? ,gateway 通过断言。
# 最终将请求路由转发到 http://rainbowSealiving-commodity [注册到 nacosd renren-fast 服务ip+端口]/????? = >url=uri+path
# 因为我们要去掉断言到 Path的/api ,所以这里我们需要使用上路径重写。
uri: lb://rainbowSealiving-commodity
predicates:
- Path=/api/commodity/** #断言,路径相匹配的进行路由
filters:
- RewritePath=/api/(?<segment>.*), /renren-fast/$\{segment}
nacos:
discovery:
server-addr: 127.0.0.1:8848 #配置nacos地址
application:
name: rainbowsealiving-gateway
GateWay 网关的跨域问题
使用之前的 config ,配置的 WEbMvcConfig 不行了,而且 GateWay 不是一个 Web 项目,引入 Web 项目反而会报错。
GateWay 5050 端口访问 ——> 9090 端口跨域问题
9090 ——》阿里云跨域问题
同理,在 config 路径下配置一个跨域处理
package com.rainbowsea.rainbowsealiving.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
public class RainbowsealivingGatewayCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
System.out.println("enter....");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
同时注意:
server:
port: 5050 #gateway监听端口
spring:
cloud:
#配置网关
#http://localhost:5050/api/commodity/brand/list
#http://www.hspliving.com/api/commodity/brand/list
gateway:
routes: #配置路由,可以有多个路由
# - id: member_routh01 # 路由id, 由程序员指定,保证唯一
# # 当前配置完成的需求说明:
# # 如果到网关的请求时 http://localhost:5050/commodity/brand/list ,gateway 通过断言。
# # 最终将请求路由转发到 http://localhost:9090/commodity/brand/list = >url=uri+path
# uri: http://localhost:9090
# predicates:
# - Path=/commodity/brand/list #断言,路径相匹配的进行路由
- id: raibnowsealiving_service_route # 路由id, 由程序员指定,保证唯一
# 当前配置完成的需求说明:
# 如果到网关的请求时 http://localhost:5050/api/service/???/??? ,gateway 通过断言。
# 最终将请求路由转发到 http://rainbowSealiving-service [注册到 nacosd renren-fast 服务ip+端口]/????? = >url=uri+path
# 因为我们要去掉断言到 Path的/api ,所以这里我们需要使用上路径重写。
uri: lb://raibnowsealiving-service
predicates:
- Path=/api/service/** #断言,路径相匹配的进行路由
filters:
# 也就是通过路径重写,最终的url 就是 http://localhost:7070
- RewritePath=/api/service/(?<segment>.*), /$\{segment}
- id: rainbowSealiving_commodity_route # 路由id, 由程序员指定,保证唯一
# 当前配置完成的需求说明:
# 如果到网关的请求时 http://localhost:5050/api/commodity/list/tree ,gateway 通过断言。
# 最终将请求路由转发到 http://rainbowSealiving-commodity [注册到 nacosd renren-fast 服务ip+端口]/????? = >url=uri+path
# 因为我们要去掉断言到 Path的/api ,所以这里我们需要使用上路径重写。
# 说明: /api/commodity/是一个更加精确的路径,必须将这组路由放在 /api/这里上
# 否则会报错
uri: lb://rainbowSealiving-commodity
predicates:
- Path=/api/commodity/** #断言,路径相匹配的进行路由
filters:
# 也就是通过路径重写,最终的url 就是 http://localhost:9090
- RewritePath=/api/(?<segment>.*), /$\{segment}
- id: rainbowsealiving_renren_fast_route # 路由id, 由程序员指定,保证唯一
# 当前配置完成的需求说明:
# 如果到网关的请求时 http://localhost:5050/api/???/??? ,gateway 通过断言。
# 最终将请求路由转发到 http://renren-fast [注册到 nacosd renren-fast 服务ip+端口]/????? = >url=uri+path
# 因为我们要去掉断言到 Path的/api ,所以这里我们需要使用上路径重写。
uri: lb://renren-fast
predicates:
- Path=/api/** #断言,路径相匹配的进行路由
filters:
# 也就是通过路径重写,最终的url 就是 http://localhost:8090
- RewritePath=/api/(?<segment>.*), /renren-fast/$\{segment}
nacos:
discovery:
server-addr: 127.0.0.1:8848 #配置nacos地址
application:
name: rainbowsealiving-gateway
方案一
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!--
引入 oss starter 我们引入的 stater 和 文档给的不一样,
请注意观察 artifactId ,小伙伴要和写的保持一致
老师说明: 因为我们在该 pom.xml 文件中有 dependencyManagement 指定了 spring-cloud-aliba
版本 2.1.0.RELEASE 因此这里,我们可以不写 version 而是使用版本仲裁
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<!-- <version>2.1.0.RELEASE</version>-->
</dependency>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--
引入 oss starter 我们引入的 stater 和 文档给的不一样,
请注意观察 artifactId ,小伙伴要和写的保持一致
老师说明: 因为我们在该 pom.xml 文件中有 dependencyManagement 指定了 spring-cloud-aliba
版本 2.1.0.RELEASE 因此这里,我们可以不写 version 而是使用版本仲裁
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
<?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.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.rainbowsea.RainbowSealiving</groupId>
<artifactId>raibnowsealiving-service</artifactId>
<version>1.0-SNAPSHOT</version>
<name>raibnowsealiving-service</name>
<description>顺平( 家居生活)- 第三方服务模块 OSS</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.5</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--
引入 oss starter 我们引入的 stater 和 文档给的不一样,
请注意观察 artifactId ,小伙伴要和写的保持一致
老师说明: 因为我们在该 pom.xml 文件中有 dependencyManagement 指定了 spring-cloud-aliba
版本 2.1.0.RELEASE 因此这里,我们可以不写 version 而是使用版本仲裁
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 引入公共模块,排除 mybatis-plus -->
<dependency>
<groupId>com.rainbowsea.RainbowSealiving</groupId>
<artifactId>RainbowSealiving-common</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 排除公共模块当中的 mybatis-plus-boot-starter 依赖-->
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
方案二:
在 Nacos 配置中心按以前讲解的方式创建命名空间, dataId 和组并将配置信息放在Nacos ,具体如图。
在 resources 下创建一个 bootstrap.properties 用于获取/访问 Nacos 当中的配置信息,也可以用 yml
#注册到 nacos的名字
spring.application.name=rainbowsealiving-service
#指定 nacos的发现注册地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#指定 nacos的配置地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
#要获取的配置文件所在的配置空间
spring.cloud.nacos.config.namespace=73bded0a-1c06-4292-a8ff-e5b177114551
#配置空间的哪一个组,这个组下可以有多个配置文件,通过 ext-config[?]来指定要加载某空间下的某组的第几个文件
spring.cloud.nacos.config.group=dev
#通过 ext-config[?]来指定要加载某空间下的某组的第几个文件
# 和指定 data-id 和是否实时刷新的配置
spring.cloud.nacos.config.ext-config[0].data-id=rainbowsealiving-service.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true
注意这里配置的 pom.xml 的相关的依赖
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--
引入 oss starter 我们引入的 stater 和 文档给的不一样,
请注意观察 artifactId ,小伙伴要和写的保持一致
老师说明: 因为我们在该 pom.xml 文件中有 dependencyManagement 指定了 spring-cloud-aliba
版本 2.1.0.RELEASE 因此这里,我们可以不写 version 而是使用版本仲裁
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-config</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
</dependencies>
<?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.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.rainbowsea.RainbowSealiving</groupId>
<artifactId>raibnowsealiving-service</artifactId>
<version>1.0-SNAPSHOT</version>
<name>raibnowsealiving-service</name>
<description>顺平( 家居生活)- 第三方服务模块 OSS</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.5</spring-cloud.version>
</properties>
<dependencies>
<!--
通过回顾,我们指定,如果该服务需要去拉取nacos配置中心的配置, 需要加入
spring-cloud-starter-alibaba-nacos-config [融汇贯通]
这里,我们使用版本仲裁,来指定version
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--引入 oss starter 我们引入的stater 和文档给的不一样,
请注意观察artifactId , 小伙伴要和老师写的保持一致
老师说明: 因为我们在该pom.xml文件中有 dependencyManagement 指定了 spring-cloud-alibaba-dependencies
版本 2.1.0.RELEASE, 因此这里,我们可以不写version 而是使用版本仲裁
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<!--<version>2.1.0.RELEASE</version>-->
</dependency>
<!-- 引入公共模块,排除 mybatis-plus -->
<dependency>
<groupId>com.rainbowsea.RainbowSealiving</groupId>
<artifactId>RainbowSealiving-common</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 排除公共模块当中的 mybatis-plus-boot-starter 依赖-->
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!--
1. 这里老师想回顾使用dependencyManagement 在进行版本控制
2. 老韩再强调一下dependencyManagement 用于指定依赖版本,
但是本身并不引入相关依赖
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<!--
老师解读:
1. type: pom 和 scope import 配合使用
2. 表示 在这个项目的子模块和当前这个模块,
在引入spring-cloud-alibaba相关依赖时 锁定版本为2.1.0.RELEASE
3. 通过 pom + import 解决maven单继承机制
-->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
通过 @Value 注解的方式,获取到 Nacos 当中的配置,再赋值到对应相等内容上。
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.rainbowsea.common.utils.R;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
public class OssServiceController {
// 装配属性
@Resource
OSS ossClient;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
}
package com.rainbowsea.rainbowsealiving.service.controller;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.rainbowsea.common.utils.R;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
public class OssServiceController {
// 装配属性
@Resource
OSS ossClient;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@RequestMapping("/oss/policy")
public R policy() {
String host = "https://" + bucket + "." + endpoint;
// host的 格 式 为 // bucketname.endpoint
// callbackUrl为 上传回调服务器的 URL,请将下面的 IP和 Port配置为您自己的真实信息。暂时不用
// String callbackUrl = "http://88.88.88.88:8888";
// 我们可以将文件按照 年-月-日的形式分目录存放在阿里云[以后优化]
// 得到 dir
// java 基础的日期 -SimpleDateFormat
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format + "/"; //用户上传文件时指定的前缀。
Map<String, String> respMap = null;
try {
long expireTime = 30; // 默认超时时间 30s
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0,
1048576000);
policyConds.addConditionItem(MatchMode.StartWith,
PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
// respMap.put("expire", formatISO8601Date(expiration));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
}
return R.ok().put("data", respMap);
}
}
阿里云 key
用户登录名称 rainbowsealiving-10000@1562236733667461.onaliyun.com
AccessKey ID LTAI5tP4G6hDJqh7FPe1Cahh
AccessKey Secret vl5kaBORH1QADEzKq9NInpRdD8JJeF
LTAI5tP4G6hDJqh7FPe1Cahh
vl5kaBORH1QADEzKq9NInpRdD8JJeF
用户登录名称 rainbowsealiving-10000@1562236733667461.onaliyun.com
AccessKey ID LTAI5tP4G6hDJqh7FPe1Cahh
AccessKey Secret vl5kaBORH1QADEzKq9NInpRdD8JJeF
后端校验器
防止,用户走后端,比如使用 Postman 进行访问,登录时候。
使用 JSR 303 校验
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现
package com.rainbowsea.rainbowsealiving.commodity.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
/**
* 家居品牌
*
* @author rainbowsea
* @email rainbowsea@gmail.com
* @date 2025-03-08 20:11:04
*/
@Data
@TableName("commodity_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 2L;
/**
* id
*/
@TableId
private Long id;
/**
* 品牌名
* 1. @NotBlank 表示 name 必须包括一个非空字符
* 2. message = "品牌名不能为空" 是老师指定一个校验消息
* 3. 如果没有指定 message = "品牌名不能为空",就会返回默认的校验信息 key = javax.validation.constion
* 4. 这个默认的消息是在 ValidationMessages_zh_CN.properties 文件配置javax.validation.constran
* 5. @NotBlank 可以用于 CharSequence
*/
@NotBlank(message = "品牌名不能为空")
private String name;
/**
* logo 因为这个 Logo 对应的是图片的 url
* 1.@NotBlank 和 @URL 组合去校验 logo
*/
@NotBlank(message = "logo 不能为空")
@URL(message = "logo 不是一个合法的 URL ")
private String logo;
/**
* 说明
*/
private String description;
/**
* 显示,isshow 后面的 s 是小写
* 1. 这里我们使用的注解是(@NotNull,他可以接收任意类型)
* 2. 如果这里使用 @NotBlank,会报错,因为 @NotBlank 不支持 Integer
* 3. 大家在开发时,需要知道注解可以用在哪些类型上,可以查看注解源码
* 4. 说明: 假如 isshow 规定时 0 / 1,这时我们后面通过自定义约束来解决
* 5. 如果是 String 类型,可以直接使用@Pattern 来进一步校验。
*/
@NotNull(message = "显示状态不能为空")
private Integer isshow;
/**
* 检索首字母 a-z, A-Z
* 因为 firstLetter 是 String 可以直接使用 @Pattern
*/
@NotBlank(message = "检索首字母不能为空")
@Pattern(regexp = "^[a-zA-Z]$",message = "检索首字母必须是 a-z 或者 A-Z")
private String firstLetter;
/**
* 排序
*/
@NotNull(message = "排序值不能为空")
@Min(value = 0,message = "排序值要求大于等于 0 ")
private Integer sort;
}
/**
* 保存
* 说明:
* 1. @Validated 注解的作用就是启用 BrandEntity 字段校验
* 2. 注解如果没有写 @Validated 这个校验规则不生效
* 3. BindingResult result: springboot 会将校验的错误放入到 result
* 4. 程序员可以通过 BindingResult result 将校验得到错误取出,然后进行相应处理
*/
@RequestMapping("/save")
//@RequiresPermissions("commodity:brand:save")
public R save(@Validated @RequestBody BrandEntity brand, BindingResult result){
// 先创建一个 Map,用于收集校验错误
Map<String,String> map = new HashMap<>();
if (result.hasErrors()) { // 如果有校验错误
// 遍历 result ,将错误信息收集到 map
result.getFieldErrors().forEach((item) ->{
// 得到 field
String field = item.getField();
// 得到校验错误信息
String message = item.getDefaultMessage();
// 放入 map
map.put(field,message);
});
return R.error(400,"品牌表单数据不合法").put("data",map);
} else { // 如果没有校验错误,入库
brandService.save(brand);
return R.ok();
}
}
全局异常处理
我们可以使用@ControllerAdvice 统一管理异常/错误
package com.rainbowsea.rainbowsealiving.commodity.exception;
import com.rainbowsea.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
* 说明:
* @ResponseBody : 表示以 json 格式返回数据
* @Slf4j 可以输出日志,便于数据
* @ControllerAdvice(basePackages = "com.rainbowsea.rainbowsealiving.commodity.controller")
* 表示统一接管 com.rainbowsea.rainbowsealiving.commodity.controller 这个路径包下的
* 抛出了的数据校验的异常,就由 @ExceptionHandler(value = MethodArgumentNotValidException.class)
* 处理
*/
@Slf4j
@ResponseBody
@ControllerAdvice(basePackages = "com.rainbowsea.rainbowsealiving.commodity.controller")
public class RainbowSealivingExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e) {
log.error(" 数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String, String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError) -> {
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return R.error(400, " 方法参数异常").put("data", errorMap);
}
/**
*
这里再写一个处理 Throwable类型的异常的方法,没有精确匹配到的异常,走这里
*/
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
return R.error(40000," 系统未知错误");
}
}
/**
* 保存
* 说明:
* 1. @Validated 注解的作用就是启用 BrandEntity 字段校验
* 2. 注解如果没有写 @Validated 这个校验规则不生效
* 3. BindingResult result: springboot 会将校验的错误放入到 result
* 4. 程序员可以通过 BindingResult result 将校验得到错误取出,然后进行相应处理
* 5.如果使用@ControllerAdvice统一接管异常,就不用单独封装返回异常信息了,将其注释
* 6.异常会统一抛出给@ControllerAdvice类
*/
@RequestMapping("/save")
//@RequiresPermissions("commodity:brand:save")
public R save(@Validated @RequestBody BrandEntity brand, BindingResult result){
// 先创建一个 Map,用于收集校验错误
//Map<String,String> map = new HashMap<>();
//
//if (result.hasErrors()) { // 如果有校验错误
// // 遍历 result ,将错误信息收集到 map
// result.getFieldErrors().forEach((item) ->{
// // 得到 field
// String field = item.getField();
// // 得到校验错误信息
// String message = item.getDefaultMessage();
// // 放入 map
// map.put(field,message);
// });
//
// return R.error(400,"品牌表单数据不合法").put("data",map);
//} else { // 如果没有校验错误,入库
// brandService.save(brand);
// return R.ok();
//}
brandService.save(brand);
return R.ok();
}
代码优化-统一状态码
- 问题分析: 公司写代码会统一规定错误/异常状态码 和 提示信息,我们可以将其抽
取成枚举类,方便管理和扩展
- 错误/异常状态码 如何规定,不同公司不太一样,遵守项目经理给的规则即可. 比如 模块编号+错误/异常编号
可以定义在一个公共模块当中,这样其它的微服务模块就都可以使用了。
package com.rainbowsea.common.exception;
/**
* 编写方法,处理没有精确匹配到的异常/错误
* 返回一个统一的信息,方便前端处理
*/
public enum RainbowSealivingCodeEnume {
UNKNOWN_EXCEPTION(40000,"系统未知异常"), // shift + ctrl + U 大小写转换
INVALID_EXCEPTION(40001, " 参数校验异常~~");
// 需要定义构造方法,不然会报错
private int code;
private String msg;
RainbowSealivingCodeEnume(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
import com.rainbowsea.common.exception.RainbowSealivingCodeEnume;
import com.rainbowsea.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
* 说明:
* @ResponseBody : 表示以 json 格式返回数据
* @Slf4j 可以输出日志,便于数据
* @ControllerAdvice(basePackages = "com.rainbowsea.rainbowsealiving.commodity.controller")
* 表示统一接管 com.rainbowsea.rainbowsealiving.commodity.controller 这个路径包下的
* 抛出了的数据校验的异常,就由 @ExceptionHandler(value = MethodArgumentNotValidException.class)
* 处理
*/
@Slf4j
@ResponseBody
@ControllerAdvice(basePackages = "com.rainbowsea.rainbowsealiving.commodity.controller")
public class RainbowSealivingExceptionControllerAdvice {
/**
* 编写方法,处理没有精确匹配到的异常/错误
* 返回一个统一的信息,方便前端处理
*/
@ExceptionHandler({Throwable.class})
public R handleValidException(Throwable throwable) {
return R.error(RainbowSealivingCodeEnume.UNKNOWN_EXCEPTION.getCode(),
RainbowSealivingCodeEnume.UNKNOWN_EXCEPTION.getMsg());
}
}
package com.rainbowsea.rainbowsealiving.commodity.exception;
import com.rainbowsea.common.exception.RainbowSealivingCodeEnume;
import com.rainbowsea.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
* 说明:
* @ResponseBody : 表示以 json 格式返回数据
* @Slf4j 可以输出日志,便于数据
* @ControllerAdvice(basePackages = "com.rainbowsea.rainbowsealiving.commodity.controller")
* 表示统一接管 com.rainbowsea.rainbowsealiving.commodity.controller 这个路径包下的
* 抛出了的数据校验的异常,就由 @ExceptionHandler(value = MethodArgumentNotValidException.class)
* 处理
*/
@Slf4j
@ResponseBody
@ControllerAdvice(basePackages = "com.rainbowsea.rainbowsealiving.commodity.controller")
public class RainbowSealivingExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e) {
log.error(" 数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String, String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError) -> {
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return R.error(RainbowSealivingCodeEnume.INVALID_EXCEPTION.getCode(),
RainbowSealivingCodeEnume.INVALID_EXCEPTION.getMsg()).put("data", errorMap);
}
/**
*
这里再写一个处理 Throwable类型的异常的方法,没有精确匹配到的异常,走这里
*/
/**
* 编写方法,处理没有精确匹配到的异常/错误
* 返回一个统一的信息,方便前端处理
*/
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
return R.error(RainbowSealivingCodeEnume.UNKNOWN_EXCEPTION.getCode(),
RainbowSealivingCodeEnume.UNKNOWN_EXCEPTION.getMsg());
}
}
分组校验
分组校验一适应多场景需求
分组校验在什么时候会使用到
- 如果我们添加品牌, 因为品牌 id 是自增的,因此不需要带 id 值
- 如果我们修改品牌, 因为品牌 id 是唯一确定记录的,因此必须带 id
-也就是说, 在不同的业务需求下,对同一个 Entity 的各个字段的数据校验可能是不同的.
大家知道,BrandEntity.java(实体对象) 在添加和修改时都会使用到,是同一个类,怎么办? ==> 解决方案: 分组校验
分组校验: 也就是针对不同的应用场景,使用不同的校验规则。
测试效果: 使用 Postman 来测试,就会看到调用不同的 API 接口, 应用的校验规则是——不同的
同理关于分组校验,自定义的分组,也是放在公共模块当中,用于其他微服务模块的调用,和使用
package com.rainbowsea.common.valid;
/**
* 1. SaveGroup 表示是保存组校验信息
*/
public interface SaveGroup {
}
package com.rainbowsea.common.valid;
/**
* 2. UpdateGroup 表示是修改组校验信息
*/
public interface UpdateGroup {
}
两个校验组,使用接口类型标识
配置好,分组校验后,我们需要在一些指定用的校验的 controller 当中使用 指定 API 接口使用哪个校验组。
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
//import org.apache.shiro.authz.annotation.RequiresPermissions;
import com.rainbowsea.common.valid.SaveGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.rainbowsea.rainbowsealiving.commodity.entity.BrandEntity;
import com.rainbowsea.rainbowsealiving.commodity.service.BrandService;
import com.rainbowsea.common.utils.PageUtils;
import com.rainbowsea.common.utils.R;
/**
* 家居品牌
*
* @author rainbowsea
* @email rainbowsea@gmail.com
* @date 2025-03-08 20:11:04
*/
@RestController
@RequestMapping("commodity/brand")
public class BrandController {
@Autowired
private BrandService brandService;
/**
* 保存
* 说明:
* 1. @Validated 注解的作用就是启用 BrandEntity 字段校验
* 2. 注解如果没有写 @Validated 这个校验规则不生效
* 3. BindingResult result: springboot 会将校验的错误放入到 result
* 4. 程序员可以通过 BindingResult result 将校验得到错误取出,然后进行相应处理
* 5.如果使用@ControllerAdvice统一接管异常,就不用单独封装返回异常信息了,将其注释
* 6.异常会统一抛出给@ControllerAdvice类
*/
@RequestMapping("/save")
//@RequiresPermissions("commodity:brand:save")
// @Validated({SaveGroup.class}) 表示调用 save 时,进行参数校验,使用的是 SaveGroup 的校验规则
public R save(@Validated({SaveGroup.class}) @RequestBody BrandEntity brand/*, BindingResult result*/){
brandService.save(brand);
return R.ok();
}
}
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
//import org.apache.shiro.authz.annotation.RequiresPermissions;
import com.rainbowsea.common.valid.SaveGroup;
import com.rainbowsea.common.valid.UpdateGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.rainbowsea.rainbowsealiving.commodity.entity.BrandEntity;
import com.rainbowsea.rainbowsealiving.commodity.service.BrandService;
import com.rainbowsea.common.utils.PageUtils;
import com.rainbowsea.common.utils.R;
/**
* 家居品牌
*
* @author rainbowsea
* @email rainbowsea@gmail.com
* @date 2025-03-08 20:11:04
*/
@RestController
@RequestMapping("commodity/brand")
public class BrandController {
@Autowired
private BrandService brandService;
/**
* 保存
* 说明:
* 1. @Validated 注解的作用就是启用 BrandEntity 字段校验
* 2. 注解如果没有写 @Validated 这个校验规则不生效
* 3. BindingResult result: springboot 会将校验的错误放入到 result
* 4. 程序员可以通过 BindingResult result 将校验得到错误取出,然后进行相应处理
* 5.如果使用@ControllerAdvice统一接管异常,就不用单独封装返回异常信息了,将其注释
* 6.异常会统一抛出给@ControllerAdvice类
*/
@RequestMapping("/save")
//@RequiresPermissions("commodity:brand:save")
// @Validated({SaveGroup.class}) 表示调用 save 时,进行参数校验,使用的是 SaveGroup 的校验规则
public R save(@Validated({SaveGroup.class}) @RequestBody BrandEntity brand/*, BindingResult result*/){
// 先创建一个 Map,用于收集校验错误
//Map<String,String> map = new HashMap<>();
//
//if (result.hasErrors()) { // 如果有校验错误
// // 遍历 result ,将错误信息收集到 map
// result.getFieldErrors().forEach((item) ->{
// // 得到 field
// String field = item.getField();
// // 得到校验错误信息
// String message = item.getDefaultMessage();
// // 放入 map
// map.put(field,message);
// });
//
// return R.error(400,"品牌表单数据不合法").put("data",map);
//} else { // 如果没有校验错误,入库
// brandService.save(brand);
// return R.ok();
//}
brandService.save(brand);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("commodity:brand:update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
Controller 请求当中配置好分组之后,还需要在对应处理 实体类
当中,对应的属性字段配置上分组内容
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import com.rainbowsea.common.valid.SaveGroup;
import com.rainbowsea.common.valid.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;
/**
* 家居品牌
*
* @author rainbowsea
* @email rainbowsea@gmail.com
* @date 2025-03-08 20:11:04
*/
@Data
@TableName("commodity_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 2L;
/**
* id
* @NotNull(message = "修改要求指定 id ",groups = {UpdateGroup.class})
* 表示 @NotNull 在 UpdateGroup.class 组中才会生效
* @Null(message = "添加不能指定 id",groups = {SaveGroup.class})
* 表示 @Null 在SaveGroup 校验组生效
*/
@TableId
@NotNull(message = "修改要求指定 id ",groups = {UpdateGroup.class})
@Null(message = "添加不能指定 id",groups = {SaveGroup.class})
private Long id
}
postman 测试
完整的 实体类-BrandEntity.java 的分组校验内容
@NotBlank(message = "品牌名不能为空",groups = {SaveGroup.class,UpdateGroup.class})
private String name;
package com.rainbowsea.rainbowsealiving.commodity.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import com.rainbowsea.common.valid.SaveGroup;
import com.rainbowsea.common.valid.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;
/**
* 家居品牌
*
* @author rainbowsea
* @email rainbowsea@gmail.com
* @date 2025-03-08 20:11:04
*/
@Data
@TableName("commodity_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 2L;
/**
* id
*
* @NotNull(message = "修改要求指定 id ",groups = {UpdateGroup.class})
* 表示 @NotNull 在 UpdateGroup.class 组中才会生效
* @Null(message = "添加不能指定 id",groups = {SaveGroup.class})
* 表示 @Null 在SaveGroup 校验组生效
*/
@TableId
@NotNull(message = "修改要求指定 id ", groups = {UpdateGroup.class})
@Null(message = "添加不能指定 id", groups = {SaveGroup.class})
private Long id;
/**
* 品牌名
* 1. @NotBlank 表示 name 必须包括一个非空字符
* 2. message = "品牌名不能为空" 是老师指定一个校验消息
* 3. 如果没有指定 message = "品牌名不能为空",就会返回默认的校验信息 key = javax.validation.constion
* 4. 这个默认的消息是在 ValidationMessages_zh_CN.properties 文件配置javax.validation.constran
* 5. @NotBlank 可以用于 CharSequence
* groups = {SaveGroup.class,UpdateGroup.class 就是@NotBlank 在 SaveGroup 和 UpdateGroup 都生成
*/
@NotBlank(message = "品牌名不能为空", groups = {SaveGroup.class, UpdateGroup.class})
private String name;
/**
* logo 因为这个 Logo 对应的是图片的 url
* 1.@NotBlank 和 @URL 组合去校验 logo
*/
@NotBlank(message = "logo 不能为空", groups = {SaveGroup.class})
@URL(message = "logo 不是一个合法的 URL ", groups = {SaveGroup.class, UpdateGroup.class})
private String logo;
/**
* 说明
*/
private String description;
/**
* 显示,isshow 后面的 s 是小写
* 1. 这里我们使用的注解是(@NotNull,他可以接收任意类型)
* 2. 如果这里使用 @NotBlank,会报错,因为 @NotBlank 不支持 Integer
* 3. 大家在开发时,需要知道注解可以用在哪些类型上,可以查看注解源码
* 4. 说明: 假如 isshow 规定时 0 / 1,这时我们后面通过自定义约束来解决
* 5. 如果是 String 类型,可以直接使用@Pattern 来进一步校验。
*/
@NotNull(message = "显示状态不能为空", groups = {SaveGroup.class, UpdateGroup.class})
private Integer isshow;
/**
* 检索首字母 a-z, A-Z
* 因为 firstLetter 是 String 可以直接使用 @Pattern
*/
@NotBlank(message = "检索首字母不能为空", groups = {SaveGroup.class})
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是 a-z 或者 A-Z", groups = {SaveGroup.class, UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(message = "排序值不能为空", groups = {SaveGroup.class})
@Min(value = 0, message = "排序值要求大于等于 0 ", groups = {SaveGroup.class, UpdateGroup.class})
private Integer sort;
}
注意事项和使用细节:
如果我们的 Controller 指定的分组校验,比如:
但是我们的 Entity 没有指定的使用哪个分组校验, 则该校验注解就失效了, 比如:
那么@NotBlank 注解 在添加和修改时,都是无效的
自定义校验——主要是通过“因为 Interig 类型 无法进行正则表达式处理”
自定义校验器在什么时候会使用到?
- 当前我们通过 Postman 添加品牌, 传入的 isshow 不是 0 或者 1, 服务端是校验不到的,先看问题
方案 1: 使用正则表达式来解决,但是实际正则表达式不能使用在 数值类型
. 是不会生效的, 看测试
方案 2: 自定义校验器:
我们想要实现的自定义校验器的格式是如下:
@EnumValidate(values = {0,1}, groups = {SaveGroup.class, UpdateGroup.class} )
private Integer isshow;
效果:
配置定义校验器步骤:
- 自定义校验器/注解,全程可以参考 @NotNull 源码来编写
- 因为这个校验在各个模块都可能使用,因此在 公共模块开发
- 修改 pom.xml , 引入自定义校验器相关 jar
引入自定义校验注解 jar
<!-- 引入相关依赖-->
<dependencies>
<!-- 引入自定义校验注解 jar -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
</dependencies>
创建一个 EnumConstraintValidator 是作为真正的校验器,即该自定义校验的逻辑业务都是在这里写的。
package com.rainbowsea.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 1. EnumConstraintValidator 是作为真正的校验器,即该自定义校验的逻辑业务都是在这里写的。
* EnumConstraintValidator 需要实现接口 ConstraintValidator
* ConstraintValidator<EnumValidate,Integer> 表示该自定义校验器是针对 @EnumValidate 传入的 Integer 类型数据
* 进行校验的。
*/
public class EnumConstraintValidator implements ConstraintValidator<EnumValidate,Integer> {
@Override
public void initialize(EnumValidate constraintAnnotation) {
}
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
return false;
}
}
在 resources 目录下创建一个 ValidationMessages.properties 的文件,该文件用于,自定义校验器,校验后,返回一个怎样的信息。
注意:这里要进行一个 unicode码的编码转换。参考:
<font style="color:rgb(51, 51, 51);">ValidationMessages_zh_CN.properties</font>
文件。
com.rainbowsea.common.valid.EnumValidate.message=\u663e\u793a\u72b6\u6001\u5fc5\u987b\u662f\u0030\u6216\u8005\u0031
创建自定义注解 EnumValiate,(参考@NotNull 源码来开发)
package com.rainbowsea.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
/**
*
老韩解读
* 1.参考@NotNull源码来开发
* 2. EnumConstraintValidator.class是自定义的真正的校验器,需要自己开发
* 3. @Constraint(validatedBy = {EnumConstraintValidator.class}) 可以指定该自定义注解和
* EnumConstraintValidator 逻辑业务处理,交给它进行处理
* 4. String message() default "{com.rainbowsea.common.valid.EnumValidate.message}"; 可以指定校验时,返回的信息
* 这里表示从这个位置得到校验得信息
* 读取, resources\ValidationMessages.properties的
key=com.rainbowsea.common.valid.EnumValidate.message
*/
@Documented
@Constraint(validatedBy = {EnumConstraintValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER,
TYPE_USE})
@Retention(RUNTIME)
public @interface EnumValidate {
String message() default "{com.rainbowsea.common.valid.EnumValidate.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// 增加 values 属性
int[] values() default {};
}
最后编写,EnumConstraintValidator.java 自定义校验上的逻辑业务处理。
package com.rainbowsea.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 1. EnumConstraintValidator 是作为真正的校验器,即该自定义校验的逻辑业务都是在这里写的。
* EnumConstraintValidator 需要实现接口 ConstraintValidator
* ConstraintValidator<EnumValidate,Integer> 表示该自定义校验器是针对 @EnumValidate 传入的 Integer 类型数据
* 进行校验的。
*/
public class EnumConstraintValidator implements ConstraintValidator<EnumValidate,Integer> {
@Override
public void initialize(EnumValidate constraintAnnotation) {
// 这里我们测试一下,看看能否得到注解出传入的 values
// 通过注解获得 values,我们
int[] values = constraintAnnotation.values();
for ( int value: values) {
System.out.println("EnumValidate注解指定的 value = " + value);
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
System.out.println("表单传入的 value ~" + value);
return false;
}
}
测试成功,则可以正式,编写自定义校验的,逻辑业务处理了。
package com.rainbowsea.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* 1. EnumConstraintValidator 是作为真正的校验器,即该自定义校验的逻辑业务都是在这里写的。
* EnumConstraintValidator 需要实现接口 ConstraintValidator
* ConstraintValidator<EnumValidate,Integer> 表示该自定义校验器是针对 @EnumValidate 传入的 Integer 类型数据
* 进行校验的。
*/
public class EnumConstraintValidator implements ConstraintValidator<EnumValidate, Integer> {
// 定义一个 set 集合,用于收集 EnumValidate 传入的 values
private Set<Integer> set = new HashSet<>();
@Override
public void initialize(EnumValidate constraintAnnotation) {
// 这里我们测试一下,看看能否得到注解出传入的 values
// 通过注解获得 values,我们
int[] values = constraintAnnotation.values();
for (int value : values) {
//System.out.println("EnumValidate注解指定的 value = " + value);
set.add(value);
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
//这个 value就是从表单传递的数值
// 判断是否在注解填写的枚举值中
//System.out.println("表单传入的 value ~" + value);
return set.contains(value); // 判断该表单当中是否含有该 set()校验集合当中的内容。有返回 true,校验成功
// 否 ,校验失败,返回失败信息。
}
}
测试:
注意事项和使用细节
一个自定义校验注解,可以由多个校验器来组合校验 。如图
大家注意, validatedBy = {EnumConstraintValidator.class} 是可以带校验器类型数组的.
自定义校验改为”使用正则表达式“
就是,将我们EnumConstraintValidator.java 自定义校验上的逻辑业务处理,将传过来的字符串,内容进行正则表达式的处理。
具体代码实现如下:
package com.rainbowsea.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
/**
*
老韩解读
* 1.参考@NotNull源码来开发
* 2. EnumConstraintValidator.class是自定义的真正的校验器,需要自己开发
* 3. @Constraint(validatedBy = {EnumConstraintValidator.class}) 可以指定该自定义注解和
* EnumConstraintValidator 逻辑业务处理,交给它进行处理
* 4. String message() default "{com.rainbowsea.common.valid.EnumValidate.message}"; 可以指定校验时,返回的信息
* 这里表示从这个位置得到校验得信息
* 读取, resources\ValidationMessages.properties的
key=com.rainbowsea.common.valid.EnumValidate.message
*/
@Documented
@Constraint(validatedBy = {EnumConstraintValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER,
TYPE_USE})
@Retention(RUNTIME)
public @interface EnumValidate {
String message() default "{com.rainbowsea.common.valid.EnumValidate.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// 增加 values 属性
int[] values() default {};
// 增加 regexp 用于正则表达式处理
String regexp() default "";
}
/**
* 显示,isshow 后面的 s 是小写
* 1. 这里我们使用的注解是(@NotNull,他可以接收任意类型)
* 2. 如果这里使用 @NotBlank,会报错,因为 @NotBlank 不支持 Integer
* 3. 大家在开发时,需要知道注解可以用在哪些类型上,可以查看注解源码
* 4. 说明: 假如 isshow 规定时 0 / 1,这时我们后面通过自定义约束来解决
* 5. 如果是 String 类型,可以直接使用@Pattern 来进一步校验。
*/
@NotNull(message = "显示状态不能为空", groups = {SaveGroup.class, UpdateGroup.class})
//@EnumValidate(values = {0,1},message = "显示状态的值需要0或者1~",groups = {SaveGroup.class,UpdateGroup.class})
@EnumValidate(regexp = "^[0-1]$",message = "显示状态的值需要0或者1~",groups = {SaveGroup.class,UpdateGroup.class})
private Integer isshow;
package com.rainbowsea.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* 1. EnumConstraintValidator 是作为真正的校验器,即该自定义校验的逻辑业务都是在这里写的。
* EnumConstraintValidator 需要实现接口 ConstraintValidator
* ConstraintValidator<EnumValidate,Integer> 表示该自定义校验器是针对 @EnumValidate 传入的 Integer 类型数据
* 进行校验的。
*/
public class EnumConstraintValidator implements ConstraintValidator<EnumValidate, Integer> {
// 定义一个获取到 用于收集 EnumValidate 传入的 regexp 正则表达式的字符串值
private String regexp;
@Override
public void initialize(EnumValidate constraintAnnotation) {
// 获取到注解 regexp() 当中的正则表达式字符串的值
regexp = constraintAnnotation.regexp();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
// 正则表达式判断
return value.toString().matches(regexp);
}
}
处理 switch 开关修改显示状态
解决思路分析:
因为修改显示状态,只需要发送 id 和 isshow 两个值,也就是说,只需要校验 BrandEntity.java 的 id 和 isshow 即可
解决思路, 单独在创建一个校验组 UpdateIsShowGroup 和一个updateIsShow() 用于该, switch 开启校验即可, api接口即可。因为我们的 switch 开关,的更新,只需要 id,和 isshow 没有问题就可以了。不需要其他的,实体类上的属性验证。
步骤:
创建 UpdateIsShowGroup 接口,该接口不需要编写内容,只是用于表示该用于 switch 开关的分组标识。创建到公共模块当中,用于其他微服务模块的时候。
package com.rainbowsea.common.valid;
public interface UpdateIsShowGroup {
}
在对应 BrandController 请求处理的位置,添加上一个映射为 /update/issow/ 的请求,同时添加上 @Validated({UpdateIsShowGroup.class} 校验错误注解处理,这里使用了方法的重载。
package com.rainbowsea.rainbowsealiving.commodity.controller;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
//import org.apache.shiro.authz.annotation.RequiresPermissions;
import com.rainbowsea.common.valid.SaveGroup;
import com.rainbowsea.common.valid.UpdateGroup;
import com.rainbowsea.common.valid.UpdateIsShowGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.rainbowsea.rainbowsealiving.commodity.entity.BrandEntity;
import com.rainbowsea.rainbowsealiving.commodity.service.BrandService;
import com.rainbowsea.common.utils.PageUtils;
import com.rainbowsea.common.utils.R;
/**
* 家居品牌
*
* @author rainbowsea
* @email rainbowsea@gmail.com
* @date 2025-03-08 20:11:04
*/
@RestController
@RequestMapping("commodity/brand")
public class BrandController {
@Autowired
private BrandService brandService;
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("commodity:brand:update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
/**
* 对 isshow Switch 控件开关上的校验,请求处理。
* @param brand
* @return
*/
@RequestMapping("/update/isshow")
public R updateIsShow(@Validated({UpdateIsShowGroup.class}) @RequestBody
BrandEntity brand) {
brandService.updateById(brand);
return R.ok();
}
}
最后,给对应的 BrandEntity.java 实体类,添加上分组校验
单独给 id 和 isshow 增加校验组
package com.rainbowsea.rainbowsealiving.commodity.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import com.rainbowsea.common.valid.EnumValidate;
import com.rainbowsea.common.valid.SaveGroup;
import com.rainbowsea.common.valid.UpdateGroup;
import com.rainbowsea.common.valid.UpdateIsShowGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Pattern;
/**
* 家居品牌
*
* @author rainbowsea
* @email rainbowsea@gmail.com
* @date 2025-03-08 20:11:04
*/
@Data
@TableName("commodity_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 2L;
/**
* id
*
* @NotNull(message = "修改要求指定 id ",groups = {UpdateGroup.class})
* 表示 @NotNull 在 UpdateGroup.class 组中才会生效
* @Null(message = "添加不能指定 id",groups = {SaveGroup.class})
* 表示 @Null 在SaveGroup 校验组生效
*/
@TableId
@NotNull(message = "修改要求指定 id ", groups = {UpdateGroup.class, UpdateIsShowGroup.class})
@Null(message = "添加不能指定 id", groups = {SaveGroup.class})
private Long id;
/**
* 显示,isshow 后面的 s 是小写
* 1. 这里我们使用的注解是(@NotNull,他可以接收任意类型)
* 2. 如果这里使用 @NotBlank,会报错,因为 @NotBlank 不支持 Integer
* 3. 大家在开发时,需要知道注解可以用在哪些类型上,可以查看注解源码
* 4. 说明: 假如 isshow 规定时 0 / 1,这时我们后面通过自定义约束来解决
* 5. 如果是 String 类型,可以直接使用@Pattern 来进一步校验。
*/
@NotNull(message = "显示状态不能为空", groups = {SaveGroup.class, UpdateGroup.class,UpdateIsShowGroup.class})
@EnumValidate(values = {0,1},message = "显示状态的值需要0或者1~",groups = {SaveGroup.class,UpdateGroup.class})
//@EnumValidate(regexp = "^[0-1]$",message = "显示状态的值需要0或者1~",groups = {SaveGroup.class,UpdateGroup.class})
private Integer isshow;
}
测试:
最后:
“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”
来源链接:https://www.cnblogs.com/TheMagicalRainbowSea/p/18840445
如有侵犯您的版权,请及时联系3500663466#qq.com(#换@),我们将第一时间删除本站数据。
暂无评论内容