|
@@ -0,0 +1,312 @@
|
|
|
|
+package com.fdkankan.gateway.filter;
|
|
|
|
+
|
|
|
|
+import cn.dev33.satoken.stp.StpUtil;
|
|
|
|
+import cn.hutool.core.net.URLDecoder;
|
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
+import com.fdkankan.common.response.ResultData;
|
|
|
|
+import com.fdkankan.gateway.log.OperLog;
|
|
|
|
+import com.fdkankan.gateway.util.CacheUtil;
|
|
|
|
+import com.fdkankan.gateway.util.WebUtil;
|
|
|
|
+import com.fdkankan.redis.util.RedisUtil;
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
+import org.reactivestreams.Publisher;
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
|
|
|
+import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
|
|
|
|
+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.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.data.mongodb.core.MongoTemplate;
|
|
|
|
+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.util.StringUtils;
|
|
|
|
+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;
|
|
|
|
+import reactor.core.publisher.Flux;
|
|
|
|
+import reactor.core.publisher.Mono;
|
|
|
|
+
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
+import java.util.*;
|
|
|
|
+
|
|
|
|
+@Component
|
|
|
|
+@Slf4j
|
|
|
|
+public class CommonLogService {
|
|
|
|
+
|
|
|
|
+ private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ RedisUtil redisUtil;
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ private MongoTemplate mongoTemplate;
|
|
|
|
+
|
|
|
|
+ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
|
|
+
|
|
|
|
+ ServerHttpRequest request = exchange.getRequest();
|
|
|
|
+ // 请求路径
|
|
|
|
+ String uri = request.getPath().pathWithinApplication().value();
|
|
|
|
+ String requestPath;
|
|
|
|
+
|
|
|
|
+ Long userId = null;
|
|
|
|
+ String userName = null;
|
|
|
|
+ String nickName = null;
|
|
|
|
+ if(uri.equals("/service/auth/manage/login")){
|
|
|
|
+ requestPath = "登录";
|
|
|
|
+ }else {
|
|
|
|
+ requestPath = getRequestPath(uri);
|
|
|
|
+ userId =Long.valueOf( StpUtil.getExtra("userId").toString());
|
|
|
|
+ userName = StpUtil.getExtra("userName").toString();
|
|
|
|
+ nickName = StpUtil.getExtra("nickName").toString();
|
|
|
|
+ }
|
|
|
|
+ String operationType;
|
|
|
|
+ if(uri.contains("manage")){
|
|
|
|
+ operationType = "manage";
|
|
|
|
+ }else {
|
|
|
|
+ operationType ="other";
|
|
|
|
+ }
|
|
|
|
+ String ipAddress = WebUtil.getIpAddress(request);
|
|
|
|
+ String browser = WebUtil.getBrowser(request);
|
|
|
|
+
|
|
|
|
+ OperLog operLog = new OperLog();
|
|
|
|
+ operLog.setMethod(request.getMethodValue());
|
|
|
|
+ operLog.setUri(uri);
|
|
|
|
+ operLog.setCreateTime(new Date());
|
|
|
|
+ operLog.setIp(ipAddress);
|
|
|
|
+ operLog.setBrowser(browser);
|
|
|
|
+ operLog.setRequestPath(requestPath);
|
|
|
|
+ operLog.setOperationType(operationType);
|
|
|
|
+
|
|
|
|
+ operLog.setUserId(userId);
|
|
|
|
+ operLog.setUserName(userName);
|
|
|
|
+ operLog.setNickName(nickName);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ MediaType mediaType = request.getHeaders().getContentType();
|
|
|
|
+
|
|
|
|
+ if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)
|
|
|
|
+ || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){
|
|
|
|
+ return writeBodyLog(exchange, chain, operLog);
|
|
|
|
+ }else{
|
|
|
|
+ return writeBasicLog(exchange, chain, operLog);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private String getRequestPath(String uri) {
|
|
|
|
+ JSONObject jsonObject = CacheUtil.manageMenuUrl.get(uri);
|
|
|
|
+
|
|
|
|
+ if(StringUtils.isEmpty(jsonObject)){
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ List<String> list = new ArrayList<>();
|
|
|
|
+ getMenuName(list,jsonObject.getString("id"));
|
|
|
|
+ Collections.reverse(list);
|
|
|
|
+ StringBuilder requestPath = new StringBuilder();
|
|
|
|
+ for (String path : list) {
|
|
|
|
+ requestPath.append("[").append(path).append("]").append("->");
|
|
|
|
+ }
|
|
|
|
+ int i = requestPath.lastIndexOf("->");
|
|
|
|
+ if(i < 0){
|
|
|
|
+ return requestPath.toString();
|
|
|
|
+ }
|
|
|
|
+ return requestPath.substring(0,i);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private int getMenuName( List<String> list,String menuId){
|
|
|
|
+ JSONObject jsonObject = CacheUtil.manageMenuId.get(menuId);
|
|
|
|
+ if(StringUtils.isEmpty(jsonObject)){
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+ list.add( jsonObject.getString("name"));
|
|
|
|
+ String parentId = jsonObject.getString("parentId");
|
|
|
|
+ if(!StringUtils.isEmpty(parentId)){
|
|
|
|
+ return getMenuName(list,parentId);
|
|
|
|
+ }
|
|
|
|
+ return 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Mono<Void> writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, OperLog operLog) {
|
|
|
|
+ StringBuilder builder = new StringBuilder();
|
|
|
|
+ MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
|
|
|
|
+ for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
|
|
|
|
+ builder.append(entry.getKey()).append("=").append(StrUtil.join(",", entry.getValue()));
|
|
|
|
+ }
|
|
|
|
+ operLog.setParams(builder.toString());
|
|
|
|
+
|
|
|
|
+ //获取响应体
|
|
|
|
+ ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, operLog);
|
|
|
|
+
|
|
|
|
+ return chain.filter(exchange.mutate().response(decoratedResponse).build())
|
|
|
|
+ .then(Mono.fromRunnable(() -> {
|
|
|
|
+ // 打印日志
|
|
|
|
+ writeAccessLog(operLog);
|
|
|
|
+ }));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 参考: org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory
|
|
|
|
+ * @param exchange
|
|
|
|
+ * @param chain
|
|
|
|
+ * @param operLog
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
+ private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, OperLog operLog) {
|
|
|
|
+ ServerRequest serverRequest = ServerRequest.create(exchange,messageReaders);
|
|
|
|
+
|
|
|
|
+ Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
|
|
|
|
+ .flatMap(body ->{
|
|
|
|
+ operLog.setParams(URLDecoder.decode(body, StandardCharsets.UTF_8));
|
|
|
|
+ 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, operLog);
|
|
|
|
+
|
|
|
|
+ // 记录普通的
|
|
|
|
+ return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build())
|
|
|
|
+ .then(Mono.fromRunnable(() -> {
|
|
|
|
+ // 打印日志
|
|
|
|
+ writeAccessLog(operLog);
|
|
|
|
+ }));
|
|
|
|
+ }));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 打印日志
|
|
|
|
+ * @param operLog 网关日志
|
|
|
|
+ */
|
|
|
|
+ private void writeAccessLog(OperLog operLog) {
|
|
|
|
+ log.info(JSON.toJSONString(operLog));
|
|
|
|
+ //日志写入mongodb
|
|
|
|
+ mongoTemplate.insert(operLog, "OperLog");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ private Route getGatewayRoute(ServerWebExchange exchange) {
|
|
|
|
+ return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 请求装饰器,重新计算 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 {
|
|
|
|
+ httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
|
|
|
|
+ }
|
|
|
|
+ return httpHeaders;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Flux<DataBuffer> getBody() {
|
|
|
|
+ return outputMessage.getBody();
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 记录响应日志
|
|
|
|
+ * 通过 DataBufferFactory 解决响应体分段传输问题。
|
|
|
|
+ */
|
|
|
|
+ private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, OperLog operLog) {
|
|
|
|
+ ServerHttpResponse response = exchange.getResponse();
|
|
|
|
+ DataBufferFactory bufferFactory = response.bufferFactory();
|
|
|
|
+
|
|
|
|
+ return new ServerHttpResponseDecorator(response) {
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
|
|
|
+ if (body instanceof Flux) {
|
|
|
|
+ // 获取响应类型,如果是 json 就打印
|
|
|
|
+ String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
|
|
|
|
+
|
|
|
|
+ if (this.getStatusCode().equals(HttpStatus.OK)
|
|
|
|
+ && StrUtil.isNotBlank(originalResponseContentType)
|
|
|
|
+// && originalResponseContentType.contains("application/json")
|
|
|
|
+ ) {
|
|
|
|
+
|
|
|
|
+ Flux<? extends DataBuffer> 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);
|
|
|
|
+
|
|
|
|
+ ResultData resultData = JSON.parseObject(responseResult, ResultData.class);
|
|
|
|
+ if(resultData.getCode() == 0){
|
|
|
|
+ operLog.setResult("操作成功");
|
|
|
|
+ if(operLog.getUserId() == null){
|
|
|
|
+ JSONObject jsonObject = (JSONObject) resultData.getData();
|
|
|
|
+ operLog.setUserId( Long.valueOf(jsonObject.getString("id")));
|
|
|
|
+ operLog.setUserName(jsonObject.getString("userName"));
|
|
|
|
+ operLog.setNickName(jsonObject.getString("nickName"));
|
|
|
|
+ }
|
|
|
|
+ }else {
|
|
|
|
+ operLog.setResult("操作失败");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return bufferFactory.wrap(content);
|
|
|
|
+ }));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // if body is not a flux. never got there.
|
|
|
|
+ return super.writeWith(body);
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+}
|