gateway进行接口日志打印

发布于:2025-08-12 ⋅ 阅读:(18) ⋅ 点赞:(0)

打印需求:

        对所有的接口打印:请求方式,请求路径,请求参数,用户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 {

    @Resource
    private PassJavaJwtProperties jwtProperties;
    @Autowired
    private SysPrintLogService sysPrintLogService;

    private static final String OPENAPI_SERVICE = "/openapi/";
    private static final String OPENAPI_CODE_PATH = "/code/";




    @Override
    public int getOrder() {
        return -20;
    }

    @Override
    public 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方法拦截响应体
             */
            @Override
            public 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请求的body
    private 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 body
        return 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";

    @Autowired
    private SysPrintLogMapper sysPrintLogMapper;

    @Autowired
    private RedisUtils redisUtils;


    /**
     * 检查Get请求是否需要打印
     * @return true:需要打印; false:不需要打印
     */
    @Override
    public boolean checkNeedPrint_GET(String requestUrl) {
        return checkWhiteList(requestUrl);
    }

    /**
     * 检查Post请求是否需要打印
     * @return true:需要打印; false:不需要打印
     */
    @Override
    public boolean checkNeedPrint_POST(String requestUrl) {
        return checkBlackList(requestUrl);
    }

    /**
     * 重新加载redis的Get请求日志打印接口白名单
     */
    @Override
    public List<SysPrintLog> loadPringLogWhiteList() {
        List<SysPrintLog> list = sysPrintLogMapper.queryPrintLogWhiteList();
        JSONArray jsonArray = JSONArray.parseArray(JSON.toJSONString(list));
        redisUtils.set(PrintLogUrlWhitelist_GET, jsonArray);
        return list;
    }

    /**
     * 重新加载redis的Post请求日志打印接口黑名单
     */
    @Override
    public 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 = ",";
    // 未知IP
    private final static String UNKNOWN = "unknown";
    // 本地 IP
    private 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才是真实ip
                if (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集群获取ip
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
            if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
                //根据网卡取本机配置的IP
                InetAddress 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;
    }


}


网站公告

今日签到

点亮在社区的每一天
去签到