Spring Cloud gateway网关怎么拦截Post请求日志
本篇内容主要讲解“Spring Cloud gateway网关怎么拦截Post请求日志”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Spring Cloud gateway网关怎么拦截Post请求日志”吧!
gateway版本是 2.0.1
1.pom结构
(部分内部项目依赖已经隐藏)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--监控相关--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><!-- redis --><!--<dependency>--> <!--<groupId>org.springframework.boot</groupId>--> <!--<artifactId>spring-boot-starter-data-redis</artifactId>--><!--</dependency>--><!-- test-scope --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency><dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.11</version></dependency><dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.11</version></dependency><dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version></dependency><!--第三方的jdbctemplatetool--><dependency> <groupId>org.crazycake</groupId> <artifactId>jdbctemplatetool</artifactId> <version>1.0.4-RELEASE</version></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId></dependency><!-- alibaba start --><dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId></dependency>
2.表结构
CREATE TABLE `zc_log_notes` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '日志信息记录表主键id', `notes` varchar(255) DEFAULT NULL COMMENT '操作记录信息', `amenu` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '一级菜单', `bmenu` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '二级菜单', `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '操作人ip地址,先用varchar存', `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '请求值', `response` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '返回值', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间', `create_user` int(11) DEFAULT NULL COMMENT '操作人id', `end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '响应时间', `status` int(1) NOT NULL DEFAULT '1' COMMENT '响应结果1成功0失败', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='日志信息记录表';
3.实体结构
@Table(catalog = "zhiche", name = "zc_log_notes")public class LogNotes { private Integer id; private String notes; private String amenu; private String bmenu; private String ip; private String params; private String response; private Date createTime; private Integer createUser; private Date endTime; private Integer status; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getNotes() { return notes; } public void setNotes(String notes) { this.notes = notes; } public String getAmenu() { return amenu; } public void setAmenu(String amenu) { this.amenu = amenu; } public String getBmenu() { return bmenu; } public void setBmenu(String bmenu) { this.bmenu = bmenu; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Integer getCreateUser() { return createUser; } public void setCreateUser(Integer createUser) { this.createUser = createUser; } public Date getEndTime() { return endTime; } public void setEndTime(Date endTime) { this.endTime = endTime; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getParams() { return params; } public void setParams(String params) { this.params = params; } public String getResponse() { return response; } public void setResponse(String response) { this.response = response; } public void setAppendResponse(String response){ if (StringUtils.isNoneBlank(this.response)) { this.response = this.response + response; } else { this.response = response; } }}
4.dao层和Service层省略..
5.filter代码
1. RequestRecorderGlobalFilter 实现了GlobalFilter和Order
package com.zc.gateway.filter;import com.zc.entity.LogNotes;import com.zc.gateway.service.FilterService;import org.slf4j.Logger;import org.slf4j.LoggerFactory;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.DataBufferUtils;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpMethod;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.lang.Nullable;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Flux;import reactor.core.publisher.Mono;import java.net.URI;import java.nio.CharBuffer;import java.nio.charset.Charset;import java.nio.charset.StandardCharsets;@Componentpublic class RequestRecorderGlobalFilter implements GlobalFilter, Ordered { @Autowired FilterService filterService; private Logger logger = LoggerFactory.getLogger(RequestRecorderGlobalFilter.class); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest originalRequest = exchange.getRequest(); URI originalRequestUrl = originalRequest.getURI(); //只记录http的请求 String scheme = originalRequestUrl.getScheme(); if ((!"http".equals(scheme) && !"https".equals(scheme))) { return chain.filter(exchange); } //这是我要打印的log-StringBuilder StringBuilder logbuilder = new StringBuilder(); //我自己的log实体 LogNotes logNotes = new LogNotes(); // 返回解码 RecorderServerHttpResponseDecorator response = new RecorderServerHttpResponseDecorator(exchange.getResponse(), logNotes, filterService); //请求解码 RecorderServerHttpRequestDecorator recorderServerHttpRequestDecorator = new RecorderServerHttpRequestDecorator(exchange.getRequest()); //增加过滤拦截吧 ServerWebExchange ex = exchange.mutate() .request(recorderServerHttpRequestDecorator) .response(response) .build(); // 观察者模式 打印一下请求log // 这里可以在 配置文件中我进行配置// if (logger.isDebugEnabled()) { response.beforeCommit(() -> Mono.defer(() -> printLog(logbuilder, response)));// } return recorderOriginalRequest(logbuilder, ex, logNotes) .then(chain.filter(ex)) .then(); } private Mono<Void> recorderOriginalRequest(StringBuilder logBuffer, ServerWebExchange exchange, LogNotes logNotes) { logBuffer.append(System.currentTimeMillis()) .append("------------"); ServerHttpRequest request = exchange.getRequest(); Mono<Void> result = recorderRequest(request, logBuffer.append("\n原始请求:\n"), logNotes); try { filterService.addLog(logNotes); } catch (Exception e) { logger.error("保存请求参数出现错误, e->{}", e.getMessage()); } return result; } private Mono<Void> recorderRequest(ServerHttpRequest request, StringBuilder logBuffer, LogNotes logNotes) { URI uri = request.getURI(); HttpMethod method = request.getMethod(); HttpHeaders headers = request.getHeaders(); logNotes.setIp(headers.getHost().getHostString()); logNotes.setAmenu("一级菜单"); logNotes.setBmenu("二级菜单"); logNotes.setNotes("操作记录"); logBuffer .append(method.toString()).append(' ') .append(uri.toString()).append('\n'); logBuffer.append("------------请求头------------\n"); headers.forEach((name, values) -> { values.forEach(value -> { logBuffer.append(name).append(":").append(value).append('\n'); }); }); Charset bodyCharset = null; if (hasBody(method)) { long length = headers.getContentLength(); if (length <= 0) { logBuffer.append("------------无body------------\n"); } else { logBuffer.append("------------body 长度:").append(length).append(" contentType:"); MediaType contentType = headers.getContentType(); if (contentType == null) { logBuffer.append("null,不记录body------------\n"); } else if (!shouldRecordBody(contentType)) { logBuffer.append(contentType.toString()).append(",不记录body------------\n"); } else { bodyCharset = getMediaTypeCharset(contentType); logBuffer.append(contentType.toString()).append("------------\n"); } } } if (bodyCharset != null) { return doRecordReqBody(logBuffer, request.getBody(), bodyCharset, logNotes) .then(Mono.defer(() -> { logBuffer.append("\n------------ end ------------\n\n"); return Mono.empty(); })); } else { logBuffer.append("------------ end ------------\n\n"); return Mono.empty(); } } //日志输出返回值 private Mono<Void> printLog(StringBuilder logBuilder, ServerHttpResponse response) { HttpStatus statusCode = response.getStatusCode(); assert statusCode != null; logBuilder.append("响应:").append(statusCode.value()).append(" ").append(statusCode.getReasonPhrase()).append('\n'); HttpHeaders headers = response.getHeaders(); logBuilder.append("------------响应头------------\n"); headers.forEach((name, values) -> { values.forEach(value -> { logBuilder.append(name).append(":").append(value).append('\n'); }); }); logBuilder.append("\n------------ end at ") .append(System.currentTimeMillis()) .append("------------\n\n"); logger.info(logBuilder.toString()); return Mono.empty(); } // @Override public int getOrder() { //在GatewayFilter之前执行 return -1; } private boolean hasBody(HttpMethod method) { //只记录这3种谓词的body// if (method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.PATCH) return true;// return false; } //记录简单的常见的文本类型的request的body和response的body private boolean shouldRecordBody(MediaType contentType) { String type = contentType.getType(); String subType = contentType.getSubtype(); if ("application".equals(type)) { return "json".equals(subType) || "x-www-form-urlencoded".equals(subType) || "xml".equals(subType) || "atom+xml".equals(subType) || "rss+xml".equals(subType); } else if ("text".equals(type)) { return true; } //暂时不记录form return false; } // 获取请求的参数 private Mono<Void> doRecordReqBody(StringBuilder logBuffer, Flux<DataBuffer> body, Charset charset, LogNotes logNotes) { return DataBufferUtils.join(body).doOnNext(buffer -> { CharBuffer charBuffer = charset.decode(buffer.asByteBuffer()); //记录我实体的请求体 logNotes.setParams(charBuffer.toString()); logBuffer.append(charBuffer.toString()); DataBufferUtils.release(buffer); }).then(); } private Charset getMediaTypeCharset(@Nullable MediaType mediaType) { if (mediaType != null && mediaType.getCharset() != null) { return mediaType.getCharset(); } else { return StandardCharsets.UTF_8; } }}
2.RecorderServerHttpRequestDecorator 继承了ServerHttpRequestDecorator
package com.zc.gateway.filter;import com.zc.entity.LogNotes;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpRequestDecorator;import reactor.core.publisher.Flux;import reactor.core.publisher.Mono;import java.util.LinkedList;import java.util.List;// requestpublic class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator { private final List<DataBuffer> dataBuffers = new LinkedList<>(); private boolean bufferCached = false; private Mono<Void> progress = null; public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate) { super(delegate); }//重写request请求体 @Override public Flux<DataBuffer> getBody() { synchronized (dataBuffers) { if (bufferCached) return copy(); if (progress == null) { progress = cache(); } return progress.thenMany(Flux.defer(this::copy)); } } private Flux<DataBuffer> copy() { return Flux.fromIterable(dataBuffers) .map(buf -> buf.factory().wrap(buf.asByteBuffer())); } private Mono<Void> cache() { return super.getBody() .map(dataBuffers::add) .then(Mono.defer(()-> { bufferCached = true; progress = null; return Mono.empty(); })); }}
3.RecorderServerHttpResponseDecorator 继承了 ServerHttpResponseDecorator
package com.zc.gateway.filter;import com.zc.entity.LogNotes;import com.zc.gateway.service.FilterService;import org.reactivestreams.Publisher;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.core.io.buffer.DataBufferFactory;import org.springframework.core.io.buffer.DataBufferUtils;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.http.server.reactive.ServerHttpResponseDecorator;import reactor.core.publisher.Flux;import reactor.core.publisher.Mono;import org.springframework.core.io.buffer.DataBuffer;import java.nio.charset.Charset;import java.util.LinkedList;import java.util.List;public class RecorderServerHttpResponseDecorator extends ServerHttpResponseDecorator { private Logger logger = LoggerFactory.getLogger(RecorderServerHttpResponseDecorator.class); private LogNotes logNotes; private FilterService filterService; RecorderServerHttpResponseDecorator(ServerHttpResponse delegate, LogNotes logNotes, FilterService filterService) { super(delegate); this.logNotes = logNotes; this.filterService = filterService; } @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { DataBufferFactory bufferFactory = this.bufferFactory(); if (body instanceof Flux) { Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body; Publisher<? extends DataBuffer> re = fluxBody.map(dataBuffer -> { // probably should reuse buffers byte[] content = new byte[dataBuffer.readableByteCount()]; // 数据读入数组 dataBuffer.read(content); // 释放掉内存 DataBufferUtils.release(dataBuffer); // 记录返回值 String s = new String(content, Charset.forName("UTF-8")); logNotes.setAppendResponse(s); try { filterService.updateLog(logNotes); } catch (Exception e) { logger.error("Response值修改日志记录出现错误->{}", e); } byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes(); return bufferFactory.wrap(uppedContent); }); return super.writeWith(re); } return super.writeWith(body); } @Override public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) { return writeWith(Flux.from(body).flatMapSequential(p -> p)); }}
注意:
网关过滤返回值 底层用到了Netty服务,在response返回的时候,有时候会写的数据是不全的,于是我在实体类中新增了一个setAppendResponse方法进行拼接, 再者,gateway的过滤器是链式结构,需要定义order排序为最先(-1),然后和预置的gateway过滤器做一个combine.
代码中用到的 dataBuffer 结构,底层其实也是类似netty的byteBuffer,用到了字节数组池,同时也用到了 引用计数器 (refInt).
为了让jvm在gc的时候垃圾得到回收,避免内存泄露,我们需要在转换字节使用的地方,显示的释放一次
DataBufferUtils.release(dataBuffer);
到此,相信大家对“Spring Cloud gateway网关怎么拦截Post请求日志”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341