Jelajahi Sumber

优化http请求体缓存处理逻辑;修复请求参数加解密异常问题;优化kafka消费者异常日志打印逻辑;

Woody 3 minggu lalu
induk
melakukan
386e4712f5

+ 3 - 3
framework-base/src/main/java/com/chelvc/framework/base/context/LoggingContextHolder.java

@@ -125,10 +125,10 @@ public final class LoggingContextHolder {
             endpoint.append("?").append(query);
         }
         if (HttpUtils.isBodyMethod(request) && !HttpUtils.isMultipartBody(request)) {
-            byte[] body = HttpUtils.getBody(request);
-            if (ObjectUtils.notEmpty(body)) {
+            String body = HttpUtils.getBody(request);
+            if (StringUtils.notEmpty(body)) {
                 endpoint.append(StringUtils.SPACE);
-                endpoint.append(StringUtils.replace(new String(body), '\n', '\0'));
+                endpoint.append(StringUtils.replace(body, '\n', '\0'));
             }
         }
         return message(endpoint.toString(), messages);

+ 68 - 8
framework-base/src/main/java/com/chelvc/framework/base/interceptor/RequestInvokeInterceptor.java

@@ -22,7 +22,10 @@ import com.chelvc.framework.base.context.LoggingContextHolder;
 import com.chelvc.framework.base.context.Result;
 import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.base.util.HttpUtils;
+import com.chelvc.framework.common.util.CodecUtils;
+import com.chelvc.framework.common.util.FileUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.filter.OncePerRequestFilter;
@@ -38,11 +41,38 @@ public class RequestInvokeInterceptor extends OncePerRequestFilter {
     /**
      * 缓存数据流对象
      */
-    static class CachingInputStream extends ServletInputStream {
+    public static class CachingInputStream extends ServletInputStream {
+        private final byte[] bytes;
         private final InputStream delegate;
 
         private CachingInputStream(byte[] bytes) {
-            this.delegate = new ByteArrayInputStream(bytes);
+            this.delegate = new ByteArrayInputStream(this.bytes = bytes);
+        }
+
+        /**
+         * 获取字节数组
+         *
+         * @return 字节数组
+         */
+        public byte[] bytes() {
+            return this.bytes(false);
+        }
+
+        /**
+         * 获取字节数组
+         *
+         * @param decode 是否解码(Base64)
+         * @return 字节数组
+         */
+        public byte[] bytes(boolean decode) {
+            if (this.bytes.length == 0) {
+                return ObjectUtils.EMPTY_BYTE_ARRAY;
+            } else if (decode) {
+                return CodecUtils.decodeBase64(this.bytes);
+            }
+            byte[] buffer = new byte[this.bytes.length];
+            System.arraycopy(this.bytes, 0, buffer, 0, this.bytes.length);
+            return buffer;
         }
 
         @Override
@@ -117,21 +147,49 @@ public class RequestInvokeInterceptor extends OncePerRequestFilter {
     /**
      * 请求缓存包装对象
      */
-    static class CachingRequestWrapper extends HttpServletRequestWrapper {
-        private final byte[] body;
+    public static class CachingRequestWrapper extends HttpServletRequestWrapper {
+        private String body;
+        private final byte[] bytes;
         private final Map<String, String[]> parameters;
 
-        private CachingRequestWrapper(HttpServletRequest request) {
+        private CachingRequestWrapper(HttpServletRequest request) throws IOException {
             super(request);
             if (request instanceof CachingRequestWrapper) {
                 this.body = ((CachingRequestWrapper) request).body;
+                this.bytes = ((CachingRequestWrapper) request).bytes;
                 this.parameters = ((CachingRequestWrapper) request).parameters;
             } else {
                 // Servlet 规范规定如果调用了getInputStream()或getReader()方法,则getParameter*()方法的行为是未定义的
                 // Tomcat、Jetty、Undertow容器均支持在调用getParameter*()方法后能正常调用getInputStream()方法
                 this.parameters = request.getParameterMap();
-                this.body = HttpUtils.getBody(request);
+                if (HttpUtils.isBodyMethod(request)) {
+                    try (InputStream input = request.getInputStream()) {
+                        if (input instanceof CachingInputStream) {
+                            this.bytes = ((CachingInputStream) input).bytes;
+                        } else {
+                            this.bytes = FileUtils.getBytes(input);
+                        }
+                    }
+                    if (this.bytes.length == 0) {
+                        this.body = StringUtils.EMPTY;
+                    }
+                } else {
+                    this.body = StringUtils.EMPTY;
+                    this.bytes = ObjectUtils.EMPTY_BYTE_ARRAY;
+                }
+            }
+        }
+
+        /**
+         * 获取请求体
+         *
+         * @return 请求体字符串
+         */
+        public String getBody() {
+            if (this.body == null) {
+                this.body = new String(this.bytes);
             }
+            return this.body;
         }
 
         @Override
@@ -173,17 +231,19 @@ public class RequestInvokeInterceptor extends OncePerRequestFilter {
 
         @Override
         public ServletInputStream getInputStream() throws IOException {
-            return new CachingInputStream(this.body);
+            return new CachingInputStream(this.bytes);
         }
     }
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
             throws ServletException, IOException {
-        if (HttpUtils.isBodyMethod(request) && !HttpUtils.isMultipartBody(request)) {
+        // 如果不是文件上传请求则对原生请求进行缓存包装处理
+        if (!HttpUtils.isMultipartBody(request)) {
             request = new CachingRequestWrapper(request);
         }
 
+        // 执行请求处理链
         try {
             chain.doFilter(request, response);
         } catch (Exception e) {

+ 17 - 10
framework-base/src/main/java/com/chelvc/framework/base/util/HttpUtils.java

@@ -22,6 +22,7 @@ import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import com.chelvc.framework.base.interceptor.RequestInvokeInterceptor;
 import com.chelvc.framework.common.model.Media;
 import com.chelvc.framework.common.util.FileUtils;
 import com.chelvc.framework.common.util.HostUtils;
@@ -226,14 +227,26 @@ public final class HttpUtils {
      * 获取请求体
      *
      * @param request 请求对象
-     * @return 请求体字节数组
+     * @return 请求体字符串
      */
-    public static byte[] getBody(@NonNull ServletRequest request) {
+    public static String getBody(@NonNull ServletRequest request) {
+        // 从请求包装器中直接获取请求体
+        if (request instanceof RequestInvokeInterceptor.CachingRequestWrapper) {
+            return ((RequestInvokeInterceptor.CachingRequestWrapper) request).getBody();
+        }
+
+        // 从数据流中获取请求体
+        byte[] bytes;
         try (InputStream input = request.getInputStream()) {
-            return FileUtils.getBytes(input);
+            if (input instanceof RequestInvokeInterceptor.CachingInputStream) {
+                bytes = ((RequestInvokeInterceptor.CachingInputStream) input).bytes();
+            } else {
+                bytes = FileUtils.getBytes(input);
+            }
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
+        return bytes.length == 0 ? StringUtils.EMPTY : new String(bytes);
     }
 
     /**
@@ -396,13 +409,7 @@ public final class HttpUtils {
      * @return 请求参数
      */
     public static String serialize(@NonNull ServletRequest request) {
-        if (isBodyMethod(request) && isJsonBody(request)) {
-            // JSON参数
-            return new String(getBody(request));
-        }
-
-        // 表单参数 + URL参数
-        return serialize(request.getParameterMap());
+        return isBodyMethod(request) && isJsonBody(request) ? getBody(request) : serialize(request.getParameterMap());
     }
 
     /**

+ 3 - 1
framework-kafka/src/main/java/com/chelvc/framework/kafka/interceptor/KafkaRecordInterceptor.java

@@ -4,6 +4,7 @@ import java.util.Map;
 
 import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.common.util.ErrorUtils;
 import com.chelvc.framework.kafka.context.KafkaContextHolder;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.kafka.clients.consumer.Consumer;
@@ -73,6 +74,7 @@ public class KafkaRecordInterceptor implements ProducerInterceptor<Object, Objec
 
     @Override
     public void failure(ConsumerRecord<Object, Object> record, Exception exception, Consumer<Object, Object> consumer) {
-        log.error("Kafka message consume failed: {}, {}", consumer.groupMetadata().groupId(), record, exception);
+        String group = consumer.groupMetadata().groupId();
+        log.error("Kafka message consume failed: {}, {}", group, record, ErrorUtils.root(exception));
     }
 }

+ 8 - 5
framework-security/src/main/java/com/chelvc/framework/security/crypto/DefaultSecurityCipherHandler.java

@@ -20,10 +20,13 @@ public class DefaultSecurityCipherHandler extends AbstractSecurityCipherHandler
 
     public DefaultSecurityCipherHandler(@NonNull SecurityProperties.Crypto properties) {
         super(properties);
-        String name = AESUtils.CBC_PKCS5PADDING, secret = properties.getSecret(), iv = properties.getIv();
-        this.encryptor = StringUtils.isEmpty(iv) ?
-                AESUtils.getEncryptor(name, secret) : AESUtils.getEncryptor(name, secret, iv);
-        this.decrypter = StringUtils.isEmpty(iv) ?
-                AESUtils.getDecrypter(name, secret) : AESUtils.getDecrypter(name, secret, iv);
+        String secret = properties.getSecret(), iv = properties.getIv();
+        if (StringUtils.isEmpty(iv)) {
+            this.encryptor = AESUtils.getEncryptor(AESUtils.ECB_PKCS5PADDING, secret);
+            this.decrypter = AESUtils.getDecrypter(AESUtils.ECB_PKCS5PADDING, secret);
+        } else {
+            this.encryptor = AESUtils.getEncryptor(AESUtils.CBC_PKCS5PADDING, secret, iv);
+            this.decrypter = AESUtils.getDecrypter(AESUtils.CBC_PKCS5PADDING, secret, iv);
+        }
     }
 }

+ 7 - 3
framework-security/src/main/java/com/chelvc/framework/security/interceptor/SecurityValidateInterceptor.java

@@ -15,6 +15,7 @@ import com.chelvc.framework.base.context.LoggingContextHolder;
 import com.chelvc.framework.base.context.Result;
 import com.chelvc.framework.base.context.Session;
 import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.base.interceptor.RequestInvokeInterceptor;
 import com.chelvc.framework.base.util.HttpUtils;
 import com.chelvc.framework.base.util.SpringUtils;
 import com.chelvc.framework.common.exception.FrameworkException;
@@ -92,10 +93,13 @@ public class SecurityValidateInterceptor implements HandlerInterceptor, WebMvcCo
     private HttpInputMessage decrypt(HttpInputMessage message) throws IOException {
         byte[] bytes;
         try (InputStream input = message.getBody()) {
-            bytes = FileUtils.getBytes(input);
+            if (input instanceof RequestInvokeInterceptor.CachingInputStream) {
+                bytes = ((RequestInvokeInterceptor.CachingInputStream) input).bytes(true);
+            } else {
+                bytes = CodecUtils.decodeBase64(FileUtils.getBytes(input));
+            }
         }
-        byte[] ciphertext = CodecUtils.decodeBase64(bytes);
-        byte[] plaintext = SecurityContextHolder.getSecurityCipherHandler().decrypt(ciphertext);
+        byte[] plaintext = SecurityContextHolder.getSecurityCipherHandler().decrypt(bytes);
         return new HttpInputMessage() {
             @Override
             public HttpHeaders getHeaders() {