微服務(wù)開(kāi)發(fā)系列 第十篇:Gateway

總概

A、技術(shù)棧
  • 開(kāi)發(fā)語(yǔ)言:Java 1.8
  • 數(shù)據(jù)庫(kù):MySQL、Redis、MongoDB、Elasticsearch
  • 微服務(wù)框架:Spring Cloud Alibaba
  • 微服務(wù)網(wǎng)關(guān):Spring Cloud Gateway
  • 服務(wù)注冊(cè)和配置中心:Nacos
  • 分布式事務(wù):Seata
  • 鏈路追蹤框架:Sleuth
  • 服務(wù)降級(jí)與熔斷:Sentinel
  • ORM框架:MyBatis-Plus
  • 分布式任務(wù)調(diào)度平臺(tái):XXL-JOB
  • 消息中間件:RocketMQ
  • 分布式鎖:Redisson
  • 權(quán)限:OAuth2
  • DevOps:Jenkins、Docker、K8S
B、源碼地址

alanchenyan/ac-mall2-cloud

C、本節(jié)實(shí)現(xiàn)目標(biāo)
  • 新建mall-gateway服務(wù),所有請(qǐng)求通過(guò)Gateway轉(zhuǎn)發(fā)
  • Gateway鑒權(quán)token
  • Gateway配置白名單
  • 所有服務(wù)swagger通過(guò)gateway訪問(wèn),并提供下列列表選擇服務(wù)
  • @RestControllerAdvice攔截Controller返回統(tǒng)一格式數(shù)據(jù)
  • @ControllerAdvice攔截返回統(tǒng)一格式Exception
D、系列

一、API Gateway

API 網(wǎng)關(guān)出現(xiàn)的原因是微服務(wù)架構(gòu)的出現(xiàn),不同的微服務(wù)一般會(huì)有不同的網(wǎng)絡(luò)地址,而外部客戶端可能需要調(diào)用多個(gè)服務(wù)的接口才能完成一個(gè)業(yè)務(wù)需求,如果讓客戶端直接與各個(gè)微服務(wù)通信,會(huì)有以下的問(wèn)題:

  • 客戶端會(huì)多次請(qǐng)求不同的微服務(wù),增加了客戶端的復(fù)雜性。

  • 存在跨域請(qǐng)求,在一定場(chǎng)景下處理相對(duì)復(fù)雜。

  • 認(rèn)證復(fù)雜,每個(gè)服務(wù)都需要獨(dú)立認(rèn)證。

  • 難以重構(gòu),隨著項(xiàng)目的迭代,可能需要重新劃分微服務(wù)。例如,可能將多個(gè)服務(wù)合并成一個(gè)或者將一個(gè)服務(wù)拆分成多個(gè)。如果客戶端直接與微服務(wù)通信,那么重構(gòu)將會(huì)很難實(shí)施。

  • 某些微服務(wù)可能使用了防火墻 / 瀏覽器不友好的協(xié)議,直接訪問(wèn)會(huì)有一定的困難。

以上這些問(wèn)題可以借助 API 網(wǎng)關(guān)解決。API 網(wǎng)關(guān)是介于客戶端和服務(wù)器端之間的中間層,所有的外部請(qǐng)求都會(huì)先經(jīng)過(guò) API 網(wǎng)關(guān)這一層。也就是說(shuō),API 的實(shí)現(xiàn)方面更多的考慮業(yè)務(wù)邏輯,而安全、性能、監(jiān)控可以交由 API 網(wǎng)關(guān)來(lái)做,這樣既提高業(yè)務(wù)靈活性又不缺安全性,典型的架構(gòu)圖如圖所示:

API 網(wǎng)關(guān)

二、Spring Cloud Gateway簡(jiǎn)介

Spring Cloud Gateway 是 Spring Cloud 的一個(gè)全新項(xiàng)目,該項(xiàng)目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術(shù)開(kāi)發(fā)的網(wǎng)關(guān),它旨在為微服務(wù)架構(gòu)提供一種簡(jiǎn)單有效的統(tǒng)一的 API 路由管理方式。

Spring Cloud Gateway作為Spring Cloud生態(tài)系統(tǒng)中的網(wǎng)關(guān),目標(biāo)是替代Netflix Zuul,其不僅提供統(tǒng)一的路由方式,并且基于Filter鏈的方式提供了網(wǎng)關(guān)基本的功能,例如:安全,監(jiān)控/指標(biāo),和限流。

由于Spring 5.0支持 Netty,Http2,而Spring Boot 2.0支持Spring 5.0,因此Spring Cloud Gateway支持 Netty和Http2。

補(bǔ)充:
1、Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何長(zhǎng)連接 ,如 WebSockets。
2、Zuul(2.x) 基于Netty。
3、Spring Cloud GateWay天?就是異步?阻塞的,基于Reactor模型,支持 WebSockets,支持限流等新特性。
4、Spring Cloud 已經(jīng)不再集成 Zuul 2.x 。

三、架構(gòu)說(shuō)明

認(rèn)證服務(wù)(mall-auth)負(fù)責(zé)認(rèn)證授權(quán),網(wǎng)關(guān)服務(wù)(mall-gateway)負(fù)責(zé)校驗(yàn)認(rèn)證和鑒權(quán),其他API服務(wù)負(fù)責(zé)處理自己的業(yè)務(wù)邏輯。安全相關(guān)的邏輯只存在于認(rèn)證服務(wù)和網(wǎng)關(guān)服務(wù)中,其他服務(wù)只是單純地提供服務(wù)而沒(méi)有任何安全相關(guān)邏輯。

具體服務(wù):

  • [mall-auth]:認(rèn)證服務(wù),負(fù)責(zé)對(duì)登錄用戶進(jìn)行認(rèn)證授權(quán)頒發(fā)token。
  • [mall-gateway]:網(wǎng)關(guān)服務(wù),負(fù)責(zé)請(qǐng)求轉(zhuǎn)發(fā)和校驗(yàn)認(rèn)證和鑒權(quán)。
  • [mall-member]:受保護(hù)的API服務(wù),用戶鑒權(quán)通過(guò)后可以訪問(wèn)該服務(wù),該類服務(wù)還有[mall-product]、[mall-search]等等。

四、代碼實(shí)現(xiàn)

4.1 新建mall-gateway服務(wù)

新建mall-gateway服務(wù)用戶token鑒權(quán)、API請(qǐng)求轉(zhuǎn)發(fā)

4.2 pom.xml
<?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>
        <artifactId>mall-pom</artifactId>
        <groupId>com.ac</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.ac</groupId>
    <artifactId>mall-gateway</artifactId>
    <version>${mall.version}</version>
    <name>mall-gateway</name>
    <description>網(wǎng)關(guān)服務(wù)</description>

    <dependencies>
        <dependency>
            <groupId>com.ac</groupId>
            <artifactId>mall-core</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.ac</groupId>
            <artifactId>mall-oauth2-module</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- Spring Cloud Gateway 是使用 netty+webflux 實(shí)現(xiàn)因此不需要再引入 web 模塊 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>4.0.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
4.3 配置路由Route(路由)、白名單

bootstrap-dev.yml

server:
  port: 6001

spring:
  application:
    name: mall-gateway

  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: dev_id
        file-extension: yml
        shared-configs:
          - data-id: common.yml
            group: DEFAULT_GROUP
            refresh: true
      discovery:
        namespace: dev_id

    gateway:
      routes:
        - id: mall-member-route            # 當(dāng)前路由的標(biāo)識(shí), 要求唯一
          uri: lb://mall-member            # lb指的是從nacos中按照名稱獲取微服務(wù),并遵循負(fù)載均衡策略
          predicates:
            - Path=/mall-member/**         # 當(dāng)請(qǐng)求路徑滿足Path指定的規(guī)則時(shí),才進(jìn)行路由轉(zhuǎn)發(fā)
          filters:
            - StripPrefix=1                # 轉(zhuǎn)發(fā)之前去掉1層路徑

        - id: mall-search-route
          uri: lb://mall-search
          predicates:
            - Path=/mall-search/**
          filters:
            - StripPrefix=1

        - id: mall-product-route
          uri: lb://mall-product
          predicates:
            - Path=/mall-product/**
          filters:
            - StripPrefix=1

        - id: mall-order-route
          uri: lb://mall-order
          predicates:
            - Path=/mall-order/**
          filters:
            - StripPrefix=1
 
#gateway swagger開(kāi)關(guān)
swagger:
  enable: true

#配置白名單路徑
mall:
  security:
    ignore:
      urls:
        - "/**/member/list"
        - "/**/redis/**"

重點(diǎn)說(shuō)明一下配置,- StripPrefix=1 轉(zhuǎn)發(fā)之前去掉1層路徑,如:127.0.0.1:6001/mall-member/member/264260572479489,去掉第一層路徑mall-member,就變成了127.0.0.1:6001/member/264260572479489,會(huì)被轉(zhuǎn)發(fā)到mall-member服務(wù)。

4.4 Application配置@ComponentScan

mall-core服務(wù)config包里的WebMvcConfigurer配置類,和mall-gateway服務(wù)里排除的spring-webmvc有沖突,因此排除該目錄下的配置類

@ComponentScan(
        value = "com.ac.*",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.ac.core.config.*")})
@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
4.5 swagger配置
4.5.1 配置類
package com.ac.gateway.config;

import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.List;

@Configuration
@Primary
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public class GateWaySwaggerConfig implements SwaggerResourcesProvider {
    public static final String API_URI = "/v2/api-docs";
    private final RouteLocator routeLocator;
    private final GatewayProperties gatewayProperties;

    public GateWaySwaggerConfig(RouteLocator routeLocator, GatewayProperties gatewayProperties) {
        this.routeLocator = routeLocator;
        this.gatewayProperties = gatewayProperties;
    }

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routes = new ArrayList<>();
        //取出gateway的route
        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
        //結(jié)合配置的route-路徑(Path),和route過(guò)濾,只獲取有效的route節(jié)點(diǎn)
        gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
                .forEach(routeDefinition -> routeDefinition.getPredicates().stream()
                        .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
                        .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
                                predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
                                        .replace("/**", API_URI)))));

        return resources;
    }

    private SwaggerResource swaggerResource(String name, String location) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion("1.0");
        return swaggerResource;
    }
}
4.5.2 controller類
package com.ac.gateway.controller;

import com.ac.gateway.config.GateWaySwaggerConfig;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;

import javax.annotation.Resource;

/**
 * @author Alan Chen
 * @description 在瀏覽器中打開(kāi)gateway的swagger地址時(shí),會(huì)將請(qǐng)求自動(dòng)打到下面API
 * http://127.0.0.1:6001/swagger-ui.html
 * @date 2023/02/22
 */
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
@RestController
public class SwaggerController {

    @Resource
    private GateWaySwaggerConfig gateWaySwaggerConfig;

    @GetMapping("/swagger-resources/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(SecurityConfigurationBuilder.builder().build(), HttpStatus.OK));
    }

    @GetMapping("/swagger-resources/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(UiConfigurationBuilder.builder().build(), HttpStatus.OK));
    }

    @GetMapping("/swagger-resources")
    public Mono<ResponseEntity> swaggerResources() {
        return Mono.just((new ResponseEntity<>(gateWaySwaggerConfig.get(), HttpStatus.OK)));
    }

    @GetMapping("/")
    public Mono<ResponseEntity> swaggerResourcesN() {
        return Mono.just((new ResponseEntity<>(gateWaySwaggerConfig.get(), HttpStatus.OK)));
    }

    @GetMapping("/csrf")
    public Mono<ResponseEntity> swaggerResourcesCsrf() {
        return Mono.just((new ResponseEntity<>(gateWaySwaggerConfig.get(), HttpStatus.OK)));
    }
}

在GateWaySwaggerConfig、SwaggerController類上都加上了@ConditionalOnProperty(name = "swagger.enable", havingValue = "true") 注解,該注解表示當(dāng)swagger.enable配置值為true時(shí),則將當(dāng)前類初始化為bean。該開(kāi)關(guān)用戶關(guān)閉生產(chǎn)環(huán)境swagger,保證服務(wù)安全性。

下拉選擇服務(wù)
4.6 @RestControllerAdvice攔截Controller返回統(tǒng)一格式數(shù)據(jù)

該配置類放在mall-core模塊

package com.ac.core.config;

import com.ac.core.response.RepResult;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Alan Chen
 * @description Controller返回參數(shù)全局包裝成ResponseResult對(duì)象
 * 使用是一般需要指定basePackages,@RestControllerAdvice(basePackages = {"com.netx.web.controller"})
 * 只攔截controller包下的類;否則swagger也會(huì)攔截影響swagger正常使用
 * @date 2023/04/15
 */
@EnableWebMvc
@Configuration
@RestControllerAdvice
public class GlobalReturnConfig implements ResponseBodyAdvice<Object>, WebMvcConfigurer {

    /**
     * 支持返回 text/plan 格式  字符串不會(huì)帶雙引號(hào)
     *
     * @return
     */
    public boolean supportTextPlan() {
        return false;
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        //排除swagger的請(qǐng)求 springfox.documentation.swagger2.web.Swagger2Controller
        if (methodParameter.getDeclaringClass().getName().contains("swagger")) {
            return false;
        }
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object returnObj, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {

        // 返回值為void
        if (returnObj == null) {
            return RepResult.success();
        }

        //全局異常會(huì)攔截統(tǒng)一封裝成ResponseResult對(duì)象,因此不需要再包裝了
        if (returnObj instanceof RepResult) {
            return returnObj;
        }

        return RepResult.success(returnObj);

    }

    /**
     * 解決不能返回單個(gè)字符的問(wèn)題
     *
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

        if (supportTextPlan()) {
            converters.add(stringHttpMessageConverter());
        }

        //創(chuàng)建fastJson消息轉(zhuǎn)換器
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();

        // 解決Content-Type cannot contain wildcard type '*'問(wèn)題
        List<MediaType> supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
        supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        supportedMediaTypes.add(MediaType.APPLICATION_PDF);
        supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
        supportedMediaTypes.add(MediaType.APPLICATION_XML);
        supportedMediaTypes.add(MediaType.IMAGE_GIF);
        supportedMediaTypes.add(MediaType.IMAGE_JPEG);
        supportedMediaTypes.add(MediaType.IMAGE_PNG);
        supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
        supportedMediaTypes.add(MediaType.TEXT_HTML);
        supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
        supportedMediaTypes.add(MediaType.TEXT_PLAIN);
        supportedMediaTypes.add(MediaType.TEXT_XML);
        converter.setSupportedMediaTypes(supportedMediaTypes);

        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        // 字段為null時(shí)依然返回到前端,而不是省略該字段
        fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
        converter.setFastJsonConfig(fastJsonConfig);

        converters.add(converter);
    }

    @Bean
    public StringHttpMessageConverter stringHttpMessageConverter() {
        return new StringHttpMessageConverter();
    }
}
查詢用戶接口

雖然查詢用戶接口,返回的是一個(gè)用戶對(duì)象,但返回到前端時(shí),統(tǒng)一返回的是RepResult格式,將用戶數(shù)據(jù)放在了data里。

統(tǒng)一返回RepResult格式
4.7 @ControllerAdvice攔截返回統(tǒng)一格式Exception

該配置放在mall-core里

package com.ac.core.exception;

import com.ac.core.i18n.I18nResource;
import com.ac.core.response.RepResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
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;

/**
 * @author Alan Chen
 * @description 全局異常處理
 * @date 2023/4/27
 */
@Slf4j
@ControllerAdvice
@Component
public class GlobalExceptionHandler {

    private I18nResource validationI18nSource;

    private I18nResource responseMessageI18nSource;

    /**
     * 是否開(kāi)啟Validator國(guó)際化功能
     * @return
     */
    protected boolean enableValidationI18n(){
        return false;
    }

    /**
     * 國(guó)際化文件地址
     * @return
     */
    protected String validationI18nSourcePath(){
        return "i18n/validation";
    }

    /**
     * 是否開(kāi)啟消息國(guó)際化
     * @return
     */
    protected boolean enableResponseMessageI18n(){
        return false;
    }

    /**
     * 消息國(guó)際化文件地址
     * @return
     */
    protected String responseMessageI18nSourcePath(){
        return "i18n/messages";
    }


    /**
     * 全局異常捕捉處理
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public RepResult errorHandler(Exception ex) {
        ex.printStackTrace();
        log.error("Exception:"+ex.getMessage());
        return RepResult.fail(ex.getMessage());
    }

    /**
     * validator校驗(yàn)失敗信息處理
     * @param exception
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public RepResult bindExceptionHandler(BindException exception) {
        exception.printStackTrace();
        return doValidationException(exception.getBindingResult());
    }

    /**
     * validator校驗(yàn)失敗信息處理
     * @param exception
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public RepResult validationHandler(MethodArgumentNotValidException exception) {
        exception.printStackTrace();
        log.error("MethodArgumentNotValidException:"+exception.getMessage());
        return doValidationException(exception.getBindingResult());
    }

    /**
     * 攔截捕捉業(yè)務(wù)異常 ServiceException.class
     * @param ex
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = ServerException.class)
    public RepResult commonExceptionHandler(ServerException ex) {

        ex.printStackTrace();
        log.error("ServiceException:"+ex.getMessage());

        if(enableResponseMessageI18n()){
            if(responseMessageI18nSource == null){
                responseMessageI18nSource = new I18nResource(responseMessageI18nSourcePath());
            }
            String messageKey = ex.getMessage();
            try{
                String message = responseMessageI18nSource.getValue(messageKey);
                String[] placeholder = ex.getPlaceholder();
                if(placeholder!=null && placeholder.length>0){
                   for(int i =0;i<placeholder.length;i++){
                       message = message.replace("#{"+(i+1)+"}",placeholder[i]);
                   }
                }
                return RepResult.info(message);
            }catch (Exception e){
                return RepResult.info(ex.getMessage());
            }
        }

        return RepResult.info(ex.getMessage());
    }


    private RepResult doValidationException(BindingResult bindingResult){
        StringBuffer stringBuffer = new StringBuffer();

        if(enableValidationI18n()){
            if(validationI18nSource == null){
                validationI18nSource = new I18nResource(validationI18nSourcePath());
            }

            for (FieldError error : bindingResult.getFieldErrors()) {
                String messageKey = error.getDefaultMessage();
                try{
                    String message = validationI18nSource.getValue(messageKey);
                    stringBuffer.append(message).append(";");
                }catch (Exception e){
                    stringBuffer.append(messageKey).append(";");
                }
            }
        }else{
            for (FieldError error : bindingResult.getFieldErrors()) {
                stringBuffer.append(error.getDefaultMessage()).append(";");
            }
        }

        log.error("BindException:"+stringBuffer.toString());
        return RepResult.info(stringBuffer.toString());
    }

}
統(tǒng)一異常格式

五、token鑒權(quán)測(cè)試

5.1 鑒權(quán)攔截成功

請(qǐng)求gateway訪問(wèn)mall-member服務(wù)接口,不攜帶token,請(qǐng)求被攔截

鑒權(quán)攔截成功
5.2 鑒權(quán)成功轉(zhuǎn)發(fā)請(qǐng)求

請(qǐng)求gateway訪問(wèn)mall-member服務(wù)接口,攜帶合法token,請(qǐng)求被正確轉(zhuǎn)發(fā)

鑒權(quán)成功轉(zhuǎn)發(fā)請(qǐng)求
5.3 白名單

在bootstrap-dev.yml里配置了白名單:

#配置白名單路徑
mall:
  security:
    ignore:
      urls:
        - "/**/member/list"
        - "/**/redis/**"

請(qǐng)求gateway訪問(wèn)mall-member服務(wù)白名單接口,不攜帶token,請(qǐng)求被正確轉(zhuǎn)發(fā)

訪問(wèn)白名單接口
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容