当前位置: 首页 > news >正文

gateway进行接口日志打印

打印需求:

        对所有的接口打印:请求方式,请求路径,请求参数,用户id,访问IP,访问时间

        对增删改操作的接口打印:接口响应

打印方案:

        给GET设置一个白名单(因为get请求大多数是查询,仅有部分增删改操作),在这个白名单里的接口,需要打印响应,不在的话就只打印一下基础信息

        给POST请求设置一个黑名单(因为post请求大多数会对数据进行操作,仅有小部分的查询),在这个黑名单里的查询接口,则不需要打印接口响应

 实现方式:

        在过滤器中进行操作

package com.xxxxxx.gateway.filter;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.koushare.common.constant.AuthConstants;
import com.koushare.common.constant.TokenConstants;
import com.koushare.common.utils.IPUtils;
import com.koushare.common.utils.StringUtils;
import com.koushare.gateway.model.CheckRequest;
import com.koushare.gateway.service.SysPrintLogService;
import com.koushare.jwt.config.PassJavaJwtProperties;
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.GlobalFilter;
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.HttpMethod;
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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import javax.annotation.Resource;import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;import static com.koushare.common.convertor.ConvertHelper.bean2map;
import static com.koushare.common.utils.IPUtils.getIpAddrByServerHttpRequest;/*** 自定义全局过滤器*/
@Component
@Slf4j
public class GlobalRequestFilter implements GlobalFilter, Ordered {@Resourceprivate PassJavaJwtProperties jwtProperties;@Autowiredprivate SysPrintLogService sysPrintLogService;private static final String OPENAPI_SERVICE = "/openapi/";private static final String OPENAPI_CODE_PATH = "/code/";@Overridepublic int getOrder() {return -20;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest serverHttpRequest = exchange.getRequest();// 获取原始响应对象和数据缓冲工厂ServerHttpResponse originalResponse = exchange.getResponse();DataBufferFactory bufferFactory = originalResponse.bufferFactory();// 原始响应对象,用于拦截和修改响应内容ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {/*** 重写writeWith方法拦截响应体*/@Overridepublic Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {if (body instanceof Flux) {Flux<? extends DataBuffer> fluxBody = Flux.from(body);return super.writeWith(fluxBody.buffer().map(dataBuffers -> {// 获取一些需要打印的参数long timestamp = System.currentTimeMillis();HttpMethod method = serverHttpRequest.getMethod();String requestUrl = serverHttpRequest.getPath().toString();String userId = Optional.ofNullable(serverHttpRequest.getHeaders().getFirst(AuthConstants.USER_ID)).filter(StringUtils::isNotBlank).orElse("未登录");String ip = IPUtils.getIpAddrByServerHttpRequest(serverHttpRequest);String params = getRequestparams(serverHttpRequest, exchange);log.info("{} ========================接口详细日志========================", timestamp);log.info("{} 请求方式:{}  请求路径: {}", timestamp, method, requestUrl);log.info("{} 请求参数: {}", timestamp, params);log.info("{} 用户ID: {}  访问IP: {}  访问时间:{}", timestamp, userId, ip, new Date());// 判断是否需要打印响应if (isUpdateDate(method, requestUrl)) {// 创建数据缓冲工厂和缓冲区,用于读取响应内容DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();DataBuffer buff = dataBufferFactory.join(dataBuffers);byte[] content = new byte[buff.readableByteCount()];buff.read(content);// 释放缓冲区资源DataBufferUtils.release(buff);// 获取响应内容类型MediaType contentType = originalResponse.getHeaders().getContentType();if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {// 如果不是JSON类型,直接返回原始内容,不进行处理log.info("{} ===============================================================", timestamp);return bufferFactory.wrap(content);}// 将字节数组转换为字符串 对响应体进行统一格式化处理String result = modifyBody(new String(content));log.info("{} 响应结果: {}", timestamp, result);log.info("{} ===============================================================", timestamp);getDelegate().getHeaders().setContentLength(result.getBytes().length);return bufferFactory.wrap(result.getBytes());} else {// 不需要打印响应结果时,直接合并并返回原始数据log.info("{} ===============================================================", timestamp);DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();DataBuffer joinedBuffer = dataBufferFactory.join(dataBuffers);byte[] content = new byte[joinedBuffer.readableByteCount()];joinedBuffer.read(content);DataBufferUtils.release(joinedBuffer);return bufferFactory.wrap(content);}}));} else {return super.writeWith(body);}}};return chain.filter(exchange.mutate().response(decoratedResponse).build());}private static String getRouteName(String requestUrl) {String serviceUrl = requestUrl.substring(requestUrl.indexOf("/") + 1);log.info("getRouteName: " + serviceUrl.substring(0, serviceUrl.indexOf("/")));return serviceUrl.substring(0, serviceUrl.indexOf("/"));}/*** 获取请求token*/private String getToken(ServerHttpRequest request) {String token = request.getHeaders().getFirst(jwtProperties.getHeader());// 如果前端设置了令牌前缀,则裁剪掉前缀if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);}return token;}/*** 获取去除路由后的path** @param requestUrl* @return*/private static String getPath(String requestUrl) {String path = requestUrl.substring(1);log.info("getPath: " + path.substring(path.indexOf("/")));return path.substring(path.indexOf("/"));}/*** 判断是否为增删改接口  只有增删改接口才会打印响应结果*/private boolean isUpdateDate(HttpMethod method, String requestUrl){switch (method) {case PUT:case DELETE:return true;case GET:return sysPrintLogService.checkNeedPrint_GET(requestUrl);case POST:return sysPrintLogService.checkNeedPrint_POST(requestUrl);default:return false;}}/*** 获取请求参数*/private String getRequestparams(ServerHttpRequest serverHttpRequest, ServerWebExchange exchange) {HttpMethod method = serverHttpRequest.getMethod();// 检查是否为文件上传请求,如果是则不打印参数MediaType contentType = serverHttpRequest.getHeaders().getContentType();if (contentType != null && (contentType.includes(MediaType.MULTIPART_FORM_DATA)|| contentType.includes(MediaType.APPLICATION_OCTET_STREAM))) {return "";}if (HttpMethod.GET.equals(method) || HttpMethod.DELETE.equals(method)) {StringBuilder params = new StringBuilder();serverHttpRequest.getQueryParams().forEach((key, value) -> {value.forEach(v -> params.append(key).append("=").append(v).append("&"));});// 移除末尾的 "&"if (params.length() > 0) {params.deleteCharAt(params.length() - 1);}return params.toString();} else if (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method)) {return getBodyContent(exchange);}return "";}// 从其他filter中copy过来的 目的是获取post请求的bodyprivate String getBodyContent(ServerWebExchange exchange){Flux<DataBuffer> body = exchange.getRequest().getBody();AtomicReference<String> bodyRef = new AtomicReference<>();// 缓存读取的request body信息body.subscribe(dataBuffer -> {CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());DataBufferUtils.release(dataBuffer);bodyRef.set(charBuffer.toString());});//获取request bodyreturn bodyRef.get();}/*** 修改响应体内容,统一JSON数据格式*/private String modifyBody(String str){JSONObject json = JSON.parseObject(str, Feature.AllowISO8601DateFormat);JSONObject.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";return JSONObject.toJSONString(json, (ValueFilter) (object, name, value) ->value == null ? "" : value, SerializerFeature.WriteDateUseDateFormat);}}

其中的service实现类:

package com.xxxxxx.gateway.service.impl;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.koushare.gateway.entity.SysPrintLog;
import com.koushare.gateway.mapper.SysPrintLogMapper;
import com.koushare.gateway.service.SysPrintLogService;
import com.koushare.redis.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Slf4j
@Service
public class SysPrintLogServiceImpl implements SysPrintLogService {private final static String PrintLogUrlWhitelist_GET = "PrintLog:UrlWhiteList";private final static String PrintLogUrlBlacklist_POST = "PrintLog:UrlBlackList";@Autowiredprivate SysPrintLogMapper sysPrintLogMapper;@Autowiredprivate RedisUtils redisUtils;/*** 检查Get请求是否需要打印* @return true:需要打印; false:不需要打印*/@Overridepublic boolean checkNeedPrint_GET(String requestUrl) {return checkWhiteList(requestUrl);}/*** 检查Post请求是否需要打印* @return true:需要打印; false:不需要打印*/@Overridepublic boolean checkNeedPrint_POST(String requestUrl) {return checkBlackList(requestUrl);}/*** 重新加载redis的Get请求日志打印接口白名单*/@Overridepublic List<SysPrintLog> loadPringLogWhiteList() {List<SysPrintLog> list = sysPrintLogMapper.queryPrintLogWhiteList();JSONArray jsonArray = JSONArray.parseArray(JSON.toJSONString(list));redisUtils.set(PrintLogUrlWhitelist_GET, jsonArray);return list;}/*** 重新加载redis的Post请求日志打印接口黑名单*/@Overridepublic List<SysPrintLog> loadPrintLogBlackList() {List<SysPrintLog> list = sysPrintLogMapper.queryPrintLogBlackList();JSONArray jsonArray = JSONArray.parseArray(JSON.toJSONString(list));redisUtils.set(PrintLogUrlBlacklist_POST, jsonArray);return list;}/*** 读redis中的白名单,不存在则读取数据库*/private List<SysPrintLog> WhiteList(){JSONArray jsonArray = redisUtils.getJsonArray(PrintLogUrlWhitelist_GET);if (jsonArray == null || jsonArray.size() == 0) {return loadPringLogWhiteList();}return jsonArray.toJavaList(SysPrintLog.class);}/*** 读redis中的黑名单,不存在则读取数据库*/private List<SysPrintLog> BlackList(){JSONArray jsonArray = redisUtils.getJsonArray(PrintLogUrlBlacklist_POST);if (jsonArray == null || jsonArray.size() == 0) {return loadPrintLogBlackList();}return jsonArray.toJavaList(SysPrintLog.class);}/*** 白名单列表中查找是否存在匹配的URL*/private boolean checkWhiteList(String requestUrl){return WhiteList().stream().anyMatch(sysPrintLog -> sysPrintLog.getUrl().equals(requestUrl));}/*** 黑名单列表中查找是否存在匹配的URL*/private boolean checkBlackList(String requestUrl){return BlackList().stream().noneMatch(sysPrintLog -> sysPrintLog.getUrl().equals(requestUrl));}}

用到的两个查询:

@Mapper
public interface SysPrintLogMapper {@Select("select id,url,description from sys_print_log_whitelist")List<SysPrintLog> queryPrintLogWhiteList();@Select("select id,url,description from sys_print_log_blacklist")List<SysPrintLog> queryPrintLogBlackList();}

以及数据库实体

@Data
@Builder(setterPrefix = "set")
@NoArgsConstructor
@AllArgsConstructor
public class SysPrintLog {private Integer id;private String url;private String description;}

获取ip地址的工具类:

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;/*** IP地址** @author zhanghai on 2018/9/17*/
@Slf4j
public class IPUtils {// 多次反向代理后会有多个ip值 的分割符private final static String IP_UTILS_FLAG = ",";// 未知IPprivate final static String UNKNOWN = "unknown";// 本地 IPprivate final static String LOCALHOST_IP = "0:0:0:0:0:0:0:1";private final static String LOCALHOST_IP1 = "127.0.0.1";/*** 获取IP地址* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址*/public static String getIpAddr(HttpServletRequest request) {String ip = null;try {ip = request.getHeader("x-forwarded-for");if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}} catch (Exception e) {log.error("IPUtils ERROR ", e);}// 使用代理,则获取第一个IP地址if (StringUtils.isEmpty(ip) && ip.length() > 15) {if (ip.indexOf(",") > 0) {ip = ip.substring(0, ip.indexOf(","));}}return ip;}public static String getIpAddrByServerHttpRequest(ServerHttpRequest request) {// 根据 HttpHeaders 获取 请求 IP地址String ip = request.getHeaders().getFirst("X-Forwarded-For");if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("x-forwarded-for");if (ip != null && ip.length() != 0 && !UNKNOWN.equalsIgnoreCase(ip)) {// 多次反向代理后会有多个ip值,第一个ip才是真实ipif (ip.contains(IP_UTILS_FLAG)) {ip = ip.split(IP_UTILS_FLAG)[0];}}}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("HTTP_CLIENT_IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("X-Real-IP");}//兼容k8s集群获取ipif (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddress().getAddress().getHostAddress();if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {//根据网卡取本机配置的IPInetAddress iNet = null;try {iNet = InetAddress.getLocalHost();} catch (UnknownHostException e) {log.error("getClientIp error: ", e);}ip = iNet.getHostAddress();}}return ip;}/*** 获取IP地址* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址*/public static String getIpAddrByHttp(ServerHttpRequest request) {String ip = null;try {ip = request.getHeaders().getFirst("x-forwarded-for");if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("WL-Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("HTTP_CLIENT_IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("HTTP_X_FORWARDED_FOR");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("X-Real-IP");}} catch (Exception e) {log.error("IPUtils ERROR ", e);}// 使用代理,则获取第一个IP地址if (StringUtils.isEmpty(ip) && ip.length() > 15) {if (ip.indexOf(",") > 0) {ip = ip.substring(0, ip.indexOf(","));}}return ip;}}

http://www.dtcms.com/a/326656.html

相关文章:

  • 刘强东的AI棋局
  • 高并发内存池 内存释放回收(6)
  • 如何生成测试报告
  • 加载模型使用torch_dtype参数来显式指定数据类型
  • 美股期权历史波动率数据研究分析教程
  • 基于STM32单片机超声波测速测距防撞报警设计
  • c# 线程的基础教学(winform 电梯模拟)
  • C9800 ISSU升级
  • 【Java工具】Java-sftp线程池上传
  • ADK[5]调用外部工具流程
  • (附源码)基于Spring Boot的4S店信息管理系统 的设计与实现
  • 每日算法刷题Day61:8.11:leetcode 堆11道题,用时2h30min
  • 【功能测试】软件集成测试思路策略与经验总结
  • HTML应用指南:利用GET请求获取全国vivo体验店门店位置信息
  • 字节后端面经
  • 内网依赖管理新思路:Nexus与CPolar的协同实践
  • Linux-FTP服务器搭建
  • 【图像算法 - 12】OpenCV-Python 入门指南:图像视频处理与可视化(代码实战 + 视频教程 + 人脸识别项目讲解)
  • DHCP服务配置与管理实战指南
  • CRMEB多商户系统(Java)v2.0更新预告:区域管理+预约商品,激活本地商业新活力!
  • NTC热敏电阻、压敏电阻和保险丝工作原理
  • FFmpeg - 基本 API大全(视频编解码相关的)
  • python每日一题练习 两个数组的交集 非常简单
  • GCN: 图卷积网络,概念以及代码实现
  • 【LeetCode刷题集】--排序(三)
  • Protocol Buffers (protobuf) API 接口完全指南
  • maven项目打包成sdk后在别的项目使用
  • 从0开始的中后台管理系统-5(部门管理以及菜单管理页面功能实现)
  • 【科研绘图系列】R语言绘制散点图折线图误差棒组合图
  • 指派问题-匈牙利算法