+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.core.wrapper; + +import cn.hutool.core.io.IoUtil; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 可重复读取请求体的包装器 + * 支持文件流直接透传,非文件流可重复读取 + * + * @author echo + * @since 2025/03/25 11:11 + **/ +public class RepeatReadRequestWrapper extends HttpServletRequestWrapper { + + private byte[] cachedBody; + private final HttpServletRequest originalRequest; + + public RepeatReadRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + this.originalRequest = request; + + // 判断是否为文件上传请求 + if (!isMultipartContent(request)) { + this.cachedBody = IoUtil.readBytes(request.getInputStream(), false); + } + } + + /** + * 检查是否为文件上传请求 + */ + private boolean isMultipartContent(HttpServletRequest request) { + return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart/"); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // 如果是文件上传,直接返回原始输入流 + if (isMultipartContent(originalRequest)) { + return originalRequest.getInputStream(); + } + + // 非文件上传,返回可重复读取的输入流 + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody); + + return new ServletInputStream() { + @Override + public boolean isFinished() { + return byteArrayInputStream.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + // 非阻塞I/O,这里可以根据需要实现 + } + + @Override + public int read() { + return byteArrayInputStream.read(); + } + }; + } + + @Override + public BufferedReader getReader() throws IOException { + // 如果是文件上传,直接返回原始Reader + if (isMultipartContent(originalRequest)) { + return originalRequest.getReader(); + } + return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(cachedBody), StandardCharsets.UTF_8)); + } +} diff --git a/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadResponseWrapper.java b/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadResponseWrapper.java new file mode 100644 index 00000000..786f9462 --- /dev/null +++ b/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadResponseWrapper.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.core.wrapper;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 可重复读取响应内容的包装器
+ * 支持缓存响应内容,便于日志记录和后续处理 (不缓存SSE)
+ *
+ * @author echo
+ * @since 2025/03/25 11:11
+ **/
+public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
+
+ private final ByteArrayOutputStream cachedOutputStream = new ByteArrayOutputStream();
+ private final PrintWriter writer = new PrintWriter(cachedOutputStream, true);
+
+ // 是否为流式响应
+ private boolean isStreamingResponse = false;
+
+ public RepeatReadResponseWrapper(HttpServletResponse response) {
+ super(response);
+ checkStreamingResponse();
+ }
+
+ @Override
+ public void setContentType(String type) {
+ super.setContentType(type);
+ // 根据 Content-Type 判断是否为流式响应
+ if (type != null) {
+ String lowerType = type.toLowerCase();
+ isStreamingResponse = lowerType.contains("text/event-stream");
+ }
+ }
+
+ private void checkStreamingResponse() {
+ String contentType = getContentType();
+ if (contentType != null) {
+ String lowerType = contentType.toLowerCase();
+ isStreamingResponse = lowerType.contains("text/event-stream");
+ }
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ checkStreamingResponse();
+ if (isStreamingResponse) {
+ // 对于 SSE 流式响应,直接返回原始响应流,不做额外处理
+ return super.getOutputStream();
+ }
+ return new ServletOutputStream() {
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ cachedOutputStream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ cachedOutputStream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ cachedOutputStream.write(b, off, len);
+ }
+ };
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException {
+ checkStreamingResponse();
+ if (isStreamingResponse) {
+ // 对于 SSE 流式响应,直接返回原始响应写入器,不做额外处理
+ return super.getWriter();
+ }
+ return writer;
+ }
+
+ public String getResponseContent() {
+ if (!isStreamingResponse) {
+ writer.flush();
+ return cachedOutputStream.toString(StandardCharsets.UTF_8);
+ }
+ return null;
+ }
+
+ public void copyBodyToResponse() throws IOException {
+ if (!isStreamingResponse && cachedOutputStream.size() > 0) {
+ getResponse().getOutputStream().write(cachedOutputStream.toByteArray());
+ }
+ }
+
+ public boolean isStreamingResponse() {
+ return isStreamingResponse;
+ }
+}
diff --git a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/AccessLogAspect.java b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/AccessLogAspect.java
index 86f7f1a0..34ab4728 100644
--- a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/AccessLogAspect.java
+++ b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/AccessLogAspect.java
@@ -26,9 +26,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
+import top.continew.starter.log.handler.LogHandler;
+import top.continew.starter.log.http.servlet.RecordableServletHttpRequest;
+import top.continew.starter.log.http.servlet.RecordableServletHttpResponse;
+import top.continew.starter.log.model.AccessLogContext;
import top.continew.starter.log.model.LogProperties;
-import java.time.Duration;
import java.time.Instant;
/**
@@ -43,9 +46,11 @@ public class AccessLogAspect {
private static final Logger log = LoggerFactory.getLogger(AccessLogAspect.class);
private final LogProperties logProperties;
+ private final LogHandler logHandler;
- public AccessLogAspect(LogProperties logProperties) {
+ public AccessLogAspect(LogProperties logProperties, LogHandler logHandler) {
this.logProperties = logProperties;
+ this.logHandler = logHandler;
}
/**
@@ -108,19 +113,18 @@ public class AccessLogAspect {
HttpServletRequest request = attributes.getRequest();
HttpServletResponse response = attributes.getResponse();
try {
- // 打印请求日志
- if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
- log.info("[{}] {}", request.getMethod(), request.getRequestURI());
- }
+ logHandler.processAccessLogStartReq(AccessLogContext.builder()
+ .startTime(startTime)
+ .request(new RecordableServletHttpRequest(request))
+ .properties(logProperties)
+ .build());
return joinPoint.proceed();
} finally {
Instant endTime = Instant.now();
- if (Boolean.TRUE.equals(logProperties.getIsPrint())) {
- Duration timeTaken = Duration.between(startTime, endTime);
- log.info("[{}] {} {} {}ms", request.getMethod(), request.getRequestURI(), response != null
- ? response.getStatus()
- : "N/A", timeTaken.toMillis());
- }
+ logHandler.processAccessLogEndReq(AccessLogContext.builder()
+ .endTime(endTime)
+ .response(new RecordableServletHttpResponse(response, response.getStatus()))
+ .build());
}
}
}
diff --git a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/LogAspect.java b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/LogAspect.java
index d9c84299..149ea807 100644
--- a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/LogAspect.java
+++ b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/aspect/LogAspect.java
@@ -32,7 +32,7 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.continew.starter.log.annotation.Log;
import top.continew.starter.log.dao.LogDao;
-import top.continew.starter.log.LogHandler;
+import top.continew.starter.log.handler.LogHandler;
import top.continew.starter.log.model.LogProperties;
import top.continew.starter.log.model.LogRecord;
import top.continew.starter.web.util.SpringWebUtils;
diff --git a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java
index dc71abec..00498a90 100644
--- a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java
+++ b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/autoconfigure/LogAutoConfiguration.java
@@ -29,9 +29,9 @@ import top.continew.starter.log.aspect.AccessLogAspect;
import top.continew.starter.log.aspect.LogAspect;
import top.continew.starter.log.dao.LogDao;
import top.continew.starter.log.dao.impl.DefaultLogDaoImpl;
+import top.continew.starter.log.filter.LogFilter;
import top.continew.starter.log.handler.AopLogHandler;
-import top.continew.starter.log.LogFilter;
-import top.continew.starter.log.LogHandler;
+import top.continew.starter.log.handler.LogHandler;
import top.continew.starter.log.model.LogProperties;
/**
@@ -49,9 +49,11 @@ public class LogAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(LogAutoConfiguration.class);
private final LogProperties logProperties;
+ private final LogHandler logHandler;
- public LogAutoConfiguration(LogProperties logProperties) {
+ public LogAutoConfiguration(LogProperties logProperties, LogHandler logHandler) {
this.logProperties = logProperties;
+ this.logHandler = logHandler;
}
/**
@@ -66,13 +68,12 @@ public class LogAutoConfiguration {
/**
* 日志切面
*
- * @param logHandler 日志处理器
- * @param logDao 日志持久层接口
+ * @param logDao 日志持久层接口
* @return {@link LogAspect }
*/
@Bean
@ConditionalOnMissingBean
- public LogAspect logAspect(LogHandler logHandler, LogDao logDao) {
+ public LogAspect logAspect(LogDao logDao) {
return new LogAspect(logProperties, logHandler, logDao);
}
@@ -84,7 +85,7 @@ public class LogAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AccessLogAspect accessLogAspect() {
- return new AccessLogAspect(logProperties);
+ return new AccessLogAspect(logProperties, logHandler);
}
/**
diff --git a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/handler/AopLogHandler.java b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/handler/AopLogHandler.java
index 203b3926..a01810ba 100644
--- a/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/handler/AopLogHandler.java
+++ b/continew-starter-log/continew-starter-log-aop/src/main/java/top/continew/starter/log/handler/AopLogHandler.java
@@ -16,8 +16,6 @@
package top.continew.starter.log.handler;
-import top.continew.starter.log.AbstractLogHandler;
-
/**
* 日志处理器-AOP 版实现
*
diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/LogFilter.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java
similarity index 71%
rename from continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/LogFilter.java
rename to continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java
index b585a1b3..acd74244 100644
--- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/LogFilter.java
+++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/filter/LogFilter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package top.continew.starter.log;
+package top.continew.starter.log.filter;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.servlet.FilterChain;
@@ -25,15 +25,13 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.core.Ordered;
import org.springframework.lang.NonNull;
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 top.continew.starter.core.wrapper.RepeatReadRequestWrapper;
+import top.continew.starter.core.wrapper.RepeatReadResponseWrapper;
import top.continew.starter.log.model.LogProperties;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.Objects;
/**
* 日志过滤器
@@ -67,20 +65,24 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
filterChain.doFilter(request, response);
return;
}
+
boolean isMatch = logProperties.isMatch(request.getRequestURI());
- // 包装输入流,可重复读取
- if (!isMatch && this.isRequestWrapper(request)) {
- request = new ContentCachingRequestWrapper(request);
- }
- // 包装输出流,可重复读取
- boolean isResponseWrapper = !isMatch && this.isResponseWrapper(response);
- if (isResponseWrapper) {
- response = new ContentCachingResponseWrapper(response);
- }
- filterChain.doFilter(request, response);
- // 更新响应(不操作这一步,会导致接口响应空白)
- if (isResponseWrapper) {
- this.updateResponse(response);
+
+ // 处理可重复读取的请求
+ HttpServletRequest requestWrapper = (isMatch || !this.isRequestWrapper(request))
+ ? request
+ : new RepeatReadRequestWrapper(request);
+
+ // 处理可重复读取的响应
+ HttpServletResponse responseWrapper = (isMatch || !this.isResponseWrapper(response))
+ ? response
+ : new RepeatReadResponseWrapper(response);
+
+ filterChain.doFilter(requestWrapper, responseWrapper);
+
+ // 如果响应被包装了,复制缓存数据到原始响应
+ if (responseWrapper instanceof RepeatReadResponseWrapper wrappedResponse) {
+ wrappedResponse.copyBodyToResponse();
}
}
@@ -121,7 +123,7 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
* @return true:是;false:否
*/
private boolean isRequestWrapper(HttpServletRequest request) {
- return !(request instanceof ContentCachingRequestWrapper);
+ return !(request instanceof RepeatReadRequestWrapper);
}
/**
@@ -131,18 +133,6 @@ public class LogFilter extends OncePerRequestFilter implements Ordered {
* @return true:是;false:否
*/
private boolean isResponseWrapper(HttpServletResponse response) {
- return !(response instanceof ContentCachingResponseWrapper);
- }
-
- /**
- * 更新响应
- *
- * @param response 响应对象
- * @throws IOException /
- */
- private void updateResponse(HttpServletResponse response) throws IOException {
- ContentCachingResponseWrapper responseWrapper = WebUtils
- .getNativeResponse(response, ContentCachingResponseWrapper.class);
- Objects.requireNonNull(responseWrapper).copyBodyToResponse();
+ return !(response instanceof RepeatReadResponseWrapper);
}
}
diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/AbstractLogHandler.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java
similarity index 73%
rename from continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/AbstractLogHandler.java
rename to continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java
index b385719e..9c44571a 100644
--- a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/AbstractLogHandler.java
+++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/handler/AbstractLogHandler.java
@@ -14,21 +14,31 @@
* limitations under the License.
*/
-package top.continew.starter.log;
+package top.continew.starter.log.handler;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.ttl.TransmittableThreadLocal;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import top.continew.starter.log.annotation.Log;
import top.continew.starter.log.enums.Include;
+import top.continew.starter.log.http.RecordableHttpRequest;
+import top.continew.starter.log.http.RecordableHttpResponse;
import top.continew.starter.log.http.servlet.RecordableServletHttpRequest;
import top.continew.starter.log.http.servlet.RecordableServletHttpResponse;
+import top.continew.starter.log.model.AccessLogContext;
+import top.continew.starter.log.model.AccessLogProperties;
import top.continew.starter.log.model.LogRecord;
+import top.continew.starter.log.util.AccessLogUtils;
import java.lang.reflect.Method;
+import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
@@ -41,6 +51,9 @@ import java.util.Set;
*/
public abstract class AbstractLogHandler implements LogHandler {
+ private static final Logger log = LoggerFactory.getLogger(AbstractLogHandler.class);
+ private final TransmittableThreadLocal
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.log.model;
+
+import top.continew.starter.log.http.RecordableHttpRequest;
+import top.continew.starter.log.http.RecordableHttpResponse;
+
+import java.time.Instant;
+
+/**
+ * 访问日志上下文
+ *
+ * @author echo
+ * @since 2.8.3
+ */
+public class AccessLogContext {
+
+ /**
+ * 开始时间
+ */
+ private final Instant startTime;
+
+ /**
+ * 结束时间
+ */
+ private Instant endTime;
+
+ /**
+ * 请求信息
+ */
+ private final RecordableHttpRequest request;
+
+ /**
+ * 响应信息
+ */
+ private final RecordableHttpResponse response;
+
+ /**
+ * 配置信息
+ */
+ private final LogProperties properties;
+
+ private AccessLogContext(Builder builder) {
+ this.startTime = builder.startTime;
+ this.endTime = builder.endTime;
+ this.request = builder.request;
+ this.response = builder.response;
+ this.properties = builder.properties;
+ }
+
+ public Instant getStartTime() {
+ return startTime;
+ }
+
+ public Instant getEndTime() {
+ return endTime;
+ }
+
+ public RecordableHttpRequest getRequest() {
+ return request;
+ }
+
+ public RecordableHttpResponse getResponse() {
+ return response;
+ }
+
+ public LogProperties getProperties() {
+ return properties;
+ }
+
+ public void setEndTime(Instant endTime) {
+ this.endTime = endTime;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Instant startTime;
+ private Instant endTime;
+ private RecordableHttpRequest request;
+ private RecordableHttpResponse response;
+ private LogProperties properties;
+
+ private Builder() {
+ }
+
+ public Builder startTime(Instant startTime) {
+ this.startTime = startTime;
+ return this;
+ }
+
+ public Builder endTime(Instant endTime) {
+ this.endTime = endTime;
+ return this;
+ }
+
+ public Builder request(RecordableHttpRequest request) {
+ this.request = request;
+ return this;
+ }
+
+ public Builder response(RecordableHttpResponse response) {
+ this.response = response;
+ return this;
+ }
+
+ public Builder properties(LogProperties properties) {
+ this.properties = properties;
+ return this;
+ }
+
+ public AccessLogContext build() {
+ return new AccessLogContext(this);
+ }
+ }
+}
diff --git a/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogProperties.java b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogProperties.java
new file mode 100644
index 00000000..9ee2b697
--- /dev/null
+++ b/continew-starter-log/continew-starter-log-core/src/main/java/top/continew/starter/log/model/AccessLogProperties.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.log.model;
+
+import top.continew.starter.web.util.SpringWebUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 访问日志输出配置
+ *
+ * @author echo
+ * @since 2.8.3
+ */
+public class AccessLogProperties {
+
+ /**
+ * 是否打印日志,开启后可打印访问日志(类似于 Nginx access log)
+ *
+ * 不记录日志也支持开启打印访问日志
+ * 开启后会在日志中输出请求参数 开启后会对超过指定长度的参数值进行截断处理 当参数值长度超过此值时,触发截断规则 默认:2000 当参数超过ultraLongParamThreshold时,强制截断到此长度 默认:50 建议配置 3-5 个非占宽字符,默认为空不追加 开启后会对敏感参数进行过滤,默认不过滤 支持精确匹配(区分大小写) 示例值:password,oldPassword
- * 不记录日志也支持开启打印访问日志
- *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.log.util;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import top.continew.starter.log.http.RecordableHttpRequest;
+import top.continew.starter.log.model.AccessLogProperties;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author echo
+ * @since 2025/03/25 19:16
+ **/
+public class AccessLogUtils {
+
+ public AccessLogUtils() {
+ }
+
+ /**
+ * 获取参数信息
+ *
+ * @param request 请求
+ * @param properties 属性
+ * @return {@link String }
+ */
+ public static String getParam(RecordableHttpRequest request, AccessLogProperties properties) {
+ // 是否需要输出参数
+ if (!properties.isReqParams()) {
+ return null;
+ }
+
+ // 参数为空返回空
+ Map