Browse Source

feat: 添加拦截器打印请求日志

weijianghai 2 years ago
parent
commit
0fe3115be9

+ 11 - 4
pom.xml

@@ -18,10 +18,6 @@
     </properties>
 
     <dependencies>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-aop</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-validation</artifactId>
@@ -62,6 +58,17 @@
             <artifactId>lombok</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-csv</artifactId>
+            <version>1.9.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui -->
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+            <version>1.6.13</version>
+        </dependency>
     </dependencies>
     <build>
         <finalName>aclTousu</finalName>

+ 33 - 11
src/main/java/com/nokia/common/R.java

@@ -1,17 +1,21 @@
 package com.nokia.common;
 
+import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 /**
  * 返回值的统一包装
  */
 @Data
-public class R {
+public class R<T> {
+    @Schema(description = "是否成功")
     private Boolean success;
+    @Schema(description = "业务码")
     private Integer code;
+    @Schema(description = "提示信息", example = "成功")
     private String message;
-
-    private Object data = null;
+    @Schema(description = "数据")
+    private T data = null;
 
     /**
      * 私有化构造方法,不允许在外部实例化
@@ -24,11 +28,20 @@ public class R {
      *
      * @return R实例
      */
-    public static R ok() {
-        R r = new R();
+    public static <T> R<T> ok() {
+        R<T> r = new R<>();
+        r.setSuccess(true);
+        r.setCode(1);
+        r.setMessage("成功");
+        return r;
+    }
+
+    public static <T> R<T> ok(T data) {
+        R<T> r = new R<>();
         r.setSuccess(true);
         r.setCode(1);
         r.setMessage("成功");
+        r.setData(data);
         return r;
     }
 
@@ -37,30 +50,39 @@ public class R {
      *
      * @return R实例
      */
-    public static R error() {
-        R r = new R();
+    public static <T> R<T> error() {
+        R<T> r = new R<>();
         r.setSuccess(false);
         r.setCode(0);
         r.setMessage("失败");
         return r;
     }
-    public R success(Boolean success) {
+
+    public static <T> R<T> error(String message) {
+        R<T> r = new R<>();
+        r.setSuccess(false);
+        r.setCode(0);
+        r.setMessage(message);
+        return r;
+    }
+
+    public R<T> success(Boolean success) {
         this.setSuccess(success);
         return this;
     }
 
 
-    public R code(Integer code) {
+    public R<T> code(Integer code) {
         this.setCode(code);
         return this;
     }
 
-    public R data(Object object) {
+    public R<T> data(T object) {
         this.setData(object);
         return this;
     }
 
-    public R message(String message) {
+    public R<T> message(String message) {
         this.setMessage(message);
         return this;
     }

+ 0 - 14
src/main/java/com/nokia/config/BeanConfig.java

@@ -1,14 +0,0 @@
-package com.nokia.config;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class BeanConfig {
-    @Bean
-    public ObjectMapper objectMapper(){
-        return new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
-    }
-}

+ 0 - 84
src/main/java/com/nokia/config/LogAspectConfig.java

@@ -1,84 +0,0 @@
-package com.nokia.config;
-
-import com.alibaba.fastjson2.JSON;
-import lombok.extern.slf4j.Slf4j;
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Pointcut;
-import org.slf4j.MDC;
-import org.springframework.stereotype.Component;
-import org.springframework.util.StopWatch;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-import org.springframework.web.multipart.MultipartFile;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-/**
- * 日志配置
- *
- */
-@Aspect
-@Component
-@Slf4j
-public class LogAspectConfig
-{
-
-    @Pointcut("execution(public * com.nokia..*Controller.*(..))")
-    public void controllerPointcut()
-    {
-        // Pointcut
-    }
-
-    @Pointcut("execution(* com.nokia..ControllerExceptionHandler.*(..))")
-    public void controllerExceptionHandlerPointcut()
-    {
-        // Pointcut
-    }
-
-    @Pointcut("controllerPointcut() || controllerExceptionHandlerPointcut()")
-    public void aroundPointCut()
-    {
-        // Pointcut
-    }
-
-    @Around("aroundPointCut()")
-    public Object around(ProceedingJoinPoint point) throws Throwable
-    {
-        StopWatch stopWatch = new StopWatch();
-        stopWatch.start();
-        MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));
-        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-        if (attributes == null)
-        {
-            return point.proceed();
-        }
-
-        HttpServletRequest request = attributes.getRequest();
-        log.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
-        Object[] args = point.getArgs();
-        List<Object> list = new ArrayList<>();
-        for (Object arg : args) {
-            if (arg instanceof ServletRequest
-                    || arg instanceof ServletResponse
-                    || arg instanceof MultipartFile
-                    || arg instanceof Exception
-            ) {
-                continue;
-            }
-            list.add(arg);
-        }
-        log.info("请求参数: {}", JSON.toJSONString(list));
-        Object result = point.proceed();
-        stopWatch.stop();
-        log.info("返回: {}", JSON.toJSONString(result));
-        log.info("耗时:{} ms", stopWatch.getTotalTimeMillis());
-        return result;
-    }
-}

+ 1 - 1
src/main/java/com/nokia/config/ControllerExceptionHandler.java → src/main/java/com/nokia/config/web/ControllerExceptionHandler.java

@@ -1,4 +1,4 @@
-package com.nokia.config;
+package com.nokia.config.web;
 
 import com.nokia.common.R;
 import lombok.extern.slf4j.Slf4j;

+ 14 - 0
src/main/java/com/nokia/config/web/MyDispatcherServlet.java

@@ -0,0 +1,14 @@
+package com.nokia.config.web;
+
+import org.springframework.web.servlet.DispatcherServlet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class MyDispatcherServlet extends DispatcherServlet {
+    @Override
+    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        // 替换request和response
+        super.doDispatch(new MyHttpServletRequestWrapper(request), new MyHttpServletResponseWrapper(response));
+    }
+}

+ 54 - 0
src/main/java/com/nokia/config/web/MyHttpServletRequestWrapper.java

@@ -0,0 +1,54 @@
+package com.nokia.config.web;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StreamUtils;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.io.*;
+
+/**
+ * 解决流只能读取一次问题
+ */
+@Slf4j
+public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
+    private byte[] bytes;
+
+    public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
+        super(request);
+        bytes = StreamUtils.copyToByteArray(request.getInputStream());
+    }
+
+    @Override
+    public ServletInputStream getInputStream() {
+        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
+        return new ServletInputStream() {
+            @Override
+            public boolean isFinished() {
+                return false;
+            }
+
+            @Override
+            public boolean isReady() {
+                return false;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener) {
+                //
+            }
+
+            @Override
+            public int read() {
+                return stream.read();
+            }
+        };
+    }
+
+    @Override
+    public BufferedReader getReader() throws UnsupportedEncodingException {
+        return new BufferedReader(new InputStreamReader(getInputStream(), super.getCharacterEncoding()));
+    }
+}

+ 47 - 0
src/main/java/com/nokia/config/web/MyHttpServletResponseWrapper.java

@@ -0,0 +1,47 @@
+package com.nokia.config.web;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * 解决流只能读取一次问题
+ */
+public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {
+    private ByteArrayOutputStream byteArrayOutputStream;
+    private ServletOutputStream servletOutputStream;
+
+    public MyHttpServletResponseWrapper(HttpServletResponse response) {
+        super(response);
+        byteArrayOutputStream = new ByteArrayOutputStream();
+        servletOutputStream = new ServletOutputStream() {
+            @Override
+            public boolean isReady() {
+                return false;
+            }
+
+            @Override
+            public void setWriteListener(WriteListener writeListener) {
+                //
+            }
+
+            @Override
+            public void write(int b) throws IOException {
+                response.getOutputStream().write(b);
+                byteArrayOutputStream.write(b);
+            }
+        };
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() {
+        return servletOutputStream;
+    }
+
+    public byte[] toByteArray() {
+        return byteArrayOutputStream.toByteArray();
+    }
+}

+ 54 - 0
src/main/java/com/nokia/config/web/MyWebMvcConfigurer.java

@@ -0,0 +1,54 @@
+package com.nokia.config.web;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+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;
+
+@Configuration
+public class MyWebMvcConfigurer implements WebMvcConfigurer {
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 添加请求日志拦截
+        registry.addInterceptor(new RequestLogHandlerInterceptor()).addPathPatterns("/**");
+        // 添加web登录拦截
+        registry.addInterceptor(new WebLoginInterceptor()).addPathPatterns("/api/web/**");
+    }
+
+    /**
+     * 使用自定义DispatcherServlet
+     */
+    @Bean
+    @Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
+    public DispatcherServlet dispatcherServlet() {
+        return new MyDispatcherServlet();
+    }
+
+//    /**
+//     * 配置消息转换器
+//     *
+//     * @param converters 转换器
+//     */
+//    @Override
+//    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+//        converters.add(mappingJackson2HttpMessageConverter());
+//    }
+//
+//    /**
+//     * 配置映射jackson2 http消息转换器
+//     *
+//     * @return {@link MappingJackson2HttpMessageConverter}
+//     */
+//    @Bean
+//    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
+//        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
+//        ObjectMapper mapper = new ObjectMapper();
+//        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+//        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+//        converter.setObjectMapper(mapper);
+//        return converter;
+//    }
+}

+ 70 - 0
src/main/java/com/nokia/config/web/RequestLogHandlerInterceptor.java

@@ -0,0 +1,70 @@
+package com.nokia.config.web;
+
+import lombok.extern.slf4j.Slf4j;
+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 javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.nio.charset.Charset;
+import java.util.UUID;
+
+/**
+ * 请求日志拦截器
+ */
+@Slf4j
+public class RequestLogHandlerInterceptor implements HandlerInterceptor {
+    /**
+     * 计时器线程变量
+     */
+    private static 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();
+        stopWatch.start();
+        // 计时器放入线程变量
+        STOP_WATCH_THREAD_LOCAL.set(stopWatch);
+        // 日志添加跟踪id
+        MDC.put("traceId", UUID.randomUUID().toString().replace("-", ""));
+        log.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
+        // 请求头参数
+//        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);
+//        }
+//        log.info("请求头参数: {}", JSON.toJSONString(headers));
+        // 查询参数
+//        Map<String, String> parameters = new HashMap<>();
+//        Enumeration<String> parameterNames = request.getParameterNames();
+//        while (parameterNames.hasMoreElements()) {
+//            String k	= parameterNames.nextElement();
+//            String v = request.getParameter(k);
+//            parameters.put(k, v);
+//        }
+//        log.info("查询参数: {}", JSON.toJSONString(parameters));
+        // 请求体参数
+        String body = StreamUtils.copyToString(request.getInputStream(), Charset.forName(request.getCharacterEncoding()));
+        log.info("请求参数: {}", StringUtils.trimAllWhitespace(body));
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
+                                @Nullable Exception ex) throws Exception {
+        MyHttpServletResponseWrapper wrapper = (MyHttpServletResponseWrapper) response;
+        String responseString = new String(wrapper.toByteArray());
+        log.info("返回 {}: {}", wrapper.getStatus(), responseString);
+        StopWatch stopWatch = STOP_WATCH_THREAD_LOCAL.get();
+        stopWatch.stop();
+        log.info("耗时 {} ms", stopWatch.getTotalTimeMillis());
+        STOP_WATCH_THREAD_LOCAL.remove();
+    }
+}

+ 37 - 0
src/main/java/com/nokia/config/web/WebLoginInterceptor.java

@@ -0,0 +1,37 @@
+package com.nokia.config.web;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/**
+ * web登录拦截
+ */
+@Slf4j
+public class WebLoginInterceptor implements HandlerInterceptor {
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        HttpSession session = request.getSession();
+        // 未登录或登录失效返回401
+        if (session == null || session.getAttribute("userinfo") == null) {
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            return false;
+        }
+        // 没有权限返回403
+        if (session.getAttribute("role") == null) {
+            response.setStatus(HttpStatus.FORBIDDEN.value());
+            return false;
+        }
+        log.debug("sessionId: {}, userinfo: {}, role: {}", session.getId(),
+                JSON.toJSONString(session.getAttribute("userinfo")),
+                JSON.toJSONString(session.getAttribute("role")));
+        // 延长session过期时间
+        session.setMaxInactiveInterval(600);
+        return true;
+    }
+}