若依的异步任务管理器

背景

在这里插入图片描述

原因

传统的同步执行

在这里插入图片描述

使用了异步任务

在这里插入图片描述

若依的异步任务管理器

若依的异步任务 统一使用 异步任务管理器进行管理

这段代码使用了链式编程来记录登录失败的信息。链式编程是一种编程风格,通过在一个语句中连续调用多个方法来简化代码。以下是对这段代码的详细解释:

AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
  1. AsyncManager.me(): 这是一个静态方法调用,返回 AsyncManager 的单例实例。AsyncManager 是一个管理异步任务的类。

  2. .execute(...): 调用 AsyncManager 实例的 execute 方法来执行一个异步任务。这个方法接受一个 Runnable 对象作为参数。

  3. AsyncFactory.recordLogininfor(...): 这是一个静态方法调用,返回一个 Runnable 对象。AsyncFactory 是一个工厂类,用于创建各种异步任务。recordLogininfor
    方法创建一个记录登录信息的任务。

  4. username: 用户名,作为记录登录信息的参数。

  5. Constants.LOGIN_FAIL: 一个常量,表示登录失败的状态。

  6. e.getMessage(): 获取异常的消息,作为记录登录信息的参数。

总结:这段代码通过调用 AsyncManagerexecute 方法,执行了一个由 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();
        }
    }

}

在处理日志的使用的一部任务存储数据

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值