SpringBoot 自定义切面+自定义注解 实现全局操作日志记录 ThreadLocal统计请求耗时

前言

最近在工作中碰到一个需求,要求对于用户进行的增删改查操作,都做一个操作日志。最简单的方式就是每个增删改查操作完成之后,都调用一个封装好的保存日志的方法。本文介绍一下基于自定义切面+自定义注解的实现方式,这种方案的优点就是对原来代码的侵入性低,并且是一种横向的拓展,和业务无关。

下面创建了一个demo项目,来演示一下具体的实现方式。

表设计

这里设计一个用户表(t_user),一个日志表(t_oper_log)。我们对用户表的数据进行增删改查,将操作日志保存到日志表。

用户表:

日志表:

代码实现

UserController

如下,现在有一个UserController,里面有增删改查四个方法,现在需要对这些方法,进行操作记录

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/list")
    public List<UserEntity> list() {
        return userService.list();
    }

    @PostMapping("/save")
    public boolean save(@RequestBody UserEntity userEntity) {
        return userService.save(userEntity);
    }

    @PostMapping("/delete")
    public boolean delete(@RequestParam("id") long id) {
        return userService.removeById(id);
    }

    @PostMapping("/update")
    public boolean update(@RequestBody UserEntity userEntity) {
        return userService.updateById(userEntity);
    }
}

自定义注解

这一步需要定义一个注解,将来会将他标注在需要进行操作记录的controller层方法上,可以指定模块名称、业务名称、业务类型。这里也可自行拓展其他的字段。

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
    /**
     * 模块名称
     *
     * @return
     */
    String module() default "";

    /**
     * 业务名称
     *
     * @return
     */
    String business() default "";

    /**
     * 业务类型
     *
     * @return
     */
    BusinessType businessType() default BusinessType.OTHER;
}
java 复制代码
public enum BusinessType {
    SELECT,
    SAVE,
    DELETE,
    UPDATE,
    OTHER;
}

自定义切面

代码如下,使用ThreadLocal变量计算了请求耗时。

java 复制代码
@Aspect
@Component
public class LogAspect {

    @Autowired
    private OperLogService operLogService;

    //请求耗时
    private static final ThreadLocal<Long> COST_TIME = new NamedThreadLocal<>("cost_time");

    /**
     * 前置通知
     *
     * @param joinPoint
     * @param log
     */
    @Before("@annotation(log)")
    public void before(JoinPoint joinPoint, Log log) {
        //设置初始值,用于计算请求耗时
        COST_TIME.set(System.currentTimeMillis());
    }

    /**
     * 返回通知
     *
     * @param joinPoint
     * @param log
     * @param result
     */
    @AfterReturning(pointcut = "@annotation(log)", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Log log, Object result) {
        insertLog(joinPoint, log, result, null);
    }

    /**
     * 异常通知
     *
     * @param joinPoint
     * @param log
     * @param e
     */
    @AfterThrowing(pointcut = "@annotation(log)", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Log log, Exception e) {
        insertLog(joinPoint, log, null, e);
    }

    /**
     * 日志记录
     *
     * @param joinPoint
     * @param log
     * @param result
     * @param e
     */
    private void insertLog(JoinPoint joinPoint, Log log, Object result, Exception e) {
        try {
            OperLogEntity entity = new OperLogEntity();
            //基本信息
            entity.setAddTime(new Date());
            entity.setAddUser(((UserEntity) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId());
            //业务信息
            entity.setModule(log.module());
            entity.setBusiness(log.business());
            entity.setBusinessType(log.businessType().toString());
            //请求信息
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            entity.setRequestIp(request.getLocalAddr().contains(":") ? "127.0.0.1" : request.getLocalAddr());
            entity.setRequestUrl(request.getRequestURI());
            entity.setRequestMethod(request.getMethod());
            //方法信息
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            entity.setMethod(className + "." + methodName + "()");
            //请求耗时
            entity.setCostTime(System.currentTimeMillis() - COST_TIME.get());
            //错误日志
            entity.setStatus(BusinessStatus.SUCCESS.ordinal());
            if (e != null) {
                entity.setStatus(BusinessStatus.FAIL.ordinal());
                entity.setErrMsg(e.getMessage().length() < 255 ? e.getMessage() : e.getMessage().substring(0, 255));
            }
            //保存日志
            operLogService.save(entity);
        } catch (Exception exception) {
            e.printStackTrace();
        } finally {
            COST_TIME.remove();
        }
    }
}

使用注解

在需要进行记录的controller层方法上标注注解

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/list")
    @Log(module = "用户管理", business = "查询列表", businessType = BusinessType.SELECT)
    public List<UserEntity> list() {
        return userService.list();
    }

    @PostMapping("/save")
    @Log(module = "用户管理", business = "保存用户", businessType = BusinessType.SAVE)
    public boolean save(@RequestBody UserEntity userEntity) {
        return userService.save(userEntity);
    }

    @PostMapping("/delete")
    @Log(module = "用户管理", business = "删除用户", businessType = BusinessType.DELETE)
    public boolean delete(@RequestParam("id") long id) {
        return userService.removeById(id);
    }

    @PostMapping("/update")
    @Log(module = "用户管理", business = "修改用户", businessType = BusinessType.UPDATE)
    public boolean update(@RequestBody UserEntity userEntity) {
        return userService.updateById(userEntity);
    }
}

总结

以上,就通过自定义切面+自定义注解 完成了全局的操作日志记录。

相关推荐
等一场春雨1 小时前
springboot 3 websocket react 系统提示,选手实时数据更新监控
spring boot·websocket·react.js
荆州克莱2 小时前
Golang的性能监控指标
spring boot·spring·spring cloud·css3·技术
AI人H哥会Java3 小时前
【Spring】控制反转(IoC)与依赖注入(DI)—IoC容器在系统中的位置
java·开发语言·spring boot·后端·spring
赖龙3 小时前
springboot restful mybatis连接mysql返回日期格式不对
spring boot·mybatis·restful
自律的kkk3 小时前
SpringBoot中使用AOP切面编程实现登录拦截
java·spring boot·aop·切面编程·登录拦截
武昌库里写JAVA3 小时前
【MySQL】MySQL 通过127.0.0.1和localhost登录的区别
spring boot·spring·毕业设计·layui·课程设计
sin22014 小时前
idea创建springBoot的五种方式
java·spring boot·intellij-idea
一休哥助手4 小时前
Spring Boot 项目中 Maven 剔除无用 Jar 引用的最佳实践
spring boot·maven·jar
Q_19284999064 小时前
基于Spring Boot的旅游推荐系统
spring boot·后端·旅游
愤怒的代码4 小时前
Spring Boot对访问密钥加密解密——RSA
java·spring boot·后端