瀏覽代碼

v1.0 20230525

lifuquan 1 年之前
當前提交
03f547ce3f

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+**/target

+ 69 - 0
README.md

@@ -0,0 +1,69 @@
+# request-log-interceptor-starter
+
+添加拦截器,打印所有的请求和返回值。
+
+## 使用方式
+
+### 1. 引入依赖
+
+```xml
+<dependency>
+    <groupId>com.nokia</groupId>
+    <artifactId>request-log-interceptor-starter</artifactId>
+    <version>1.0</version>
+</dependency>
+```
+
+### 2. 注入拦截器
+
+- 自行注入方式
+
+```java
+// 引入配置类
+@Configuration
+public class RequestLogConfiguration implements WebMvcConfigurer {
+
+    /**
+     * 添加拦截器
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 添加日志拦截器
+        registry.addInterceptor(new RequestLogHandlerInterceptor()).addPathPatterns("/**");
+    }
+
+    /**
+     * 注入自定义的RequestLogDispatcherServlet替代默认的DispatcherServlet
+     */
+    @Bean
+    @Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
+    public DispatcherServlet dispatcherServlet() {
+        return new RequestLogDispatcherServlet();
+    }
+
+}
+```
+
+- 配置方式(不推荐)
+
+```yml
+request:
+  log:
+    enable: true
+spring:
+  main:
+    allow-bean-definition-overriding: true
+```
+
+> 注意,这里需要打开配置`spring.main.allow-bean-definition-overriding=true`,允许覆盖springbean,这样才能实现使用starter注入的DispatcherServlet替代默认的。这不是一种安全的做法,因此不建议使用。
+
+## 版本更新记录
+
+### v1.0
+
+1. 实现了打印日志的功能,日志样式如下:
+
+```text
+2023-05-25 18:26:44.649  INFO 9520 --- [nio-8080-exec-1] c.n.r.i.RequestLogHandlerInterceptor     : 请求地址: http://127.0.0.1:8080/test POST,请求头: {content-length=0, host=127.0.0.1:8080, content-type=application/json, connection=close, accept-encoding=gzip, deflate, user-agent=vscode-restclient},请求参数:
+2023-05-25 18:26:44.667  INFO 9520 --- [nio-8080-exec-1] c.n.r.i.RequestLogHandlerInterceptor     : 耗时 18 ms, 返回 200: ===
+```

+ 73 - 0
pom.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-parent</artifactId>
+        <version>2.6.14</version>
+        <relativePath />
+    </parent>
+
+    <groupId>com.nokia</groupId>
+    <artifactId>request-log-interceptor-starter</artifactId>
+    <version>1.0</version>
+
+    <packaging>jar</packaging>
+
+    <properties>
+        <!-- 跳过测试代码 -->
+        <skipTests>true</skipTests>
+        <!-- 指定java的版本 -->
+        <java.version>1.8</java.version>
+        <!-- 文件拷贝时的编码 -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <!-- 指定maven-compiler-plugin的配置属性开始 -->
+        <!-- 编译时的编码 -->
+        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
+        <!-- 指定编译的版本 -->
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
+        <!-- 指定maven-compiler-plugin的配置属性结束 -->
+    </properties>
+
+    <dependencies>
+        <!-- spring-boot-starter-web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!-- spring-boot-autoconfigure -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <!-- spring-boot-configuration-processor -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <!--lombok-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <distributionManagement>
+        <repository>
+            <id>nokia</id>
+            <url>http://nokia.tianhaikj.tk:18081/artifactory/maven-local-releases</url>
+        </repository>
+        <snapshotRepository>
+            <id>nokia</id>
+            <url>http://nokia.tianhaikj.tk:18081/artifactory/maven- ocal-snapshots</url>
+        </snapshotRepository>
+    </distributionManagement>
+</project>

+ 40 - 0
src/main/java/com/nokia/request_log/config/RequestLogAutoConfiguration.java

@@ -0,0 +1,40 @@
+package com.nokia.request_log.config;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import com.nokia.request_log.interceptor.RequestLogHandlerInterceptor;
+import com.nokia.request_log.servlet.RequestLogDispatcherServlet;
+
+/**
+ * 自动配置类,需要在resuources/META-INF/spring.factories文件进行配置
+ */
+@Configuration
+@ConditionalOnProperty(name = "request.log.enable", havingValue = "true", matchIfMissing = false)
+public class RequestLogAutoConfiguration implements WebMvcConfigurer {
+
+    /**
+     * 添加拦截器
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 添加日志拦截器
+        registry.addInterceptor(new RequestLogHandlerInterceptor()).addPathPatterns("/**");
+    }
+
+    /**
+     * 注入自定义的RequestLogDispatcherServlet替代默认的DispatcherServlet
+     */
+    @Bean
+    @Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
+    public DispatcherServlet dispatcherServlet() {
+        return new RequestLogDispatcherServlet();
+    }
+
+}

+ 70 - 0
src/main/java/com/nokia/request_log/interceptor/RequestLogHandlerInterceptor.java

@@ -0,0 +1,70 @@
+package com.nokia.request_log.interceptor;
+
+import java.nio.charset.Charset;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.MDC;
+import org.springframework.lang.Nullable;
+import org.springframework.util.StopWatch;
+import org.springframework.util.StreamUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import com.nokia.request_log.servlet.entity.RepeatableHttpServletResponseWrapper;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 拦截器--添加堆请求和返回结果的输出,并输出从请求开始到访问结束的时长
+ */
+@Slf4j
+public class RequestLogHandlerInterceptor implements HandlerInterceptor {
+
+    private final ThreadLocal<StopWatch> STOP_WATCH_THREAD_LOCAL = new ThreadLocal<>();
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
+            throws Exception {
+        // 启动一个秒表进行计时
+        StopWatch stopWatch = new StopWatch();
+        STOP_WATCH_THREAD_LOCAL.set(stopWatch);
+        stopWatch.start();
+        // 日志添加跟踪id
+        MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));
+        // 请求头参数
+        Map<String, String> headers = new HashMap<>();
+        Enumeration<String> headerNames = request.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+            String k = headerNames.nextElement();
+            String v = request.getHeader(k);
+            headers.put(k, v);
+        }
+        String body = StreamUtils.copyToString(request.getInputStream(),
+                Charset.forName(request.getCharacterEncoding()));
+        log.info("请求地址: {} {},请求头: {},请求参数: {}",
+                request.getRequestURL().toString(),
+                request.getMethod(),
+                headers,
+                StringUtils.trimAllWhitespace(body));
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
+            @Nullable Exception ex) throws Exception {
+        RepeatableHttpServletResponseWrapper wrapper = (RepeatableHttpServletResponseWrapper) response;
+        String responseString = new String(wrapper.toByteArray());
+        responseString = responseString.length() > 1000 ? responseString.substring(0, 1000) : responseString;
+        StopWatch stopWatch = STOP_WATCH_THREAD_LOCAL.get();
+        stopWatch.stop();
+        // 返回结果打印前1000个字符
+        log.info("耗时 {} ms, 返回 {}: {}", stopWatch.getTotalTimeMillis(), wrapper.getStatus(), responseString);
+        STOP_WATCH_THREAD_LOCAL.remove();
+    }
+}

+ 25 - 0
src/main/java/com/nokia/request_log/servlet/RequestLogDispatcherServlet.java

@@ -0,0 +1,25 @@
+package com.nokia.request_log.servlet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.servlet.DispatcherServlet;
+
+import com.nokia.request_log.servlet.entity.RepeatableHttpServletRequestWrapper;
+import com.nokia.request_log.servlet.entity.RepeatableHttpServletResponseWrapper;
+
+/**
+ * 自定义DispatcherServlet
+ */
+public class RequestLogDispatcherServlet extends DispatcherServlet {
+
+    /**
+     * 使用可重复读的reuqest和respone替换原来参数
+     */
+    @Override
+    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        super.doDispatch(new RepeatableHttpServletRequestWrapper(request),
+                new RepeatableHttpServletResponseWrapper(response));
+    }
+
+}

+ 66 - 0
src/main/java/com/nokia/request_log/servlet/entity/RepeatableHttpServletRequestWrapper.java

@@ -0,0 +1,66 @@
+package com.nokia.request_log.servlet.entity;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.springframework.util.StreamUtils;
+
+/**
+ * 可重复读取的HttpServletRequestWrapper
+ */
+public class RepeatableHttpServletRequestWrapper extends HttpServletRequestWrapper {
+    // 承载request中的数据,以便重复读取
+    private byte[] bytes;
+
+    public RepeatableHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
+        super(request);
+        // 读取request中的数据到bytes
+        bytes = StreamUtils.copyToByteArray(request.getInputStream());
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
+        // 匿名内部类实现ServletInputStream
+        return new ServletInputStream() {
+            @Override
+            public boolean isFinished() {
+                return stream.available() <= 0;
+            }
+
+            @Override
+            public boolean isReady() {
+                return stream.available() > 0;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener) {
+                try {
+                    readListener.onDataAvailable();
+                    if (isFinished()) {
+                        readListener.onAllDataRead();
+                    }
+                } catch (IOException e) {
+                    readListener.onError(e);
+                }
+            }
+
+            @Override
+            public int read() {
+                return stream.read();
+            }
+        };
+    }
+
+    @Override
+    public BufferedReader getReader() throws IOException {
+        return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
+    }
+}

+ 51 - 0
src/main/java/com/nokia/request_log/servlet/entity/RepeatableHttpServletResponseWrapper.java

@@ -0,0 +1,51 @@
+package com.nokia.request_log.servlet.entity;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+public class RepeatableHttpServletResponseWrapper extends HttpServletResponseWrapper {
+
+    private ByteArrayOutputStream byteArrayOutputStream;
+    private ServletOutputStream servletOutputStream;
+
+    public RepeatableHttpServletResponseWrapper(HttpServletResponse response) throws IOException {
+        super(response);
+        // 从当前的response获取ServletOutputStream
+        ServletOutputStream outputStream = response.getOutputStream();
+        byteArrayOutputStream = new ByteArrayOutputStream();
+        // 匿名内部类实现ServletOutputStream
+        servletOutputStream = new ServletOutputStream() {
+
+            @Override
+            public boolean isReady() {
+                return outputStream.isReady();
+            }
+
+            @Override
+            public void setWriteListener(WriteListener writeListener) {
+                outputStream.setWriteListener(writeListener);
+            }
+
+            @Override
+            public void write(int b) throws IOException {
+                // 写入原有的基础上同时写入ByteArrayOutputStream
+                outputStream.write(b);
+                byteArrayOutputStream.write(b);
+            }
+        };
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        return servletOutputStream;
+    }
+
+    public byte[] toByteArray() {
+        return byteArrayOutputStream.toByteArray();
+    }
+}

+ 2 - 0
src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.nokia.request_log.config.RequestLogAutoConfiguration

+ 8 - 0
src/main/resources/WEB-INF/requestLogDispatcherServlet-servlet.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
+    version="4.0">
+
+
+</web-app>