SpringBoot应用自定义注解记录操作日志
By: Date: 2018年7月30日 Categories: 程序 标签:,

在项目里我们常常有一种需求,要在系统中记录用户的操作情况,需要记录用户请求的路径,表单参数等信息。针对这类需求,倘若对所有的请求都记录操作日志,当在访问量大并且系统功能点多的情况下日志量可能也会非常可观,然而其中一些可有可无的操作并不是我们非常需要关注的,这种情况下我们可以根据需要,选择性的对部分重要业务的操作进行记录,同时我希望这样的操作日志可以由框架统一来处理,不需要在开发业务模块的过程中过多的关注记录日志的过程,所以这里我们可以通过自定义注解,以及AOP的方式来将日志记录在数据库中或者文件当中。这里我们的业务非常的简单就记录到数据库中,方便展示。

1.首先自定义注解

/**
 * 定义操作日志注解
 * @author admin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SysOperationLog {
  String value() default "";
}
这里@Target(ElementType.METHOD)标记表示我们此处的自定义注解应用在方法上。当然,还有其他的类型:
@Target(ElementType.TYPE) //类、接口、枚举、注解
@Target(ElementType.METHOD) //方法
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE) //局部变量
@Target(ElementType.ANNOTATION_TYPE) //注解
@Target(ElementType.PACKAGE) //包

2.定义切面,实现自定义日志

@Aspect
@Order(5)
@Component
public class SysOperationLogAspect {
 
    @Autowired
    private SysLogService sysLogService;
 
    public static final String ACCOUNT = "匿名用户";
    public static final String LOGOUT_URI = "/logout";
    public static final String SHORT_PKG = "c.s.s.c";
 
    //保存线程共享变量,开始执行时间
    ThreadLocal<Long> startTime = new ThreadLocal<>();
    //日志ID
    ThreadLocal<String> logId = new ThreadLocal<>();
 
    //定义一个切入点,匹配带有@SysOperationLog注解的方法
    @Pointcut("@annotation(com.story.storyvueweb.config.log.SysOperationLog)")
    private void storySysLog() {
    }
 
    /**
     * 环绕通知
     * @param joinPoint
     */
    @Around("storySysLog()")
    public Object advice(ProceedingJoinPoint joinPoint) {
        Object o = null;
        try {
            o = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        // 返回请求的结果
        return o;
    }
 
    /**
     * 在请求的方法执行前执行
     * @param joinPoint
     * @throws Throwable
     */
    @Before("storySysLog()&amp;&amp;@annotation(SysOperationLog)")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
 
        //记下请求起始时间
        startTime.set(System.currentTimeMillis());
        //生成logid
        logId.set(UUID.randomUUID().toString());
         
        SpiritLoginUser loginUser = LoginUserUtils.loginUser();
        final String method = request.getMethod();  //请求方法
        final String url = request.getRequestURL().toString();
        final String uri = request.getRequestURI();
        final String ip = IPUtils.getIpAddr(request);
        final String account = loginUser != null ? loginUser.getAccount() : "匿名用户";
        final String clazz = joinPoint.getSignature().getDeclaringTypeName()
                .replaceAll("com.story.storyvueweb.controller", SHORT_PKG);
        final String methodName = joinPoint.getSignature().getName();
        final Object[] args = joinPoint.getArgs();
        final String params = Arrays.toString(args);
        sysLogService.recordLog(new SysLogPO(logId.get(),account, ip, method, url, uri, clazz, methodName,
                DateUtils.currentDate(), null, params));
    }
 
    /**
     * 在方法执行后环绕结束后执行
     */
    @After("storySysLog()")
    public void doAfter() {
        // 计算出本次请求用时,更新到日志中
        sysLogService.recordLog(logId.get(), System.currentTimeMillis() - startTime.get());
    }
}
@Around环绕通知中的ProceedingJoinPoint表示连接点对象,可以通过反射执行目标对象连接点,也就是请求访问的方法。
@Before中的JoinPoint用以获取连接点运行时的参数,签名对象,上下文等信息。

这里我们已经在切面中将需要的参数放到了一个封装好的对象中,后面主要通过SysLogService来做,它的接口定义很简单:

public interface SysLogService extends StoryAbstractService<SysLogReqDTO, SysLogRespDTO,Long> {
 
    /**
     * 记录日志
     * @param entity
     */
    public void recordLog(SysLogPO entity);
     
    /**
     * 更新日志中耗时
     * @param id
     * @param spendTime  持续时间
     */
    public void recordLog(String id, Long spendTime);
}


日志我这里放在了数据库中,当然也可以选择放在文件中,我们只需要修改实现类就可以了。

这里也简单的看下我们的SysLog对象定义:

@Data
@Entity
@Table(name="t_sys_log")
public class SysLogPO {
    private String id;
    private String account;
    private String ip;
    private String requestMethod;
    private String url;
    private String uri;
    private String clazz;
    private String methodName;
    private Date visitTime;
    private Long spendTime;
    private String params;
 
    public SysLogPO(String id, String account, String ip, String requestMethod, String url, String uri, String clazz,String methodName, Date visitTime, Long spendTime, String params) {
        super();
        this.id = id;
        this.account = account;
        this.ip = ip;
        this.requestMethod = requestMethod;
        this.url = url;
        this.uri = uri;
        this.clazz = clazz;
        this.methodName = methodName;
        this.visitTime = visitTime;
        this.spendTime = spendTime;
        this.params = params;
    }
 
    public SysLogPO() {
        super();
    }
}

3.在方法中使用注解

@RestController
@RequestMapping("/sysmgr/user")
public class UserController {
 
    @SysOperationLog
    @PostMapping(value = "/update_user")
    public BaseResp<UserRespDTO> updateUser(@RequestBody UserReqDTO userVO) {
        BaseResp<UserRespDTO> result = new BaseResp<>();
        return result;
    }
}
我们只需要在方法声明上增加一个注解@SysOperationLog,就可以在被请求时记录下请求的参数以及耗时。

最后我们可以通过查询,呈现在页面上的效果就是这样:
SpringBoot应用自定义注解记录操作日志
日志详细

发表评论

电子邮件地址不会被公开。 必填项已用*标注