添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
import com.alibaba.fastjson.JSON; import com.mk.gateway.event.GatewayLogEvent; import com.mk.gateway.util.IpUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.support.CachedBodyOutputMessage; import org.springframework.context.ApplicationEventPublisher; import org.springframework.util.ObjectUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.*; import com.mk.common.beans.GatewayLog; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.reactivestreams.Publisher; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.support.BodyInserterContext; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; 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.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.HandlerStrategies; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ServerWebExchange; @Slf4j @Component public class GatewayLogFilter implements GlobalFilter, Ordered { @Autowired private ApplicationEventPublisher applicationEventPublisher; private final List> messageReaders = HandlerStrategies.withDefaults().messageReaders(); private static final String CONTENT_TYPE = "application/json"; // 请求来源应用 private static final String REQUEST_ORIGIN_APP = "Request-Origin-App"; // 自定义请求头,转发之前删除自定义请求头 private static final List CUSTOM_HEADERS = Arrays.asList("sign", "timestamp", "random", "Request-Origin-App"); @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); // 请求路径 String requestPath = request.getPath().pathWithinApplication().value(); // 获取路由信息 Route route = getGatewayRoute(exchange); String ipAddress = IpUtil.getIpAddr(request); GatewayLog gatewayLog = new GatewayLog(); gatewayLog.setOrigin(request.getHeaders().getFirst(REQUEST_ORIGIN_APP)); gatewayLog.setSchema(request.getURI().getScheme()); gatewayLog.setRequestMethod(request.getMethodValue()); gatewayLog.setRequestPath(requestPath); gatewayLog.setTargetServer(route.getUri().toString()); gatewayLog.setStartTime(new Date().getTime()); gatewayLog.setIp(ipAddress); gatewayLog.setRouteConfig(JSON.toJSONString(route)); Map headers = new HashMap<>(); for (String key : request.getHeaders().keySet()) { headers.put(key, request.getHeaders().getFirst(key)); gatewayLog.setHeaders(JSON.toJSONString(headers)); MediaType mediaType = request.getHeaders().getContentType(); if (request.getHeaders().getContentType() != null) { gatewayLog.setRequestContentType(request.getHeaders().getContentType().toString()); if(mediaType != null && (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType))){ return writeBodyLog(exchange, chain, gatewayLog); }else{ return writeBasicLog(exchange, chain, gatewayLog); @Override public int getOrder() { // 必须小于等于-2,否则无法获取相应结果 return -2; * 获取路由信息 * @param exchange * @return private Route getGatewayRoute(ServerWebExchange exchange) { return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); private Mono writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) { StringBuilder builder = new StringBuilder(); MultiValueMap queryParams = exchange.getRequest().getQueryParams(); gatewayLog.setRequestBody(getUrlParamsByMap(queryParams)); // 修改Header ServerHttpRequest mutableReq = exchange.getRequest().mutate().headers(httpHeaders -> { // 删除自定义header for (String customHeader : CUSTOM_HEADERS) { httpHeaders.remove(customHeader); }).build(); //获取响应体 ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog); return chain.filter(exchange.mutate().request(mutableReq).response(decoratedResponse).build()) .then(Mono.fromRunnable(() -> { // 打印日志 writeAccessLog(gatewayLog); * 解决 request body 只能读取一次问题, * 参考: org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory * @param exchange * @param chain * @param gatewayLog * @return private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) { ServerRequest serverRequest = ServerRequest.create(exchange,messageReaders); Mono modifiedBody = serverRequest.bodyToMono(String.class) .flatMap(body ->{ gatewayLog.setRequestBody(body); return Mono.just(body); // 通过 BodyInserter 插入 body(支持修改body), 避免 request body 只能获取一次 BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); HttpHeaders headers = new HttpHeaders(); headers.putAll(exchange.getRequest().getHeaders()); // the new content type will be computed by bodyInserter // and then set in the request decorator headers.remove(HttpHeaders.CONTENT_LENGTH); CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers); return bodyInserter.insert(outputMessage,new BodyInserterContext()) .then(Mono.defer(() -> { // 重新封装请求 ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage); // 记录响应日志 ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog); // 记录普通的 return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()) .then(Mono.fromRunnable(() -> { // 打印日志 writeAccessLog(gatewayLog); * 打印日志 * @param gatewayLog 网关日志 private void writeAccessLog(GatewayLog gatewayLog) { applicationEventPublisher.publishEvent(new GatewayLogEvent(this, gatewayLog)); * 请求装饰器,重新计算 headers * @param exchange * @param headers * @param outputMessage * @return private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) { return new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public HttpHeaders getHeaders() { long contentLength = headers.getContentLength(); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.putAll(super.getHeaders()); if (contentLength > 0) { httpHeaders.setContentLength(contentLength); } else { // TODO: this causes a 'HTTP/1.1 411 Length Required' // on // httpbin.org httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); // 删除自定义header for (String customHeader : CUSTOM_HEADERS) { headers.remove(customHeader); return httpHeaders; @Override public Flux getBody() { return outputMessage.getBody(); * 记录响应日志 * 通过 DataBufferFactory 解决响应体分段传输问题。 private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) { ServerHttpResponse response = exchange.getResponse(); DataBufferFactory bufferFactory = response.bufferFactory(); return new ServerHttpResponseDecorator(response) { @Override public Mono writeWith(Publisher body) { if (body instanceof Flux) { Date responseTime = new Date(); gatewayLog.setEndTime(responseTime.getTime()); // 计算执行时间 long executeTime = (responseTime.getTime() - gatewayLog.getStartTime()); gatewayLog.setExecuteTime(executeTime); gatewayLog.setStatus(response.getStatusCode().value() == 200 ? "成功" : "失败"); // 获取响应类型,如果是 json 就打印 String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR); if (Objects.equals(this.getStatusCode(), HttpStatus.OK) && StringUtils.isNotBlank(originalResponseContentType) && originalResponseContentType.contains(CONTENT_TYPE)) { Flux fluxBody = Flux.from(body); return super.writeWith(fluxBody.buffer().map(dataBuffers -> { // 合并多个流集合,解决返回体分段传输 DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); DataBuffer join = dataBufferFactory.join(dataBuffers); byte[] content = new byte[join.readableByteCount()]; join.read(content); // 释放掉内存 DataBufferUtils.release(join); String responseResult = new String(content, StandardCharsets.UTF_8); gatewayLog.setResponseData(responseResult); return bufferFactory.wrap(content); // if body is not a flux. never got there. return super.writeWith(body); * 将map参数转换成url参数 * @param map * @return private String getUrlParamsByMap(MultiValueMap map) { if (ObjectUtils.isEmpty(map)) { return ""; StringBuilder sb = new StringBuilder(); for (Map.Entry> entry : map.entrySet()) { sb.append(entry.getKey()).append("=").append(entry.getValue().get(0)); sb.append("&"); String s = sb.toString(); if (s.endsWith("&")) { s = StringUtils.substringBeforeLast(s, "&"); return s;

日志收集效果

个人博客纯净版SpringCloud Gateway网关收集请求日志 | 代码搬运工网关负责服务的转发,所有通过网关转发的服务。都可以通过网关进行收集相关请求日志,具体实现如下:日志实体类package com.mk.common.beans;import lombok.*;@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class GatewayLog { /**请求来源**/ pri accesslog : enabled : true # enable or disable logging, default value is true. timeZone : Asia/Seoul # log datetime zone setting, default is UTC 自定义访问用户信息解析器 public interface AccessUserInformationResolver { String resolveAccessUserInformation ( ServerWebExchange serverWebExchange ); Acces
日志 打印 收集 是开发中调试和定位线上问题的关键手段也是重中之重, gateway 作为 请求 入口转发的核心模块,合理、规范的 日志 打印很重要。增加一个 请求 入参过滤器,用来打印入参信息。 ResponseLogFilter 网关 请求 的响应报文不在这里打印,post body 参数没有打印...
请求 应答 日志 时在日常开发调试问题的重要手段之一,那么如何基于 Spring Cloud Gateway 做呢,请看我上代码。 创建RecorderServerHttpRequestDecorator,缓存 请求 参数,解决body只能读一次问题。 public class RecorderServerHttpRequestDecorator extends ServerHttpRequestDe...
这几天有个对 SpringCloud - Gateway 网关 请求 参数做安全校验的需求,网上多数教程不适用于 Spring Boot 2.1+版本。我在 SpringCloud gateway Github中的Issues找到了答案,放上连接:作者采纳的答案。 具体代码如下: @Componentpublic class CacheBody Gateway Filter implements Ordered, Globa...
Spring Cloud Gateway 是一个非常流行的 微服务 网关 ,在 微服务 架构设计中有着重要的作用,可以用来实现各种功能,例如路由转发、负载均衡、流量控制、限流等;除此之外, Spring Cloud Gateway 还能够支持统一认证,即只需要一次认证,就能够访问所有的 微服务 ,这对于复杂的 微服务 架构来说非常的有用。 实现统一认证的实现方法如下: 1. 接入认证中心:首先需要在 Spring Cloud Gateway 中接入一个认证中心,比如使用 Spring Security 或 OAuth2 进行认证,实现单点登录。 2. 统一身份验证:在用户登录认证成功之后,会生成一个 token, Spring Cloud Gateway 会通过这个 token 来进行用户身份认证。而在 微服务 中,只需要进行简单的 token 验证就能够获取到用户信息,并进行相应的操作。 3. 统一资源管理:在认证中心中进行配置,指定可以访问的资源,进而限制了用户访问 微服务 的范围。这样就能够确保只有获得了相应权限的用户才能够访问 微服务 。 4. 日志 跟踪: Spring Cloud Gateway 还能够记录每个 请求 的详细信息和用户信息,进而进行 日志 跟踪和监控,这对于故障排除和性能优化非常重要。 总之,实现 Spring Cloud Gateway 的统一认证,能够提供更好的安全性,同时提高开发效率,显著降低了 微服务 的管理难度,减轻了 微服务 的管理负担。
Eden4J: 我理解错了。我参照大佬的做法但是在base64图片上传的时候,会出现一个问题,不知道从哪里定位问题。错误如下:[TID:N/A] 2023-03-27 11:38:34.767 ERROR 30056 --- [ctor-http-nio-4] r.n.channel.ChannelOperationsHandler : [id: 0x53e73793, L:/192.168.1.53:6868 ! R:/192.168.1.62:56218] Error was received while reading the incoming data. The connection will be closed. io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1 at io.netty.util.internal.ReferenceCountUpdater.toLiveRealRefCnt(ReferenceCountUpdater.java:74) SpringCloud Gateway网关收集请求日志 chaojunma: 不太明白您的意思 SpringCloud Gateway网关收集请求日志 chaojunma: 针对内部应用可以用应用白名单或者IP白名单,如果对于外部系统请求网关,可以生成对称公钥和私钥及应用appKey,请求网关之前先通过公钥和appKey获取签名、随机值等,然后将appkey、签名和随机值加入到请求头,网关通过全局过滤器做验签,验证通过则放行,验证失败则直接返回验证失败的提示