diff --git a/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/event/SysLogListener.java b/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/event/SysLogListener.java index d40f04364..34683df17 100755 --- a/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/event/SysLogListener.java +++ b/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/event/SysLogListener.java @@ -24,15 +24,25 @@ import com.pig4cloud.pigx.common.core.jackson.PigxJavaTimeModule; import com.pig4cloud.pigx.common.log.config.PigxLogProperties; import com.pig4cloud.pigx.common.log.util.JacksonSensitiveFieldUtil; import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Part; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; +import org.springframework.core.io.Resource; import org.springframework.scheduling.annotation.Async; import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -47,11 +57,9 @@ public class SysLogListener implements InitializingBean { /** * 忽略序列化的对象类型 */ - private final static Class[] ignoreClass = {ServletRequest.class, BindingResult.class}; - - /** - * new 一个 避免日志脱敏策略影响全局ObjectMapper - */ + private static final Class[] IGNORE_CLASSES = {ServletRequest.class, ServletResponse.class, BindingResult.class, + MultipartFile.class, Part.class, InputStream.class, OutputStream.class, Reader.class, Writer.class, + Resource.class, File.class, Path.class}; private final RemoteLogService remoteLogService; @@ -69,7 +77,7 @@ public class SysLogListener implements InitializingBean { Object[] args = (Object[]) source.getBody(); List list = CollUtil.toList(args); // 删除部分无法序列化的参数 - list.removeIf(obj -> Arrays.stream(ignoreClass).anyMatch(clazz -> clazz.isAssignableFrom(obj.getClass()))); + list.removeIf(this::isIgnoredParameter); try { // 序列化参数 @@ -84,6 +92,19 @@ public class SysLogListener implements InitializingBean { remoteLogService.saveLog(source); } + /** + * 判断请求参数是否属于不可异步序列化的类型 + *

+ * 请求结束后 Tomcat 会清理 multipart 临时文件,流、文件类参数在异步线程中序列化会触发 + * NoSuchFileException,需要在序列化前剔除。 + * @param obj 请求参数,可能为 null(null 可正常序列化,予以保留) + * @return true 表示需要从序列化列表中剔除 + */ + private boolean isIgnoredParameter(Object obj) { + return Objects.nonNull(obj) + && Arrays.stream(IGNORE_CLASSES).anyMatch(clazz -> clazz.isAssignableFrom(obj.getClass())); + } + @Override public void afterPropertiesSet() { String[] ignorableFieldNames = logProperties.getExcludeFields().toArray(new String[0]); diff --git a/pigx-common/pigx-common-log/src/test/java/com/pig4cloud/pigx/common/log/event/SysLogListenerTests.java b/pigx-common/pigx-common-log/src/test/java/com/pig4cloud/pigx/common/log/event/SysLogListenerTests.java new file mode 100644 index 000000000..86d518a71 --- /dev/null +++ b/pigx-common/pigx-common-log/src/test/java/com/pig4cloud/pigx/common/log/event/SysLogListenerTests.java @@ -0,0 +1,97 @@ +package com.pig4cloud.pigx.common.log.event; + +import com.pig4cloud.pigx.admin.api.dto.SysLogDTO; +import com.pig4cloud.pigx.admin.api.feign.RemoteLogService; +import com.pig4cloud.pigx.common.core.util.R; +import com.pig4cloud.pigx.common.log.config.PigxLogProperties; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * {@link SysLogListener} 请求参数过滤测试 + * + * @author lengleng + */ +class SysLogListenerTests { + + /** + * MultipartFile 参数必须在序列化前被剔除,且日志仍正常保存 + */ + @Test + void filtersMultipartFileBeforeSerializingRequestParams() { + RemoteLogService remoteLogService = mock(RemoteLogService.class); + when(remoteLogService.saveLog(any(SysLogDTO.class))).thenReturn(R.ok(Boolean.TRUE)); + + PigxLogProperties logProperties = new PigxLogProperties(); + logProperties.setExcludeFields(List.of()); + SysLogListener listener = new SysLogListener(remoteLogService, logProperties); + listener.afterPropertiesSet(); + + SysLogDTO sysLog = new SysLogDTO(); + sysLog.setBody(new Object[] { new ThrowingMultipartFile() }); + + listener.saveSysLog(new SysLogEvent(sysLog)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SysLogDTO.class); + verify(remoteLogService).saveLog(captor.capture()); + assertThat(captor.getValue().getParams()).isEqualTo("[]"); + assertThat(captor.getValue().getBody()).isNull(); + } + + private static class ThrowingMultipartFile implements MultipartFile { + + @Override + public String getName() { + throw new AssertionError("MultipartFile should be filtered before serialization"); + } + + @Override + public String getOriginalFilename() { + throw new AssertionError("MultipartFile should be filtered before serialization"); + } + + @Override + public String getContentType() { + throw new AssertionError("MultipartFile should be filtered before serialization"); + } + + @Override + public boolean isEmpty() { + throw new AssertionError("MultipartFile should be filtered before serialization"); + } + + @Override + public long getSize() { + throw new AssertionError("MultipartFile should be filtered before serialization"); + } + + @Override + public byte[] getBytes() throws IOException { + throw new AssertionError("MultipartFile should be filtered before serialization"); + } + + @Override + public InputStream getInputStream() throws IOException { + throw new AssertionError("MultipartFile should be filtered before serialization"); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + throw new AssertionError("MultipartFile should be filtered before serialization"); + } + + } + +}