初始化提交
This commit is contained in:
25
gateway/gateway-web/.gitignore
vendored
Normal file
25
gateway/gateway-web/.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/build/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
79
gateway/gateway-web/pom.xml
Normal file
79
gateway/gateway-web/pom.xml
Normal file
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>business.chaoran</groupId>
|
||||
<artifactId>gateway-web</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>gateway-web</name>
|
||||
<description>Demo Gateway Api project for Spring Boot</description>
|
||||
|
||||
<parent>
|
||||
<groupId>business.chaoran</groupId>
|
||||
<artifactId>gateway</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<!--core基础类库-->
|
||||
<dependency>
|
||||
<groupId>business.chaoran</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!--api网关-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
<!--redis限流-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
</dependency>
|
||||
<!--swagger文档-->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
<!--网关签权客户端-->
|
||||
<dependency>
|
||||
<groupId>business.chaoran</groupId>
|
||||
<artifactId>authentication-client</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>0.9.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.1.4.RELEASE</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>build-info</goal>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<finalName>gateway-web</finalName>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,23 @@
|
||||
package com.springboot.cloud.gateway;
|
||||
|
||||
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
|
||||
import com.alicp.jetcache.anno.config.EnableMethodCache;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients(basePackages = "com.springboot.cloud.auth.client")
|
||||
@EnableCircuitBreaker
|
||||
@EnableMethodCache(basePackages = "com.springboot.cloud")
|
||||
@EnableCreateCacheAnnotation
|
||||
public class GatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package com.springboot.cloud.gateway.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.springboot.cloud.gateway.events.BusReceiver;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
|
||||
import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class BusConfig {
|
||||
|
||||
private static final String EXCHANGE_NAME = "spring-boot-exchange";
|
||||
private static final String ROUTING_KEY = "gateway-route";
|
||||
|
||||
@Value("${spring.application.name}")
|
||||
private String appName;
|
||||
|
||||
@Bean
|
||||
Queue queue() {
|
||||
String queueName = new Base64UrlNamingStrategy(appName + ".").generateName();
|
||||
log.info("queue name:{}", queueName);
|
||||
return new Queue(queueName, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TopicExchange exchange() {
|
||||
log.info("exchange:{}", EXCHANGE_NAME);
|
||||
return new TopicExchange(EXCHANGE_NAME);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Binding binding(Queue queue, TopicExchange exchange) {
|
||||
log.info("binding {} to {} with {}", queue, exchange, ROUTING_KEY);
|
||||
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory, MessageListenerAdapter messageListenerAdapter, Queue queue) {
|
||||
log.info("init simpleMessageListenerContainer: {}", queue.getName());
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueueNames(queue.getName());
|
||||
container.setMessageListener(messageListenerAdapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessageListenerAdapter messageListenerAdapter(BusReceiver busReceiver, MessageConverter messageConverter) {
|
||||
log.info("new listener");
|
||||
return new MessageListenerAdapter(busReceiver, messageConverter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageConverter messageConverter() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
return new ContentTypeDelegatingMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.springboot.cloud.gateway.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.validation.Validator;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
public class DefaultRedisRateLimiter extends RedisRateLimiter {
|
||||
|
||||
Config getDefaultConfig() {
|
||||
return super.getConfig().get("defaultFilters");
|
||||
}
|
||||
|
||||
public DefaultRedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
|
||||
RedisScript<List<Long>> script,
|
||||
@Qualifier("defaultValidator") Validator validator) {
|
||||
super(redisTemplate, script, validator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Response> isAllowed(String routeId, String id) {
|
||||
if (null == super.getConfig().get(routeId))
|
||||
getConfig().put(routeId, getDefaultConfig());
|
||||
return super.isAllowed(routeId, id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.springboot.cloud.gateway.config;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 自定义限流标志的key,多个维度可以从这里入手
|
||||
* exchange对象中获取服务ID、请求信息,用户信息等
|
||||
*/
|
||||
@Component
|
||||
public class RequestRateLimiterConfig {
|
||||
|
||||
/**
|
||||
* ip地址限流
|
||||
*
|
||||
* @return 限流key
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public KeyResolver remoteAddressKeyResolver() {
|
||||
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求路径限流
|
||||
*
|
||||
* @return 限流key
|
||||
*/
|
||||
@Bean
|
||||
public KeyResolver apiKeyResolver() {
|
||||
return exchange -> Mono.just(exchange.getRequest().getPath().value());
|
||||
}
|
||||
|
||||
/**
|
||||
* username限流
|
||||
*
|
||||
* @return 限流key
|
||||
*/
|
||||
@Bean
|
||||
public KeyResolver userKeyResolver() {
|
||||
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.springboot.cloud.gateway.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
import springfox.documentation.swagger.web.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/swagger-resources")
|
||||
public class SwaggerHandler {
|
||||
|
||||
@Autowired(required = false)
|
||||
private SecurityConfiguration securityConfiguration;
|
||||
|
||||
@Autowired(required = false)
|
||||
private UiConfiguration uiConfiguration;
|
||||
|
||||
private final SwaggerResourcesProvider swaggerResources;
|
||||
|
||||
@Autowired
|
||||
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
|
||||
this.swaggerResources = swaggerResources;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/configuration/security")
|
||||
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(
|
||||
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
|
||||
}
|
||||
|
||||
@GetMapping("/configuration/ui")
|
||||
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(
|
||||
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
|
||||
}
|
||||
|
||||
@GetMapping("")
|
||||
public Mono<ResponseEntity> swaggerResources() {
|
||||
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package com.springboot.cloud.gateway.config;
|
||||
|
||||
import com.springboot.cloud.gateway.service.RouteService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
import springfox.documentation.swagger.web.SwaggerResource;
|
||||
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@Primary
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class SwaggerProvider implements SwaggerResourcesProvider {
|
||||
public static final String API_URI = "/v2/api-docs";
|
||||
|
||||
@Autowired
|
||||
private final RouteService routeService;
|
||||
|
||||
@Override
|
||||
public List<SwaggerResource> get() {
|
||||
List<SwaggerResource> resources = new ArrayList<>();
|
||||
routeService.getRouteDefinitions().stream()
|
||||
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
|
||||
.filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName()))
|
||||
.peek(predicateDefinition -> log.debug("路由配置参数:{}", predicateDefinition.getArgs()))
|
||||
.forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
|
||||
predicateDefinition.getArgs().get("pattern").replace("/**", API_URI)))));
|
||||
log.debug("resources:{}", resources);
|
||||
return resources;
|
||||
}
|
||||
|
||||
private SwaggerResource swaggerResource(String name, String location) {
|
||||
SwaggerResource swaggerResource = new SwaggerResource();
|
||||
swaggerResource.setName(name);
|
||||
swaggerResource.setSwaggerVersion("2.0");
|
||||
swaggerResource.setUrl(location);
|
||||
return swaggerResource;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.springboot.cloud.gateway.events;
|
||||
|
||||
import com.springboot.cloud.gateway.service.RouteService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
//TODO 待实现删除,更新操作
|
||||
@Component
|
||||
@Slf4j
|
||||
public class BusReceiver {
|
||||
|
||||
@Autowired
|
||||
private RouteService routeService;
|
||||
|
||||
public void handleMessage(RouteDefinition routeDefinition) {
|
||||
log.info("Received Message:<{}>", routeDefinition);
|
||||
routeService.save(routeDefinition);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.springboot.cloud.gateway.exception;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ResourceProperties;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
|
||||
import org.springframework.boot.web.reactive.error.ErrorAttributes;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.server.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class CustomErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
|
||||
|
||||
@Autowired
|
||||
private GateWayExceptionHandlerAdvice gateWayExceptionHandlerAdvice;
|
||||
|
||||
/**
|
||||
* Create a new {@code DefaultErrorWebExceptionHandler} instance.
|
||||
*
|
||||
* @param errorAttributes the error attributes
|
||||
* @param resourceProperties the resources configuration properties
|
||||
* @param errorProperties the error configuration properties
|
||||
* @param applicationContext the current application context
|
||||
*/
|
||||
public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
|
||||
ErrorProperties errorProperties, ApplicationContext applicationContext) {
|
||||
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
|
||||
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
|
||||
Map<String, Object> error = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
|
||||
HttpStatus errorStatus = getHttpStatus(error);
|
||||
Throwable throwable = getError(request);
|
||||
return ServerResponse.status(errorStatus)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.body(BodyInserters.fromObject(gateWayExceptionHandlerAdvice.handle(throwable)));
|
||||
//.doOnNext((resp) -> logError(request, errorStatus));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package com.springboot.cloud.gateway.exception;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.web.ResourceProperties;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.reactive.error.ErrorAttributes;
|
||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
@ConditionalOnClass(WebFluxConfigurer.class)
|
||||
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
|
||||
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
|
||||
public class ExceptionAutoConfiguration {
|
||||
|
||||
private ServerProperties serverProperties;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private ResourceProperties resourceProperties;
|
||||
|
||||
private List<ViewResolver> viewResolvers;
|
||||
|
||||
private ServerCodecConfigurer serverCodecConfigurer;
|
||||
|
||||
public ExceptionAutoConfiguration(ServerProperties serverProperties,
|
||||
ResourceProperties resourceProperties,
|
||||
ObjectProvider<List<ViewResolver>> viewResolversProvider,
|
||||
ServerCodecConfigurer serverCodecConfigurer,
|
||||
ApplicationContext applicationContext) {
|
||||
this.serverProperties = serverProperties;
|
||||
this.applicationContext = applicationContext;
|
||||
this.resourceProperties = resourceProperties;
|
||||
this.viewResolvers = viewResolversProvider
|
||||
.getIfAvailable(() -> Collections.emptyList());
|
||||
this.serverCodecConfigurer = serverCodecConfigurer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
|
||||
DefaultErrorWebExceptionHandler exceptionHandler = new CustomErrorWebExceptionHandler(
|
||||
errorAttributes, this.resourceProperties,
|
||||
this.serverProperties.getError(), this.applicationContext);
|
||||
exceptionHandler.setViewResolvers(this.viewResolvers);
|
||||
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
|
||||
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
|
||||
return exceptionHandler;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
package com.springboot.cloud.gateway.exception;
|
||||
|
||||
import com.springboot.cloud.common.core.entity.vo.Result;
|
||||
import com.springboot.cloud.common.core.exception.SystemErrorType;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.netty.channel.ConnectTimeoutException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class GateWayExceptionHandlerAdvice {
|
||||
|
||||
@ExceptionHandler(value = {ResponseStatusException.class})
|
||||
public Result handle(ResponseStatusException ex) {
|
||||
log.error("response status exception:{}", ex.getMessage());
|
||||
return Result.fail(SystemErrorType.GATEWAY_ERROR);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {ConnectTimeoutException.class})
|
||||
public Result handle(ConnectTimeoutException ex) {
|
||||
log.error("connect timeout exception:{}", ex.getMessage());
|
||||
return Result.fail(SystemErrorType.GATEWAY_CONNECT_TIME_OUT);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {NotFoundException.class})
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public Result handle(NotFoundException ex) {
|
||||
log.error("not found exception:{}", ex.getMessage());
|
||||
return Result.fail(SystemErrorType.GATEWAY_NOT_FOUND_SERVICE);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {ExpiredJwtException.class})
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
public Result handle(ExpiredJwtException ex) {
|
||||
log.error("ExpiredJwtException:{}", ex.getMessage());
|
||||
return Result.fail(SystemErrorType.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {SignatureException.class})
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
public Result handle(SignatureException ex) {
|
||||
log.error("SignatureException:{}", ex.getMessage());
|
||||
return Result.fail(SystemErrorType.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {MalformedJwtException.class})
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
public Result handle(MalformedJwtException ex) {
|
||||
log.error("MalformedJwtException:{}", ex.getMessage());
|
||||
return Result.fail(SystemErrorType.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {RuntimeException.class})
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result handle(RuntimeException ex) {
|
||||
log.error("runtime exception:{}", ex.getMessage());
|
||||
return Result.fail();
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {Exception.class})
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result handle(Exception ex) {
|
||||
log.error("exception:{}", ex.getMessage());
|
||||
return Result.fail();
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {Throwable.class})
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result handle(Throwable throwable) {
|
||||
Result result = Result.fail();
|
||||
if (throwable instanceof ResponseStatusException) {
|
||||
result = handle((ResponseStatusException) throwable);
|
||||
} else if (throwable instanceof ConnectTimeoutException) {
|
||||
result = handle((ConnectTimeoutException) throwable);
|
||||
} else if (throwable instanceof NotFoundException) {
|
||||
result = handle((NotFoundException) throwable);
|
||||
} else if (throwable instanceof RuntimeException) {
|
||||
result = handle((RuntimeException) throwable);
|
||||
} else if (throwable instanceof Exception) {
|
||||
result = handle((Exception) throwable);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
package com.springboot.cloud.gateway.filter;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.springboot.cloud.auth.client.service.IAuthService;
|
||||
import com.springboot.cloud.gateway.service.PermissionService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 请求url权限校验
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "com.springboot.cloud.auth.client")
|
||||
@Slf4j
|
||||
public class AccessGatewayFilter implements GlobalFilter {
|
||||
|
||||
private static final String X_CLIENT_TOKEN_USER = "x-client-token-user";
|
||||
private static final String X_CLIENT_TOKEN = "x-client-token";
|
||||
private static final String X_CLIENT_SECRET = "gateway-web";
|
||||
private static final String USER_CURRENT_COMPANY="company";
|
||||
|
||||
/**
|
||||
* 由authentication-client模块提供签权的feign客户端
|
||||
*/
|
||||
@Autowired
|
||||
private IAuthService authService;
|
||||
|
||||
@Autowired
|
||||
private PermissionService permissionService;
|
||||
|
||||
/**
|
||||
* 1.首先网关检查token是否有效,无效直接返回401,不调用签权服务
|
||||
* 2.调用签权服务器看是否对该请求有权限,有权限进入下一个filter,没有权限返回401
|
||||
*
|
||||
* @param exchange
|
||||
* @param chain
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String authentication = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
String url = request.getPath().value();
|
||||
String method = request.getMethod().name();
|
||||
//后端识别companyId
|
||||
String companyId = Optional.ofNullable(request.getQueryParams().toSingleValueMap().get("companyId")).orElseGet(String::new);
|
||||
log.debug("url:{},headers:{}", url, request.getHeaders());
|
||||
//不需要网关签权的url
|
||||
if (authService.ignoreAuthentication(url)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
//调用签权服务看用户是否有权限,若有权限进入下一个filter
|
||||
if (permissionService.permission(companyId, authentication, url)) {
|
||||
//设置服务间调用请求头
|
||||
ServerHttpRequest.Builder builder = request.mutate();
|
||||
String originStr = new StringBuilder().append(url).append(method).append(X_CLIENT_TOKEN).append(X_CLIENT_SECRET).toString();
|
||||
String md5Secret = DigestUtils.md5DigestAsHex(originStr.getBytes());
|
||||
builder.header(X_CLIENT_TOKEN, md5Secret);
|
||||
builder.header(USER_CURRENT_COMPANY,companyId);
|
||||
//将jwt token中的用户信息传给服务
|
||||
builder.header(X_CLIENT_TOKEN_USER, getUserToken(authentication));
|
||||
return chain.filter(exchange.mutate().request(builder.build()).build());
|
||||
}
|
||||
return unauthorized(exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取jwt token中的数据,转为json
|
||||
*
|
||||
* @param authentication
|
||||
* @return
|
||||
*/
|
||||
private String getUserToken(String authentication) {
|
||||
String token = "{}";
|
||||
try {
|
||||
token = new ObjectMapper().writeValueAsString(authService.getJwt(authentication).getBody());
|
||||
return token;
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("token json error:{}", e.getMessage());
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网关拒绝,返回401
|
||||
*
|
||||
* @param
|
||||
*/
|
||||
private Mono<Void> unauthorized(ServerWebExchange serverWebExchange) {
|
||||
serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
DataBuffer buffer = serverWebExchange.getResponse()
|
||||
.bufferFactory().wrap(HttpStatus.UNAUTHORIZED.getReasonPhrase().getBytes());
|
||||
return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.springboot.cloud.gateway.filter;
|
||||
|
||||
import com.springboot.cloud.gateway.config.SwaggerProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
//@Component
|
||||
@Slf4j
|
||||
public class SwaggerHeaderFilter implements GlobalFilter, Ordered {
|
||||
|
||||
private static final String HEADER_NAME = "X-Forwarded-Prefix";
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String path = request.getURI().getPath();
|
||||
if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
|
||||
log.info("basePath: {}", basePath);
|
||||
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
|
||||
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
|
||||
return chain.filter(newExchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -200;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.springboot.cloud.gateway.routes;
|
||||
|
||||
import com.springboot.cloud.gateway.service.RouteService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
|
||||
|
||||
@Autowired
|
||||
private RouteService routeService;
|
||||
|
||||
@Override
|
||||
public Flux<RouteDefinition> getRouteDefinitions() {
|
||||
return Flux.fromIterable(routeService.getRouteDefinitions());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(Mono<RouteDefinition> route) {
|
||||
return route.flatMap(routeDefinition -> {
|
||||
routeService.save(routeDefinition);
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> delete(Mono<String> routeId) {
|
||||
return routeId.flatMap(id -> {
|
||||
routeService.delete(id);
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.springboot.cloud.gateway.service;
|
||||
|
||||
public interface PermissionService {
|
||||
/**
|
||||
*
|
||||
* @param companyId
|
||||
* @param authentication
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
boolean permission(String companyId,String authentication, String url);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.springboot.cloud.gateway.service;
|
||||
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface RouteService {
|
||||
Collection<RouteDefinition> getRouteDefinitions();
|
||||
|
||||
boolean save(RouteDefinition routeDefinition);
|
||||
|
||||
boolean delete(String routeId);
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.springboot.cloud.gateway.service.impl;
|
||||
|
||||
import com.springboot.cloud.auth.client.service.IAuthService;
|
||||
import com.springboot.cloud.gateway.service.PermissionService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PermissionServiceImpl implements PermissionService {
|
||||
|
||||
/**
|
||||
* 由authentication-client模块提供签权的feign客户端
|
||||
*/
|
||||
@Autowired
|
||||
private IAuthService authService;
|
||||
|
||||
@Override
|
||||
public boolean permission(String companyId, String authentication, String url) {
|
||||
return authService.hasPermission(companyId,authentication, url);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package com.springboot.cloud.gateway.service.impl;
|
||||
|
||||
import com.alicp.jetcache.Cache;
|
||||
import com.alicp.jetcache.anno.CacheType;
|
||||
import com.alicp.jetcache.anno.CreateCache;
|
||||
import com.springboot.cloud.gateway.service.RouteService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class RouteServiceImpl implements RouteService {
|
||||
|
||||
private static final String GATEWAY_ROUTES = "gateway_routes::";
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@CreateCache(name = GATEWAY_ROUTES, cacheType = CacheType.REMOTE)
|
||||
private Cache<String, RouteDefinition> gatewayRouteCache;
|
||||
|
||||
private Map<String, RouteDefinition> routeDefinitionMaps = new HashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
private void loadRouteDefinition() {
|
||||
log.info("loadRouteDefinition, 开始初使化路由");
|
||||
Set<String> gatewayKeys = stringRedisTemplate.keys(GATEWAY_ROUTES + "*");
|
||||
if (CollectionUtils.isEmpty(gatewayKeys)) {
|
||||
return;
|
||||
}
|
||||
log.info("预计初使化路由, gatewayKeys:{}", gatewayKeys);
|
||||
// 去掉key的前缀
|
||||
Set<String> gatewayKeyIds = gatewayKeys.stream().map(key -> {
|
||||
return key.replace(GATEWAY_ROUTES, StringUtils.EMPTY);
|
||||
}).collect(Collectors.toSet());
|
||||
Map<String, RouteDefinition> allRoutes = gatewayRouteCache.getAll(gatewayKeyIds);
|
||||
log.info("gatewayKeys:{}", allRoutes);
|
||||
// 以下代码原因是,jetcache将RouteDefinition返序列化后,uri发生变化,未初使化,导致路由异常,以下代码是重新初使化uri
|
||||
allRoutes.values().forEach(routeDefinition -> {
|
||||
try {
|
||||
routeDefinition.setUri(new URI(routeDefinition.getUri().toASCIIString()));
|
||||
} catch (URISyntaxException e) {
|
||||
log.error("网关加载RouteDefinition异常:", e);
|
||||
}
|
||||
});
|
||||
routeDefinitionMaps.putAll(allRoutes);
|
||||
log.info("共初使化路由信息:{}", routeDefinitionMaps.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<RouteDefinition> getRouteDefinitions() {
|
||||
return routeDefinitionMaps.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save(RouteDefinition routeDefinition) {
|
||||
routeDefinitionMaps.put(routeDefinition.getId(), routeDefinition);
|
||||
log.info("新增路由1条:{},目前路由共{}条", routeDefinition, routeDefinitionMaps.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(String routeId) {
|
||||
routeDefinitionMaps.remove(routeId);
|
||||
log.info("删除路由1条:{},目前路由共{}条", routeId, routeDefinitionMaps.size());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.springboot.cloud.gateway;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
public class GatewayApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user