通用型产品发布解决方案(基于分布式微服务技术栈:SpringBoot+SpringCloud+Spring CloudAlibaba+Vue+ElementUI+MyBatis-Plus+MySQL+Git+Maven+Linux+Docker+Nginx – 《02》

通用型产品发布解决方案(基于分布式微服务技术栈: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();

    }

代码优化-统一状态码

  1. 问题分析: 公司写代码会统一规定错误/异常状态码 和 提示信息,我们可以将其抽

取成枚举类,方便管理和扩展

  1. 错误/异常状态码 如何规定,不同公司不太一样,遵守项目经理给的规则即可. 比如 模块编号+错误/异常编号

可以定义在一个公共模块当中,这样其它的微服务模块就都可以使用了。

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());
    }
}

分组校验

分组校验一适应多场景需求

分组校验在什么时候会使用到

  1. 如果我们添加品牌, 因为品牌 id 是自增的,因此不需要带 id 值

  1. 如果我们修改品牌, 因为品牌 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 类型 无法进行正则表达式处理”

自定义校验器在什么时候会使用到?

  1. 当前我们通过 Postman 添加品牌, 传入的 isshow 不是 0 或者 1, 服务端是校验不到的,先看问题

方案 1: 使用正则表达式来解决,但是实际正则表达式不能使用在 数值类型. 是不会生效的, 看测试

方案 2: 自定义校验器:

我们想要实现的自定义校验器的格式是如下:

@EnumValidate(values = {0,1}, groups = {SaveGroup.class, UpdateGroup.class} )
private Integer isshow;

效果:

配置定义校验器步骤:

  1. 自定义校验器/注解,全程可以参考 @NotNull 源码来编写
  2. 因为这个校验在各个模块都可能使用,因此在 公共模块开发
  3. 修改 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

© 版权声明
THE END
支持一下吧
点赞14 分享
评论 抢沙发
头像
请文明发言!
提交
头像

昵称

取消
昵称表情代码快捷回复

    暂无评论内容