This commit is contained in:
clay
2024-03-06 17:44:09 +08:00
commit adaec0eadd
1493 changed files with 219939 additions and 0 deletions

72
gateway/pom.xml Normal file
View File

@@ -0,0 +1,72 @@
<?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">
<parent>
<artifactId>fateverse</artifactId>
<groupId>cn.fateverse</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<artifactId>gateway</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Gateway 网关依赖,内置WebFlux 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel Gateway -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!-- 客户端负载均衡loadbalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Swagger UI 美化文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!-- Redis 基础封装工具 -->
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>common-redis</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.3</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,19 @@
package cn.fateverse.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* 网关启动程序 todo 张开怀 nacos数据修改动态监听并刷新本地路由
*
* @author clay
*/
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
System.out.println("网关启动成功");
}
}

View File

@@ -0,0 +1,69 @@
package cn.fateverse.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.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @author Binlin B Wang
*/
@Primary
@Component
public class CustomSwaggerResourceProvider implements SwaggerResourcesProvider {
/**
* Swagger2默认的url后缀
*/
public static final String V_3_API_DOCS = "/v3/api-docs";
/**
* 网关路由
*/
@Resource
private RouteLocator routeLocator;
@Resource
private GatewayProperties gatewayProperties;
/**
* 聚合其他服务接口
*
* @return
*/
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resourceList = new ArrayList<>();
List<String> routes = new ArrayList<>();
//获取网关中配置的route
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resourceList.add(
swaggerResource(
"服务名称:" + routeDefinition.getId() + " 请求路径前缀 : /" + routeDefinition.getId(),
//routeDefinition.getId(),
predicateDefinition
.getArgs()
.get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("/**", V_3_API_DOCS)))));
return resourceList;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setUrl(location);
swaggerResource.setSwaggerVersion("3.0");
return swaggerResource;
}
}

View File

@@ -0,0 +1,21 @@
package cn.fateverse.gateway.config;
import cn.fateverse.gateway.handler.SentinelFallbackHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* @author Clay
* @date 2022/10/27
*/
@Configuration(proxyBeanMethods = false)
public class GatewayConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelFallbackHandler sentinelGatewayExceptionHandler() {
return new SentinelFallbackHandler();
}
}

View File

@@ -0,0 +1,55 @@
package cn.fateverse.gateway.controller;
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;
/**
* swagger聚合文档资源链接
*
* @author Clay
* @date 2022/10/31
*/
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerResourceController {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerResourceController(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,25 @@
package cn.fateverse.gateway.exception;
import cn.fateverse.common.core.exception.CustomException;
/**
* @author Clay
* @date 2023-10-15
*/
public class BlackListException extends CustomException {
public BlackListException(String message) {
super(message);
}
public BlackListException(String message, Integer code) {
super(message, code);
}
public BlackListException(String message, Throwable e) {
super(message, e);
}
public BlackListException() {
}
}

View File

@@ -0,0 +1,86 @@
package cn.fateverse.gateway.filter;
import cn.fateverse.common.core.utils.IpBackUtils;
import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.common.core.result.Result;
import cn.fateverse.gateway.util.IpUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author Clay
* @date 2022/10/27
*/
@Slf4j
@Component
public class RequestGlobalFilter implements GlobalFilter, Ordered {
private final RedisTemplate<String, String> redisTemplate;
public RequestGlobalFilter(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String remoteAddress = IpUtils.getIP(request);
log.info("Method:{{}} Host:{{}} 远程ip:{{}} Path:{{}} Query:{{}}", request.getMethod().name(), request.getURI().getHost(), remoteAddress, request.getURI().getPath(), request.getQueryParams());
Boolean flag = redisTemplate.opsForSet().isMember(IpBackUtils.BLACK_LIST_IP, remoteAddress);
if (flag != null && flag) {
return errorInfo(exchange, Result.error(HttpStatus.FORBIDDEN, "ip为黑名单,无权访问"));
}
return chain.filter(exchange);
}
/**
* 返回response
*
* @param exchange
* @param result
* @return
*/
public static Mono<Void> errorInfo(ServerWebExchange exchange, Result<String> result) {
// 自定义返回格式
return Mono.defer(() -> {
byte[] bytes;
try {
bytes = new ObjectMapper().writeValueAsBytes(result);
} catch (JsonProcessingException e) {
log.error("网关响应异常:", e);
throw new CustomException("信息序列化异常");
} catch (Exception e) {
log.error("网关响应异常:", e);
throw new CustomException("写入响应异常");
}
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
response.setStatusCode(result.getStatus());
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Flux.just(buffer));
});
}
@Override
public int getOrder() {
return 0;
}
}

View File

@@ -0,0 +1,39 @@
package cn.fateverse.gateway.handler;
import cn.fateverse.gateway.util.GatewayResultUtils;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
/**
* 自定义限流异常处理
*
* @author clay
*/
public class SentinelFallbackHandler implements WebExceptionHandler {
private Mono<Void> writeResponse(ServerResponse response, ServerWebExchange exchange) {
return GatewayResultUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试");
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
if (!BlockException.isBlockException(ex)) {
return Mono.error(ex);
}
return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange));
}
private Mono<ServerResponse> handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
}

View File

@@ -0,0 +1,73 @@
package cn.fateverse.gateway.util;
import cn.fateverse.common.core.enums.ResultEnum;
import cn.fateverse.common.core.result.Result;
import com.alibaba.fastjson2.JSON;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import reactor.core.publisher.Mono;
/**
* 客户端工具类
*
* @author Clay
*/
public class GatewayResultUtils {
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value) {
return webFluxResponseWriter(response, HttpStatus.OK, value, ResultEnum.ERROR.code);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param code 响应状态码
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value, int code) {
return webFluxResponseWriter(response, HttpStatus.OK, value, code);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code) {
return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);
}
/**
* 设置webflux模型响应
*
* @param response ServerHttpResponse
* @param contentType content-type
* @param status http状态码
* @param code 响应状态码
* @param value 响应内容
* @return Mono<Void>
*/
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code) {
response.setStatusCode(status);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
Result<?> result = Result.error(code, value.toString());
DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
return response.writeWith(Mono.just(dataBuffer));
}
}

View File

@@ -0,0 +1,58 @@
package cn.fateverse.gateway.util;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Optional;
/**
* IP工具类
* @date 20-5-15 10:03
*/
public class IpUtils {
private static final String IP_UNKNOWN = "unknown";
private static final String IP_LOCAL = "127.0.0.1";
private static final int IP_LEN = 15;
/**
* 获取客户端真实ip
* @param request request
* @return 返回ip
*/
public static String getIP(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ipAddress = headers.getFirst("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = headers.getFirst("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = headers.getFirst("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = Optional.ofNullable(request.getRemoteAddress())
.map(address -> address.getAddress().getHostAddress())
.orElse("");
if (IP_LOCAL.equals(ipAddress)) {
// 根据网卡取本机配置的IP
try {
InetAddress inet = InetAddress.getLocalHost();
ipAddress = inet.getHostAddress();
} catch (UnknownHostException e) {
// ignore
}
}
}
// 对于通过多个代理的情况第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > IP_LEN) {
int index = ipAddress.indexOf(",");
if (index > 0) {
ipAddress = ipAddress.substring(0, index);
}
}
return ipAddress;
}
}

View File

@@ -0,0 +1,29 @@
## Spring
#spring:
# cloud:
# nacos:
# discovery:
# # 服务注册地址
# server-addr: 10.7.127.185:48848
# namespace: clay
#
#dubbo:
# registry:
# parameters:
# namespace: dubbo-clay
# Spring
spring:
cloud:
nacos:
discovery:
# 服务注册地址
# server-addr: 10.7.127.185:38848
server-addr: 162.14.111.170:8848
namespace: gary
dubbo:
registry:
parameters:
namespace: dubbo-gary

View File

@@ -0,0 +1,11 @@
# Spring
spring:
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: nacos.fateverse.svc.cluster.local:8848
management:
server:
port: 9595

View File

@@ -0,0 +1,36 @@
# Tomcat
server:
port: 8001
swagger:
enabled: true
# Spring
spring:
application:
# 应用名称
name: gateway
profiles:
# 环境配置
active: dev
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 192.168.101.108:8848
username: nacos
password: nacos
namespace: ${spring.profiles.active}
config:
# 配置中心地址
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: ${spring.profiles.active}
file-extension: yaml
shared-configs:
- data-id: application-${spring.profiles.active}.yaml
refresh: true
sentinel:
transport:
dashboard: localhost:5000