mirror of
https://gitee.com/orangeform/orange-admin.git
synced 2026-01-17 18:46:36 +08:00
commit:1.6版本发布
This commit is contained in:
@@ -72,8 +72,6 @@
|
||||
<orderEntry type="library" name="Maven: com.alibaba.csp:sentinel-core:1.7.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.alibaba.csp:sentinel-reactor-adapter:1.7.1" level="project" />
|
||||
<orderEntry type="module" module-name="common-redis" />
|
||||
<orderEntry type="library" name="Maven: redis.clients:jedis:3.1.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.7.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.redisson:redisson:3.12.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.netty:netty-common:4.1.45.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.netty:netty-codec:4.1.45.Final" level="project" />
|
||||
|
||||
@@ -4,7 +4,6 @@ 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;
|
||||
@@ -48,11 +47,6 @@ public class GatewayApplication {
|
||||
return new RequestLogFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResponseLogFilter responseLogPostFilter() {
|
||||
return new ResponseLogFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApplicationContextHolder applicationContextHolder() {
|
||||
return new ApplicationContextHolder();
|
||||
|
||||
@@ -40,15 +40,10 @@ public class ApplicationConfig {
|
||||
*/
|
||||
private String credentialIpList;
|
||||
/**
|
||||
* Session在Redis中的过期时间(秒)。
|
||||
* 缺省值是 one day + 60s
|
||||
*/
|
||||
private int sessionIdRedisExpiredSeconds = 86460;
|
||||
/**
|
||||
* Session的用户权限在Redis中的过期时间(秒)。
|
||||
* Session会话和用户权限在Redis中的过期时间(秒)。
|
||||
* 缺省值是 one day
|
||||
*/
|
||||
private int permRedisExpiredSeconds = 86400;
|
||||
private int sessionExpiredSeconds = 86400;
|
||||
/**
|
||||
* 基于完全等于(equals)判定规则的白名单地址集合,过滤效率高于whitelistUrlPattern。
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.orange.demo.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.orange.demo.common.core.constant.ErrorCodeEnum;
|
||||
import com.orange.demo.common.core.object.ResponseResult;
|
||||
import com.orange.demo.common.core.object.TokenData;
|
||||
@@ -14,6 +15,9 @@ import com.orange.demo.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;
|
||||
@@ -30,16 +34,13 @@ 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;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局后处理过滤器。主要用于将用户的会话信息存到缓存服务器,以及在登出时清除缓存中的会话数据。
|
||||
@@ -53,7 +54,7 @@ public class AuthenticationPostFilter implements GlobalFilter, Ordered {
|
||||
@Autowired
|
||||
private ApplicationConfig appConfig;
|
||||
@Autowired
|
||||
private JedisPool jedisPool;
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
@@ -134,20 +135,19 @@ public class AuthenticationPostFilter implements GlobalFilter, Ordered {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
byte[] allBytes = new byte[dataCount];
|
||||
int offset = 0;
|
||||
for (DataBuffer dataBuffer : dataBuffers) {
|
||||
int length = dataBuffer.readableByteCount();
|
||||
dataBuffer.read(allBytes, offset, length);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
list.add(new String(content, StandardCharsets.UTF_8));
|
||||
offset += length;
|
||||
}
|
||||
StringBuilder responseBuilder = new StringBuilder(dataCount + 1);
|
||||
for (String data : list) {
|
||||
responseBuilder.append(data);
|
||||
}
|
||||
return responseBuilder.toString();
|
||||
return new String(allBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -162,14 +162,9 @@ public class AuthenticationPostFilter implements GlobalFilter, Ordered {
|
||||
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();
|
||||
}
|
||||
String sessionId = (String) exchange.getAttributes().get(GatewayConstant.SESSION_ID_KEY_NAME);
|
||||
redissonClient.getBucket(RedisKeyUtil.makeSessionIdKeyForRedis(sessionId)).deleteAsync();
|
||||
redissonClient.getSet(RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId)).deleteAsync();
|
||||
}
|
||||
return responseBody;
|
||||
}
|
||||
@@ -201,6 +196,10 @@ public class AuthenticationPostFilter implements GlobalFilter, Ordered {
|
||||
if (StringUtils.isBlank(showName)) {
|
||||
return ResponseResult.error(errorCode, "内部错误,用户显示名没有正确返回!");
|
||||
}
|
||||
String loginName = tokenData.getString("loginName");
|
||||
if (StringUtils.isBlank(showName)) {
|
||||
return ResponseResult.error(errorCode, "内部错误,用户登录名没有正确返回!");
|
||||
}
|
||||
String sessionId = tokenData.getString("sessionId");
|
||||
if (StringUtils.isBlank(sessionId)) {
|
||||
return ResponseResult.error(errorCode, "内部错误,SESSION_ID没有正确返回!");
|
||||
@@ -209,26 +208,19 @@ public class AuthenticationPostFilter implements GlobalFilter, Ordered {
|
||||
Map<String, Object> claims = new HashMap<>(1);
|
||||
claims.put(GatewayConstant.SESSION_ID_KEY_NAME, sessionId);
|
||||
String token = JwtUtil.generateToken(claims, appConfig.getExpiration(), appConfig.getTokenSigningKey());
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
// 3. 更新缓存
|
||||
// 3.1 sessionId -> userId 是hash结构的缓存
|
||||
String sessionIdKey = RedisKeyUtil.makeSessionIdKeyForRedis(sessionId);
|
||||
Transaction t = jedis.multi();
|
||||
for (String tokenKey : tokenData.keySet()) {
|
||||
t.hset(sessionIdKey, tokenKey, tokenData.getString(tokenKey));
|
||||
}
|
||||
t.expire(sessionIdKey, appConfig.getSessionIdRedisExpiredSeconds());
|
||||
// 3.2 sessionId -> permList 是set结构的缓存
|
||||
JSONArray permSet = loginData.getJSONArray("permSet");
|
||||
if (permSet != null) {
|
||||
String sessionPermKey = RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId);
|
||||
for (int i = 0; i < permSet.size(); ++i) {
|
||||
String perm = permSet.getString(i);
|
||||
t.sadd(sessionPermKey, perm);
|
||||
}
|
||||
t.expire(sessionPermKey, appConfig.getPermRedisExpiredSeconds());
|
||||
}
|
||||
t.exec();
|
||||
// 3. 更新缓存
|
||||
String sessionIdKey = RedisKeyUtil.makeSessionIdKeyForRedis(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.makeSessionPermIdKeyForRedis(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);
|
||||
|
||||
@@ -14,6 +14,8 @@ 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;
|
||||
@@ -26,13 +28,10 @@ import org.springframework.util.AntPathMatcher;
|
||||
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.Map;
|
||||
|
||||
/**
|
||||
* 全局前处理过滤器。主要用于用户操作权限验证。
|
||||
@@ -46,7 +45,7 @@ public class AuthenticationPreFilter implements GlobalFilter, Ordered {
|
||||
@Autowired
|
||||
private ApplicationConfig appConfig;
|
||||
@Autowired
|
||||
private JedisPool jedisPool;
|
||||
private RedissonClient redissonClient;
|
||||
/**
|
||||
* Ant Pattern模式的白名单地址匹配器。
|
||||
*/
|
||||
@@ -76,61 +75,58 @@ public class AuthenticationPreFilter implements GlobalFilter, Ordered {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
// 先基于sessionId获取userInfo
|
||||
String sessionId = (String) c.get(GatewayConstant.SESSION_ID_KEY_NAME);
|
||||
String sessionIdKey = RedisKeyUtil.makeSessionIdKeyForRedis(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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,10 +147,13 @@ public class AuthenticationPreFilter implements GlobalFilter, Ordered {
|
||||
return token;
|
||||
}
|
||||
|
||||
private boolean hasPermission(Jedis jedis, String sessionId, String url) {
|
||||
private boolean hasPermission(RedissonClient redissonClient, String sessionId, String url) {
|
||||
// 对于退出登录操作,不需要进行权限验证,仅仅确认是已经登录的合法用户即可。
|
||||
return url.equals(GatewayConstant.ADMIN_LOGOUT_URL)
|
||||
|| Boolean.TRUE.equals(jedis.sismember(RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId), url));
|
||||
if (url.equals(GatewayConstant.ADMIN_LOGOUT_URL)) {
|
||||
return true;
|
||||
}
|
||||
String permKey = RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId);
|
||||
return redissonClient.getSet(permKey).contains(url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ 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;
|
||||
|
||||
@@ -24,16 +25,27 @@ public class RequestLogFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
String traceId = MyCommonUtil.generateUuid();
|
||||
log.info("开始请求,app={gateway}, url={}", exchange.getRequest().getURI().getPath());
|
||||
final 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);
|
||||
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);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
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 Jerry
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user