package com.gkhy.exam.framework.aspectj; import cn.hutool.core.convert.Convert; import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.util.URLUtil; import cn.hutool.extra.servlet.ServletUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.filter.SimplePropertyPreFilter; import com.gkhy.exam.common.annotation.Log; import com.gkhy.exam.common.domain.entity.SysUser; import com.gkhy.exam.common.enums.BusinessStatus; import com.gkhy.exam.common.utils.SecurityUtils; import com.gkhy.exam.common.utils.ServletUtils; import com.gkhy.exam.common.utils.StringUtils; import com.gkhy.exam.framework.manager.AsyncManager; import com.gkhy.exam.framework.manager.factory.AsyncFactory; import com.gkhy.exam.system.domain.SysOperLog; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.NamedThreadLocal; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.multipart.MultipartFile; import springfox.documentation.spring.web.json.Json; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; /** * 操作日志记录处理 */ @Aspect @Component @Slf4j public class LogAspect { /** 排除敏感属性字段 */ public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; private static final ThreadLocal TIME_THREADLOCAL=new NamedThreadLocal<>("Cost Time"); private static final int PARAM_MAX_LENGTH = 2000; @Pointcut("execution(public * com.gkhy.exam.*.controller..*.*(..))") public void logPointCut(){ } /** * 处理请求前执行 */ @Before(value = "@annotation(controllerLog)") public void doBefore(JoinPoint joinPoint, Log controllerLog) { TIME_THREADLOCAL.set(System.currentTimeMillis()); } /** * 处理完请求后执行 * * @param joinPoint 切点 */ @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { handleLog(joinPoint, controllerLog, null, jsonResult); } /** * 拦截异常操作 * * @param joinPoint 切点 * @param e 异常 */ @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) { handleLog(joinPoint, controllerLog, e, null); } protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { try { // 获取当前的用户 SysUser currentUser = SecurityUtils.getLoginUser().getUser(); // *========数据库日志=========*// SysOperLog operLog = new SysOperLog(); operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); // 请求的地址 HttpServletRequest request = ServletUtils.getRequest(); // 你的项目获取request的方式 String realIp = ServletUtil.getClientIP(request); operLog.setOperIp(realIp); operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); if (currentUser != null) { operLog.setOperName(currentUser.getUsername()); if (StringUtils.isNotNull(currentUser.getDeptName()) && StringUtils.isNotEmpty(currentUser.getDeptName())) { operLog.setDeptName(currentUser.getDeptName()); } } if (e != null) { operLog.setStatus(BusinessStatus.FAIL.ordinal()); operLog.setErrorMsg(StringUtils.substring(Convert.toStr(e.getMessage(), ExceptionUtil.getMessage(e)), 0, 2000)); } // 设置方法名称 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); operLog.setMethod(className + "." + methodName + "()"); // 设置请求方式 operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); // 处理设置注解上的参数 getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); // 设置消耗时间 operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); // 保存数据库 AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } catch (Exception exp) { // 记录本地异常日志 log.error("异常信息:{}", exp.getMessage()); exp.printStackTrace(); } finally { TIME_THREADLOCAL.remove(); } } /** * 获取注解中对方法的描述信息 用于Controller层注解 * * @param log 日志 * @param operLog 操作日志 * @throws Exception */ public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception { // 设置action动作 operLog.setBusinessType(log.businessType().ordinal()); // 设置标题 operLog.setTitle(log.title()); // 设置操作人类别 operLog.setOperatorType(log.operatorType().ordinal()); // 是否需要保存request,参数和值 if (log.isSaveRequestData()) { // 获取参数的信息,传入到数据库中。 setRequestValue(joinPoint, operLog, log.excludeParamNames()); } // 是否需要保存response,参数和值 if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) { operLog.setJsonResult(StringUtils.substring(JSONObject.toJSONString(jsonResult), 0, 2000)); } } /** * 获取请求的参数,放到log中 * * @param operLog 操作日志 * @throws Exception 异常 */ private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception { Map map = ServletUtils.getRequest().getParameterMap(); if (StringUtils.isNotEmpty(map)) { String params = JSON.toJSONString(map, excludePropertyPreFilter(excludeParamNames)); operLog.setOperParam(StringUtils.substring(params, 0, PARAM_MAX_LENGTH)); } else { Object args = joinPoint.getArgs(); if (StringUtils.isNotNull(args)) { String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); operLog.setOperParam(params); } } } /** * 忽略敏感属性 */ public SimplePropertyPreFilter excludePropertyPreFilter(String[] excludeParamNames) { // 1. 合并全局排除属性和自定义排除属性 String[] allExcludeNames = ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames); // 2. 创建SimplePropertyPreFilter(你找到的这个类),指定排除属性 SimplePropertyPreFilter filter = new SimplePropertyPreFilter(); // 核心方法:添加需要排除的属性名(1.x的标准API) filter.getExcludes().addAll(java.util.Arrays.asList(allExcludeNames)); return filter; } /** * 参数拼装 */ private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) { StringBuilder params = new StringBuilder(); if (paramsArray != null && paramsArray.length > 0) { for (Object o : paramsArray) { if (StringUtils.isNotNull(o) && !isFilterObject(o)) { try { Object jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); params.append(jsonObj).append(" "); if (params.length() >= PARAM_MAX_LENGTH) { return StringUtils.substring(params.toString(), 0, PARAM_MAX_LENGTH); } } catch (Exception e) { log.error("请求参数拼装异常 msg:{}, 参数:{}", e.getMessage(), paramsArray, e); } } } } return params.toString(); } /** * 处理完请求后执行 * * @param joinPoint 切点 */ @Around("logPointCut()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{ SysUser user= SecurityUtils.getLoginUserWithoutError()!=null?SecurityUtils.getLoginUserWithoutError().getUser():null; long startTime = System.currentTimeMillis(); //获取当前请求对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); StringBuffer requestURL = request.getRequestURL(); JSONObject webLog = new JSONObject(); String urlStr = request.getRequestURL().toString(); webLog.put("basePath", StringUtils.removeSuffix(urlStr, URLUtil.url(urlStr).getPath())); webLog.put("ip", ServletUtil.getClientIP(request,null)); webLog.put("method",request.getMethod()); Object params=getParameter(method, joinPoint.getArgs()); webLog.put("parameter",params); webLog.put("uri",request.getRequestURI()); webLog.put("url",requestURL.toString()); if(user!=null) { webLog.put("userName", user.getName()); } log.info(webLog.toString()); Object result = joinPoint.proceed(); if (result == null) { //如果切到了 没有返回类型的void方法,这里直接返回 return null; } long endTime = System.currentTimeMillis(); webLog.put("result",StringUtils.sub(JSON.toJSONString(result),0,2000)); webLog.put("spendTime",endTime - startTime); log.info(webLog.toString()); return result; } /** * 拦截异常操作 * @param joinPoint * @param e */ @AfterThrowing(value = "logPointCut()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint,Exception e){ //获取当前请求对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String urlStr = request.getRequestURL().toString(); log.error("@AfterThrowing异常通知:url={},出错了error_message={}", urlStr,e.getMessage()); } /** * 根据方法和传入的参数获取请求参数 */ private Object getParameter(Method method, Object[] args) { List argList = new ArrayList<>(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { //将RequestBody注解修饰的参数作为请求参数 RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); if (requestBody != null) { argList.add(args[i]); } //将RequestParam注解修饰的参数作为请求参数 RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); if (requestParam != null) { Map map = new HashMap<>(); String key = parameters[i].getName(); if (StringUtils.isNotEmpty(requestParam.value())) { key = requestParam.value(); } map.put(key, args[i]); argList.add(map); } } if (argList.size() == 0) { return null; } else if (argList.size() == 1) { return argList.get(0); } else { return argList; } } /** * 判断是否需要过滤的对象。 * * @param o 对象信息。 * @return 如果是需要过滤的对象,则返回true;否则返回false。 */ @SuppressWarnings("rawtypes") public boolean isFilterObject(final Object o) { Class clazz = o.getClass(); if (clazz.isArray()) { return clazz.getComponentType().isAssignableFrom(MultipartFile.class); } else if (Collection.class.isAssignableFrom(clazz)) { Collection collection = (Collection) o; for (Object value : collection) { return value instanceof MultipartFile; } } else if (Map.class.isAssignableFrom(clazz)) { Map map = (Map) o; for (Object value : map.entrySet()) { Map.Entry entry = (Map.Entry) value; return entry.getValue() instanceof MultipartFile; } } return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult; } }