總概
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、源碼地址
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、系列
- 微服務(wù)開(kāi)發(fā)系列 第一篇:項(xiàng)目搭建
- 微服務(wù)開(kāi)發(fā)系列 第二篇:Nacos
- 微服務(wù)開(kāi)發(fā)系列 第三篇:OpenFeign
- 微服務(wù)開(kāi)發(fā)系列 第四篇:分頁(yè)查詢
- 微服務(wù)開(kāi)發(fā)系列 第五篇:Redis
- 微服務(wù)開(kāi)發(fā)系列 第六篇:Redisson
- 微服務(wù)開(kāi)發(fā)系列 第七篇:RocketMQ
- 微服務(wù)開(kāi)發(fā)系列 第八篇:Elasticsearch
- 微服務(wù)開(kāi)發(fā)系列 第九篇:OAuth2
- 微服務(wù)開(kāi)發(fā)系列 第十篇:Gateway
- 微服務(wù)開(kāi)發(fā)系列 第十一篇:XXL-JOB
- 微服務(wù)開(kāi)發(fā)系列 第十二篇:MongoDB
- 微服務(wù)開(kāi)發(fā)系列 第n篇:AOP請(qǐng)求日志監(jiān)控
- 微服務(wù)開(kāi)發(fā)系列 第n篇:自定義校驗(yàn)注解
一、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)圖如圖所示:

二、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ù)安全性。

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里。

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

五、token鑒權(quán)測(cè)試
5.1 鑒權(quán)攔截成功
請(qǐng)求gateway訪問(wèn)mall-member服務(wù)接口,不攜帶token,請(qǐng)求被攔截

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

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ā)
