spring boot学习系列之统一日志处理7

in Java with 0 comment

在安全审计方面或者日志分析中,很多时候都要记录用户的操作行为,特别是用户登录和特殊模块的操作。下面学习日志统一处理。将使用AOP、采用注解切点的方式做个横向的操作日志写入数据库操作。

定义AOP日志处理类,将自定义注解作为切点,目标方法执行后,构造日志,写入数据库。

@Aspect
@Component
public class LogAopComponent {
    private static final Logger logger = LoggerFactory.getLogger(LogAopComponent.class);
    @Autowired
    ExceptionLogExecutors executors;
    /**
     * 注解切点
     * 
     * @author wenqy
     */
    @Pointcut(“@annotation(com.wenqy.log.OperateLog)”)
    private void pointCutMethod(){}
    /**
     * 基于切点
     * @param joinPoint
     * @author wenqy
     * @throws ClassNotFoundException 
     */
    @After(“pointCutMethod()”)
    public void recordOperateLog(JoinPoint joinPoint) throws ClassNotFoundException{
        OperateLogVO operateLog = createOperateLog(joinPoint);
        // 扔到线程池执行
        this.executors.execute(new OperateLogThread(operateLog ));
    }
    /**
     * 获取操作日志
     * @param joinPoint
     * @return
     * @throws ClassNotFoundException
     * @author wenqy
     */
    public OperateLogVO createOperateLog(JoinPoint joinPoint) throws ClassNotFoundException {
        OperateLogVO operateLog = new OperateLogVO();
        String clientIp = WebUtils.getClientIp();
        operateLog.setHostIp(clientIp);
        // TODO 取session
        operateLog.setUserId(IdGenerator.randomLong());
        operateLog.setOperateTime(new Date());
        // 获取操作方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取方法类名
        String targetName = joinPoint.getTarget().getClass().getName();
        Class<?> targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        for (Method method : methods){
            if(!method.getName().equals(methodName)){
                continue;
            }
            // 获取方法注解
            OperateLog logAnnotation = method.getAnnotation(OperateLog.class);
            operateLog.setAction(logAnnotation.action());
            operateLog.setModule(logAnnotation.module());
            operateLog.setRemark(logAnnotation.remark());
            operateLog.setSubSystem(logAnnotation.subSystem());
            break;
        }
        return operateLog;
    }
}

自定义切点注解,描述日志信息。

/**
 * 日志注解
 * 
 * @version V5.0
 * @author wenqy
 * @date   2018年2月14日
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) // 目标方法
@Documented
public @interface OperateLog {
    /**
     * 子系统
     * @return
     * @author wenqy
     */
    String subSystem() default “”;
    /**
     * 模块
     * @return
     * @author wenqy
     */
    String module() default “”;
    /**
     * 动作
     * @return
     * @author wenqy
     */
    String action() default “”;
    /**
     * 备注
     * @return
     * @author wenqy
     */
    String remark() default “”;
}

根据注解信息和用户信息,定义操作日志VO类

public class OperateLogVO  implements Serializable {
    private Long id;
    private String subSystem;
    private String module;
    private String action;
    private String remark;
    private String hostIp; // ip
    private Date operateTime; // 操作时间
    private Long userId; // 操作用户Id
// 忽略setter getter
}

定义保存操作日志线程

/**
 * 操作日志保存线程
 * 
 * @version V5.0
 * @author wenqy
 * @date   2018年2月14日
 */
public class OperateLogThread implements Runnable {
    private OperateLogVO operateLogVO;
    public OperateLogThread(OperateLogVO operateLogVO) {
        this.operateLogVO = operateLogVO;
    }
    @Override
    public void run() {
        // 加载异常日志处理服务
        OperateLogService logService = (OperateLogService) SpringAppContextHolder
                .getBean(OperateLogService.class);
        logService.saveOperateLog(operateLogVO);
    }
}

控制层中要记录操作日志的方法定义引用

@OperateLog(subSystem=“系统管理”, module=“系统登录”, action=“login”,remark=“用户登录”)
@RequestMapping(value = “/doLogin”)
@ResponseBody
public Object doLogin(HttpServletRequest request) {
       String email = request.getParameter(“email”);
       String password = request.getParameter(“password”);
       System.out.println(email + ” “ + password);
       return “success”;
}

启动应用,访问/doLogin URL链接,查看是否是否插库

operator_log

使用的线程池为上篇spring boot学习系列之统一异常处理6中定义的线程池,保存操作日志的Service和Mybatis Mapper也与上篇异常日志保存类似,代码就不贴了。也有采用父类定义操作日志方法或者利用拦截器的写法,这些都可以实现。