LogInterceptor.java 10.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package top.charles7c.cnadmin.monitor.interceptor;

19
import java.time.LocalDateTime;
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import io.swagger.v3.oas.annotations.Operation;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import cn.hutool.core.collection.CollUtil;
39
import cn.hutool.core.date.LocalDateTimeUtil;
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;

import top.charles7c.cnadmin.common.model.dto.OperationLog;
import top.charles7c.cnadmin.common.util.IpUtils;
import top.charles7c.cnadmin.common.util.ServletUtils;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
import top.charles7c.cnadmin.common.util.holder.LogContextHolder;
import top.charles7c.cnadmin.monitor.annotation.Log;
import top.charles7c.cnadmin.monitor.config.properties.LogProperties;
import top.charles7c.cnadmin.monitor.enums.LogLevelEnum;
import top.charles7c.cnadmin.monitor.model.entity.SysLog;

/**
 * 操作日志拦截器
 *
 * @author Charles7c
 * @since 2022/12/24 21:14
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LogInterceptor implements HandlerInterceptor {

    private final LogProperties operationLogProperties;

    @Override
70
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
71 72 73 74 75 76 77 78 79 80
        if (!checkIsNeedRecord(handler, request)) {
            return true;
        }

        // 记录操作时间
        this.logCreateTime();
        return true;
    }

    @Override
81
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
        // 记录请求耗时及异常信息
        SysLog sysLog = this.logElapsedTimeAndException();
        if (sysLog == null) {
            return;
        }

        // 记录描述
        this.logDescription(sysLog, handler);
        // 记录请求信息
        this.logRequest(sysLog, request);
        // 记录响应信息
        this.logResponse(sysLog, response);

        // 保存操作日志
        SpringUtil.getApplicationContext().publishEvent(sysLog);
    }

    /**
     * 记录操作时间
     */
    private void logCreateTime() {
        OperationLog operationLog = new OperationLog();
        operationLog.setCreateUser(LoginHelper.getUserId());
105
        operationLog.setCreateTime(LocalDateTime.now());
106 107 108 109 110 111 112 113 114 115 116 117 118 119
        LogContextHolder.set(operationLog);
    }

    /**
     * 记录请求耗时及异常信息
     *
     * @return 日志信息
     */
    private SysLog logElapsedTimeAndException() {
        OperationLog operationLog = LogContextHolder.get();
        if (operationLog != null) {
            LogContextHolder.remove();
            SysLog sysLog = new SysLog();
            sysLog.setCreateTime(operationLog.getCreateTime());
120
            sysLog.setElapsedTime(System.currentTimeMillis() - LocalDateTimeUtil.toEpochMilli(sysLog.getCreateTime()));
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
            sysLog.setLogLevel(LogLevelEnum.INFO);

            // 记录异常信息
            Exception exception = operationLog.getException();
            if (exception != null) {
                sysLog.setLogLevel(LogLevelEnum.ERROR);
                sysLog.setException(ExceptionUtil.stacktraceToString(operationLog.getException(), -1));
            }
            return sysLog;
        }
        return null;
    }

    /**
     * 记录日志描述
     *
     * @param sysLog
     *            日志信息
     * @param handler
     *            处理器
     */
142
    private void logDescription(SysLog sysLog, Object handler) {
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Operation methodOperation = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Operation.class);
        Log methodLog = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Log.class);

        if (methodOperation != null) {
            sysLog.setDescription(
                StrUtil.isNotBlank(methodOperation.summary()) ? methodOperation.summary() : "请在该接口方法上指定操作日志描述");
        }
        // 例如:@Log("获取验证码") -> 获取验证码
        if (methodLog != null && StrUtil.isNotBlank(methodLog.value())) {
            sysLog.setDescription(methodLog.value());
        }
    }

    /**
     * 记录请求信息
     *
     * @param sysLog
     *            日志信息
     * @param request
     *            请求对象
     */
165
    private void logRequest(SysLog sysLog, HttpServletRequest request) {
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
        sysLog.setRequestUrl(StrUtil.isBlank(request.getQueryString()) ? request.getRequestURL().toString()
            : request.getRequestURL().append("?").append(request.getQueryString()).toString());
        sysLog.setRequestMethod(request.getMethod());
        sysLog.setRequestHeader(this.desensitize(ServletUtil.getHeaderMap(request)));
        String requestBody = this.getRequestBody(request);
        if (StrUtil.isNotBlank(requestBody)) {
            sysLog.setRequestBody(this.desensitize(
                JSONUtil.isTypeJSON(requestBody) ? JSONUtil.parseObj(requestBody) : ServletUtil.getParamMap(request)));
        }
        sysLog.setRequestIp(ServletUtil.getClientIP(request));
        sysLog.setLocation(IpUtils.getCityInfo(sysLog.getRequestIp()));
        sysLog.setBrowser(ServletUtils.getBrowser(request));
        sysLog.setCreateUser(sysLog.getCreateUser() == null ? LoginHelper.getUserId() : sysLog.getCreateUser());
    }

    /**
     * 记录响应信息
     *
     * @param sysLog
     *            日志信息
     * @param response
     *            响应对象
     */
    private void logResponse(SysLog sysLog, HttpServletResponse response) {
        sysLog.setStatusCode(response.getStatus());
191
        sysLog.setResponseHeader(this.desensitize(ServletUtil.getHeadersMap(response)));
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
        // 响应体(不记录非 JSON 响应数据)
        String responseBody = this.getResponseBody(response);
        if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) {
            sysLog.setResponseBody(responseBody);
        }
    }

    /**
     * 数据脱敏
     *
     * @param waitDesensitizeData
     *            待脱敏数据
     * @return 脱敏后的 JSON 字符串数据
     */
    private String desensitize(Map waitDesensitizeData) {
        String desensitizeDataStr = JSONUtil.toJsonStr(waitDesensitizeData);
        try {
            if (CollUtil.isEmpty(waitDesensitizeData)) {
                return desensitizeDataStr;
            }

            for (String desensitizeProperty : operationLogProperties.getDesensitize()) {
                waitDesensitizeData.computeIfPresent(desensitizeProperty, (k, v) -> "****************");
                waitDesensitizeData.computeIfPresent(desensitizeProperty.toLowerCase(), (k, v) -> "****************");
                waitDesensitizeData.computeIfPresent(desensitizeProperty.toUpperCase(), (k, v) -> "****************");
            }
            return JSONUtil.toJsonStr(waitDesensitizeData);
        } catch (Exception ignored) {
        }
        return desensitizeDataStr;
    }

    /**
     * 获取请求体
     *
     * @param request
     *            请求对象
     * @return 请求体
     */
    private String getRequestBody(HttpServletRequest request) {
        String requestBody = "";
        ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if (wrapper != null) {
            requestBody = StrUtil.utf8Str(wrapper.getContentAsByteArray());
        }
        return requestBody;
    }

    /**
     * 获取响应体
     *
     * @param response
     *            响应对象
     * @return 响应体
     */
    private String getResponseBody(HttpServletResponse response) {
        String responseBody = "";
        ContentCachingResponseWrapper wrapper =
            WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            responseBody = StrUtil.utf8Str(wrapper.getContentAsByteArray());
        }
        return responseBody;
    }

    /**
     * 检查是否要记录操作日志
     *
     * @param handler
     *            /
     * @param request
     *            /
     * @return true 需要记录,false 不需要记录
     */
    private boolean checkIsNeedRecord(Object handler, HttpServletRequest request) {
        // 1、未启用时,不需要记录操作日志
        if (!(handler instanceof HandlerMethod) || Boolean.FALSE.equals(operationLogProperties.getEnabled())) {
            return false;
        }

        // 2、排除不需要记录日志的接口
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Log methodLog = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Log.class);
        // 2.1 请求方式不要求记录且请求上没有 @Log 注解,则不记录操作日志
        if (operationLogProperties.getExcludeMethods().contains(request.getMethod()) && methodLog == null) {
            return false;
        }
        // 2.2 如果接口上既没有 @Log 注解,也没有 @Operation 注解,则不记录操作日志
        Operation methodOperation = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Operation.class);
        if (methodLog == null && methodOperation == null) {
            return false;
        }
        // 2.3 如果接口被隐藏,不记录操作日志
        if (methodOperation != null && methodOperation.hidden()) {
            return false;
        }
        // 2.4 如果接口上有 @Log 注解,但是要求忽略该接口,则不记录操作日志
        return methodLog == null || !methodLog.ignore();
    }
}