feat: 1、新增操作日志增加处理器,方便业务进行扩展(如针对响应内容超过2000字符的处理、或操作日志其他属性的增强);2、以配置的方式扩展Job的白名单;3、修复Job方法所在的Bean是一个AOP代理类时会出现空指针异常的问题

pull/496/head
潇湘振宇 2024-06-08 13:23:28 +08:00
parent 4f5bf990bf
commit b66e9cd7b6
5 changed files with 72 additions and 6 deletions

View File

@ -2,8 +2,10 @@ package com.ruoyi.framework.aspectj;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterReturning;
@ -12,10 +14,12 @@ import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal; import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters; import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
@ -24,6 +28,7 @@ import com.ruoyi.common.enums.BusinessStatus;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.handler.OperLogEnhanceHandler;
import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.domain.SysOperLog;
@ -45,6 +50,9 @@ public class LogAspect
/** 计算操作消耗时间 */ /** 计算操作消耗时间 */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time"); private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
@Autowired
private OperLogEnhanceHandler operLogEnhanceHandler;
/** /**
* *
*/ */
@ -116,6 +124,8 @@ public class LogAspect
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 设置消耗时间 // 设置消耗时间
operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
// 操作日志扩展点
operLogEnhanceHandler.extension(operLog);
// 保存数据库 // 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
} }
@ -155,7 +165,7 @@ public class LogAspect
// 是否需要保存response参数和值 // 是否需要保存response参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
{ {
operLog.setJsonResult(StringUtils.substring(JSONObject.toJSONString(jsonResult), 0, 2000)); operLog.setJsonResult(operLogEnhanceHandler.getJsonResult(log, operLog, jsonResult));
} }
} }

View File

@ -1,9 +1,13 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.EnableAspectJAutoProxy;
import com.ruoyi.framework.handler.OperLogEnhanceHandler;
/** /**
* *
* *
@ -17,4 +21,10 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
public class ApplicationConfig public class ApplicationConfig
{ {
@Bean
@ConditionalOnMissingBean
public OperLogEnhanceHandler operLogEnhanceHandler() {
return new OperLogEnhanceHandler() {};
}
} }

View File

@ -0,0 +1,32 @@
package com.ruoyi.framework.handler;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysOperLog;
/**
*
* @author
*/
public interface OperLogEnhanceHandler {
/**
* JSON
* @param log
* @param operLog
* @param jsonResult
* @return
*/
default String getJsonResult(Log log, SysOperLog operLog, Object result) {
return StringUtils.substring(JSONObject.toJSONString(result), 0, 2000);
}
/**
*
* @param operLog
*/
default void extension(SysOperLog operLog) {
}
}

View File

@ -4,6 +4,7 @@ import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -38,6 +39,9 @@ public class SysJobController extends BaseController
{ {
private String prefix = "monitor/job"; private String prefix = "monitor/job";
@Value("${job.whiteList:}")
private List<String> whiteList;
@Autowired @Autowired
private ISysJobService jobService; private ISysJobService jobService;
@ -153,7 +157,7 @@ public class SysJobController extends BaseController
{ {
return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规"); return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
} }
else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) else if (!ScheduleUtils.whiteList(job.getInvokeTarget(), whiteList.toArray(new String[whiteList.size()])))
{ {
return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
} }
@ -201,7 +205,7 @@ public class SysJobController extends BaseController
{ {
return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规"); return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规");
} }
else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) else if (!ScheduleUtils.whiteList(job.getInvokeTarget(), whiteList.toArray(new String[whiteList.size()])))
{ {
return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
} }

View File

@ -1,5 +1,6 @@
package com.ruoyi.quartz.util; package com.ruoyi.quartz.util;
import org.apache.commons.lang3.ArrayUtils;
import org.quartz.CronScheduleBuilder; import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger; import org.quartz.CronTrigger;
import org.quartz.Job; import org.quartz.Job;
@ -10,6 +11,8 @@ import org.quartz.Scheduler;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder; import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey; import org.quartz.TriggerKey;
import org.springframework.aop.support.AopUtils;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.constant.ScheduleConstants;
import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.common.exception.job.TaskException;
@ -123,19 +126,26 @@ public class ScheduleUtils
* *
* *
* @param invokeTarget * @param invokeTarget
* @param extendedWhiteList
* @return * @return
*/ */
public static boolean whiteList(String invokeTarget) public static boolean whiteList(String invokeTarget, String... extendedWhiteList)
{ {
String[] whiteList = StringUtils.isEmpty(extendedWhiteList) ? Constants.JOB_WHITELIST_STR
: ArrayUtils.addAll(extendedWhiteList, Constants.JOB_WHITELIST_STR);
String packageName = StringUtils.substringBefore(invokeTarget, "("); String packageName = StringUtils.substringBefore(invokeTarget, "(");
int count = StringUtils.countMatches(packageName, "."); int count = StringUtils.countMatches(packageName, ".");
if (count > 1) if (count > 1)
{ {
return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR); return StringUtils.containsAnyIgnoreCase(invokeTarget, whiteList);
} }
Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]);
if (AopUtils.isAopProxy(obj)) {
obj = AopUtils.getTargetClass(obj);
}
String beanPackageName = obj.getClass().getPackage().getName(); String beanPackageName = obj.getClass().getPackage().getName();
return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR) return StringUtils.containsAnyIgnoreCase(beanPackageName, whiteList)
&& !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR); && !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR);
} }
} }