diff --git a/src/main/java/com/epmet/jmreport/aop/RequestLogAspect.java b/src/main/java/com/epmet/jmreport/aop/RequestLogAspect.java index 9edd286..2f10c83 100644 --- a/src/main/java/com/epmet/jmreport/aop/RequestLogAspect.java +++ b/src/main/java/com/epmet/jmreport/aop/RequestLogAspect.java @@ -1,40 +1,166 @@ -//package com.epmet.jmreport.aop; -// -//import com.epmet.commons.tools.aspect.BaseRequestLogAspect; -//import org.aspectj.lang.ProceedingJoinPoint; -//import org.aspectj.lang.annotation.Around; -//import org.aspectj.lang.annotation.Aspect; -//import org.springframework.core.annotation.Order; -//import org.springframework.stereotype.Component; -//import org.springframework.web.context.request.RequestAttributes; -//import org.springframework.web.context.request.RequestContextHolder; -//import org.springframework.web.context.request.ServletRequestAttributes; -// -//import javax.servlet.http.HttpServletRequest; -// -///** -// * 日志/异常处理切面实现,调用父类方法完成日志记录和异常处理。 -// */ -//@Aspect -//@Component -//@Order(0) -//public class RequestLogAspect { -// -// @Override -// @Around(value = "execution(* com...*Controller*.*(..)) ") -// public Object proceed(ProceedingJoinPoint point) throws Throwable { -// return super.proceed(point, getRequest()); -// } -// -// /** -// * 获取Request对象 -// * -// * @return -// */ -// private HttpServletRequest getRequest() { -// RequestAttributes ra = RequestContextHolder.getRequestAttributes(); -// ServletRequestAttributes sra = (ServletRequestAttributes) ra; -// return sra.getRequest(); -// } -// -//} +package com.epmet.jmreport.aop; + +import com.alibaba.fastjson.JSON; +import com.epmet.jmreport.constants.JmReportConstants; +import com.epmet.jmreport.constants.ThreadLocalConstant; +import com.epmet.jmreport.utils.ExceptionUtils; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.jeecg.modules.jmreport.common.vo.Result; +import org.slf4j.MDC; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestAttributes; +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.time.Duration; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * 日志/异常处理切面实现,调用父类方法完成日志记录和异常处理。 + */ +@Aspect +@Component +@Order(0) +@Slf4j +public class RequestLogAspect { + + @Around(value = "execution(* org.jeecg..*.*(..)) " + + "&& (@annotation(org.springframework.web.bind.annotation.RequestMapping)) " + + "|| @annotation(org.springframework.web.bind.annotation.GetMapping)" + + "|| @annotation(org.springframework.web.bind.annotation.PostMapping)") + public Object proceed(ProceedingJoinPoint point) throws Throwable { + HttpServletRequest request = getRequest(); + + String requestURI = request.getRequestURI(); + String method = request.getMethod(); + // 获取事务流水号 + String transactionSerial = request.getHeader(JmReportConstants.TRANSACTION_SERIAL_KEY); + + Map requestHeaders = getRequestHeaders(request); + + // 设置traceId,日志链路追踪 + MDC.put(JmReportConstants.TRANSACTION_SERIAL_KEY, transactionSerial); + + Object result; + LocalDateTime startTime = LocalDateTime.now(); + + try { + Object[] args = point.getArgs(); + ThreadLocalConstant.requestParam.set(Arrays.toString(point.getArgs())); + log.info(">>>>>>>>请求信息>>>>>>>>:事务流水号:{},url:{} ,method:{},请求参数:{},请求头:{}", + transactionSerial, requestURI, method, objectsToString(args), requestHeaders); + result = point.proceed(); + resultInfoLog(transactionSerial, getExecPeriod(startTime), result); + } catch (Throwable e) { + result = Result.FAIL("请求失败", null); + String exceptionMsg = ExceptionUtils.getThrowableErrorStackTrace(e); + resultErrorLog(transactionSerial, getExecPeriod(startTime), result, e.getMessage(), exceptionMsg); + } + return result; + } + + /** + * 获取Request对象 + * + * @return + */ + private HttpServletRequest getRequest() { + RequestAttributes ra = RequestContextHolder.getRequestAttributes(); + ServletRequestAttributes sra = (ServletRequestAttributes) ra; + return sra.getRequest(); + } + + /** + * @Description 获取请求头信息 + * @return + * @author wxz + * @date 2021.03.31 13:59 + */ + private Map getRequestHeaders(HttpServletRequest request) { + Enumeration headerNames = request.getHeaderNames(); + HashMap headerMap = new HashMap<>(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + String headerValue = request.getHeader(headerName); + headerMap.put(headerName, headerValue); + } + return headerMap; + } + + /** + * 将请求对象转换为String + * @param args + * @return + */ + private String objectsToString(Object[] args) { + if (args == null) { + return null; + } else { + StringBuilder builder = new StringBuilder("["); + for (Object object : args) { + if (object != null + && !(object instanceof ServletRequest) + && !(object instanceof ServletResponse) + && !(object instanceof MultipartFile) + ) { + try { + // 尝试作为json解析 + String objectString = JSON.toJSONString(object); + builder.append(objectString); + } catch (Exception e) { + builder.append(object.toString()); + } + builder.append(","); + } + } + builder.append("]"); + return builder.toString(); + } + } + /** + * info日志 + * @param transactionSerial + * @param execTimeMillis + * @param result + */ + private void resultInfoLog(String transactionSerial, Long execTimeMillis, Object result) { + log.info("<<<<<<<<正常响应<<<<<<<<:事务流水号:{}, 执行时长:{}ms, 响应数据:{}", + transactionSerial, execTimeMillis, result == null ? result : result.toString()); + } + + /** + * 计算执行周期 + * @param startTime + * @return + */ + private Long getExecPeriod(LocalDateTime startTime) { + LocalDateTime endTime = LocalDateTime.now(); + return Duration.between(startTime, endTime).toMillis(); + } + /** + * 异常错误级别日志 + * @param transactionSerial + * @param execTimeMillis + * @param result + * @param exceptionMsg + * @param exceptionDetail + */ + private void resultErrorLog(String transactionSerial, Long execTimeMillis, Object result, String exceptionMsg, String exceptionDetail) { + log.error("<<<<<<<<异常响应<<<<<<<<:事务流水号:{}, 执行时长:{}ms, 响应数据:{}, 异常信息:{}, 堆栈信息:{}", + transactionSerial, execTimeMillis, result == null ? result : result.toString(), exceptionMsg, exceptionDetail); + } + + +} diff --git a/src/main/java/com/epmet/jmreport/constants/JmReportConstants.java b/src/main/java/com/epmet/jmreport/constants/JmReportConstants.java new file mode 100644 index 0000000..1f5897f --- /dev/null +++ b/src/main/java/com/epmet/jmreport/constants/JmReportConstants.java @@ -0,0 +1,71 @@ +package com.epmet.jmreport.constants; + +/** + * 公用参数常量 + */ +public interface JmReportConstants { + + /** + * app类型-居民端端 + */ + String APP_RESI = "resi"; + /** + * app类型-政府端 + */ + String APP_GOV = "gov"; + /** + * app类型-运营端 + */ + String APP_OPER = "oper"; + + /** + * 基层治理平台端 + */ + String APP_IC = "ic"; + + /** + * PC端:web + */ + String CLIENT_WEB = "web"; + /** + * 微信小程序:wxmp + */ + String CLIENT_WXMP = "wxmp"; + + /** + * 客户来源App + * */ + String APP = "app"; + + /** + * 用户Id + * */ + String USER_ID = "userId"; + + /** + * 客户端 + * */ + String CLIENT = "client"; + + /** + * 客户ID + */ + String CUSTOMER_ID = "customerId"; + + /** + * 事务流水号,每次请求串起来的多个服务拥有相同的流水号,便于日志追踪 + */ + String TRANSACTION_SERIAL_KEY = "Transaction-Serial"; + /** + * app类型-工作端 + */ + String APP_WORK = "work"; + /** + * 来源类型-话题:topic + */ + String TOPIC = "topic"; + /** + * 来源类型-议题:issue + */ + String ISSUE = "issue"; +} diff --git a/src/main/java/com/epmet/jmreport/constants/ThreadLocalConstant.java b/src/main/java/com/epmet/jmreport/constants/ThreadLocalConstant.java new file mode 100644 index 0000000..d90fe6f --- /dev/null +++ b/src/main/java/com/epmet/jmreport/constants/ThreadLocalConstant.java @@ -0,0 +1,23 @@ +package com.epmet.jmreport.constants; + +/** + * ThreadLocal常亮 + */ +public class ThreadLocalConstant { + + /** + * 存储所需操作权限的 ThreadLocal + */ + public static final ThreadLocal requirePermissionTl = new ThreadLocal<>(); + + /** + * 用于向DataFilterInterceptor传递权限过滤的sql片段(需要在Controller相关的AOP中进行清理,防止变量残留) + */ + public static final ThreadLocal sqlFilter = new ThreadLocal(); + + /** + * 用于本次的获取请求参数 + */ + public static final ThreadLocal requestParam = new ThreadLocal(); + +} diff --git a/src/main/java/com/epmet/jmreport/utils/ExceptionUtils.java b/src/main/java/com/epmet/jmreport/utils/ExceptionUtils.java new file mode 100644 index 0000000..9c44cb0 --- /dev/null +++ b/src/main/java/com/epmet/jmreport/utils/ExceptionUtils.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2018 人人开源 All rights reserved. + * + * https://www.renren.io + * + * 版权所有,侵权必究! + */ + +package com.epmet.jmreport.utils; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Exception工具类 + * + * @author Mark sunlightcs@gmail.com + */ +public class ExceptionUtils { + + /** + * 获取异常信息 + * @param ex 异常 + * @return 返回异常信息 + */ + public static String getErrorStackTrace(Exception ex){ + return getThrowableErrorStackTrace(ex); + } + + public static String getThrowableErrorStackTrace(Throwable ex) { + StringWriter sw = null; + PrintWriter pw = null; + try { + sw = new StringWriter(); + pw = new PrintWriter(sw, true); + ex.printStackTrace(pw); + }finally { + try { + if(pw != null) { + pw.close(); + } + } catch (Exception e) { + + } + try { + if(sw != null) { + sw.close(); + } + } catch (IOException e) { + + } + } + + return sw.toString(); + } +}