初始化提交

This commit is contained in:
2021-01-20 18:30:23 +08:00
commit 3eb965f380
208 changed files with 8103 additions and 0 deletions

25
gateway/gateway-web/.gitignore vendored Normal file
View 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/

View 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>

View File

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

View File

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

View File

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

View File

@ -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"));
}
}

View File

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

View File

@ -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;
}
}

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

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

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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() {
}
}