在安全审计方面或者日志分析中,很多时候都要记录用户的操作行为,特别是用户登录和特殊模块的操作。下面学习日志统一处理。将使用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链接,查看是否是否插库
使用的线程池为上篇spring boot学习系列之统一异常处理6中定义的线程池,保存操作日志的Service和Mybatis Mapper也与上篇异常日志保存类似,代码就不贴了。也有采用父类定义操作日志方法或者利用拦截器的写法,这些都可以实现。
发表评论