潘志宝
2024-11-21 70079bd0772207e88414176f5ca9edb3dd4bd236
提交 | 用户 | 时间
e7c126 1 package com.iailab.framework.apilog.core.interceptor;
H 2
3 import cn.hutool.core.collection.CollUtil;
4a47e4 4 import cn.hutool.core.io.FileUtil;
H 5 import cn.hutool.core.io.resource.ResourceUtil;
e7c126 6 import cn.hutool.core.util.StrUtil;
H 7 import com.iailab.framework.common.util.servlet.ServletUtils;
8 import com.iailab.framework.common.util.spring.SpringUtils;
9 import lombok.extern.slf4j.Slf4j;
10 import org.springframework.util.StopWatch;
11 import org.springframework.web.method.HandlerMethod;
12 import org.springframework.web.servlet.HandlerInterceptor;
13
14 import javax.servlet.http.HttpServletRequest;
15 import javax.servlet.http.HttpServletResponse;
4a47e4 16 import java.lang.reflect.Method;
H 17 import java.util.List;
e7c126 18 import java.util.Map;
4a47e4 19 import java.util.Optional;
H 20 import java.util.stream.IntStream;
e7c126 21
H 22 /**
23  * API 访问日志 Interceptor
24  *
25  * 目的:在非 prod 环境时,打印 request 和 response 两条日志到日志文件(控制台)中。
26  *
27  * @author iailab
28  */
29 @Slf4j
30 public class ApiAccessLogInterceptor implements HandlerInterceptor {
31
32     public static final String ATTRIBUTE_HANDLER_METHOD = "HANDLER_METHOD";
33
34     private static final String ATTRIBUTE_STOP_WATCH = "ApiAccessLogInterceptor.StopWatch";
35
36     @Override
37     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
38         // 记录 HandlerMethod,提供给 ApiAccessLogFilter 使用
39         HandlerMethod handlerMethod = handler instanceof HandlerMethod ? (HandlerMethod) handler : null;
40         if (handlerMethod != null) {
41             request.setAttribute(ATTRIBUTE_HANDLER_METHOD, handlerMethod);
42         }
43
44         // 打印 request 日志
45         if (!SpringUtils.isProd()) {
46             Map<String, String> queryString = ServletUtils.getParamMap(request);
47             String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;
48             if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) {
49                 log.info("[preHandle][开始请求 URL({}) 无参数]", request.getRequestURI());
50             } else {
51                 log.info("[preHandle][开始请求 URL({}) 参数({})]", request.getRequestURI(),
52                         StrUtil.blankToDefault(requestBody, queryString.toString()));
53             }
54             // 计时
55             StopWatch stopWatch = new StopWatch();
56             stopWatch.start();
57             request.setAttribute(ATTRIBUTE_STOP_WATCH, stopWatch);
4a47e4 58             // 打印 Controller 路径
H 59             printHandlerMethodPosition(handlerMethod);
e7c126 60         }
H 61         return true;
62     }
63
64     @Override
65     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
66         // 打印 response 日志
67         if (!SpringUtils.isProd()) {
68             StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH);
69             stopWatch.stop();
70             log.info("[afterCompletion][完成请求 URL({}) 耗时({} ms)]",
71                     request.getRequestURI(), stopWatch.getTotalTimeMillis());
72         }
73     }
74
4a47e4 75     /**
H 76      * 打印 Controller 方法路径
77      */
78     private void printHandlerMethodPosition(HandlerMethod handlerMethod) {
79         if (handlerMethod == null) {
80             return;
81         }
82         Method method = handlerMethod.getMethod();
83         Class<?> clazz = method.getDeclaringClass();
84         try {
85             // 获取 method 的 lineNumber
86             List<String> clazzContents = FileUtil.readUtf8Lines(
87                     ResourceUtil.getResource(null, clazz).getPath().replace("/target/classes/", "/src/main/java/")
88                             + clazz.getSimpleName() + ".java");
89             Optional<Integer> lineNumber = IntStream.range(0, clazzContents.size())
90                     .filter(i -> clazzContents.get(i).contains(" " + method.getName() + "(")) // 简单匹配,不考虑方法重名
91                     .mapToObj(i -> i + 1) // 行号从 1 开始
92                     .findFirst();
93             if (!lineNumber.isPresent()) {
94                 return;
95             }
96             // 打印结果
97             System.out.printf("\tController 方法路径:%s(%s.java:%d)\n", clazz.getName(), clazz.getSimpleName(), lineNumber.get());
98         } catch (Exception ignore) {
99             // 忽略异常。原因:仅仅打印,非重要逻辑
100         }
101     }
102
e7c126 103 }