mirror of
https://gitee.com/orangeform/orange-admin.git
synced 2026-01-17 18:46:36 +08:00
重命名 orange-demo-uaa 为 orange-demo-multi-uaa
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package com.orangeforms.gateway;
|
||||
|
||||
import com.orangeforms.common.core.util.ApplicationContextHolder;
|
||||
import com.orangeforms.gateway.filter.AuthenticationPostFilter;
|
||||
import com.orangeforms.gateway.filter.AuthenticationPreFilter;
|
||||
import com.orangeforms.gateway.filter.RequestLogFilter;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.cloud.client.SpringCloudApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 网关服务启动类。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
|
||||
@SpringCloudApplication
|
||||
public class GatewayApplication {
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/fallback")
|
||||
static class FallbackController {
|
||||
@GetMapping("")
|
||||
public String fallback() {
|
||||
return "GATEWAY FALLBACK!!!";
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationPreFilter authenticationPreFilter() {
|
||||
return new AuthenticationPreFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationPostFilter authenticationPostFilter() {
|
||||
return new AuthenticationPostFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RequestLogFilter requestLogPreFilter() {
|
||||
return new RequestLogFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApplicationContextHolder applicationContextHolder() {
|
||||
return new ApplicationContextHolder();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.orangeforms.gateway.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 网关业务配置类。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Data
|
||||
@RefreshScope
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "application")
|
||||
public class ApplicationConfig {
|
||||
|
||||
/**
|
||||
* token加密用的密钥,该值的长度最少10个字符(过短会报错)。
|
||||
*/
|
||||
private String tokenSigningKey;
|
||||
/**
|
||||
* 客户端或者浏览器在提交http请求时,携带token的header name,如 Authorization
|
||||
*/
|
||||
private String tokenHeaderKey;
|
||||
/**
|
||||
* 令牌Token在被刷新之后,服务器Http应答的header name,客户端或浏览器需要保存并替换原有的token,用于下次发送时携带
|
||||
*/
|
||||
private String refreshedTokenHeaderKey;
|
||||
/**
|
||||
* 令牌的过期时间,单位毫秒
|
||||
*/
|
||||
private Long expiration;
|
||||
/**
|
||||
* 授信ip列表,没有填写表示全部信任。多个ip之间逗号分隔,如: http://10.10.10.1:8080,http://10.10.10.2:8080
|
||||
*/
|
||||
private String credentialIpList;
|
||||
/**
|
||||
* Session会话和用户权限在Redis中的过期时间(秒)。
|
||||
* 缺省值是 one day
|
||||
*/
|
||||
private int sessionExpiredSeconds = 86400;
|
||||
/**
|
||||
* 基于完全等于(equals)判定规则的白名单地址集合,过滤效率高于whitelistUrlPattern。
|
||||
*/
|
||||
private Set<String> whitelistUrl;
|
||||
/**
|
||||
* 基于Ant Pattern模式判定规则的白名单地址集合。如:/aa/**。
|
||||
*/
|
||||
private Set<String> whitelistUrlPattern;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.orangeforms.gateway.config;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.reactive.CorsWebFilter;
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* 跨域信任配置类。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig {
|
||||
|
||||
@Bean
|
||||
public CorsWebFilter corsFilter(ApplicationConfig appConfig) {
|
||||
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(new PathPatternParser());
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
if (StringUtils.isNotBlank(appConfig.getCredentialIpList())) {
|
||||
String[] credentialIpList = StringUtils.split(appConfig.getCredentialIpList(), ",");
|
||||
if (credentialIpList.length > 0) {
|
||||
for (String ip : credentialIpList) {
|
||||
config.addAllowedOrigin(ip);
|
||||
}
|
||||
}
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
config.addExposedHeader(appConfig.getRefreshedTokenHeaderKey());
|
||||
config.setAllowCredentials(true);
|
||||
configSource.registerCorsConfiguration("/**", config);
|
||||
}
|
||||
return new CorsWebFilter(configSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.orangeforms.gateway.config;
|
||||
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Web通用过滤器配置类。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Configuration
|
||||
public class FilterConfig {
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<Filter> characterEncodingFilterRegistration() {
|
||||
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>(
|
||||
new org.springframework.web.filter.CharacterEncodingFilter());
|
||||
filterRegistrationBean.addUrlPatterns("/*");
|
||||
filterRegistrationBean.addInitParameter("encoding", StandardCharsets.UTF_8.name());
|
||||
// forceEncoding强制response也被编码,另外即使request中已经设置encoding,forceEncoding也会重新设置
|
||||
filterRegistrationBean.addInitParameter("forceEncoding", "true");
|
||||
filterRegistrationBean.setAsyncSupported(true);
|
||||
return filterRegistrationBean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.orangeforms.gateway.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Spring Cloud Gateway的Sentinel流控配置类。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Configuration
|
||||
public class SentinelConfig {
|
||||
|
||||
private final List<ViewResolver> viewResolvers;
|
||||
private final ServerCodecConfigurer serverCodecConfigurer;
|
||||
|
||||
public SentinelConfig(
|
||||
ObjectProvider<List<ViewResolver>> viewResolversProvider,
|
||||
ServerCodecConfigurer serverCodecConfigurer) {
|
||||
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
|
||||
this.serverCodecConfigurer = serverCodecConfigurer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
|
||||
// Register the block exception handler for Spring Cloud Gateway.
|
||||
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public GlobalFilter sentinelGatewayFilter() {
|
||||
return new SentinelGatewayFilter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.orangeforms.gateway.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||
import org.springframework.cloud.gateway.support.NameUtils;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
import springfox.documentation.swagger.web.SwaggerResource;
|
||||
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/***
|
||||
* 返回Swagger UI需要读取的资源数据,这里是微服务的路由数据。
|
||||
*
|
||||
* @author Knife4j Team。
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Primary
|
||||
@AllArgsConstructor
|
||||
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
|
||||
|
||||
private final RouteLocator routeLocator;
|
||||
private final GatewayProperties gatewayProperties;
|
||||
|
||||
@Override
|
||||
public List<SwaggerResource> get() {
|
||||
List<SwaggerResource> resources = new ArrayList<>();
|
||||
List<String> routes = new ArrayList<>();
|
||||
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
|
||||
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
|
||||
.forEach(route -> route.getPredicates().stream()
|
||||
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
|
||||
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
|
||||
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
|
||||
.replace("**", "v2/api-docs")))));
|
||||
return resources;
|
||||
}
|
||||
|
||||
private SwaggerResource swaggerResource(String name, String location) {
|
||||
SwaggerResource swaggerResource = new SwaggerResource();
|
||||
swaggerResource.setName(name);
|
||||
swaggerResource.setLocation(location);
|
||||
swaggerResource.setSwaggerVersion("2.0");
|
||||
return swaggerResource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.orangeforms.gateway.constant;
|
||||
|
||||
/**
|
||||
* 网关业务相关的常量对象。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
public final class GatewayConstant {
|
||||
|
||||
/**
|
||||
* 请求进入网关的开始时间。
|
||||
*/
|
||||
public static final String START_TIME_ATTRIBUTE = "startTime";
|
||||
|
||||
/**
|
||||
* 登录URL。
|
||||
*/
|
||||
public static final String ADMIN_LOGIN_URL = "/admin/upms/login/doLogin";
|
||||
|
||||
/**
|
||||
* UAA登录URL。
|
||||
*/
|
||||
public static final String ADMIN_LOGIN_BY_UAA_URL = "/admin/upms/login/doLoginByUaa";
|
||||
|
||||
/**
|
||||
* 获取UAA登录验证的重定向URL。
|
||||
*/
|
||||
public static final String GET_UAA_LOGIN_URL = "/admin/upms/login/getUaaLoginUrl";
|
||||
|
||||
/**
|
||||
* 登出URL。
|
||||
*/
|
||||
public static final String ADMIN_LOGOUT_URL = "/admin/upms/login/doLogout";
|
||||
|
||||
/**
|
||||
* sessionId的键名称。
|
||||
*/
|
||||
public static final String SESSION_ID_KEY_NAME = "sessionId";
|
||||
|
||||
/**
|
||||
* 私有构造函数,明确标识该常量类的作用。
|
||||
*/
|
||||
private GatewayConstant() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package com.orangeforms.gateway.filter;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.orangeforms.common.core.constant.ErrorCodeEnum;
|
||||
import com.orangeforms.common.core.object.ResponseResult;
|
||||
import com.orangeforms.common.core.object.TokenData;
|
||||
import com.orangeforms.common.core.util.JwtUtil;
|
||||
import com.orangeforms.common.core.util.MyCommonUtil;
|
||||
import com.orangeforms.common.core.util.RedisKeyUtil;
|
||||
import com.orangeforms.gateway.config.ApplicationConfig;
|
||||
import com.orangeforms.gateway.constant.GatewayConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局后处理过滤器。主要用于将用户的会话信息存到缓存服务器,以及在登出时清除缓存中的会话数据。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Slf4j
|
||||
public class AuthenticationPostFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Autowired
|
||||
private ApplicationConfig appConfig;
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest originalRequest = exchange.getRequest();
|
||||
ServerHttpResponse originalResponse = exchange.getResponse();
|
||||
String refreshedToken =
|
||||
(String) exchange.getAttributes().get(appConfig.getRefreshedTokenHeaderKey());
|
||||
if (refreshedToken != null) {
|
||||
originalResponse.getHeaders().add(appConfig.getRefreshedTokenHeaderKey(), refreshedToken);
|
||||
}
|
||||
if (!originalRequest.getURI().getPath().equals(GatewayConstant.ADMIN_LOGIN_URL)
|
||||
&& !originalRequest.getURI().getPath().equals(GatewayConstant.ADMIN_LOGIN_BY_UAA_URL)
|
||||
&& !originalRequest.getURI().getPath().equals(GatewayConstant.ADMIN_LOGOUT_URL)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
|
||||
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Mono<Void> writeWith(@NonNull Publisher<? extends DataBuffer> bodyData) {
|
||||
StringBuilder sb = new StringBuilder(128);
|
||||
sb.append("url: ")
|
||||
.append(originalRequest.getURI().getPath())
|
||||
.append(" -- status: ")
|
||||
.append(getStatusCode());
|
||||
if (getStatusCode() != HttpStatus.OK) {
|
||||
log.error(sb.toString());
|
||||
return super.writeWith(bodyData);
|
||||
}
|
||||
if (!(bodyData instanceof Flux)) {
|
||||
return super.writeWith(bodyData);
|
||||
}
|
||||
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) bodyData;
|
||||
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
|
||||
// 读取完整的服务应答消息体。
|
||||
String responseBody = readResponseBody(dataBuffers);
|
||||
originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
// 先判断body中是否包含数据。
|
||||
if (StringUtils.isBlank(responseBody)) {
|
||||
sb.append(" -- Internal Error, no RESPONSE DATA returns !!");
|
||||
log.error(sb.toString());
|
||||
String errorMessage = "后台服务没有任何数据返回!";
|
||||
responseBody = JSON.toJSONString(
|
||||
ResponseResult.error(ErrorCodeEnum.SERVER_INTERNAL_ERROR, errorMessage));
|
||||
byte[] uppedContent = new String(responseBody.getBytes(), StandardCharsets.UTF_8).getBytes();
|
||||
originalResponse.getHeaders().setContentLength(uppedContent.length);
|
||||
return bufferFactory.wrap(uppedContent);
|
||||
}
|
||||
// 处理登录和登出请求。
|
||||
String result;
|
||||
try {
|
||||
result = doProcess(exchange, responseBody);
|
||||
} catch (Exception e) {
|
||||
setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
String errorMsg = "Server Internal Error";
|
||||
sb.append(errorMsg);
|
||||
log.error(sb.toString(), e);
|
||||
result = JSON.toJSONString(
|
||||
ResponseResult.error(ErrorCodeEnum.SERVER_INTERNAL_ERROR, errorMsg));
|
||||
}
|
||||
byte[] uppedContent = new String(result.getBytes(), StandardCharsets.UTF_8).getBytes();
|
||||
originalResponse.getHeaders().setContentLength(uppedContent.length);
|
||||
return bufferFactory.wrap(uppedContent);
|
||||
}));
|
||||
}
|
||||
};
|
||||
return chain.filter(exchange.mutate().response(decoratedResponse).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回过滤器在在调用链上的优先级。
|
||||
*
|
||||
* @return 数值越低,优先级越高。
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
// -1 is response write filter, must be called before that
|
||||
return -2;
|
||||
}
|
||||
|
||||
private String readResponseBody(List<? extends DataBuffer> dataBuffers) {
|
||||
int dataCount = 0;
|
||||
for (DataBuffer dataBuffer : dataBuffers) {
|
||||
dataCount += dataBuffer.readableByteCount();
|
||||
}
|
||||
byte[] allBytes = new byte[dataCount];
|
||||
int offset = 0;
|
||||
for (DataBuffer dataBuffer : dataBuffers) {
|
||||
int length = dataBuffer.readableByteCount();
|
||||
dataBuffer.read(allBytes, offset, length);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
offset += length;
|
||||
}
|
||||
return new String(allBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private String doProcess(ServerWebExchange exchange, String responseBody) {
|
||||
// 这个解析出来的就是upms登录或登出接口返回的ResponseResult对象。
|
||||
ServerHttpRequest originalRequest = exchange.getRequest();
|
||||
if (originalRequest.getURI().getPath().equals(GatewayConstant.ADMIN_LOGIN_BY_UAA_URL)
|
||||
|| originalRequest.getURI().getPath().equals(GatewayConstant.ADMIN_LOGIN_URL)) {
|
||||
// 处理登录服务的消息体,同时重构该消息体,并最终返回前端。
|
||||
ResponseResult<JSONObject> result = processLoginResponse(responseBody);
|
||||
return JSON.toJSONString(result);
|
||||
}
|
||||
if (originalRequest.getURI().getPath().equals(GatewayConstant.ADMIN_LOGOUT_URL)) {
|
||||
ResponseResult<Void> result = JSON.parseObject(responseBody, ResponseResult.class);
|
||||
if (result.isSuccess()) {
|
||||
String sessionId = (String) exchange.getAttributes().get(GatewayConstant.SESSION_ID_KEY_NAME);
|
||||
redissonClient.getBucket(RedisKeyUtil.makeSessionIdKey(sessionId)).deleteAsync();
|
||||
redissonClient.getSet(RedisKeyUtil.makeSessionPermIdKey(sessionId)).deleteAsync();
|
||||
}
|
||||
return responseBody;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ResponseResult<JSONObject> processLoginResponse(String responseBody) {
|
||||
ResponseResult<JSONObject> responseResult = JSON.parseObject(responseBody, ResponseResult.class);
|
||||
if (!responseResult.isSuccess()) {
|
||||
return responseResult;
|
||||
}
|
||||
JSONObject loginData = responseResult.getData();
|
||||
// 1. 先验证登陆服务器返回的应答数据是否正确
|
||||
JSONObject tokenData = loginData.getJSONObject(TokenData.REQUEST_ATTRIBUTE_NAME);
|
||||
ErrorCodeEnum errorCode = ErrorCodeEnum.SERVER_INTERNAL_ERROR;
|
||||
if (tokenData == null) {
|
||||
return ResponseResult.error(errorCode, "内部错误,用户登录令牌对象没有正确返回!");
|
||||
}
|
||||
Long userId = tokenData.getLong("userId");
|
||||
if (MyCommonUtil.isBlankOrNull(userId)) {
|
||||
return ResponseResult.error(errorCode, "内部错误,用户Id没有正确返回!");
|
||||
}
|
||||
Boolean isAdmin = tokenData.getBoolean("isAdmin");
|
||||
if (isAdmin == null) {
|
||||
return ResponseResult.error(errorCode, "内部错误,是否为管理员标记没有正确返回!");
|
||||
}
|
||||
String showName = tokenData.getString("showName");
|
||||
if (StringUtils.isBlank(showName)) {
|
||||
return ResponseResult.error(errorCode, "内部错误,用户显示名没有正确返回!");
|
||||
}
|
||||
String loginName = tokenData.getString("loginName");
|
||||
if (StringUtils.isBlank(loginName)) {
|
||||
return ResponseResult.error(errorCode, "内部错误,用户登录名没有正确返回!");
|
||||
}
|
||||
String sessionId = tokenData.getString("sessionId");
|
||||
if (StringUtils.isBlank(sessionId)) {
|
||||
return ResponseResult.error(errorCode, "内部错误,SESSION_ID没有正确返回!");
|
||||
}
|
||||
// 2. 生成sessionId并存放到token中
|
||||
Map<String, Object> claims = new HashMap<>(1);
|
||||
claims.put(GatewayConstant.SESSION_ID_KEY_NAME, sessionId);
|
||||
String token = JwtUtil.generateToken(claims, appConfig.getExpiration(), appConfig.getTokenSigningKey());
|
||||
// 3. 更新缓存
|
||||
String sessionIdKey = RedisKeyUtil.makeSessionIdKey(sessionId);
|
||||
String sessionData = JSON.toJSONString(tokenData, SerializerFeature.WriteNonStringValueAsString);
|
||||
RBucket<String> bucket = redissonClient.getBucket(sessionIdKey);
|
||||
bucket.set(sessionData);
|
||||
bucket.expire(appConfig.getSessionExpiredSeconds(), TimeUnit.SECONDS);
|
||||
// 3.2 sessionId -> permList 是set结构的缓存
|
||||
JSONArray permSet = loginData.getJSONArray("permSet");
|
||||
if (permSet != null) {
|
||||
String sessionPermKey = RedisKeyUtil.makeSessionPermIdKey(sessionId);
|
||||
RSet<String> redisPermSet = redissonClient.getSet(sessionPermKey);
|
||||
redisPermSet.addAll(permSet.stream().map(Object::toString).collect(Collectors.toSet()));
|
||||
redisPermSet.expire(appConfig.getSessionExpiredSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
// 4. 构造返回给用户的应答,将加密后的令牌返回给前端。
|
||||
loginData.put(TokenData.REQUEST_ATTRIBUTE_NAME, token);
|
||||
// 5. 这里需要移除权限资源集合的数据,验证在后端进行,无需返回给前端。
|
||||
loginData.remove("permSet");
|
||||
return ResponseResult.success(loginData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.orangeforms.gateway.filter;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.orangeforms.common.core.constant.ErrorCodeEnum;
|
||||
import com.orangeforms.common.core.object.ResponseResult;
|
||||
import com.orangeforms.common.core.object.TokenData;
|
||||
import com.orangeforms.common.core.util.JwtUtil;
|
||||
import com.orangeforms.common.core.util.RedisKeyUtil;
|
||||
import com.orangeforms.common.core.util.IpUtil;
|
||||
import com.orangeforms.gateway.config.ApplicationConfig;
|
||||
import com.orangeforms.gateway.constant.GatewayConstant;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 全局前处理过滤器。主要用于用户操作权限验证。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Slf4j
|
||||
public class AuthenticationPreFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Autowired
|
||||
private ApplicationConfig appConfig;
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
/**
|
||||
* Ant Pattern模式的白名单地址匹配器。
|
||||
*/
|
||||
private final AntPathMatcher antMatcher = new AntPathMatcher();
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
String url = request.getURI().getPath();
|
||||
// 判断是否为白名单请求,以及一些内置不需要验证的请求。(登录请求也包含其中)。
|
||||
if (this.shouldNotFilter(url)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
String token = this.getTokenFromRequest(request);
|
||||
Claims c = JwtUtil.parseToken(token, appConfig.getTokenSigningKey());
|
||||
if (JwtUtil.isNullOrExpired(c)) {
|
||||
log.warn("EXPIRED request [{}] from REMOTE-IP [{}].", url, IpUtil.getRemoteIpAddress(request));
|
||||
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
byte[] responseBody = JSON.toJSONString(ResponseResult.error(ErrorCodeEnum.UNAUTHORIZED_LOGIN,
|
||||
"用户登录已过期或尚未登录,请重新登录!")).getBytes(StandardCharsets.UTF_8);
|
||||
return response.writeWith(Flux.just(response.bufferFactory().wrap(responseBody)));
|
||||
}
|
||||
// 这里判断是否需要定时刷新token
|
||||
if (JwtUtil.needToRefresh(c)) {
|
||||
exchange.getAttributes().put(appConfig.getRefreshedTokenHeaderKey(),
|
||||
JwtUtil.generateToken(c, appConfig.getExpiration(), appConfig.getTokenSigningKey()));
|
||||
}
|
||||
// 先基于sessionId获取userInfo
|
||||
String sessionId = (String) c.get(GatewayConstant.SESSION_ID_KEY_NAME);
|
||||
String sessionIdKey = RedisKeyUtil.makeSessionIdKey(sessionId);
|
||||
RBucket<String> sessionData = redissonClient.getBucket(sessionIdKey);
|
||||
JSONObject tokenData = null;
|
||||
if (sessionData.isExists()) {
|
||||
tokenData = JSON.parseObject(sessionData.get());
|
||||
}
|
||||
if (tokenData == null) {
|
||||
log.warn("UNAUTHORIZED request [{}] from REMOTE-IP [{}] because no sessionId exists in redis.",
|
||||
url, IpUtil.getRemoteIpAddress(request));
|
||||
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
byte[] responseBody = JSON.toJSONString(ResponseResult.error(ErrorCodeEnum.UNAUTHORIZED_LOGIN,
|
||||
"用户会话已失效,请重新登录!")).getBytes(StandardCharsets.UTF_8);
|
||||
return response.writeWith(Flux.just(response.bufferFactory().wrap(responseBody)));
|
||||
}
|
||||
String userId = tokenData.getString("userId");
|
||||
if (StringUtils.isBlank(userId)) {
|
||||
log.warn("UNAUTHORIZED request [{}] from REMOTE-IP [{}] because userId is empty in redis.",
|
||||
url, IpUtil.getRemoteIpAddress(request));
|
||||
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
byte[] responseBody = JSON.toJSONString(ResponseResult.error(ErrorCodeEnum.UNAUTHORIZED_LOGIN,
|
||||
"用户登录验证信息已过期,请重新登录!")).getBytes(StandardCharsets.UTF_8);
|
||||
return response.writeWith(Flux.just(response.bufferFactory().wrap(responseBody)));
|
||||
}
|
||||
String showName = tokenData.getString("showName");
|
||||
// 因为http header中不支持中文传输,所以需要编码。
|
||||
try {
|
||||
showName = URLEncoder.encode(showName, StandardCharsets.UTF_8.name());
|
||||
tokenData.put("showName", showName);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
log.error("Failed to call AuthenticationPreFilter.filter.", e);
|
||||
}
|
||||
boolean isAdmin = tokenData.getBoolean("isAdmin");
|
||||
if (Boolean.FALSE.equals(isAdmin) && !this.hasPermission(redissonClient, sessionId, url)) {
|
||||
log.warn("FORBIDDEN request [{}] from REMOTE-IP [{}] for USER [{} -- {}] no perm!",
|
||||
url, IpUtil.getRemoteIpAddress(request), userId, showName);
|
||||
response.setStatusCode(HttpStatus.FORBIDDEN);
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
byte[] responseBody = JSON.toJSONString(ResponseResult.error(ErrorCodeEnum.NO_OPERATION_PERMISSION,
|
||||
"用户对该URL没有访问权限,请核对!")).getBytes(StandardCharsets.UTF_8);
|
||||
return response.writeWith(Flux.just(response.bufferFactory().wrap(responseBody)));
|
||||
}
|
||||
// 将session中关联的用户信息,添加到当前的Request中。转发后,业务服务可以根据需要自定读取。
|
||||
tokenData.put("sessionId", sessionId);
|
||||
exchange.getAttributes().put(GatewayConstant.SESSION_ID_KEY_NAME, sessionId);
|
||||
ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(
|
||||
TokenData.REQUEST_ATTRIBUTE_NAME, tokenData.toJSONString()).build();
|
||||
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
|
||||
return chain.filter(mutableExchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回过滤器在在调用链上的优先级。
|
||||
*
|
||||
* @return 数值越低,优先级越高。
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return HIGHEST_PRECEDENCE + 10000;
|
||||
}
|
||||
|
||||
private String getTokenFromRequest(ServerHttpRequest request) {
|
||||
String token = request.getHeaders().getFirst(appConfig.getTokenHeaderKey());
|
||||
if (StringUtils.isBlank(token)) {
|
||||
token = request.getQueryParams().getFirst(appConfig.getTokenHeaderKey());
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private boolean hasPermission(RedissonClient redissonClient, String sessionId, String url) {
|
||||
// 对于退出登录操作,不需要进行权限验证,仅仅确认是已经登录的合法用户即可。
|
||||
if (url.equals(GatewayConstant.ADMIN_LOGOUT_URL)) {
|
||||
return true;
|
||||
}
|
||||
String permKey = RedisKeyUtil.makeSessionPermIdKey(sessionId);
|
||||
return redissonClient.getSet(permKey).contains(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前请求的url是否为配置中的白名单地址。以及一些内置的不需要登录即可访问的url。
|
||||
* @param url 请求的url。
|
||||
* @return 是返回true,否则false。
|
||||
*/
|
||||
private boolean shouldNotFilter(String url) {
|
||||
// 这里过滤和swagger相关的url
|
||||
if (url.endsWith("/v2/api-docs") || url.endsWith("/v2/api-docs-ext")) {
|
||||
return true;
|
||||
}
|
||||
if (url.equals(GatewayConstant.ADMIN_LOGIN_BY_UAA_URL)
|
||||
|| url.equals(GatewayConstant.GET_UAA_LOGIN_URL)
|
||||
|| url.equals(GatewayConstant.ADMIN_LOGIN_URL)) {
|
||||
return true;
|
||||
}
|
||||
// 先过滤直接匹配的白名单url。
|
||||
if (CollectionUtils.isNotEmpty(appConfig.getWhitelistUrl())) {
|
||||
if (appConfig.getWhitelistUrl().contains(url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 过滤ant pattern模式的白名单url。
|
||||
if (CollectionUtils.isNotEmpty(appConfig.getWhitelistUrlPattern())) {
|
||||
for (String urlPattern : appConfig.getWhitelistUrlPattern()) {
|
||||
if (antMatcher.match(urlPattern, url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.orangeforms.gateway.filter;
|
||||
|
||||
import com.orangeforms.common.core.constant.ApplicationConstant;
|
||||
import com.orangeforms.common.core.util.MyCommonUtil;
|
||||
import com.orangeforms.gateway.constant.GatewayConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 链路日志前置过虑器。
|
||||
* 为整个链路生成唯一的traceId,并存储在Request Head中。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Slf4j
|
||||
public class RequestLogFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
final String traceId = MyCommonUtil.generateUuid();
|
||||
MDC.put(ApplicationConstant.HTTP_HEADER_TRACE_ID, traceId);
|
||||
log.info("开始请求,app={gateway}, url={}", exchange.getRequest().getURI().getPath());
|
||||
// 分别记录traceId和执行开始时间。
|
||||
exchange.getAttributes().put(GatewayConstant.START_TIME_ATTRIBUTE, System.currentTimeMillis());
|
||||
ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(
|
||||
ApplicationConstant.HTTP_HEADER_TRACE_ID, traceId).build();
|
||||
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
|
||||
ServerHttpResponse response = mutableExchange.getResponse();
|
||||
response.beforeCommit(() -> {
|
||||
response.getHeaders().set(ApplicationConstant.HTTP_HEADER_TRACE_ID, traceId);
|
||||
return Mono.empty();
|
||||
});
|
||||
return chain.filter(mutableExchange).then(Mono.fromRunnable(() -> {
|
||||
Long startTime = exchange.getAttribute(GatewayConstant.START_TIME_ATTRIBUTE);
|
||||
MDC.put(ApplicationConstant.HTTP_HEADER_TRACE_ID, traceId);
|
||||
long elapse = 0;
|
||||
if (startTime != null) {
|
||||
elapse = System.currentTimeMillis() - startTime;
|
||||
}
|
||||
log.info("请求完成, app={gateway}, url={},elapse={}", exchange.getRequest().getURI().getPath(), elapse);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回过滤器在在调用链上的优先级。
|
||||
*
|
||||
* @return 数值越低,优先级越高。
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return HIGHEST_PRECEDENCE + 9900;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.orangeforms.gateway.handler;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
import springfox.documentation.swagger.web.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Swagger的资源请求处理器。
|
||||
*
|
||||
* @author Knife4j Team。
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@RestController
|
||||
public class SwaggerHandler {
|
||||
|
||||
@Autowired(required = false)
|
||||
private SecurityConfiguration securityConfiguration;
|
||||
|
||||
@Autowired(required = false)
|
||||
private UiConfiguration uiConfiguration;
|
||||
|
||||
private final SwaggerResourcesProvider swaggerResources;
|
||||
|
||||
@Autowired
|
||||
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
|
||||
this.swaggerResources = swaggerResources;
|
||||
}
|
||||
|
||||
@GetMapping("/swagger-resources/configuration/security")
|
||||
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(
|
||||
Optional.ofNullable(securityConfiguration)
|
||||
.orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
|
||||
}
|
||||
|
||||
@GetMapping("/swagger-resources/configuration/ui")
|
||||
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
|
||||
return Mono.just(new ResponseEntity<>(
|
||||
Optional.ofNullable(uiConfiguration)
|
||||
.orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
|
||||
}
|
||||
|
||||
@GetMapping("/swagger-resources")
|
||||
public Mono<ResponseEntity> swaggerResources() {
|
||||
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
spring:
|
||||
application:
|
||||
name: gateway
|
||||
profiles:
|
||||
active: dev
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: localhost:8848
|
||||
config:
|
||||
server-addr: localhost:8848
|
||||
file-extension: yaml
|
||||
# 共享配置文件,排序越高后,优先级越高。
|
||||
shared-configs:
|
||||
- data-id: application-dev.yaml
|
||||
group: DEFAULT_GROUP
|
||||
refresh: true
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- 本项目全部使用log4j2性能上有很大提升 -->
|
||||
|
||||
<!--monitorInterval="60" 自动检测配置文件更改时间 单位为秒 最小值为5 -->
|
||||
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出。 -->
|
||||
<configuration monitorInterval="20" status="OFF">
|
||||
<!--日志变量 -->
|
||||
<properties>
|
||||
<!-- 日志主目录 ,需要保存到文件时请自己配置-->
|
||||
<property name="LOG_HOME">./zzlogs/gateway</property>
|
||||
<!-- 日志备份目录 -->
|
||||
<property name="BACKUP_HOME">./zzlogs/gateway/backup</property>
|
||||
<!-- 日志输出级别 -->
|
||||
<property name="OUTPUT_LOG_LEVEL">info</property>
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="LOG_PATTERN">
|
||||
<!-- 输出格式%d{HH:mm:ss}时间24小时制 -->
|
||||
<!-- %-5p日志级别 5位左对齐 [%t]线程名 [%c]类名 -->
|
||||
<!--%l:输出日志事件的发生位置,相当于%c.%M(%F:%L)的组合,包括类全名、方法、文件名以及在代码中的行数。例如:test.TestLog4j.main(TestLog4j.java:10)。 -->
|
||||
<!-- 另一种输出风格<PatternLayout pattern="级别%-5p [%d{YYYY-MM-dd HH:mm:ss}] [%t] 位置[%l] - 信息:%msg%n" /> -->
|
||||
<!-- [%-5p][%d{yy-MM-dd HH:mm:ss}][%t]==>%m==>%c==>%L%n -->
|
||||
[%-5p] [%d{YYYY-MM-dd HH:mm:ss}] [%t] ==> %msg%n
|
||||
</property>
|
||||
<property name="LOG_PATTERN_EX">
|
||||
<!-- 下面注释中 %traceid 为SkyWalking 中的traceid -->
|
||||
[%-5p] [%d{YYYY-MM-dd HH:mm:ss}] T:[%X{traceId}] [%t] ==> [%traceId] %msg%n
|
||||
</property>
|
||||
<!-- 日志保留天数 -->
|
||||
<property name="EVERY_FILE_COUNT">31</property>
|
||||
<!-- 日志切割的最小单位 -->
|
||||
<property name="EVERY_FILE_SIZE">20M</property>
|
||||
</properties>
|
||||
|
||||
<appenders>
|
||||
<!--Kafka输出 -->
|
||||
<Kafka name="kafka_log" topic="zz-log-topic" syncSend="false" ignoreExceptions="false">
|
||||
<PatternLayout pattern="${LOG_PATTERN_EX}"/>
|
||||
<Property name="bootstrap.servers">localhost:9092</Property>
|
||||
<Property name="max.block.ms">10000</Property>
|
||||
</Kafka>
|
||||
<!--控制台输出 -->
|
||||
<console name="console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
</console>
|
||||
<!--每次大小超过size,则这size大小的日志会自动进行压缩,作为存档 -->
|
||||
<rollingFile name="file_log" fileName="${LOG_HOME}/gateway.log"
|
||||
filePattern="${LOG_HOME}/gateway-%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<PatternLayout charset="UTF-8" pattern="${LOG_PATTERN_EX}"/>
|
||||
<!-- 日志切割的最小单位 -->
|
||||
<SizeBasedTriggeringPolicy size="${EVERY_FILE_SIZE}"/>
|
||||
<!-- 默认的日志文件数量 -->
|
||||
<DefaultRolloverStrategy max="${EVERY_FILE_COUNT}"/>
|
||||
</rollingFile>
|
||||
</appenders>
|
||||
|
||||
<!-- 然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
|
||||
<!-- 这里我们把输出到控制台appender的日志级别设置为DEBUG,便于调试。但是输出文件我们缺省为INFO,两者均可随时修改。-->
|
||||
<Loggers>
|
||||
<Root level="${OUTPUT_LOG_LEVEL}">
|
||||
<AppenderRef ref="console"/>
|
||||
</Root>
|
||||
<Logger name="springfox.documentation" additivity="false" level="error">
|
||||
<AppenderRef ref="console"/>
|
||||
</Logger>
|
||||
<!-- AsyncLogger 是基于Disruptor的全量异步队列,性能极高,队列默认大小4096。-->
|
||||
<!-- 队列默认值可通过JVM参数设置,参考博客:https://www.jianshu.com/p/82469047acbf -->
|
||||
<AsyncLogger name="com.orangeforms" additivity="false" level="info">
|
||||
<AppenderRef ref="console"/>
|
||||
<AppenderRef ref="kafka_log"/>
|
||||
<AppenderRef ref="file_log"/>
|
||||
</AsyncLogger>
|
||||
</Loggers>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user