SpringBoot AOP+注解 全局日志记录

发布于:2025-07-12 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、需求描述

如何优雅地记录用户操作日志?

网站后台,功能开发完成后,新增了一个需求,即需要记录用户的各种操作记录。
由于是在开发后期,如果针对每一个功能都去添加一段记录日志的代码,工作量较大、代码侵入性太强,因此采用AOP+注解的方式实现。可读性大大提高,且便于维护和扩展。
AOP:面向切面编程,在不修改现有逻辑代码的情况下,增强功能,恰好体现了spring的理念:无入侵式
自定义注解:当被注解的方法执行时,可以通过切面逻辑捕获操作信息,并将这些信息存储到数据库或写入日志文件

二、代码实现

1.自定义注解

如下自定义一个注解,包含两个属性。这两个属性会在接口方法处使用@AdminLog赋值,并在aop中获取并记录到数据库

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解:记录后台用户操作日志
 * @author sxd
 */
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface AdminLog {
    String value() default "";
    int type() default 0;  // 操作日志类型(1查询,2添加,3修改,4删除,5导入,6导出)
}

2.aop配置

import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.jeecg.common.api.dto.LogDTO;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.IpUtils;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.modules.base.service.BaseCommonService;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * Aop:自定义注解:记录后台用户操作日志
 *
 * @author: sxd
 * @date: 2025-05-23 16:50
 **/
@Aspect
@Component
public class AdminLogAop {
    @Resource
    private BaseCommonService baseCommonService;

    // 使用自定义的注解作为切入点
    @Pointcut("@annotation(org.jeecg.modules.aop.AdminLog)")
    public void adminLogPointcut() {

    }

    @Around("adminLogPointcut()")
    public Object aroundAdminLogPointcut(ProceedingJoinPoint pjp) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 执行方法
        Object result = pjp.proceed();
        long costTime = System.currentTimeMillis() - beginTime;

        // 获取request
        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        // 注解中的信息
        String message = method.getAnnotation(AdminLog.class).value();
        int operateType = method.getAnnotation(AdminLog.class).type();
        // 类名
        String className = pjp.getTarget().getClass().getName();
        // 方法名
        String methodName = signature.getName();
        // 请求参数
        Object[] args = pjp.getArgs();
        StringBuilder params = new StringBuilder();
        for (Object arg : args) {
            if (arg instanceof MultipartFile) {
                params.append(" ").append(((MultipartFile) arg).getOriginalFilename());
            } else if (arg instanceof StandardMultipartHttpServletRequest) {
                StandardMultipartHttpServletRequest request1 = (StandardMultipartHttpServletRequest) arg;
                for (MultipartFile file : request1.getFileMap().values()) {
                    params.append(" ").append(file.getOriginalFilename());
                }
            } else {
                // arg的部分类型无需处理,因此屏蔽报错
                try {
                    params.append(" ").append(JSONObject.toJSONString(arg));
                } catch (Exception e) {
                    // nothing to do
                }
            }
        }
        // LogDTO 根据实际情况保存需要的信息即可
        LogDTO dto = new LogDTO();
        dto.setLogType(0);
        dto.setLogContent(message);
        dto.setOperateType(operateType);
        dto.setMethod(className + "." + methodName);
        dto.setRequestParam(params.toString());
        //设置IP地址
        dto.setIp(IpUtils.getIpAddr(request));
        //获取登录用户信息
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (sysUser != null) {
            dto.setUserid(sysUser.getUsername());
            dto.setUsername(sysUser.getRealname());
        }
        dto.setCostTime(costTime);
        dto.setCreateTime(new Date());
        //保存系统日志
        baseCommonService.addLog(dto);
        return result;
    }

}

3.日志记录类

aop中的baseCommonService.addLog(dto);是为了将日志记录到数据库,根据实际情况保存需要的信息即可
方法如下:

@Override
public void addLog(LogDTO logDTO) {
    log.info("addLog-received:{}", JSONObject.toJSONString(logDTO));
    if(oConvertUtils.isEmpty(logDTO.getId())){
        logDTO.setId(String.valueOf(IdWorker.getId()));
    }
    //保存日志(异常捕获处理,防止数据太大存储失败,导致业务失败)JT-238
    try {   
        logDTO.setCreateTime(new Date());
        baseCommonMapper.saveLog(logDTO);
    } catch (Exception e) {
        log.warn(" LogContent length : "+logDTO.getLogContent().length());
        log.warn(e.getMessage());
    }
}

4.调用

在需要记录日志的接口上使用自定义的注解@AdminLog 即可

@AdminLog(value = "任务活动-新增活动", type = CommonConstant.OPERATE_TYPE_2)
@ApiOperation("活动添加")
@RequiresPermissions("game:activity:save")
@RequestMapping(value = "save", method = RequestMethod.POST)
public Result<String> save(@RequestBody GameActivitySaveDTO dto) {
    if (dto.getId() != null && dto.getId() != 0) {
        throw new JeecgBootException("参数错误");
    }
    gameActivityService.activitySaveOrUpdate(dto);
    return Result.ok("添加成功");
}

网站公告

今日签到

点亮在社区的每一天
去签到