package com.doumee.core.annotation.trace; 
 | 
  
 | 
import com.alibaba.fastjson.JSON; 
 | 
import com.doumee.core.constants.ExceptionLevel; 
 | 
import com.doumee.core.model.ApiResponse; 
 | 
import com.doumee.core.model.LoginUserInfo; 
 | 
import com.doumee.core.servlet.ServletDuplicateInputStream; 
 | 
import com.doumee.core.servlet.ServletDuplicateOutputStream; 
 | 
import com.doumee.core.utils.Utils; 
 | 
import com.doumee.dao.system.model.SystemTraceLog; 
 | 
import com.doumee.service.system.SystemTraceLogService; 
 | 
import io.swagger.annotations.Api; 
 | 
import io.swagger.annotations.ApiOperation; 
 | 
import lombok.extern.slf4j.Slf4j; 
 | 
import org.apache.commons.lang3.StringUtils; 
 | 
import org.apache.shiro.SecurityUtils; 
 | 
import org.apache.shiro.web.servlet.ShiroHttpServletRequest; 
 | 
import org.springframework.beans.factory.annotation.Autowired; 
 | 
import org.springframework.beans.factory.annotation.Value; 
 | 
import org.springframework.http.HttpMethod; 
 | 
import org.springframework.stereotype.Component; 
 | 
import org.springframework.web.method.HandlerMethod; 
 | 
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 
 | 
  
 | 
import javax.servlet.http.HttpServletRequest; 
 | 
import javax.servlet.http.HttpServletResponse; 
 | 
import java.io.IOException; 
 | 
import java.lang.reflect.Method; 
 | 
import java.util.Date; 
 | 
  
 | 
/** 
 | 
 * 跟踪日志处理 
 | 
 * @author Eva.Caesar Liu 
 | 
 * @date 2022/03/15 09:54 
 | 
 */ 
 | 
@Slf4j 
 | 
@Component 
 | 
public class TraceInterceptor extends HandlerInterceptorAdapter { 
 | 
  
 | 
    @Value("${project.version}") 
 | 
    private String systemVersion; 
 | 
  
 | 
    @Value("${trace.exclude-patterns:}") 
 | 
    private String excludePatterns; 
 | 
  
 | 
    @Value("${trace.smart}") 
 | 
    private Boolean isSmart; 
 | 
  
 | 
    private static final String ATTRIBUTE_TRACE_ID = "eva-trace-id"; 
 | 
  
 | 
    private static final String ATTRIBUTE_TRACE_TIME = "eva-trace-time"; 
 | 
  
 | 
    private static final int MAX_STORE_REQUEST_PARAM_SIZE = 1888; 
 | 
  
 | 
    private static final int MAX_STORE_REQUEST_RESULT_SIZE = 1888; 
 | 
  
 | 
    private static final int MAX_STORE_EXCEPTION_STACK_SIZE = 4888; 
 | 
  
 | 
    private static final String MORE_DETAIL_STRING = "\n\n---------- more content is missing here ... ----------\n\n"; 
 | 
  
 | 
    @Autowired 
 | 
    private SystemTraceLogService systemTraceLogService; 
 | 
  
 | 
    @Override 
 | 
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { 
 | 
        try { 
 | 
            if (!(handler instanceof HandlerMethod) || !(request instanceof ShiroHttpServletRequest)) { 
 | 
                return Boolean.TRUE; 
 | 
            } 
 | 
            if (!this.allowTrace(request, handler)) { 
 | 
                return Boolean.TRUE; 
 | 
            } 
 | 
            SystemTraceLog traceLog = new SystemTraceLog(); 
 | 
            Date now = new Date(); 
 | 
            HandlerMethod handlerMethod = (HandlerMethod) handler; 
 | 
            Method method = handlerMethod.getMethod(); 
 | 
            Trace methodTrace = method.getAnnotation(Trace.class); 
 | 
            Trace classTrace = method.getDeclaringClass().getAnnotation(Trace.class); 
 | 
            // 获取跟踪类型 
 | 
            TraceType traceType = methodTrace == null ? null : methodTrace.type(); 
 | 
            if (traceType == null) { 
 | 
                traceType = this.smartType(request); 
 | 
            } 
 | 
            // 用户信息 
 | 
            LoginUserInfo userInfo = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal(); 
 | 
            if (userInfo != null) { 
 | 
                traceLog.setUserId(userInfo.getId()); 
 | 
                traceLog.setUsername(userInfo.getUsername()); 
 | 
                traceLog.setUserRealname(userInfo.getReal_name()); 
 | 
                traceLog.setUserRoles(StringUtils.join(userInfo.getRoles(), ",")); 
 | 
                traceLog.setUserPermissions(StringUtils.join(userInfo.getPermissions(), ",")); 
 | 
            } 
 | 
            // 操作信息 
 | 
            traceLog.setOperaModule(this.getModule(handler)); 
 | 
            traceLog.setOperaType(traceType.getType()); 
 | 
            traceLog.setOperaRemark(this.getOperaRemark(handler, traceType)); 
 | 
            traceLog.setOperaTime(now); 
 | 
            // 请求信息 
 | 
            traceLog.setRequestUri(request.getRequestURI()); 
 | 
            traceLog.setRequestMethod(request.getMethod()); 
 | 
            if (methodTrace == null || methodTrace.withRequestParameters() || (classTrace != null && classTrace.withRequestParameters())) { 
 | 
                String requestParameters = request.getQueryString(); 
 | 
                if (HttpMethod.POST.matches(request.getMethod())) { 
 | 
                    requestParameters = ((ServletDuplicateInputStream)request.getInputStream()).getBody(); 
 | 
                } 
 | 
                traceLog.setRequestParams( 
 | 
                        requestParameters != null && requestParameters.length() > MAX_STORE_REQUEST_PARAM_SIZE 
 | 
                                ? requestParameters.substring(0, MAX_STORE_REQUEST_PARAM_SIZE) + MORE_DETAIL_STRING 
 | 
                                : requestParameters 
 | 
                ); 
 | 
            } 
 | 
            // 辅助信息 
 | 
            traceLog.setServerIp(Utils.Server.getIP()); 
 | 
            traceLog.setIp(Utils.User_Client.getIP(request)); 
 | 
            traceLog.setSystemVersion(systemVersion); 
 | 
            traceLog.setPlatform(Utils.User_Client.getPlatform(request)); 
 | 
            traceLog.setClientInfo(Utils.User_Client.getBrowser(request)); 
 | 
            traceLog.setOsInfo(Utils.User_Client.getOS(request)); 
 | 
            systemTraceLogService.create(traceLog); 
 | 
            request.setAttribute(ATTRIBUTE_TRACE_ID, traceLog.getId()); 
 | 
            request.setAttribute(ATTRIBUTE_TRACE_TIME, now.getTime()); 
 | 
        } catch (Exception e) { 
 | 
            log.warn("Eva @Trace throw an exception, you can get detail message by debug mode."); 
 | 
            log.debug(e.getMessage(), e); 
 | 
        } 
 | 
        return Boolean.TRUE; 
 | 
    } 
 | 
  
 | 
    @Override 
 | 
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException { 
 | 
        // 获取跟踪ID 
 | 
        Object traceId = request.getAttribute(ATTRIBUTE_TRACE_ID); 
 | 
        Object traceTime = request.getAttribute(ATTRIBUTE_TRACE_TIME); 
 | 
        request.removeAttribute(ATTRIBUTE_TRACE_ID); 
 | 
        request.removeAttribute(ATTRIBUTE_TRACE_TIME); 
 | 
        if (traceId == null) { 
 | 
            return; 
 | 
        } 
 | 
        // 计算操作耗时 
 | 
        SystemTraceLog traceLog = new SystemTraceLog(); 
 | 
        traceLog.setId(Integer.valueOf(traceId.toString())); 
 | 
        traceLog.setOperaSpendTime(Integer.valueOf("" + (System.currentTimeMillis() - Long.valueOf(traceTime.toString())))); 
 | 
        // 记录操作日志状态 
 | 
        String operaType = response.getHeader("eva-opera-type"); 
 | 
        // - 下载接口处理,无需记录响应内容 
 | 
        if ("download".equals(operaType)) { 
 | 
            handleDownloadResponse(traceLog, ex); 
 | 
            return; 
 | 
        } 
 | 
        // - 返回json的接口处理 
 | 
        handleJsonResponse(traceLog, request, response, handler, ex); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * JSON响应处理 
 | 
     */ 
 | 
    private void handleJsonResponse(SystemTraceLog traceLog, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException { 
 | 
        HandlerMethod handlerMethod = (HandlerMethod) handler; 
 | 
        Method method = handlerMethod.getMethod(); 
 | 
        Trace methodTrace = method.getAnnotation(Trace.class); 
 | 
        Trace classTrace = method.getDeclaringClass().getAnnotation(Trace.class); 
 | 
        String responseBody = ((ServletDuplicateOutputStream) response.getOutputStream()).getContent(); 
 | 
        ApiResponse apiResponse = null; 
 | 
        try { 
 | 
            apiResponse = JSON.parseObject(responseBody, ApiResponse.class); 
 | 
        } catch (Exception e) { 
 | 
        } 
 | 
        // 记录响应结果 
 | 
        if (methodTrace == null || methodTrace.withRequestResult() || (classTrace != null && classTrace.withRequestResult())) { 
 | 
            String requestResult = responseBody; 
 | 
            if (apiResponse != null) { 
 | 
                // 排除exception信息 
 | 
                String e = apiResponse.getException(); 
 | 
                apiResponse.setException(null); 
 | 
                requestResult = JSON.toJSONString(apiResponse); 
 | 
                apiResponse.setException(e); 
 | 
            } 
 | 
            traceLog.setRequestResult( 
 | 
                    requestResult != null && requestResult.length() > MAX_STORE_REQUEST_RESULT_SIZE 
 | 
                            ? requestResult.substring(0, MAX_STORE_REQUEST_RESULT_SIZE) + MORE_DETAIL_STRING 
 | 
                            : requestResult 
 | 
            ); 
 | 
        } 
 | 
        // 请求成功 
 | 
        if (ex == null && (apiResponse != null && apiResponse.isSuccess())) { 
 | 
            traceLog.setStatus(TraceStatus.SUCCESS.getCode()); 
 | 
            systemTraceLogService.updateById(traceLog); 
 | 
            return; 
 | 
        } 
 | 
        // 请求失败 
 | 
        traceLog.setStatus(TraceStatus.FAILED.getCode()); 
 | 
        String exceptionStack = null; 
 | 
        // 全局捕获到的未处理的异常 
 | 
        if (apiResponse != null && apiResponse.getException() != null) { 
 | 
            exceptionStack = apiResponse.getException(); 
 | 
            traceLog.setExceptionLevel(ExceptionLevel.DANGER.getLevel()); 
 | 
        } 
 | 
        // 其它异常情况 
 | 
        if (exceptionStack == null) { 
 | 
            // 未捕获到的未处理的异常 
 | 
            if (ex != null) { 
 | 
                StackTraceElement[] trace = ex.getStackTrace(); 
 | 
                StringBuilder exception = new StringBuilder(ex + "\n"); 
 | 
                for (StackTraceElement traceElement : trace) { 
 | 
                    exception.append("\tat ").append(traceElement).append("\n"); 
 | 
                } 
 | 
                exceptionStack = exception.toString(); 
 | 
                traceLog.setExceptionLevel(ExceptionLevel.DANGER.getLevel()); 
 | 
            } 
 | 
            // 业务异常 
 | 
            else if (apiResponse != null) { 
 | 
                traceLog.setExceptionLevel(ExceptionLevel.LOW.getLevel()); 
 | 
                exceptionStack = apiResponse.getMessage(); 
 | 
            } 
 | 
            // 响应格式非JSON的异常(在设计角度上,这类接口不应记录操作日志,此处做一个警告处理) 
 | 
            else { 
 | 
                traceLog.setExceptionLevel(ExceptionLevel.WARN.getLevel()); 
 | 
                exceptionStack = "Eva can not trace for action " + request.getRequestURI(); 
 | 
            } 
 | 
        } 
 | 
        traceLog.setExceptionStack( 
 | 
                exceptionStack != null && exceptionStack.length() > MAX_STORE_EXCEPTION_STACK_SIZE 
 | 
                        ? exceptionStack.substring(0, MAX_STORE_EXCEPTION_STACK_SIZE) + MORE_DETAIL_STRING 
 | 
                        : exceptionStack); 
 | 
        systemTraceLogService.updateById(traceLog); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 下载响应处理 
 | 
     */ 
 | 
    private void handleDownloadResponse (SystemTraceLog traceLog, Exception ex) { 
 | 
        if (ex == null) { 
 | 
            traceLog.setStatus(TraceStatus.SUCCESS.getCode()); 
 | 
            systemTraceLogService.updateById(traceLog); 
 | 
            return; 
 | 
        } 
 | 
        // 出现异常 
 | 
        traceLog.setStatus(TraceStatus.FAILED.getCode()); 
 | 
        StackTraceElement[] trace = ex.getStackTrace(); 
 | 
        StringBuilder exception = new StringBuilder(ex + "\n"); 
 | 
        for (StackTraceElement traceElement : trace) { 
 | 
            exception.append("\tat ").append(traceElement).append("\n"); 
 | 
        } 
 | 
        traceLog.setExceptionStack(exception.length() > MAX_STORE_EXCEPTION_STACK_SIZE 
 | 
                ? exception.substring(0, MAX_STORE_EXCEPTION_STACK_SIZE) + MORE_DETAIL_STRING 
 | 
                : exception.toString()); 
 | 
        traceLog.setExceptionLevel(ExceptionLevel.DANGER.getLevel()); 
 | 
        systemTraceLogService.updateById(traceLog); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 获取跟踪模块 
 | 
     */ 
 | 
    private String getModule (Object handler) { 
 | 
        HandlerMethod handlerMethod = (HandlerMethod) handler; 
 | 
        Method method = handlerMethod.getMethod(); 
 | 
        // 优先读取方法上的@Trace模块配置,如果无注解或无备注,则读取类上@Trace模块配置 
 | 
        Trace trace = method.getAnnotation(Trace.class); 
 | 
        if (trace == null || "".equals(trace.module())) { 
 | 
            trace = method.getDeclaringClass().getAnnotation(Trace.class); 
 | 
        } 
 | 
        String module = ""; 
 | 
        if (trace != null && StringUtils.isNotBlank(trace.module())) { 
 | 
            module = trace.module(); 
 | 
        } 
 | 
        // 从@Api注解中读取 
 | 
        if (StringUtils.isBlank(module)) { 
 | 
            Api api = method.getDeclaringClass().getAnnotation(Api.class); 
 | 
            if (api != null) { 
 | 
                module = StringUtils.join(api.tags(), ", "); 
 | 
            } 
 | 
        } 
 | 
        return module; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 获取操作备注 
 | 
     */ 
 | 
    private String getOperaRemark (Object handler, TraceType traceType) { 
 | 
        HandlerMethod handlerMethod = (HandlerMethod) handler; 
 | 
        Method method = handlerMethod.getMethod(); 
 | 
        // 从Trace注解中获取 
 | 
        Trace trace = method.getAnnotation(Trace.class); 
 | 
        if (trace != null && StringUtils.isNotBlank(trace.remark())) { 
 | 
            return trace.remark(); 
 | 
        } 
 | 
        // 从ApiOperation注解中获取 
 | 
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); 
 | 
        if (apiOperation != null && StringUtils.isNotBlank(apiOperation.value())) { 
 | 
            return apiOperation.value(); 
 | 
        } 
 | 
        return traceType.getRemark(); 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 是否允许跟踪 
 | 
     */ 
 | 
    private Boolean allowTrace (HttpServletRequest request, Object handler) { 
 | 
        HandlerMethod handlerMethod = (HandlerMethod) handler; 
 | 
        Method method = handlerMethod.getMethod(); 
 | 
        Trace methodTrace = method.getAnnotation(Trace.class); 
 | 
        Trace classTrace = method.getDeclaringClass().getAnnotation(Trace.class); 
 | 
        // 非智能模式 
 | 
        if (!isSmart) { 
 | 
            if (methodTrace == null && classTrace == null) { 
 | 
                return Boolean.FALSE; 
 | 
            } 
 | 
            if (methodTrace == null && classTrace.exclude()) { 
 | 
                return Boolean.FALSE; 
 | 
            } 
 | 
        } 
 | 
        // 方法排除 
 | 
        if (methodTrace != null && methodTrace.exclude()) { 
 | 
            return Boolean.FALSE; 
 | 
        } 
 | 
        // 类排除 
 | 
        if (methodTrace == null && classTrace != null && classTrace.exclude()) { 
 | 
            return Boolean.FALSE; 
 | 
        } 
 | 
        // 路径排除 
 | 
        String[] patterns = excludePatterns.split(","); 
 | 
        if (methodTrace == null && patterns.length > 0) { 
 | 
            String uri = request.getRequestURI(); 
 | 
            for (String pattern : patterns) { 
 | 
                if (uri.matches(pattern.trim())) { 
 | 
                    return Boolean.FALSE; 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
        return Boolean.TRUE; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 获取跟踪类型 
 | 
     */ 
 | 
    private TraceType smartType (HttpServletRequest request) { 
 | 
        // 批量删除 
 | 
        if (request.getRequestURI().matches(smartPattern("delete/batch"))) { 
 | 
            return TraceType.DELETE_BATCH; 
 | 
        } 
 | 
        // 删除 
 | 
        if (request.getRequestURI().matches(smartPattern("delete"))) { 
 | 
            return TraceType.DELETE; 
 | 
        } 
 | 
        // 新增 
 | 
        if (request.getRequestURI().matches(smartPattern("create"))) { 
 | 
            return TraceType.CREATE; 
 | 
        } 
 | 
        // 修改 
 | 
        if (request.getRequestURI().matches(smartPattern("update"))) { 
 | 
            return TraceType.UPDATE; 
 | 
        } 
 | 
        // 导入 
 | 
        if (request.getRequestURI().matches(smartPattern("import"))) { 
 | 
            return TraceType.IMPORT; 
 | 
        } 
 | 
        // 导出 
 | 
        if (request.getRequestURI().matches(smartPattern("export"))) { 
 | 
            return TraceType.EXPORT; 
 | 
        } 
 | 
        // 重置 
 | 
        if (request.getRequestURI().matches(smartPattern("reset"))) { 
 | 
            return TraceType.RESET; 
 | 
        } 
 | 
        return TraceType.UNKNOWN; 
 | 
    } 
 | 
  
 | 
    /** 
 | 
     * 获取自动适配正则 
 | 
     */ 
 | 
    private String smartPattern (String keyword) { 
 | 
        return ".+/" + keyword + "[a-zA-Z0-9\\-\\_]*$"; 
 | 
    } 
 | 
} 
 |