引言

在 Web 应用中,操作日志记录是一项重要的功能。它可以帮助我们跟踪用户的操作行为,为问题排查提供线索,同时也可以作为审计的依据。在Spring框架中,我们可以利用AOP(Aspect-oriented Programming,面向切面编程)来实现这一功能。

创建操作日志记录注解

首先,我们需要创建一个用于记录操作日志的注解,如下所示:

package com.emo.common.core.annotation;

import java.lang.annotation.*;

/**
 * 操作日志记录注解
 *
 * @author J.
 */
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {

    /**
     * 操作功能
     */
    String value() default "";

    /**
     * 操作模块
     */
    String module() default "";

    /**
     * 备注
     */
    String comments() default "";

    /**
     * 是否记录请求参数
     */
    boolean param() default true;

    /**
     * 是否记录返回结果
     */
    boolean result() default true;

}


这个注解可以通过 value() 方法来描述操作功能,通过 module() 方法来指定操作模块,通过 comments() 方法来添加备注。同时,我们还可以通过 param() 和 result() 方法来决定是否记录请求参数和返回结果。

实现注解处理器

然后,我们需要创建一个注解处理器,用于处理带有 @OperationLog 注解的方法。在这个处理器中,我们可以使用Spring AOP的功能来获取被注解方法的相关信息,如方法名、参数、返回值等,并将这些信息记录到日志中。

package com.emo.common.core.aspect;

import com.emo.common.core.annotation.OperationLog;
import com.emo.common.system.entity.OperationRecord;
import com.emo.common.system.entity.User;
import com.emo.common.system.service.OperationRecordService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

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

@Aspect
@Component
public class OperationLogAspect {
    @Resource
    private OperationRecordService operationRecordService;

    @Pointcut("@annotation(com.eleadmin.common.core.annotation.OperationLog)")
    public void operationLog() {
    }

    @AfterReturning(pointcut = "operationLog()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        saveLog(joinPoint, result);
    }

    private void saveLog(JoinPoint joinPoint, Object result) {
        OperationRecord record = new OperationRecord();

        User user = getLoginUser();
        if (user != null) {
            record.setUserId(user.getUserId());
        }

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (attributes == null ? null : attributes.getRequest());
        if (request != null) {
            record.setUrl(request.getRequestURI());
            record.setRequestMethod(request.getMethod());
            record.setIp(request.getRemoteAddr());
        }

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        record.setMethod(joinPoint.getTarget().getClass().getName() + "." + signature.getName());

        Method method = signature.getMethod();
        if (method != null) {
            OperationLog ol = method.getAnnotation(OperationLog.class);
            if (ol != null) {
                //记录操作功能
                record.setDescription(ol.value());
                //记录操作模块
                record.setModule(ol.module());
                // 记录请求参数
                // 记录请求结果
            }
        }

        operationRecordService.saveAsync(record);
    }

    private User getLoginUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null) {
            Object object = authentication.getPrincipal();
            if (object instanceof User) {
                return (User) object;
            }
        }
        return null;
    }
}



在方法上使用注解

    @PreAuthorize("hasAuthority('sys:user:remove')")
    @OperationLog
    @ApiOperation("删除用户")
    @DeleteMapping("/{id}")
    public ApiResult<?> remove(@PathVariable("id") Integer id) {
        if (userService.removeById(id)) {
            return success("删除成功");
        }
        return fail("删除失败");
    }

总结

通过以上步骤,我们实现了在Spring AOP中记录操作日志的功能。这种方法的优点是,我们不需要在每个需要记录日志的方法中添加重复的日志记录代码,只需在方法上添加一个注解即可。这样做不仅减少了代码的冗余,还提高了代码的可读性和可维护性。