springboot 打印所有接口及所在类
·
package com.ruoyi.framework.security.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
/**
* 请求日志过滤器,用于打印所有请求的详细信息(包括POST请求体)
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // 确保是第一个执行的过滤器
public class RequestLoggingFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(RequestLoggingFilter.class);
// 最大日志长度(避免日志过大)
private static final int MAX_PAYLOAD_LENGTH = 2000;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 包装请求和响应,用于缓存请求体和响应体
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
try {
// 继续过滤器链
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
// 在finally块中打印日志,确保无论是否异常都会执行
logRequestDetails(wrappedRequest);
// logResponseDetails(wrappedResponse);
// 打印可直接运行的 OkHttp 复现代码(仅在调试时开启,生产慎用)
// logOkhttp(wrappedRequest);
// 必须将缓存的响应体复制回原始响应,否则客户端收不到响应
wrappedResponse.copyBodyToResponse();
}
}
private void logOkhttp(ContentCachingRequestWrapper wrappedRequest) {
String okHttpCode = buildOkHttpRequestCode(wrappedRequest);
System.out.println("\n【OkHttp 复现代码】(可直接复制运行):\n"
+"```\n"
+okHttpCode
+"\n```");
}
/**
* 打印请求详情(包括POST请求体)
*/
private void logRequestDetails(ContentCachingRequestWrapper request) {
try {
StringBuilder sb = new StringBuilder();
sb.append("\n============================================");
sb.append("\n=============== 请求详情 START ===============");
sb.append("\n请求URL: ").append(request.getRequestURL().toString());
sb.append("\n请求方法: ").append(request.getMethod());
sb.append("\n请求URI: ").append(request.getRequestURI());
// sb.append("\n客户端IP: ").append(getClientIp(request));
sb.append("\n查询参数: ").append(request.getQueryString());
// // 打印请求头
// sb.append("\n\n请求头信息:");
// Enumeration<String> headerNames = request.getHeaderNames();
// while (headerNames.hasMoreElements()) {
// String headerName = headerNames.nextElement();
// sb.append("\n ").append(headerName).append(": ").append(request.getHeader(headerName));
// }
// // 打印URL参数(GET参数)
// Map<String, String[]> parameterMap = request.getParameterMap();
// if (!parameterMap.isEmpty()) {
// sb.append("\n\nURL参数:");
// for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
// sb.append("\n ").append(entry.getKey()).append(": ");
// String[] values = entry.getValue();
// if (values != null && values.length > 0) {
// sb.append(String.join(", ", values));
// }
// }
// }
// 打印请求体(POST/PUT等请求)
String requestBody = getRequestBody(request);
if (requestBody != null && !requestBody.isEmpty()) {
sb.append("\n请求体:");
// 如果内容过长,截断显示
if (requestBody.length() > MAX_PAYLOAD_LENGTH) {
sb.append("\n").append(requestBody.substring(0, MAX_PAYLOAD_LENGTH))
.append("\n...(内容过长,已截断,总长度:").append(requestBody.length()).append(")");
} else {
sb.append("\n").append(requestBody);
}
}
sb.append("\n=============== 请求详情 END ================");
sb.append("\n============================================\n");
LOG.info(sb.toString());
} catch (Exception e) {
LOG.error("打印请求日志失败", e);
}
}
/**
* 打印响应详情
*/
private void logResponseDetails(ContentCachingResponseWrapper response) {
try {
StringBuilder sb = new StringBuilder();
sb.append("\n============================================");
sb.append("\n=============== 响应详情 START ===============");
sb.append("\n响应状态码: ").append(response.getStatus());
// 打印响应头
sb.append("\n\n响应头信息:");
for (String headerName : response.getHeaderNames()) {
sb.append("\n ").append(headerName).append(": ").append(response.getHeader(headerName));
}
// 打印响应体
String responseBody = getResponseBody(response);
if (responseBody != null && !responseBody.isEmpty()) {
sb.append("\n\n响应体:");
// 如果内容过长,截断显示
if (responseBody.length() > MAX_PAYLOAD_LENGTH) {
sb.append("\n").append(responseBody.substring(0, MAX_PAYLOAD_LENGTH))
.append("\n...(内容过长,已截断,总长度:").append(responseBody.length()).append(")");
} else {
sb.append("\n").append(responseBody);
}
}
sb.append("\n=============== 响应详情 END ================");
sb.append("\n============================================\n");
LOG.info(sb.toString());
} catch (Exception e) {
LOG.error("打印响应日志失败", e);
}
}
/**
* 获取请求体内容(从缓存中读取,不会消耗流)
*/
private String getRequestBody(ContentCachingRequestWrapper request) {
ContentCachingRequestWrapper wrapper =
WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
byte[] content = wrapper.getContentAsByteArray();
if (content.length > 0) {
try {
return new String(content, 0, content.length, wrapper.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
return new String(content);
}
}
}
return "";
}
/**
* 获取响应体内容
*/
private String getResponseBody(ContentCachingResponseWrapper response) {
ContentCachingResponseWrapper wrapper =
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null) {
byte[] content = wrapper.getContentAsByteArray();
if (content.length > 0) {
try {
return new String(content, 0, content.length, wrapper.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
return new String(content);
}
}
}
return "";
}
/**
* 获取客户端真实IP地址
*/
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 如果是多个IP,取第一个
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
/**
* 排除不需要记录日志的请求(如静态资源)
*/
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
// 排除静态资源
return path.contains("/static/")
|| path.contains("/css/")
|| path.contains("/js/")
|| path.contains("/images/")
|| path.contains("/fonts/")
|| path.contains("/favicon.ico")
|| path.contains("/actuator/")
|| path.contains("/swagger/")
|| path.contains("/v3/api-docs");
}
/**
* 根据 HttpServletRequest 构建可直接运行的 OkHttp 请求代码(支持 GET/POST/PUT/DELETE 等,带请求头和请求体)
* 方便开发/测试时直接复制到 Java 代码中复现请求
*/
private String buildOkHttpRequestCode(ContentCachingRequestWrapper request) {
String method = request.getMethod();
String url = request.getRequestURL().toString();
String queryString = request.getQueryString();
if (queryString != null && !queryString.isEmpty()) {
url += "?" + queryString;
}
String requestBody = getRequestBody(request);
StringBuilder code = new StringBuilder();
code.append("OkHttpClient client = new OkHttpClient();\n\n");
// 构建 RequestBody
String requestBodyCode = "";
if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method) || "PATCH".equalsIgnoreCase(method)) {
if (requestBody != null && !requestBody.isEmpty()) {
// 尝试智能判断 Content-Type
String contentType = request.getContentType();
if (contentType != null && contentType.contains("application/json")) {
requestBodyCode = String.format("RequestBody body = RequestBody.create(\"%s\", MediaType.parse(\"application/json; charset=utf-8\"));\n",
escapeJavaString(requestBody));
} else if (contentType != null && contentType.contains("x-www-form-urlencoded")) {
// 表单格式需要解析参数重建(这里简化处理)
requestBodyCode = String.format("RequestBody body = RequestBody.create(\"%s\", MediaType.parse(\"application/x-www-form-urlencoded; charset=utf-8\"));\n",
escapeJavaString(requestBody));
} else {
requestBodyCode = String.format("RequestBody body = RequestBody.create(\"%s\", MediaType.parse(\"text/plain; charset=utf-8\"));\n",
escapeJavaString(requestBody));
}
} else {
requestBodyCode = "RequestBody body = RequestBody.create(\"\", null);\n";
}
}
// 构建 Request.Builder
code.append("Request request = new Request.Builder()\n");
code.append(" .url(\"").append(url).append("\")\n");
// 添加常见请求头
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
// 过滤掉可能导致重复或无效的头
if ("host".equalsIgnoreCase(name) ||
"content-length".equalsIgnoreCase(name) ||
"connection".equalsIgnoreCase(name)) {
continue;
}
// 特殊处理 Authorization、Cookie 等敏感头(可根据需要脱敏)
if ("authorization".equalsIgnoreCase(name)) {
// value = maskAuthorization(value);
value = value;
} else if ("cookie".equalsIgnoreCase(name)) {
// value = maskCookie(value);
value = value;
}
code.append(" .addHeader(\"").append(name).append("\", \"").append(escapeJavaString(value)).append("\")\n");
}
// 添加方法和 body
if ("GET".equalsIgnoreCase(method)) {
code.append(" .get()\n");
} else if ("POST".equalsIgnoreCase(method)) {
code.append(requestBodyCode);
code.append(" .post(body)\n");
} else if ("PUT".equalsIgnoreCase(method)) {
code.append(requestBodyCode);
code.append(" .put(body)\n");
} else if ("DELETE".equalsIgnoreCase(method)) {
if (!requestBody.isEmpty()) {
code.append(requestBodyCode);
code.append(" .delete(body)\n");
} else {
code.append(" .delete()\n");
}
} else if ("PATCH".equalsIgnoreCase(method)) {
code.append(requestBodyCode);
code.append(" .patch(body)\n");
} else {
code.append(" .method(\"").append(method).append("\", ")
.append(requestBody.isEmpty() ? "null" : "body").append(")\n");
}
code.append(" .build();\n\n");
code.append("Response response = client.newCall(request).execute();\n");
code.append("System.out.println(response.body().string());");
return code.toString();
}
/**
* Java 字符串转义(处理换行、引号等)
*/
private String escapeJavaString(String str) {
if (str == null) return "";
return str.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
/**
* 简单脱敏 Authorization 头(如 Bearer token)
*/
private String maskAuthorization(String auth) {
if (auth == null) return auth;
if (auth.toLowerCase().startsWith("bearer ")) {
String token = auth.substring(7);
if (token.length() > 10) {
return "Bearer " + token.substring(0, 6) + "****" + token.substring(token.length() - 4);
}
}
return "****";
}
/**
* 简单脱敏 Cookie
*/
private String maskCookie(String cookie) {
return cookie != null ? "Cookie已脱敏" : "";
}
}
更多推荐



所有评论(0)