背景

原因
传统的同步执行

使用了异步任务

若依的异步任务管理器
若依的异步任务 统一使用
异步任务管理器进行管理
这段代码使用了链式编程来记录登录失败的信息。链式编程是一种编程风格,通过在一个语句中连续调用多个方法来简化代码。以下是对这段代码的详细解释:
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
AsyncManager.me(): 这是一个静态方法调用,返回AsyncManager的单例实例。AsyncManager是一个管理异步任务的类。
.execute(...): 调用AsyncManager实例的execute方法来执行一个异步任务。这个方法接受一个Runnable对象作为参数。
AsyncFactory.recordLogininfor(...): 这是一个静态方法调用,返回一个Runnable对象。AsyncFactory是一个工厂类,用于创建各种异步任务。recordLogininfor
方法创建一个记录登录信息的任务。
username: 用户名,作为记录登录信息的参数。
Constants.LOGIN_FAIL: 一个常量,表示登录失败的状态。
e.getMessage(): 获取异常的消息,作为记录登录信息的参数。
总结:这段代码通过调用
AsyncManager的execute方法,执行了一个由AsyncFactory
创建的记录登录失败信息的异步任务。这个任务记录了用户名、登录失败状态和异常消息。
异步任务管理器
常见的饿汉式
构造函数私有化,防止有人创建新的实例对象- 生成一个单例,但是私有化,
无法直接访问单例对象暴露一个方法,供外部使用这个单例
/**
* 单例模式
*/
private AsyncManager(){}
private static AsyncManager me = new AsyncManager();
public static AsyncManager me()
{
return me;
}
对外提供了两个方法
- execute 执行定时任务(经常使用)
- shutdown 停止任务线程池(不建议使用)
/**
* 执行任务
*
* @param task 任务
*/
public void execute(TimerTask task)
{
// 异步任务在10毫秒后执行
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
/**
* 停止任务线程池
*/
public void shutdown()
{
// 关闭线程池 但是不会立即关闭,而是等到线程任务都执行完了才关闭
// 常规情况下,不建议关闭线程池,因为线程池是共享资源,通常情况下会被多个模块共享
Threads.shutdownAndAwaitTermination(executor);
}
线程池的配置对象
异步任务的线程池的使用的 IOC中提前配置的线程池对象

线程池对象
/**
* 执行周期性或定时任务
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService()
{
// 创建一个定时任务线程池
return new ScheduledThreadPoolExecutor(corePoolSize,
// 设置线程工厂,命名模式为 "schedule-pool-%d",并将线程设置为守护线程
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
// 设置拒绝策略为调用者运行策略
new ThreadPoolExecutor.CallerRunsPolicy())
{
@Override
protected void afterExecute(Runnable r, Throwable t)
{
// 在任务执行后调用父类的 afterExecute 方法
super.afterExecute(r, t);
// 打印任务执行中的异常
Threads.printException(r, t);
}
};
}
异步任务工厂
有了
线程池,还需要有任务,任务通过异步任务工厂统一创建

流程图总结

操作日志中也用到异步任务
- 记录操作日志在任何系统中都是十分常见的操作
- 操作日志的记录最好有两个特性:
- 与页面代码
解耦(AOP切面)- 用户
无感,不要增加原本业务的执行时间(异步任务)

使用

注解
若依自定义了一个注解
@Log,常用的注解参数:
- 标题
- 业务类型
- 操作者类型

package com.dkd.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.dkd.common.enums.BusinessType;
import com.dkd.common.enums.OperatorType;
/**
* 自定义操作日志记录注解
*
* @author ruoyi
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/**
* 模块
*/
public String title() default "";
/**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人类别
*/
public OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
}
切面类
切面类是如何识别到@Log切面的?
在@Before、@AfterReturning和@AfterThrowing注解中,使用@annotation(controllerLog)表达式来定义切点,这个表达式表示任何被@Log注解标记的方法都会触发这些切面方法。以下是详细解释:
@Before(value = "@annotation(controllerLog)")表示在执行任何被@Log注解标记的方法之前,都会执行boBefore方法。controllerLog是@Log注解的一个实例,允许在boBefore方法中访问@Log注解的属性。通过这种方式,
@annotation(controllerLog)表达式定义了一个切点,匹配所有被@Log
注解标记的方法,并在这些方法执行前、成功返回后或抛出异常时,分别执行相应的切面方法。
package com.dkd.framework.aspectj;
/**
* 操作日志记录处理
*
* @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" };
/** 计算操作消耗时间 */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
/**
* 处理请求前执行
*/
@Before(value = "@annotation(controllerLog)")
public void boBefore(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
{
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddr();
operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
if (loginUser != null)
{
operLog.setOperName(loginUser.getUsername());
SysUser currentUser = loginUser.getUser();
if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept()))
{
operLog.setDeptName(currentUser.getDept().getDeptName());
}
}
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);
// 设置消耗时间
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();
}
}
}
在处理日志的使用的一部任务存储数据

2068

被折叠的 条评论
为什么被折叠?



