spring - AOP(6)- 记录后台管理员操作日志

一、需求

1.1 问题

后台一些涉及到新增、编辑、删除等敏感操作的需要记录下操作日志,包含操作人、操作内容、请求参数等等信息。

1.2 思路

  • 统一对Controller层的方法进行拦截,记录下请求信息
  • 通过自定义注解,定义操作内容
  • 通过Spring AOP集成AspectJ来实现,利用AspectJ里的相关注解可以方便的配置切面、定义切点

二、实现

2.1 环境准备

Maven引入 spring-boot-starter-aop stater ,这里会自动引入spring-aopaspectjweaver 两个jar包:

  • spring-aop:基于代理的AOP支持
  • aspectjweaver:AspectJ,一个AOP框架,扩展了Java语言。通过AspectJ注解,简称@AspectJ,可以快速配置切面,定义切点。由于Spring支持方法级的切点,所以仅对@AspectJ提供了有限的支持,也就是只使用了AspectJ的一部分功能。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2 定义注解

定义个方法级OptLog注解,该注解注释的方法表示要记录操作日志。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OptLogger {
    /**
     * 描述信息
     */
    String value() default "";
}

2.3 使用@AspectJ配置切面、定义切点

主要用到了@Aspect、@Pointcut、@Before这三个注解:

  • @Aspect:定义切面
  • @Pointcut:定义切点,借助@annotation(com.xxx.OptLogger)扫描注释了指定注解的方法
  • @Before:前置通知,在方法执行前执行,类似还有@After、@Around等等

实现思路:

  • 通过@Pointcut("@annotation(com.xxx.OptLogger)")定义切点,表示注释了OptLogger注解的方法
  • 通过前置通知@Before("")拦截
  • 获取请求相关信息
    • 假设请求参数中有个token可以获取到用户信息,记录下用户名(不管是什么机制,只要能获取到用户信息即可)
    • 获取请求ip
    • 通过切点获取拦截方法上的OptLogger注解的描述信息
    • 通过切点获取拦截方法的参数
  • 线程池异步记录操作信息
@Aspect
@Component
public class OptLogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(OptLogAspect.class);

    @Autowired
    private UserService userService;
    @Autowired
    private OptLogService optLogService;
    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor taskExecutor;

    /**
     * 定义切点,表示注释了OptLogger注解的目标类方法
     */
    @Pointcut("@annotation(com.momo.optlog.web.optlog.annotation.OptLogger)")
    public void optLogAspect() {
    }

    /**
     * 前置通知 用于拦截有OptLogger注解注释的方法
     *
     * @param joinPoint 连接点
     * @return
     */
    @Before("optLogAspect()")
    public void before(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        try {
            String token = request.getParameter("token");
            if (StringUtils.isBlank(token)) {
                return;
            }
            User user = userService.getByToken(token);
            if (user == null) {
                return;
            }

            String ip = IpUtil.getRemoteHost(request);
            String opt = getOptLogMethodDesc(joinPoint);
            String args = JsonUtil.obj2json(getMethodArgs(joinPoint));
            this.recordOptLog(ip, user.getUserName(), opt, args);
        } catch (Exception e) {
            LOGGER.error("record admin opt fail", e);
        }
    }

    private void recordOptLog(String ip, String userName, String opt, String args) {
        taskExecutor.submit(() -> {
            OptLogAddDTO optLogDTO = OptLogAddDTO.builder()
                    .userName(userName)
                    .opt(opt)
                    .ip(ip)
                    .args(args)
                    .build();
            if (!optLogService.add(optLogDTO)) {
                LOGGER.error("save opt log fail, params={}", JsonUtil.obj2json(optLogDTO));
            }
        });
    }

    /**
     * 获取OptLogger注解的描述信息
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws NoSuchMethodException
     */
    public static String getOptLogMethodDesc(JoinPoint joinPoint) throws NoSuchMethodException {
        // 获取拦截的方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String methodName = methodSignature.getName();
        Class[] parameterTypes = methodSignature.getMethod().getParameterTypes();
        Method method = methodSignature.getDeclaringType().getMethod(methodName, parameterTypes);

        // 方法上的注解
        boolean isPresent = method.isAnnotationPresent(OptLogger.class);
        if (isPresent) {
            OptLogger optLogger = method.getAnnotation(OptLogger.class);
            return optLogger.value();
        }
        return "";
    }

    /**
     * 获取方法参数
     *
     * @param joinPoint
     * @return
     */
    public static List<Object> getMethodArgs(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            return new ArrayList<>(0);
        }

        return Arrays.stream(args)
                .filter(arg ->
                        !(arg instanceof Model)
                        && !(arg instanceof ModelMap)
                        && !(arg instanceof BeanPropertyBindingResult)
                        && !(arg instanceof MultipartFile))
                .collect(Collectors.toList());
    }
}

2.4 记录操作日志

以UserController为例,在需要记录日志的接口上添加注解@OptLogger(“描述信息”)。

  • 例如新增用户接口,在执行addUser()方法前,会先执行OptLogAspect#before()方法,并记录操作人、ip、操作内容(新增用户)、请求参数等信息。其他接口都类似。
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user")
    public CommonResponse list() {
        List<User> userList = userService.getUesrList();
        return CommonResponse.okResult(userList);
    }

    @PostMapping("/user")
    @OptLogger("新增用户")
    public CommonResponse addUser(@RequestBody UserAddReqVO req) {
        boolean isSuccess = userService.add(req);
        return CommonResponse.okResult(isSuccess);
    }

    @PutMapping("/user")
    @OptLogger("编辑用户")
    public CommonResponse updateUser(@RequestBody UserUpdateReqVO req) {
        boolean isSuccess = userService.update(req);
        return CommonResponse.okResult(isSuccess);
    }

    @DeleteMapping("/user/{id}")
    @OptLogger("删除用户")
    public CommonResponse deleteUser(@PathVariable long id) {
        boolean isSuccess = userService.delete(id);
        return CommonResponse.okResult(isSuccess);
    }

    @DeleteMapping("/user/{id}")
    @OptLogger("删除用户")
    public CommonResponse deleteUser(@PathVariable long id) {
        boolean isSuccess = userService.delete(id);
        return CommonResponse.okResult(isSuccess);
    }

}

2.5 总结

实现的核心逻辑基本上都列出来了,实现的比较粗糙,仅供参考。
项目源码:https://github.com/mytt-10566/springboot-optlog

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值