后端——AOP异步日志

需求分析

在SpringBoot系统中,一般会对访问系统的请求做日志记录的需求,确保系统的安全维护以及查看接口的调用情况,可以使用AOP对controller层的接口进行增强,作日志记录。日志保存在数据库当中,为了避免影响接口的响应,降低用户体验度,采用异步的方式记录日志,避免日志记录阻塞接口请求

实现原理

通过定义AOP切面,访问接口之前,使用前置通知记录一些有用的数据,如ip地址、请求方式、操作人等等,接口执行完之后使用后置通知记录本次请求执行的实践、执行结果等等信息。

实现代码

AOP日志切面

定义切点表达式指向Controller的所有方法,即指向所有接口

java 复制代码
/**
 * @Description 日志切面类
 */
@Aspect
@Component
public class SysLogAspect {

    @Autowired
    SysLogDao sysLogDao;

    /**
     * 开始时间
     */
    private Long startTime;

    /**
     * ip
     */
    private String ip;

    /**
     * 请求接口名
     */
    private String interfaceName;

    /**
     * 请求方式
     */
    private String methodWays;

    /**
     * 请求方法路径
     */
    private String requestMethodUrl;

    /**
     * 请求类方法参数
     */
    private String requestArgs;

    //声明切点
    @Pointcut("execution(public * com.*.*.controller.*.*(..)) || execution(public * com.*.controller.*.*(..))"
    private void aspect() {
    }

    @Before("aspect()")
    public void before(JoinPoint joinPoint) throws NoSuchMethodException {
        //开始访问的时间
        startTime = System.currentTimeMillis();
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //获取请求ip
        ip = request.getRemoteAddr();
        //获取请求方法名
        interfaceName = request.getRequestURI();
        //获取请求方式
        methodWays = request.getMethod();
        //获取请求方法地址
        requestMethodUrl = joinPoint.getSignature().toString();
        //获取请求参数
        requestArgs = Arrays.toString(joinPoint.getArgs());
    }

    @AfterReturning(pointcut = "aspect()")
    public void methodAfterReturning() {
        Long endTime = System.currentTimeMillis();
        //执行时长
        Double time = (endTime - startTime) / 1000.0;
        SysLog sysLog = new SysLog();
        //获取当前用户
        Subject currentUser = SecurityUtils.getSubject();
        UserLoginInfo subject = (UserLoginInfo) currentUser.getPrincipal();
        if (!ObjectUtils.isEmpty(subject)) {
            //用户名
            sysLog.setUserName(subject.getUserName());
        }
        //用户操作
        switch (methodWays) {
            case "GET":
                sysLog.setOperation(BusinessType.SELECT.getValue());
                break;
            case "POST":
                sysLog.setOperation(BusinessType.INSERT.getValue());
                break;
            case "PUT":
                sysLog.setOperation(BusinessType.UPDATE.getValue());
                break;
            case "DELETE":
                sysLog.setOperation(BusinessType.DELETE.getValue());
                break;
        }
        //请求IP
        sysLog.setIp(ip);
        //请求类方法参数
        sysLog.setParams(requestArgs);
        //执行时长
        sysLog.setTime(time.toString());
        //请求方法路径
        sysLog.setMethod(requestMethodUrl);
        //入库时间
        String createTime = DateTimeUtils.getNowDateTime();
        sysLog.setCreateTime(createTime);
        //获取系统接口路径信息列表
        List<ApiInfo> apiInfos = sysLogDao.selectApiInfos();
        for (ApiInfo apiInfo : apiInfos) {
            //调用接口路径与接口信息列表进行匹配
            if (apiInfo.getApiName().equals(interfaceName)) {
                //菜单模块
                sysLog.setMenuModel(apiInfo.getMenuOperation());
                break;
            }
        }
        //异步新增日志
        AsyncManager.me().execute(AsyncFactory.recordOper(sysLog));
    }
}

异步任务管理器(线程池)

定义一个单例的异步任务管理器,使用线程池完成异步操作

java 复制代码
/**
 * @description: 异步任务管理器
 **/
public class AsyncManager {
    /**
     * 操作延迟10毫秒
     */
    private final int OPERATE_DELAY_TIME = 10;

    /**
     * 异步操作任务调度线程池
     */
    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");

    /**
     * 单例模式
     */
    private AsyncManager(){}

    private static AsyncManager me = new AsyncManager();

    public static AsyncManager me()
    {
        return me;
    }

    /**
     * 执行任务
     *
     * @param task 任务
     */
    public void execute(TimerTask task)
    {
        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
    }

    /**
     * 停止任务线程池
     */
    public void shutdown()
    {
        ThreadsUtil.shutdownAndAwaitTermination(executor);
    }
}

异步工厂

这里定义异步工厂去创建日记记录的任务,同时也方便系统扩展其他的异步操作

java 复制代码
public class AsyncFactory {

    /**
     * 操作日志记录
     *
     * @param operLog 操作日志信息
     * @return 任务task
     */
    public static TimerTask recordOper(final SysLog operLog) {
        return new TimerTask() {
            @Override
            public void run() {
                // 远程查询操作地点
                SpringUtils.getBean(SysLogService.class).addSysLogInfo(operLog);
            }
        };
    }

}
相关推荐
达文汐2 分钟前
【困难】力扣算法题解析LeetCode332:重新安排行程
java·数据结构·经验分享·算法·leetcode·力扣
培风图南以星河揽胜2 分钟前
Java版LeetCode热题100之零钱兑换:动态规划经典问题深度解析
java·leetcode·动态规划
启山智软26 分钟前
【中大企业选择源码部署商城系统】
java·spring·商城开发
我真的是大笨蛋28 分钟前
深度解析InnoDB如何保障Buffer与磁盘数据一致性
java·数据库·sql·mysql·性能优化
怪兽源码1 小时前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
恒悦sunsite1 小时前
Redis之配置只读账号
java·redis·bootstrap
梦里小白龙1 小时前
java 通过Minio上传文件
java·开发语言
人道领域1 小时前
javaWeb从入门到进阶(SpringBoot事务管理及AOP)
java·数据库·mysql
sheji52612 小时前
JSP基于信息安全的读书网站79f9s--程序+源码+数据库+调试部署+开发环境
java·开发语言·数据库·算法
毕设源码-邱学长2 小时前
【开题答辩全过程】以 基于Java Web的电子商务网站的用户行为分析与个性化推荐系统为例,包含答辩的问题和答案
java·开发语言