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