新增网关项目
This commit is contained in:
parent
05529b64a1
commit
c103825998
99
api-gateway/pom.xml
Normal file
99
api-gateway/pom.xml
Normal file
@ -0,0 +1,99 @@
|
||||
<?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>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.6.3</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<properties>
|
||||
<spring.cloud.version>3.1.1</spring.cloud.version>
|
||||
<spring.boot.version>2.6.3</spring.boot.version>
|
||||
<spring.boot.maven.plugin.version>${spring.boot.version}</spring.boot.maven.plugin.version>
|
||||
</properties>
|
||||
<artifactId>api-gateway</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
<version>${spring.cloud.version}</version>
|
||||
</dependency>
|
||||
<!-- SpringCloud Alibaba Nacos -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
<version>2021.0.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringCloud Alibaba Nacos Config -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
<version>2021.0.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<!--下面两个bootstrap.yml启动-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
<version>${spring.cloud.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-context</artifactId>
|
||||
<version>${spring.cloud.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-loadbalancer</artifactId>
|
||||
<version>${spring.cloud.version}</version>
|
||||
</dependency>
|
||||
<!-- SpringBoot Actuator-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring.boot.maven.plugin.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,18 @@
|
||||
package com.heyu.api.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
|
||||
/**
|
||||
* https://blog.csdn.net/qq_38380025/article/details/84936466
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
public class GatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.heyu.api.gateway.config;
|
||||
|
||||
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureAfter({CachingServiceInstanceListSupplier.class, NacosServiceDiscovery.class})
|
||||
public class CustomLoadBalancerConfiguration {
|
||||
|
||||
|
||||
@Bean
|
||||
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
|
||||
|
||||
//这里返回了我自己定义的方法
|
||||
return new MyRandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.heyu.api.gateway.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* XXX
|
||||
*
|
||||
* @author weiyachao
|
||||
* @since 2023/9/22 17:35
|
||||
*/
|
||||
@Configuration
|
||||
public class GatewayConfiguration {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package com.heyu.api.gateway.config;
|
||||
|
||||
import com.heyu.api.gateway.config.properties.CorsProperties;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* 全局跨域配置.
|
||||
*
|
||||
*/
|
||||
// @AutoConfiguration
|
||||
// @EnableConfigurationProperties(CorsProperties.class)
|
||||
public class GlobalCorsConfiguration {
|
||||
|
||||
/**
|
||||
* 允许跨域调用的过滤器.
|
||||
*
|
||||
* @param corsProperties 跨域配置
|
||||
*/
|
||||
// @Bean
|
||||
// @ConditionalOnMissingBean(CorsFilter.class)
|
||||
public CorsFilter corsFilter(CorsProperties corsProperties) {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
// 设置允许跨域访问的域名
|
||||
config.setAllowedOriginPatterns(corsProperties.getAllowedOriginPatterns());
|
||||
// 设置允许跨域访问的方法
|
||||
config.setAllowedMethods(corsProperties.getAllowedMethods());
|
||||
// 设置允许跨域访问的请求头
|
||||
config.setAllowedHeaders(corsProperties.getAllowedHeaders());
|
||||
config.setExposedHeaders(corsProperties.getExposedHeaders());
|
||||
// 允许跨越发送cookie
|
||||
config.setAllowCredentials(corsProperties.getAllowCredentials());
|
||||
config.setMaxAge(corsProperties.getMaxAge());
|
||||
|
||||
// 对接口配置跨域设置
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.heyu.api.gateway.config;
|
||||
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Admin-WPG
|
||||
*
|
||||
*/
|
||||
// https://blog.csdn.net/agulahaha/article/details/130783152
|
||||
@LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfiguration.class)
|
||||
public class MyLoadBalanceAutoConfiguration {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
package com.heyu.api.gateway.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* @author wutao
|
||||
* @description 自定义负载均衡
|
||||
* @date 2021-08-27
|
||||
* 自定义实现,就是去实现这个ReactorServiceInstanceLoadBalancer接口。
|
||||
*
|
||||
* 默认实现的随机负载、轮训负载可查看RandomLoadBalancer、RoundRobinLoadBalancer类
|
||||
*
|
||||
*/
|
||||
public class MyRandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
|
||||
|
||||
private static final Log log = LogFactory.getLog(MyRandomLoadBalancer.class);
|
||||
|
||||
private final String serviceId;
|
||||
|
||||
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
|
||||
|
||||
private final static String DEPLOY_SERVER_IPS = "ips";
|
||||
|
||||
/**
|
||||
* @param serviceInstanceListSupplierProvider a provider of
|
||||
* {@link ServiceInstanceListSupplier} that will be used to get available instances
|
||||
* @param serviceId id of the service for which to choose an instance
|
||||
*/
|
||||
public MyRandomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
|
||||
String serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public Mono<Response<ServiceInstance>> choose(Request request) {
|
||||
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
|
||||
.getIfAvailable(NoopServiceInstanceListSupplier::new);
|
||||
|
||||
return supplier.get(request).next()
|
||||
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
|
||||
}
|
||||
|
||||
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
|
||||
List<ServiceInstance> serviceInstances) {
|
||||
List<ServiceInstance> services = Lists.newArrayList();
|
||||
services.addAll(serviceInstances);
|
||||
for (ServiceInstance server : serviceInstances) {
|
||||
String hostPost = new StringBuffer(server.getHost()).append("_").append(server.getPort()).toString();
|
||||
log.info("MyRandomLoadBalancer hostPost:" + hostPost);
|
||||
}
|
||||
|
||||
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(services);
|
||||
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
|
||||
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
|
||||
}
|
||||
return serviceInstanceResponse;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
|
||||
if (instances.isEmpty()) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("No servers available for service: " + serviceId);
|
||||
}
|
||||
return new EmptyResponse();
|
||||
}
|
||||
|
||||
int index = ThreadLocalRandom.current().nextInt(instances.size());
|
||||
|
||||
ServiceInstance instance = instances.get(index);
|
||||
return new DefaultResponse(instance);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package com.heyu.api.gateway.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Created by wutao on 2019/5/5.
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean(name={"stringRedisTemplate"})
|
||||
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory){
|
||||
StringRedisTemplate redisTemplate = new StringRedisTemplate();
|
||||
redisTemplate.setConnectionFactory(factory);
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
redisTemplate.setConnectionFactory(factory);
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ValueOperations<String, String> valueOperations(RedisTemplate redisTemplate) {
|
||||
return redisTemplate.opsForValue();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.heyu.api.gateway.config;
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
@EnableWebFluxSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity){
|
||||
httpSecurity
|
||||
.authorizeExchange()
|
||||
//.pathMatchers("/actuator/**").denyAll()
|
||||
.pathMatchers("/**").permitAll()
|
||||
.pathMatchers(HttpMethod.OPTIONS).permitAll()
|
||||
.anyExchange().authenticated()
|
||||
.and()
|
||||
.csrf()
|
||||
.disable()
|
||||
.cors();
|
||||
return httpSecurity.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
package com.heyu.api.gateway.config.properties;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 跨域配置.
|
||||
*
|
||||
* @author yangning
|
||||
* @since 2022/11/7 14:45
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
// @ConfigurationProperties(prefix = "application.cors")
|
||||
public class CorsProperties {
|
||||
|
||||
/**
|
||||
* 允许跨域访问的域名.
|
||||
*/
|
||||
private List<String> allowedOriginPatterns;
|
||||
|
||||
/**
|
||||
* 允许跨域访问的方法.
|
||||
*/
|
||||
private List<String> allowedMethods;
|
||||
|
||||
/**
|
||||
* 允许跨域访问的请求头.
|
||||
*/
|
||||
private List<String> allowedHeaders;
|
||||
|
||||
/**
|
||||
* 暴露的响应头.
|
||||
*/
|
||||
private List<String> exposedHeaders;
|
||||
|
||||
/**
|
||||
* 是否允许跨域发送cookie.
|
||||
*/
|
||||
private Boolean allowCredentials = true;
|
||||
|
||||
/**
|
||||
* 跨域访问有效期.
|
||||
*/
|
||||
private Long maxAge = 1800L;
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package com.heyu.api.gateway.config.properties;
|
||||
|
||||
public class LoadBalanceProperties {
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package com.heyu.api.gateway.config.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Xss配置.
|
||||
*
|
||||
* @author yangning
|
||||
* @since 2023/4/7 18:31
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@RefreshScope
|
||||
@ConfigurationProperties(prefix = "security.xss")
|
||||
public class XssProperties {
|
||||
|
||||
/**
|
||||
* Xss开关.
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 排除路径.
|
||||
*/
|
||||
private List<String> excludeUrls = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 访问ip
|
||||
*/
|
||||
private String authIP;
|
||||
|
||||
/**
|
||||
* IP允许访问的url
|
||||
*/
|
||||
private List<String> authUrls = new ArrayList<>();
|
||||
}
|
||||
@ -0,0 +1,265 @@
|
||||
package com.heyu.api.gateway.filter;
|
||||
|
||||
import com.alibaba.cloud.commons.lang.StringUtils;
|
||||
import com.heyu.api.gateway.config.properties.XssProperties;
|
||||
import com.heyu.api.gateway.util.ApiConstants;
|
||||
import com.heyu.api.gateway.util.Log4Constans;
|
||||
import com.heyu.api.gateway.util.OrderUtils;
|
||||
import com.heyu.api.gateway.util.UserAuthContains;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* </p>*日志配置
|
||||
* @author wulin
|
||||
* @since 2023-08-07
|
||||
*
|
||||
* https://blog.csdn.net/qq_26274037/article/details/127800559
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class AuthWebFilter implements WebFilter {
|
||||
private static final String UNKNOWN = "unknown";
|
||||
private static final String LOCALHOST = "127.0.0.1";
|
||||
private static final String SEPARATOR = ",";
|
||||
|
||||
//由nginx配置透传过来
|
||||
private static final String HEADER_X_FORWARDED_FOR = "X-Forwarded-For";
|
||||
private static final String HEADER_PROXY_CLIENT_IP = "Proxy-Client-IP";
|
||||
private static final String HEADER_WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";
|
||||
|
||||
|
||||
@Autowired
|
||||
XssProperties xssProperties;
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String traceId = OrderUtils.getUserPoolOrder();
|
||||
String requestId = request.getId();//请求id
|
||||
MDC.put(Log4Constans.TRACE_ID, traceId);
|
||||
String url = request.getPath().toString();
|
||||
URI URIAll = request.getURI();
|
||||
String uri = URIAll.getPath();
|
||||
if("/favicon.ico".equals(uri)){
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
DataBuffer bodyDataBuffer = response.bufferFactory().wrap("".getBytes());
|
||||
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
|
||||
return response.writeWith(Mono.just(bodyDataBuffer));
|
||||
}
|
||||
|
||||
HttpHeaders httpHeaders = request.getHeaders();
|
||||
List<String> paths = httpHeaders.get(ApiConstants.X_FORWARDED_PATH);
|
||||
String xForwardedPath = null;
|
||||
if (!CollectionUtils.isEmpty(paths) && uri.equals("/")) {
|
||||
String path = paths.get(0);
|
||||
xForwardedPath = path;
|
||||
}
|
||||
|
||||
String realIpAddress = getRealIpAddress(request);
|
||||
String m = request.getMethod().toString();
|
||||
log.info("AuthWebFilter api start time:{} ip:{} method:{} url:{},uri:{} param:{} headers:{}",
|
||||
startTime,
|
||||
realIpAddress,
|
||||
m,
|
||||
request.getPath(),
|
||||
uri,
|
||||
request.getQueryParams(),
|
||||
httpHeaders
|
||||
);
|
||||
|
||||
if(xssProperties.getEnabled()) {
|
||||
if (xssProperties.getAuthIP() != null && xssProperties.getAuthIP().contains(realIpAddress)) {
|
||||
log.info("白名单IP,免验签");
|
||||
return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);
|
||||
}
|
||||
if (xssProperties.getExcludeUrls().contains(url)) {
|
||||
log.info("排除免签url内,直接通过");
|
||||
return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);
|
||||
}
|
||||
if (xssProperties.getAuthUrls().size() > 0) {
|
||||
for (String aurl : xssProperties.getAuthUrls()) {
|
||||
if (url.contains(aurl)) {
|
||||
log.info("免登陆url,免验签");
|
||||
return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
String api_token = exchange.getRequest().getHeaders().getFirst(UserAuthContains.API_TOKEN);
|
||||
String api_type = exchange.getRequest().getHeaders().getFirst(UserAuthContains.API_TYPE);
|
||||
// String key = RedisConstans.IOT_TOKEN.concat(api_token);
|
||||
// return reactiveRedisTemplate.getExpire(key).map(Duration::getSeconds).flatMap(ttl -> {
|
||||
// if (ttl == -1) {
|
||||
// // 用户没登陆
|
||||
// return Mono.error(new RuntimeException("未登录"));
|
||||
// } else if (ttl <= 3600) {
|
||||
// // token 将要失效
|
||||
// return reactiveRedisTemplate.expire(key, Duration.ofDays(7)).then(chain.filter(exchange));
|
||||
// } else {
|
||||
// // 正常登录
|
||||
// return chain.filter(exchange);
|
||||
// }
|
||||
// });
|
||||
if (ObjectUtils.isEmpty(api_token) || ObjectUtils.isEmpty(api_type)) {
|
||||
log.info("未传输token和type,需要登录");
|
||||
return Mono.error(new RuntimeException("未登录"));
|
||||
}
|
||||
return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);
|
||||
}
|
||||
log.info("未开启xss验签认证");
|
||||
|
||||
return continueRequest(exchange, chain, realIpAddress, requestId, startTime,traceId,xForwardedPath);
|
||||
}
|
||||
|
||||
|
||||
private Mono<Void> continueRequest(ServerWebExchange exchange, WebFilterChain chain, String customerIp, String requestId,
|
||||
long startTime,String traceId,String xForwardedPath){
|
||||
ServerWebExchange.Builder ex = exchange.mutate();
|
||||
ex.response(getResponse(exchange, requestId,traceId));
|
||||
ex.request(getRequest(exchange, customerIp, requestId,traceId,xForwardedPath));
|
||||
|
||||
return chain.filter(ex.build()).contextWrite(context -> {
|
||||
Context contextTmp = context.put(Log4Constans.TRACE_ID, traceId);
|
||||
return contextTmp;
|
||||
}).doFinally(single ->{
|
||||
long endTime = System.currentTimeMillis();
|
||||
log.info("api end time:{}, total time:{}", endTime, endTime - startTime);
|
||||
MDC.remove(LogMdcConfiguration.PRINT_LOG_ID);
|
||||
});
|
||||
}
|
||||
|
||||
private ServerHttpResponse getResponse(ServerWebExchange exchange, String requestId,String traceId){
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
return new ServerHttpResponseDecorator(response){
|
||||
@Override
|
||||
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body){
|
||||
MDC.put(Log4Constans.TRACE_ID, traceId);
|
||||
if (body instanceof Flux) {
|
||||
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
|
||||
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
|
||||
MDC.put(Log4Constans.TRACE_ID, traceId);
|
||||
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
|
||||
DataBuffer joinBuffer = dataBufferFactory.join(dataBuffers);
|
||||
byte[] returnContent = new byte[joinBuffer.readableByteCount()];
|
||||
joinBuffer.read(returnContent);
|
||||
DataBufferUtils.release(joinBuffer);
|
||||
String returnStr = new String(returnContent, StandardCharsets.UTF_8);
|
||||
log.info("response:{}", returnStr);
|
||||
return response.bufferFactory().wrap(returnContent);
|
||||
}));
|
||||
}else if(body instanceof Mono){
|
||||
Mono<DataBuffer> monoBody = Mono.from(body);
|
||||
return super.writeWith(monoBody.map(dataBuffer -> {
|
||||
MDC.put(Log4Constans.TRACE_ID, traceId);
|
||||
log.info("response:{}", dataBuffer.toString(StandardCharsets.UTF_8));
|
||||
return response.bufferFactory().wrap(dataBuffer.toString(StandardCharsets.UTF_8).getBytes());
|
||||
}));
|
||||
}
|
||||
return super.writeWith(body);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private ServerHttpRequest getRequest(ServerWebExchange exchange, String customerIp, String requestId, String traceId, String xForwardedPath) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
if (StringUtils.isNotEmpty(xForwardedPath)) {
|
||||
String originXForwardedPath = xForwardedPath;
|
||||
if (xForwardedPath.contains(ApiConstants.API_USER)) {
|
||||
xForwardedPath = "/" + ApiConstants.API_USER + xForwardedPath.split(ApiConstants.API_USER)[1];
|
||||
}
|
||||
log.info("getRequest xForwardedPath is not null originXForwardedPath:{},xForwardedPath:{}",originXForwardedPath, xForwardedPath);
|
||||
request = request.mutate().path(xForwardedPath).build();
|
||||
}
|
||||
|
||||
ServerHttpRequest newRequest = new ServerHttpRequestDecorator(request) {
|
||||
/*@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
Flux<DataBuffer> body = this.getDelegate().getBody();
|
||||
return body.map(dataBuffer -> {
|
||||
log.info("request:{}", dataBuffer.toString(StandardCharsets.UTF_8));
|
||||
return dataBuffer;
|
||||
});
|
||||
}*/
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.putAll(super.getHeaders());
|
||||
httpHeaders.set(HEADER_X_FORWARDED_FOR, customerIp);
|
||||
httpHeaders.set(Log4Constans.IP, customerIp);
|
||||
httpHeaders.set(Log4Constans.TRACE_ID, traceId);
|
||||
return httpHeaders;
|
||||
}
|
||||
};
|
||||
return newRequest;
|
||||
}
|
||||
/**
|
||||
* 获取真实客户端IP
|
||||
* @param serverHttpRequest
|
||||
* @return
|
||||
*/
|
||||
public static String getRealIpAddress(ServerHttpRequest serverHttpRequest) {
|
||||
String ipAddress;
|
||||
try {
|
||||
// 1.根据常见的代理服务器转发的请求ip存放协议,从请求头获取原始请求ip。
|
||||
ipAddress = serverHttpRequest.getHeaders().getFirst(HEADER_X_FORWARDED_FOR);
|
||||
if (StringUtils.isEmpty(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = serverHttpRequest.getHeaders().getFirst(HEADER_PROXY_CLIENT_IP);
|
||||
}
|
||||
if (StringUtils.isEmpty(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
|
||||
ipAddress = serverHttpRequest.getHeaders().getFirst(HEADER_WL_PROXY_CLIENT_IP);
|
||||
}
|
||||
|
||||
// 2.如果没有转发的ip,则取当前通信的请求端的ip
|
||||
if (StringUtils.isEmpty(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
|
||||
InetSocketAddress inetSocketAddress = serverHttpRequest.getRemoteAddress();
|
||||
if(inetSocketAddress != null) {
|
||||
ipAddress = inetSocketAddress.getAddress().getHostAddress();
|
||||
}
|
||||
}
|
||||
|
||||
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
|
||||
// "***.***.***.***"
|
||||
if (ipAddress != null) {
|
||||
ipAddress = ipAddress.split(SEPARATOR)[0].trim();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析请求IP失败", e);
|
||||
ipAddress = "解析IP失败";
|
||||
}
|
||||
return ipAddress == null ? "解析IP失败" : ipAddress;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.heyu.api.gateway.filter;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 全局缓存获取body请求数据(解决流不能重复读取问题).
|
||||
*
|
||||
* @author yangning
|
||||
* @since 2022/12/14 16:07
|
||||
*/
|
||||
@Component
|
||||
public class GlobalCacheRequestFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
// 只缓存json类型请求
|
||||
// if (!WebFluxUtils.isJsonRequest(exchange)) {
|
||||
// return chain.filter(exchange);
|
||||
// }
|
||||
return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> {
|
||||
if (serverHttpRequest == exchange.getRequest()) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
return chain.filter(exchange.mutate().request(serverHttpRequest).build());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
package com.heyu.api.gateway.filter;
|
||||
|
||||
import com.heyu.api.gateway.util.Log4Constans;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import reactor.core.CoreSubscriber;
|
||||
import reactor.core.publisher.Hooks;
|
||||
import reactor.core.publisher.Operators;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* </p>*日志上下文切换配置
|
||||
* @author wulin
|
||||
* @since 2023-08-07
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class LogMdcConfiguration {
|
||||
public static String PRINT_LOG_ID = Log4Constans.TRACE_ID;
|
||||
private int errHashcode;
|
||||
@PostConstruct
|
||||
public void contextOperatorHook() {
|
||||
Hooks.onEachOperator(PRINT_LOG_ID, Operators.lift((r, c) ->{
|
||||
return new MdcContextSubscriber(c);
|
||||
}));
|
||||
}
|
||||
@PreDestroy
|
||||
public void cleanupHook() {
|
||||
Hooks.resetOnEachOperator(PRINT_LOG_ID);
|
||||
}
|
||||
|
||||
class MdcContextSubscriber<T> implements CoreSubscriber<T> {
|
||||
private CoreSubscriber<T> coreSubscriber;
|
||||
public MdcContextSubscriber(CoreSubscriber<T> c){
|
||||
coreSubscriber = c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
coreSubscriber.onComplete();
|
||||
//暂时去掉,让每次切换时更换,即onNext插入
|
||||
//MDC.remove(PRINT_LOG_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
|
||||
if(errHashcode != throwable.hashCode()){
|
||||
errHashcode = throwable.hashCode();
|
||||
log.info("异常{}", throwable);
|
||||
}
|
||||
coreSubscriber.onError(throwable);
|
||||
MDC.remove(PRINT_LOG_ID);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Subscription subscription) {
|
||||
coreSubscriber.onSubscribe(subscription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(T t) {
|
||||
if(coreSubscriber.currentContext().hasKey(PRINT_LOG_ID)){
|
||||
MDC.put(PRINT_LOG_ID, coreSubscriber.currentContext().get(PRINT_LOG_ID));
|
||||
}
|
||||
coreSubscriber.onNext(t);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Context currentContext() {
|
||||
return coreSubscriber.currentContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package com.heyu.api.gateway.handler;
|
||||
|
||||
import com.heyu.api.gateway.util.WebFluxUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 网关统一异常处理.
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
@Order(-1)
|
||||
@Configuration
|
||||
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
final ServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
if (exchange.getResponse().isCommitted()) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
|
||||
String msg;
|
||||
|
||||
if (ex instanceof NotFoundException) {
|
||||
msg = "服务未找到";
|
||||
} else if (ex instanceof ResponseStatusException) {
|
||||
ResponseStatusException responseStatusException = (ResponseStatusException) ex;
|
||||
msg = responseStatusException.getMessage();
|
||||
} else if (ex instanceof RuntimeException) {
|
||||
msg = ex.getMessage();
|
||||
} else {
|
||||
msg = "内部服务器错误";
|
||||
}
|
||||
|
||||
log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage());
|
||||
|
||||
return WebFluxUtils.webFluxResponseWriter(response, msg);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
package com.heyu.api.gateway.util;
|
||||
|
||||
public class ApiConstants {
|
||||
public static final Integer Int_0 = new Integer(0);
|
||||
public static final Integer Int_1 = new Integer(1);
|
||||
public static final Integer Int_2 = new Integer(2);
|
||||
public static final Integer Int_3 = new Integer(3);
|
||||
|
||||
/**
|
||||
* 不需要 注解名称
|
||||
*/
|
||||
public static final String NOT_INTERCEPT_ANNOTATION_NAME = "NotIntercept";
|
||||
/**
|
||||
* token相关的所有信息
|
||||
*/
|
||||
public static final String TOKEN_INFO = "TOKEN_INFO$";
|
||||
|
||||
/**
|
||||
* account 信息
|
||||
*/
|
||||
public static final String ACCOUNT_INFO = "ACCOUNT_INFO$";
|
||||
|
||||
/**
|
||||
* token 一分钟访问次数
|
||||
*/
|
||||
public static final String TOKEN_MINUTES_VISIT_COUNT = "TOKEN_MINUTES_VISIT_COUNT$";
|
||||
|
||||
/**
|
||||
* account账号一分钟访问次数
|
||||
*/
|
||||
public static final String ACCOUNT_MINUTES_VISIT_COUNT = "ACCOUNT_MINUTES_VISIT_COUNT$";
|
||||
|
||||
/**
|
||||
* update account amount lock
|
||||
*/
|
||||
public static final String UPDATE_ACCOUNT_AMOUNT_LOCK = "UPDATE_ACCOUNT_AMOUNT_LOCK$";
|
||||
|
||||
|
||||
public static String SYSTEM = "system";
|
||||
|
||||
public static String USER_NAME = "user_name";
|
||||
|
||||
public static String TOKEN = "token";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 任务调度参数key
|
||||
*/
|
||||
public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";
|
||||
|
||||
|
||||
/**
|
||||
* 定时任务状态
|
||||
*
|
||||
* @author chenshun
|
||||
* @email sunlightcs@gmail.com
|
||||
* @date 2016年12月3日 上午12:07:22
|
||||
*/
|
||||
public enum ScheduleStatus {
|
||||
/**
|
||||
* 正常
|
||||
*/
|
||||
NORMAL(0),
|
||||
/**
|
||||
* 暂停
|
||||
*/
|
||||
PAUSE(1);
|
||||
|
||||
private int value;
|
||||
|
||||
ScheduleStatus(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static final String VERIFY_INTERFACE = "verifyInterface";
|
||||
|
||||
public static final String ECHOBACK = "echoback";
|
||||
public static final String X_FORWARDED_PATH = "X-Forwarded-Path";
|
||||
|
||||
public static final String API_USER = "api-user";
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.heyu.api.gateway.util;
|
||||
|
||||
public class Log4Constans {
|
||||
|
||||
/**
|
||||
* traceId ,日志编号
|
||||
*/
|
||||
public static String TRACE_ID = "logid";
|
||||
|
||||
/**
|
||||
* ip 值
|
||||
*/
|
||||
public static String IP = "request_ip";
|
||||
|
||||
|
||||
/**
|
||||
* 请求uri
|
||||
*/
|
||||
public static String URI = "request_uri";
|
||||
|
||||
/**
|
||||
* 耗时
|
||||
*/
|
||||
public static String START_TIME = "start_time";
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package com.heyu.api.gateway.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
|
||||
/**
|
||||
* 订单号生成工具
|
||||
* 枚举相关信息,查看 https://www.showdoc.cc/web/#/page/495445295769339
|
||||
*/
|
||||
@Slf4j
|
||||
public class OrderUtils {
|
||||
|
||||
public static String getUserPoolOrder() {
|
||||
SimpleDateFormat dateformat = new SimpleDateFormat("SSSyyyyMMddHHmmss");
|
||||
StringBuffer sb = new StringBuffer();
|
||||
return sb.
|
||||
append((int) (Math.random() * 1000)).append(dateformat.format(System.currentTimeMillis())).toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static String getOrderNo(String pre) {
|
||||
SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
|
||||
StringBuffer sb = new StringBuffer(pre);
|
||||
return sb.
|
||||
append((int) (Math.random() * 1000)).append(dateformat.format(System.currentTimeMillis())).toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.heyu.api.gateway.util;
|
||||
|
||||
/**
|
||||
* XXX
|
||||
*
|
||||
* @author weiyachao 包含
|
||||
* @since 2023/9/20 16:25
|
||||
*/
|
||||
public interface UserAuthContains {
|
||||
|
||||
String API_TOKEN = "Api-Token";
|
||||
|
||||
String API_TYPE = "Api-Type";
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
package com.heyu.api.gateway.util;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import java.util.HashMap;
|
||||
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 org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* WebFlux 工具类.
|
||||
*/
|
||||
public class WebFluxUtils {
|
||||
|
||||
/**
|
||||
* 是否是Json请求.
|
||||
*
|
||||
* @param exchange HTTP请求
|
||||
*/
|
||||
public static boolean isJsonRequest(ServerWebExchange exchange) {
|
||||
String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
|
||||
return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置webflux模型响应.
|
||||
*
|
||||
* @param response ServerHttpResponse
|
||||
* @param value 响应内容
|
||||
* @return Mono
|
||||
*/
|
||||
public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value) {
|
||||
return webFluxResponseWriter(response, HttpStatus.OK, value, HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置webflux模型响应.
|
||||
*
|
||||
* @param response ServerHttpResponse
|
||||
* @param code 响应状态码
|
||||
* @param value 响应内容
|
||||
* @return Mono
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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);
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("message", value.toString());
|
||||
map.put("status", code);
|
||||
map.put("code", "500");
|
||||
map.put("timestamp", System.currentTimeMillis());
|
||||
DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(map).getBytes());
|
||||
return response.writeWith(Mono.just(dataBuffer));
|
||||
}
|
||||
}
|
||||
84
api-gateway/src/main/resources/bootstrap-dev.yml
Normal file
84
api-gateway/src/main/resources/bootstrap-dev.yml
Normal file
@ -0,0 +1,84 @@
|
||||
spring:
|
||||
cloud:
|
||||
inetutils:
|
||||
preferredNetworks:
|
||||
- 192.168 #注册到nacos中心优先匹配的IP
|
||||
gateway:
|
||||
routes:
|
||||
- id: heyu-api-user-api
|
||||
uri: lb://heyu-api-user-api
|
||||
predicates:
|
||||
- Path=/api-user/**
|
||||
filters:
|
||||
# 转发时去掉一层路径
|
||||
- StripPrefix=1
|
||||
- id: heyu-api-websocket
|
||||
uri: lb://heyu-api-websocket
|
||||
predicates:
|
||||
- Path=/api-websocket/**
|
||||
filters:
|
||||
# 转发时去掉一层路径
|
||||
- StripPrefix=1
|
||||
globalcors:
|
||||
corsConfigurations:
|
||||
'[/**]':
|
||||
# 允许携带认证信息
|
||||
allow-credentials: true
|
||||
# 允许跨域的源(网站域名/ip),设置*为全部
|
||||
allowedOriginPatterns: "*"
|
||||
# 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
|
||||
allowedMethods: "*"
|
||||
# 允许跨域请求里的head字段,设置*为全部
|
||||
allowedHeaders: "*"
|
||||
config:
|
||||
# 如果本地配置优先级高,那么 override-none 设置为 true,包括系统环境变量、本地配置文件等配置
|
||||
override-none: true
|
||||
# 如果想要远程配置优先级高,那么 allow-override 设置为 false,如果想要本地配置优先级高那么 allow-override 设置为 true
|
||||
allow-override: true
|
||||
# 只有系统环境变量或者系统属性才能覆盖远程配置文件的配置,本地配置文件中配置优先级低于远程配置;注意本地配置文件不是系统属性
|
||||
override-system-properties: false
|
||||
nacos:
|
||||
discovery:
|
||||
# 服务注册地址
|
||||
server-addr: nacos.iwulin.tech:30571
|
||||
namespace: ${spring.profiles.active}
|
||||
password: hz_HeYu@202518
|
||||
config:
|
||||
namespace: ${spring.profiles.active}
|
||||
# 配置中心地址
|
||||
server-addr: nacos.iwulin.tech:30571
|
||||
password: hz_HeYu@202518
|
||||
# 配置文件格式
|
||||
file-extension: yml
|
||||
# 共享配置
|
||||
#shared-configs:
|
||||
# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
|
||||
redis:
|
||||
password: O^pv_3#slxQ
|
||||
port: 32048
|
||||
host: rrdd.iwulin.tech
|
||||
timeout: 5000
|
||||
# 安全配置
|
||||
security:
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
enabled: false
|
||||
# 排除的路径
|
||||
exclude-urls:
|
||||
- /api-user/user/login/pwd
|
||||
- /api-user/ip/api/getIp
|
||||
- /api-websocket/push/message
|
||||
- /api-websocket/websocket/box
|
||||
- /api-websocket/websocket/customer
|
||||
- /api-websocket/websocket/tts/token
|
||||
- /api-websocket/websocket/init/sysTalkAnswer
|
||||
# ip免登白名单
|
||||
auth-ip: #127.0.0.1,192.168.8.141
|
||||
#免登,免校验url
|
||||
auth-urls:
|
||||
- /actuator
|
||||
- /websocket
|
||||
#application:
|
||||
# cors:
|
||||
# allowed-crigin-patterns:
|
||||
# - /api-user/user/user/login/pwd
|
||||
18
api-gateway/src/main/resources/bootstrap-online.yml
Normal file
18
api-gateway/src/main/resources/bootstrap-online.yml
Normal file
@ -0,0 +1,18 @@
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
# 服务注册地址
|
||||
server-addr: 127.0.0.1:8848
|
||||
namespace: ${spring.profiles.active}
|
||||
#password: hz_HeYu@202518
|
||||
config:
|
||||
# 配置中心地址
|
||||
server-addr: 127.0.0.1:8848
|
||||
namespace: ${spring.profiles.active}
|
||||
#password: hz_HeYu@202518
|
||||
# 配置文件格式
|
||||
file-extension: yml
|
||||
# 共享配置
|
||||
#shared-configs:
|
||||
# - application.${spring.cloud.nacos.config.file-extension}
|
||||
32
api-gateway/src/main/resources/bootstrap.yml
Normal file
32
api-gateway/src/main/resources/bootstrap.yml
Normal file
@ -0,0 +1,32 @@
|
||||
server:
|
||||
port: 8080
|
||||
spring:
|
||||
application:
|
||||
name: heyu-api-gateway
|
||||
profiles:
|
||||
active: dev
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
allow-circular-references: true
|
||||
|
||||
|
||||
|
||||
zuul:
|
||||
ribbon:
|
||||
eager-load:
|
||||
enabled: true
|
||||
clients: heyu-api-user-api
|
||||
ignoredServices: '*'
|
||||
host:
|
||||
connect-timeout-millis: 10000
|
||||
socket-timeout-millis: 10000
|
||||
ratelimit:
|
||||
key-prefix: pig-ratelimite
|
||||
enabled: true
|
||||
repository: REDIS
|
||||
behind-proxy: true
|
||||
policies:
|
||||
sina-upms-service:
|
||||
limit: 100
|
||||
quota: 100
|
||||
refresh-interval: 3
|
||||
85
api-gateway/src/main/resources/logback-dev.xml
Normal file
85
api-gateway/src/main/resources/logback-dev.xml
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="true">
|
||||
<!-- 日志存放路径 -->
|
||||
<property name="log.path" value="${user.home}/logs/gateway-api" />
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] [%X{logid}]- %msg%n" />
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 系统日志输出 -->
|
||||
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/all.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd}_all.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>INFO</level>
|
||||
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- 系统日志输出 -->
|
||||
<appender name="file_warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/warn.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd}_wran.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>WARN</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/error.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd}_error.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>ERROR</level>
|
||||
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!--系统操作日志-->
|
||||
<root level="info">
|
||||
<appender-ref ref="file_info" />
|
||||
<appender-ref ref="file_error" />
|
||||
<appender-ref ref="file_warn" />
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
85
api-gateway/src/main/resources/logback-prod.xml
Normal file
85
api-gateway/src/main/resources/logback-prod.xml
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<!-- 日志存放路径 -->
|
||||
<property name="log.path" value="${user.home}/logs/gateway-api" />
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] [%X{logid}]- %msg%n" />
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 系统日志输出 -->
|
||||
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/all.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd}_all.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>INFO</level>
|
||||
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- 系统日志输出 -->
|
||||
<appender name="file_warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/warn.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd}_wran.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>WARN</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/error.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd}_error.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>ERROR</level>
|
||||
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!--系统操作日志-->
|
||||
<root level="info">
|
||||
<appender-ref ref="file_info" />
|
||||
<appender-ref ref="file_error" />
|
||||
<appender-ref ref="file_warn" />
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
84
api-gateway/src/main/resources/logback-test.xml
Normal file
84
api-gateway/src/main/resources/logback-test.xml
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
<!-- 日志存放路径 -->
|
||||
<property name="log.path" value="${user.home}/logs/gateway-api" />
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] [%X{logid}] - %msg%n" />
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 系统日志输出 -->
|
||||
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/all.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd}_all.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>INFO</level>
|
||||
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- 系统日志输出 -->
|
||||
<appender name="file_warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/warn.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd}_wran.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>WARN</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${log.path}/error.log</file>
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/%d{yyyy-MM-dd}_error.log</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>15</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
<charset>UTF-8</charset> <!-- 设置字符集 -->
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<!-- 过滤的级别 -->
|
||||
<level>ERROR</level>
|
||||
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!--系统操作日志-->
|
||||
<root level="info">
|
||||
<appender-ref ref="file_info" />
|
||||
<appender-ref ref="file_error" />
|
||||
<appender-ref ref="file_warn" />
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
Loading…
x
Reference in New Issue
Block a user