后端——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);
            }
        };
    }

}
相关推荐
wangchunyu1145 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
paopaokaka_luck6 小时前
流浪动物救助与收养系统(DeepSeek AI对话、 ECharts多维分析、救助流程衔接、领养与回访衔接、 捐赠确认与公示、志愿者协作)
java·前端·javascript·人工智能·mysql·echarts·推荐算法
Thomas.Sir6 小时前
第9课:SpringBoot4.x Web开发基础:WebMvc自动配置、静态资源、请求参数接收
java·spring boot
程序 代码狂人6 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
2501_944521597 小时前
Deepmerge 跨端三方库仓颉适配全流程
java·python·eclipse·github
南归北隐8 小时前
Spring AI Alibaba Graph框架实现MCP数据库查询实战
java·后端·spring ai·mcp·spring ai mcp
古法老登瞎折腾8 小时前
大数据智能运维实战(九):让 AI 查日志——找到异常,也保留上下文
java·大数据·运维·人工智能·spring ai·mcp
ai大模型-探索者8 小时前
Function Call与MCP(区别、联系与实战)
java·服务器·网络
略略略咯咯8 小时前
啊啊啊啊啊
java·服务器·数据库
SL_staff9 小时前
运维救火实战:钉钉API变更致薪资计算中断,如何用可视化逻辑引擎5分钟热修复
java·开源·全栈