Spring Boot AOP记录用户操作日志
一、添加依赖
在Spring框架中,使用AOP配合自定义注解可以方便的实现用户操作的监控。首先搭建一个基本的Spring Boot Web环境开启Spring Boot,然后引入必要依赖:
<!-- aop依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.22</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>二、自定义注解
定义一个方法级别的@Log注解,用于标注需要监控的方法:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceLog{Stringvalue()default"";}三、创建库表和实体
3.1 建表
在数据库中创建一张sys_log表,用于保存用户的操作日志,数据库采用mysql5.7.23
droptableifexists`sys_log`;createtable`sys_log`(`id`int(20)notnullauto_incrementcomment'id',`username`varchar(50)charactersetutf8collateutf8_general_cinullcomment'用户名',`operation`varchar(50)charactersetutf8collateutf8_general_cinullcomment'用户操作',`time`int(11)nullcomment'响应时间',`method`varchar(200)charactersetutf8collateutf8_general_cinullcomment'请求方法',`params`varchar(500)charactersetutf8collateutf8_general_cinullcomment'请求参数',`ip`varchar(64)charactersetutf8collateutf8_general_cinullcomment'ip地址',`create_time`DATETIMEnullcomment'创建时间',primarykey(`id`)usingbtree)engine=innodbauto_increment=1characterset=utf8collate=utf8_general_ci row_format=dynamic;3.2 创建实体
库表对应的实体:
@Getter@SetterpublicclassSysLogimplementsSerializable{privatestaticfinallongserialVersionUID=-6309732882044872293L;privateIntegerid;privateStringusername;privateStringoperation;privateIntegertime;privateStringmethod;privateStringparams;privateStringip;@JsonFormat(timezone="GMT+8",pattern="yyyy-MM-dd HH:mm:ss")privateDatecreateTime;}四、保存日志的方法
为了方便,这里直接使用Spring JdbcTemplate来操作数据库。定义一个SysLogDao接口,包含一个保存操作日志的抽象方法:
publicinterfaceSysLogDao{voidsaveSysLog(SysLogsyslog);}其实现方法:
@RepositorypublicclassSysLogDaoImplimplementsSysLogDao{@AutowiredprivateJdbcTemplatejdbcTemplate;@OverridepublicvoidsaveSysLog(SysLogsyslog){StringBuffersql=newStringBuffer("insert into sys_log ");sql.append("(username,operation,time,method,params,ip,create_time) ");sql.append("values(:username,:operation,:time,:method,");sql.append(":params,:ip,:createTime)");NamedParameterJdbcTemplatenpjt=newNamedParameterJdbcTemplate(this.jdbcTemplate.getDataSource());npjt.update(sql.toString(),newBeanPropertySqlParameterSource(syslog));}}五、切面和切点
定义一个LogAspect类,使用@Aspect标注让其成为一个切面,切点为使用@Log注解标注的方法,使用@Around环绕通知:
@Aspect@ComponentpublicclassLogAspect{@AutowiredprivateSysLogDaosysLogDao;@Pointcut("@annotation(com.wno704.boot.aspect.Log)")publicvoidpointcut(){}@Around("pointcut()")publicObjectaround(ProceedingJoinPointpoint){Objectresult=null;longbeginTime=System.currentTimeMillis();try{// 执行方法result=point.proceed();}catch(Throwablee){e.printStackTrace();}// 执行时长(毫秒)longtime=System.currentTimeMillis()-beginTime;// 保存日志saveLog(point,time);returnresult;}privatevoidsaveLog(ProceedingJoinPointjoinPoint,longtime){MethodSignaturesignature=(MethodSignature)joinPoint.getSignature();Methodmethod=signature.getMethod();SysLogsysLog=newSysLog();LoglogAnnotation=method.getAnnotation(Log.class);if(logAnnotation!=null){// 注解上的描述sysLog.setOperation(logAnnotation.value());}// 请求的方法名StringclassName=joinPoint.getTarget().getClass().getName();StringmethodName=signature.getName();sysLog.setMethod(className+"."+methodName+"()");// 请求的方法参数值Object[]args=joinPoint.getArgs();// 请求的方法参数名称LocalVariableTableParameterNameDiscovereru=newLocalVariableTableParameterNameDiscoverer();String[]paramNames=u.getParameterNames(method);if(args!=null&¶mNames!=null){Stringparams="";for(inti=0;i<args.length;i++){params+=" "+paramNames[i]+": "+args[i];}sysLog.setParams(params);}// 获取requestHttpServletRequestrequest=HttpContextUtils.getHttpServletRequest();// 设置IP地址sysLog.setIp(IPUtils.getIpAddr(request));// 模拟一个用户名sysLog.setUsername("mrbird");sysLog.setTime((int)time);sysLog.setCreateTime(newDate());// 保存系统日志sysLogDao.saveSysLog(sysLog);}}六、测试
TestController:
@RestControllerpublicclassTestController{@Log("执行方法一")@GetMapping("/one")publicvoidmethodOne(Stringname){}@Log("执行方法二")@GetMapping("/two")publicvoidmethodTwo()throwsInterruptedException{Thread.sleep(2000);}@Log("执行方法三")@GetMapping("/three")publicvoidmethodThree(Stringname,Stringage){}}最终项目目录如下图所示:
启动项目,分别访问:
http://localhost:8080/web/one?name=wno704
http://localhost:8080/web/two
http://localhost:8080/web/three?name=wno704&age=28
查询数据库:
