| | |
| | | package com.ruoyi.framework.aspectj;
|
| | |
|
| | | import java.util.Collection;
|
| | | import java.util.Map;
|
| | | import javax.servlet.http.HttpServletRequest;
|
| | | import javax.servlet.http.HttpServletResponse;
|
| | | import org.aspectj.lang.JoinPoint;
|
| | | import org.aspectj.lang.annotation.AfterReturning;
|
| | | import org.aspectj.lang.annotation.AfterThrowing;
|
| | | import org.aspectj.lang.annotation.Aspect;
|
| | | import org.slf4j.Logger;
|
| | | import org.slf4j.LoggerFactory;
|
| | | import org.springframework.stereotype.Component;
|
| | | import org.springframework.validation.BindingResult;
|
| | | import org.springframework.web.multipart.MultipartFile;
|
| | | import org.springframework.web.servlet.HandlerMapping;
|
| | | import com.alibaba.fastjson2.JSON;
|
| | | import com.ruoyi.common.annotation.Log;
|
| | | import com.ruoyi.common.core.domain.model.LoginUser;
|
| | | import com.ruoyi.common.enums.BusinessStatus;
|
| | | import com.ruoyi.common.enums.HttpMethod;
|
| | | import com.ruoyi.common.filter.PropertyPreExcludeFilter;
|
| | | import com.ruoyi.common.utils.SecurityUtils;
|
| | | import com.ruoyi.common.utils.ServletUtils;
|
| | | import com.ruoyi.common.utils.StringUtils;
|
| | | import com.ruoyi.common.utils.ip.IpUtils;
|
| | | import com.ruoyi.framework.manager.AsyncManager;
|
| | | import com.ruoyi.framework.manager.factory.AsyncFactory;
|
| | | import com.ruoyi.system.domain.SysOperLog;
|
| | |
|
| | | /**
|
| | | * 操作日志记录处理
|
| | | * |
| | | * @author ruoyi
|
| | | */
|
| | | @Aspect
|
| | | @Component
|
| | | public class LogAspect
|
| | | {
|
| | | private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
|
| | |
|
| | | /** 排除敏感属性字段 */
|
| | | public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
|
| | |
|
| | | /**
|
| | | * 处理完请求后执行
|
| | | *
|
| | | * @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
|
| | | {
|
| | | // 获取当前的用户
|
| | | LoginUser loginUser = SecurityUtils.getLoginUser();
|
| | |
|
| | | // *========数据库日志=========*//
|
| | | SysOperLog operLog = new SysOperLog();
|
| | | operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
|
| | | // 请求的地址
|
| | | String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
|
| | | operLog.setOperIp(ip);
|
| | | operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
|
| | | if (loginUser != null)
|
| | | {
|
| | | operLog.setOperName(loginUser.getUsername());
|
| | | }
|
| | |
|
| | | if (e != null)
|
| | | {
|
| | | operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
| | | operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 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);
|
| | | // 保存数据库
|
| | | AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
|
| | | }
|
| | | catch (Exception exp)
|
| | | {
|
| | | // 记录本地异常日志
|
| | | log.error("==前置通知异常==");
|
| | | log.error("异常信息:{}", exp.getMessage());
|
| | | exp.printStackTrace();
|
| | | }
|
| | | }
|
| | |
|
| | | /**
|
| | | * 获取注解中对方法的描述信息 用于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);
|
| | | }
|
| | | // 是否需要保存response,参数和值
|
| | | if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
|
| | | {
|
| | | operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
|
| | | }
|
| | | }
|
| | |
|
| | | /**
|
| | | * 获取请求的参数,放到log中
|
| | | * |
| | | * @param operLog 操作日志
|
| | | * @throws Exception 异常
|
| | | */
|
| | | private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
|
| | | {
|
| | | String requestMethod = operLog.getRequestMethod();
|
| | | if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
|
| | | {
|
| | | String params = argsArrayToString(joinPoint.getArgs());
|
| | | operLog.setOperParam(StringUtils.substring(params, 0, 2000));
|
| | | }
|
| | | else
|
| | | {
|
| | | Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
| | | operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
|
| | | }
|
| | | }
|
| | |
|
| | | /**
|
| | | * 参数拼装
|
| | | */
|
| | | private String argsArrayToString(Object[] paramsArray)
|
| | | {
|
| | | String params = "";
|
| | | if (paramsArray != null && paramsArray.length > 0)
|
| | | {
|
| | | for (Object o : paramsArray)
|
| | | {
|
| | | if (StringUtils.isNotNull(o) && !isFilterObject(o))
|
| | | {
|
| | | try
|
| | | {
|
| | | String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter());
|
| | | params += jsonObj.toString() + " ";
|
| | | }
|
| | | catch (Exception e)
|
| | | {
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | return params.trim();
|
| | | }
|
| | |
|
| | | /**
|
| | | * 忽略敏感属性
|
| | | */
|
| | | public PropertyPreExcludeFilter excludePropertyPreFilter()
|
| | | {
|
| | | return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES);
|
| | | }
|
| | |
|
| | | /**
|
| | | * 判断是否需要过滤的对象。
|
| | | * |
| | | * @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;
|
| | | }
|
| | | }
|
| | | package com.ruoyi.framework.aspectj; |
| | | |
| | | import cn.hutool.core.lang.Dict; |
| | | import cn.hutool.core.map.MapUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import com.ruoyi.common.annotation.Log; |
| | | import com.ruoyi.common.core.domain.event.OperLogEvent; |
| | | import com.ruoyi.common.enums.BusinessStatus; |
| | | import com.ruoyi.common.enums.HttpMethod; |
| | | import com.ruoyi.common.helper.LoginHelper; |
| | | import com.ruoyi.common.utils.JsonUtils; |
| | | import com.ruoyi.common.utils.ServletUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.spring.SpringUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.aspectj.lang.JoinPoint; |
| | | import org.aspectj.lang.annotation.AfterReturning; |
| | | import org.aspectj.lang.annotation.AfterThrowing; |
| | | import org.aspectj.lang.annotation.Aspect; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.validation.BindingResult; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import jakarta.servlet.http.HttpServletRequest; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import java.util.Collection; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 操作日志记录处理 |
| | | * |
| | | * @author Lion Li |
| | | */ |
| | | @Slf4j |
| | | @Aspect |
| | | @Component |
| | | public class LogAspect { |
| | | |
| | | /** |
| | | * 排除敏感属性字段 |
| | | */ |
| | | public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; |
| | | |
| | | /** |
| | | * 处理完请求后执行 |
| | | * |
| | | * @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 { |
| | | |
| | | // *========数据库日志=========*// |
| | | OperLogEvent operLog = new OperLogEvent(); |
| | | operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); |
| | | // 请求的地址 |
| | | String ip = ServletUtils.getClientIP(); |
| | | operLog.setOperIp(ip); |
| | | operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); |
| | | operLog.setOperName(LoginHelper.getUsername()); |
| | | |
| | | if (e != null) { |
| | | operLog.setStatus(BusinessStatus.FAIL.ordinal()); |
| | | operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 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); |
| | | // 发布事件保存数据库 |
| | | SpringUtils.context().publishEvent(operLog); |
| | | } catch (Exception exp) { |
| | | // 记录本地异常日志 |
| | | log.error("异常信息:{}", exp.getMessage()); |
| | | exp.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取注解中对方法的描述信息 用于Controller层注解 |
| | | * |
| | | * @param log 日志 |
| | | * @param operLog 操作日志 |
| | | * @throws Exception |
| | | */ |
| | | public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogEvent 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); |
| | | } |
| | | // 是否需要保存response,参数和值 |
| | | if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) { |
| | | operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取请求的参数,放到log中 |
| | | * |
| | | * @param operLog 操作日志 |
| | | * @throws Exception 异常 |
| | | */ |
| | | private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog) throws Exception { |
| | | String requestMethod = operLog.getRequestMethod(); |
| | | if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) { |
| | | String params = argsArrayToString(joinPoint.getArgs()); |
| | | operLog.setOperParam(StringUtils.substring(params, 0, 2000)); |
| | | } else { |
| | | Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); |
| | | MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES); |
| | | operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 参数拼装 |
| | | */ |
| | | private String argsArrayToString(Object[] paramsArray) { |
| | | StringBuilder params = new StringBuilder(); |
| | | if (paramsArray != null && paramsArray.length > 0) { |
| | | for (Object o : paramsArray) { |
| | | if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) { |
| | | try { |
| | | String str = JsonUtils.toJsonString(o); |
| | | Dict dict = JsonUtils.parseMap(str); |
| | | if (MapUtil.isNotEmpty(dict)) { |
| | | MapUtil.removeAny(dict, EXCLUDE_PROPERTIES); |
| | | str = JsonUtils.toJsonString(dict); |
| | | } |
| | | params.append(str).append(" "); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return params.toString().trim(); |
| | | } |
| | | |
| | | /** |
| | | * 判断是否需要过滤的对象。 |
| | | * |
| | | * @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; |
| | | } |
| | | } |