新增网关项目

This commit is contained in:
wulin 2025-03-24 21:15:41 +08:00
parent 05529b64a1
commit c103825998
28 changed files with 1560 additions and 0 deletions

99
api-gateway/pom.xml Normal file
View 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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接口
*
* 默认实现的随机负载轮训负载可查看RandomLoadBalancerRoundRobinLoadBalancer类
*
*/
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);
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
package com.heyu.api.gateway.config.properties;
public class LoadBalanceProperties {
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

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

View 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

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

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

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

View File

@ -21,6 +21,7 @@
<module>api-mapper</module>
<module>api-third</module>
<module>api-web</module>
<module>api-gateway</module>
</modules>
<version>0.0.1-SNAPSHOT</version>
<description>易借消息服务</description>