一、添加依赖 在Spring框架中,使用AOP配合自定义注解可以方便的实现用户操作的监控。首先搭建一个基本的Spring Boot Web环境开启Spring Boot,然后引入必要依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!-- 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注解,用于标注需要监控的方法:
1 2 3 4 5 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { String value() default ""; }
三、创建库表和实体 3.1 建表 在数据库中创建一张sys_log表,用于保存用户的操作日志,数据库采用mysql5.7.23
1 2 3 4 5 6 7 8 9 10 11 12 drop table if exists `sys_log`; create table `sys_log` ( `id` int(20) not null auto_increment comment 'id', `username` varchar(50) character set utf8 collate utf8_general_ci null comment '用户名', `operation` varchar(50) character set utf8 collate utf8_general_ci null comment '用户操作', `time` int(11) null comment '响应时间', `method` varchar(200) character set utf8 collate utf8_general_ci null comment '请求方法', `params` varchar(500) character set utf8 collate utf8_general_ci null comment '请求参数', `ip` varchar(64) character set utf8 collate utf8_general_ci null comment 'ip地址', `create_time` DATETIME null comment '创建时间', primary key (`id`) using btree ) engine = innodb auto_increment = 1 character set = utf8 collate = utf8_general_ci row_format = dynamic;
3.2 创建实体 库表对应的实体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Getter @Setter public class SysLog implements Serializable { private static final long serialVersionUID = -6309732882044872298L; private Integer id; private String username; private String operation; private Integer time; private String method; private String params; private String ip; @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; }
四、保存日志的方法 为了方便,这里直接使用Spring JdbcTemplate来操作数据库。定义一个SysLogDao接口,包含一个保存操作日志的抽象方法:
1 2 3 public interface SysLogDao { void saveSysLog(SysLog syslog); }
其实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Repository public class SysLogDaoImpl implements SysLogDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public void saveSysLog(SysLog syslog) { StringBuffer sql = new StringBuffer("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)"); NamedParameterJdbcTemplate npjt = new NamedParameterJdbcTemplate(this.jdbcTemplate.getDataSource()); npjt.update(sql.toString(), new BeanPropertySqlParameterSource(syslog)); } }
五、切面和切点 定义一个LogAspect类,使用@Aspect标注让其成为一个切面,切点为使用@Log注解标注的方法,使用@Around环绕通知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @Aspect @Component public class LogAspect { @Autowired private SysLogDao sysLogDao; @Pointcut("@annotation(com.wno704.boot.aspect.Log)") public void pointcut() { } @Around("pointcut()") public Object around(ProceedingJoinPoint point) { Object result = null; long beginTime = System.currentTimeMillis(); try { // 执行方法 result = point.proceed(); } catch (Throwable e) { e.printStackTrace(); } // 执行时长(毫秒) long time = System.currentTimeMillis() - beginTime; // 保存日志 saveLog(point, time); return result; } private void saveLog(ProceedingJoinPoint joinPoint, long time) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SysLog sysLog = new SysLog(); Log logAnnotation = method.getAnnotation(Log.class); if (logAnnotation != null) { // 注解上的描述 sysLog.setOperation(logAnnotation.value()); } // 请求的方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); sysLog.setMethod(className + "." + methodName + "()"); // 请求的方法参数值 Object[] args = joinPoint.getArgs(); // 请求的方法参数名称 LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); String[] paramNames = u.getParameterNames(method); if (args != null && paramNames != null) { String params = ""; for (int i = 0; i < args.length; i++) { params += " " + paramNames[i] + ": " + args[i]; } sysLog.setParams(params); } // 获取request HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); // 设置IP地址 sysLog.setIp(IPUtils.getIpAddr(request)); // 模拟一个用户名 sysLog.setUsername("mrbird"); sysLog.setTime((int) time); sysLog.setCreateTime(new Date()); // 保存系统日志 sysLogDao.saveSysLog(sysLog); } }
六、测试 TestController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController public class TestController { @Log("执行方法一") @GetMapping("/one") public void methodOne(String name) { } @Log("执行方法二") @GetMapping("/two") public void methodTwo() throws InterruptedException { Thread.sleep(2000); } @Log("执行方法三") @GetMapping("/three") public void methodThree(String name, String age) { } }
最终项目目录如下图所示:
启动项目,分别访问:http://localhost:8080/web/one?name=wno704 http://localhost:8080/web/two http://localhost:8080/web/three?name=wno704&age=28
查询数据库: