mirror of
https://gitee.com/orangeform/orange-admin.git
synced 2026-01-17 18:46:36 +08:00
commit:修改微服务工程目录名称
This commit is contained in:
62
orange-demo-multi-service/application/gateway/pom.xml
Normal file
62
orange-demo-multi-service/application/gateway/pom.xml
Normal file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.orange.demo</groupId>
|
||||
<artifactId>application</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>gateway</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>gateway</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<!-- 网关服务 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.orange.demo</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<!-- 通用组件依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.orange.demo</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<groupId>mysql</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<groupId>com.alibaba</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.orange.demo.gateway;
|
||||
|
||||
import com.orange.demo.common.core.util.ApplicationContextHolder;
|
||||
import com.orange.demo.gateway.filter.AuthenticationPostFilter;
|
||||
import com.orange.demo.gateway.filter.AuthenticationPreFilter;
|
||||
import com.orange.demo.gateway.filter.RequestLogFilter;
|
||||
import com.orange.demo.gateway.filter.ResponseLogFilter;
|
||||
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 Orange Team
|
||||
* @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
|
||||
public ResponseLogFilter responseLogPostFilter() {
|
||||
return new ResponseLogFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApplicationContextHolder applicationContextHolder() {
|
||||
return new ApplicationContextHolder();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.orange.demo.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;
|
||||
|
||||
/**
|
||||
* 网关业务配置类。
|
||||
*
|
||||
* @author Orange Team
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Data
|
||||
@RefreshScope
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "application")
|
||||
public class ApplicationConfig {
|
||||
|
||||
/**
|
||||
* token加密时的盐
|
||||
*/
|
||||
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 + 60s
|
||||
*/
|
||||
private int sessionIdRedisExpiredSeconds = 86460;
|
||||
/**
|
||||
* Session的用户权限在Redis中的过期时间(秒)。
|
||||
* 缺省值是 one day
|
||||
*/
|
||||
private int permRedisExpiredSeconds = 86400;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.orange.demo.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 Orange Team
|
||||
* @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.orange.demo.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 Orange Team
|
||||
* @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.orange.demo.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 Orange Team
|
||||
* @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,36 @@
|
||||
package com.orange.demo.gateway.constant;
|
||||
|
||||
/**
|
||||
* 网关业务相关的常量对象。
|
||||
*
|
||||
* @author Orange Team
|
||||
* @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/login/doLogin";
|
||||
|
||||
/**
|
||||
* 登出URL。
|
||||
*/
|
||||
public static final String ADMIN_LOGOUT_URL = "/admin/login/doLogout";
|
||||
|
||||
/**
|
||||
* sessionId的键名称。
|
||||
*/
|
||||
public static final String SESSION_ID_KEY_NAME = "sessionId";
|
||||
|
||||
/**
|
||||
* 私有构造函数,明确标识该常量类的作用。
|
||||
*/
|
||||
private GatewayConstant() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package com.orange.demo.gateway.filter;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.orange.demo.common.core.constant.ErrorCodeEnum;
|
||||
import com.orange.demo.common.core.object.ResponseResult;
|
||||
import com.orange.demo.common.core.object.TokenData;
|
||||
import com.orange.demo.common.core.util.JwtUtil;
|
||||
import com.orange.demo.common.core.util.MyCommonUtil;
|
||||
import com.orange.demo.common.core.util.RedisKeyUtil;
|
||||
import com.orange.demo.gateway.config.ApplicationConfig;
|
||||
import com.orange.demo.gateway.constant.GatewayConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.reactivestreams.Publisher;
|
||||
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 redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
import redis.clients.jedis.Transaction;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局后处理过滤器。主要用于将用户的会话信息存到缓存服务器,以及在登出时清除缓存中的会话数据。
|
||||
*
|
||||
* @author Orange Team
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Slf4j
|
||||
public class AuthenticationPostFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Autowired
|
||||
private ApplicationConfig appConfig;
|
||||
@Autowired
|
||||
private JedisPool jedisPool;
|
||||
|
||||
@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_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) {
|
||||
List<String> list = new LinkedList<>();
|
||||
int dataCount = 0;
|
||||
for (DataBuffer dataBuffer : dataBuffers) {
|
||||
dataCount += dataBuffer.readableByteCount();
|
||||
byte[] content = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(content);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
list.add(new String(content, StandardCharsets.UTF_8));
|
||||
}
|
||||
StringBuilder responseBuilder = new StringBuilder(dataCount + 1);
|
||||
for (String data : list) {
|
||||
responseBuilder.append(data);
|
||||
}
|
||||
return responseBuilder.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private String doProcess(ServerWebExchange exchange, String responseBody) {
|
||||
// 这个解析出来的就是upms登录或登出接口返回的ResponseResult对象。
|
||||
ServerHttpRequest originalRequest = exchange.getRequest();
|
||||
if (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);
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
pipeline.del(RedisKeyUtil.makeSessionIdKeyForRedis(sessionId));
|
||||
pipeline.del(RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId));
|
||||
pipeline.sync();
|
||||
}
|
||||
}
|
||||
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, "内部错误,用户登录令牌对象没有正确返回!");
|
||||
}
|
||||
Integer userId = tokenData.getInteger("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 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. 更新缓存
|
||||
// 3.1 sessionId -> userId 是hash结构的缓存
|
||||
String sessionIdKey = RedisKeyUtil.makeSessionIdKeyForRedis(sessionId);
|
||||
String sessionPermKey = null;
|
||||
JSONArray permSet = null;
|
||||
if (Boolean.FALSE.equals(isAdmin)) {
|
||||
// 3.2 sessionId -> permList 是set结构的缓存
|
||||
sessionPermKey = RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId);
|
||||
permSet = loginData.getJSONArray("permSet");
|
||||
}
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
Transaction t = jedis.multi();
|
||||
for (String tokenKey : tokenData.keySet()) {
|
||||
t.hset(sessionIdKey, tokenKey, tokenData.getString(tokenKey));
|
||||
}
|
||||
t.expire(sessionIdKey, appConfig.getSessionIdRedisExpiredSeconds());
|
||||
if (permSet != null) {
|
||||
for (int i = 0; i < permSet.size(); ++i) {
|
||||
String perm = permSet.getString(i);
|
||||
t.sadd(sessionPermKey, perm);
|
||||
}
|
||||
t.expire(sessionPermKey, appConfig.getPermRedisExpiredSeconds());
|
||||
}
|
||||
t.exec();
|
||||
}
|
||||
// 4. 构造返回给用户的应答
|
||||
JSONObject resultJsonData = new JSONObject();
|
||||
resultJsonData.put(TokenData.REQUEST_ATTRIBUTE_NAME, token);
|
||||
resultJsonData.put("isAdmin", isAdmin);
|
||||
resultJsonData.put("showName", showName);
|
||||
JSONArray menuList = loginData.getJSONArray("menuList");
|
||||
if (CollectionUtils.isNotEmpty(menuList)) {
|
||||
resultJsonData.put("menuList", menuList);
|
||||
}
|
||||
if (Boolean.FALSE.equals(isAdmin)) {
|
||||
JSONArray permCodeList = loginData.getJSONArray("permCodeList");
|
||||
if (CollectionUtils.isNotEmpty(permCodeList)) {
|
||||
resultJsonData.put("permCodeList", permCodeList);
|
||||
}
|
||||
}
|
||||
return ResponseResult.success(resultJsonData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package com.orange.demo.gateway.filter;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.orange.demo.common.core.constant.ErrorCodeEnum;
|
||||
import com.orange.demo.common.core.object.ResponseResult;
|
||||
import com.orange.demo.common.core.object.TokenData;
|
||||
import com.orange.demo.common.core.util.JwtUtil;
|
||||
import com.orange.demo.common.core.util.RedisKeyUtil;
|
||||
import com.orange.demo.common.core.util.IpUtil;
|
||||
import com.orange.demo.gateway.config.ApplicationConfig;
|
||||
import com.orange.demo.gateway.constant.GatewayConstant;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局前处理过滤器。主要用于用户操作权限验证。
|
||||
*
|
||||
* @author Orange Team
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Slf4j
|
||||
public class AuthenticationPreFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Autowired
|
||||
private ApplicationConfig appConfig;
|
||||
@Autowired
|
||||
private JedisPool jedisPool;
|
||||
|
||||
private static List<String> whitelistUrlPattern = new LinkedList<>();
|
||||
static {
|
||||
// 这里可以添加URL部分匹配的白名单列表
|
||||
// 另外解释一下,数据库中配置的白名单列表,在doLogin中,直接合并到当前用户的权限列表中了。
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
String url = request.getURI().getPath();
|
||||
// 登录请求,直接转发给login验证服务器。
|
||||
// NOTE: 所有不需要登录验证的url,都可以添加在下面。
|
||||
if (url.equals(GatewayConstant.ADMIN_LOGIN_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()));
|
||||
}
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
// 先基于sessionId获取userInfo
|
||||
String sessionId = (String) c.get(GatewayConstant.SESSION_ID_KEY_NAME);
|
||||
Map<String, String> userMap = jedis.hgetAll(RedisKeyUtil.makeSessionIdKeyForRedis(sessionId));
|
||||
if (userMap == 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 = userMap.get("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)));
|
||||
}
|
||||
boolean isAdmin = false;
|
||||
String isAdminString = userMap.get("isAdmin");
|
||||
if (Boolean.parseBoolean(isAdminString)) {
|
||||
isAdmin = true;
|
||||
}
|
||||
String showName = userMap.get("showName");
|
||||
// 因为http header中不支持中文传输,所以需要编码。
|
||||
try {
|
||||
showName = URLEncoder.encode(showName, StandardCharsets.UTF_8.name());
|
||||
userMap.put("showName", showName);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
log.error("Failed to call AuthenticationPreFilter.filter.", e);
|
||||
}
|
||||
// 对于isAdmin == false的用户,继续查找权限资源信息是否存在
|
||||
if (Boolean.FALSE.equals(isAdmin)
|
||||
&& !this.hasPermission(jedis, 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中。转发后,业务服务可以根据需要自定读取。
|
||||
JSONObject tokenData = new JSONObject();
|
||||
tokenData.putAll(userMap);
|
||||
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(Jedis jedis, String sessionId, String url) {
|
||||
// 对于退出登录操作,不需要进行权限验证,仅仅确认是已经登录的合法用户即可。
|
||||
if (url.equals(GatewayConstant.ADMIN_LOGOUT_URL)
|
||||
|| Boolean.TRUE.equals(jedis.sismember(RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId), url))) {
|
||||
return true;
|
||||
}
|
||||
for (String urlPattern : whitelistUrlPattern) {
|
||||
if (url.startsWith(urlPattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.orange.demo.gateway.filter;
|
||||
|
||||
import com.orange.demo.common.core.constant.ApplicationConstant;
|
||||
import com.orange.demo.common.core.util.MyCommonUtil;
|
||||
import com.orange.demo.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.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 链路日志前置过虑器。
|
||||
* 为整个链路生成唯一的traceId,并存储在Request Head中。
|
||||
*
|
||||
* @author Orange Team
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Slf4j
|
||||
public class RequestLogFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
String traceId = MyCommonUtil.generateUuid();
|
||||
// 分别记录traceId和执行开始时间。
|
||||
exchange.getAttributes().put(ApplicationConstant.HTTP_HEADER_TRACE_ID, 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();
|
||||
MDC.put(ApplicationConstant.HTTP_HEADER_TRACE_ID, traceId);
|
||||
log.info("开始请求,app={gateway}, url={}", exchange.getRequest().getURI().getPath());
|
||||
return chain.filter(mutableExchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回过滤器在在调用链上的优先级。
|
||||
*
|
||||
* @return 数值越低,优先级越高。
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return HIGHEST_PRECEDENCE + 9900;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.orange.demo.gateway.filter;
|
||||
|
||||
import com.orange.demo.common.core.constant.ApplicationConstant;
|
||||
import com.orange.demo.gateway.constant.GatewayConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 链路日志后置过虑器。
|
||||
* 将整个链路的traceId存储在Response Head中,并返回给前端,便于问题定位。
|
||||
*
|
||||
* @author Orange Team
|
||||
* @date 2020-08-08
|
||||
*/
|
||||
@Slf4j
|
||||
public class ResponseLogFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
// 下面两个属性,都是在RequestLogFilter过滤器中设置的。
|
||||
String traceId = exchange.getAttribute(ApplicationConstant.HTTP_HEADER_TRACE_ID);
|
||||
Long startTime = exchange.getAttribute(GatewayConstant.START_TIME_ATTRIBUTE);
|
||||
if (StringUtils.isNotBlank(traceId)) {
|
||||
MDC.put(ApplicationConstant.HTTP_HEADER_TRACE_ID, traceId);
|
||||
exchange.getResponse().getHeaders().add(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 chain.filter(exchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回过滤器在在调用链上的优先级。
|
||||
*
|
||||
* @return 数值越低,优先级越高。
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
// -1 is response write filter, must be called before that
|
||||
return -10;
|
||||
}
|
||||
}
|
||||
@@ -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,70 @@
|
||||
<?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">
|
||||
<!-- 下面注释中 %X{PtxId}, SpanId: %X{PspanId} 为PinPoint 中的traceid -->
|
||||
[%-5p] [%d{YYYY-MM-dd HH:mm:ss}] 请求Id[%X{traceId}] [%t] ==> [TxId: %X{PtxId}, SpanId: %X{PspanId}] %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>
|
||||
<!-- AsyncLogger 是基于Disruptor的全量异步队列,性能极高,队列默认大小4096。-->
|
||||
<!-- 队列默认值可通过JVM参数设置,参考博客:https://www.jianshu.com/p/82469047acbf -->
|
||||
<AsyncLogger name="com.orange.demo" 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