简介


一次偶然的机会,在做一个系统的时候需要记录用户操作日志,以下分享下。

步骤


1、依赖

需要引入相应依赖,这里肯定时能和数据库关联了(简单的来说就是能够在浏览器上进行增删改查)。

  1. <!-- aop的依赖 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-aop</artifactId>
  5. </dependency>

2、自定义日志注解

定义一个方法级别的@Log注解,用于标注需要监控的方法:

  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5. /**
  6. * 定义一个Log的日志注解,用在方法上
  7. */
  8. @Target(ElementType.METHOD)
  9. @Retention(RetentionPolicy.RUNTIME)
  10. public @interface Log {
  11. String value() default "";
  12. }

3、创建库表和实体

在数据库中创建一张sys_operation_log表,用于保存用户的操作日志

a. 数据库

  1. SET FOREIGN_KEY_CHECKS=0;
  2. -- ----------------------------
  3. -- Table structure for sys_operation_log
  4. -- ----------------------------
  5. DROP TABLE IF EXISTS `sys_operation_log`;
  6. CREATE TABLE `sys_operation_log` (
  7. `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户操作日志主键',
  8. `LOG_NAME` varchar(255) DEFAULT NULL COMMENT '日志名称',
  9. `USER_ID` varchar(30) DEFAULT NULL COMMENT '用户id',
  10. `API` varchar(255) DEFAULT NULL COMMENT 'api名称',
  11. `METHOD` varchar(255) DEFAULT NULL COMMENT '方法名称',
  12. `CREATE_TIME` datetime DEFAULT NULL COMMENT '创建时间',
  13. `SUCCEED` tinyint(4) DEFAULT NULL COMMENT '是否执行成功(0失败1成功)',
  14. `MESSAGE` varchar(255) DEFAULT NULL COMMENT '具体消息备注',
  15. PRIMARY KEY (`ID`) USING BTREE
  16. ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='操作日志';
  17. -- ----------------------------
  18. -- Records of sys_operation_log
  19. -- ----------------------------
  20. INSERT INTO `sys_operation_log` VALUES ('17', '业务操作日志', 'tom', '/resource/menus', 'GET', '2018-04-22 16:05:05', '1', null);
  21. INSERT INTO `sys_operation_log` VALUES ('18', '业务操作日志', 'tom', '/resource/menus', 'GET', '2018-04-22 16:05:09', '1', null);
  22. INSERT INTO `sys_operation_log` VALUES ('19', '业务操作日志', 'tom', '/resource/api/-1/1/10', 'GET', '2018-04-22 16:08:15', '1', null);

b. 实体类

  1. import com.fasterxml.jackson.annotation.JsonFormat;
  2. import lombok.Data;
  3. import java.io.Serializable;
  4. import java.util.Date;
  5. /**
  6. * 用户操作日志主键
  7. * Create by ck on 2019/6/17
  8. */
  9. @Data
  10. public class sysOperationLog implements Serializable {
  11. private static final long serialVersionUID = 8147899229715719433L;
  12. /**
  13. * 主键
  14. */
  15. private Integer id;
  16. /**
  17. * 日志名称
  18. */
  19. private String logName;
  20. /**
  21. * 用户id
  22. */
  23. private String userId;
  24. /**
  25. * api名称
  26. */
  27. private String api;
  28. /**
  29. * 方法名称
  30. */
  31. private String method;
  32. /**
  33. * 是否执行成功 0失败 -- 1成功
  34. */
  35. private Byte succeed;
  36. /**
  37. * 详细备注
  38. */
  39. private String message;
  40. /**
  41. * 创建时间
  42. */
  43. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  44. private Date createTime;
  45. }

4、切面和切点

定义一个LogAspect类,使用@Aspect标注让其成为一个切面,切点为使用@Log注解标注的方法,使用@Around环绕通知:

  1. import com.ck.syscheck.annotate.Log;
  2. import com.ck.syscheck.model.SysOperationLog;
  3. import com.ck.syscheck.service.SysOperationService;
  4. import org.aspectj.lang.ProceedingJoinPoint;
  5. import org.aspectj.lang.annotation.Around;
  6. import org.aspectj.lang.annotation.Aspect;
  7. import org.aspectj.lang.annotation.Pointcut;
  8. import org.aspectj.lang.reflect.MethodSignature;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
  11. import org.springframework.stereotype.Component;
  12. import java.lang.reflect.Method;
  13. import java.util.Date;
  14. @Aspect
  15. @Component
  16. public class LogAspect {
  17. @Autowired
  18. private SysOperationService sysOperationService;
  19. @Pointcut("@annotation(com.ck.syscheck.annotate.Log)")
  20. public void pointcut() { }
  21. @Around("pointcut()")
  22. public Object around(ProceedingJoinPoint point) {
  23. Object result = null;
  24. long beginTime = System.currentTimeMillis();
  25. try {
  26. // 执行方法
  27. result = point.proceed();
  28. } catch (Throwable e) {
  29. e.printStackTrace();
  30. }
  31. // 执行时长(毫秒)
  32. long time = System.currentTimeMillis() - beginTime;
  33. // 保存日志
  34. saveLog(point, time);
  35. return result;
  36. }
  37. private void saveLog(ProceedingJoinPoint joinPoint, long time) {
  38. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  39. Method method = signature.getMethod();
  40. SysOperationLog sysOperationLog = new SysOperationLog();
  41. Log logAnnotation = method.getAnnotation(Log.class);
  42. if (logAnnotation != null) {
  43. // 注解上的描述
  44. sysOperationLog.setApi(logAnnotation.value());
  45. }
  46. // 请求的方法名
  47. String className = joinPoint.getTarget().getClass().getName();
  48. String methodName = signature.getName();
  49. sysOperationLog.setMethod(className + "." + methodName + "()");
  50. // 请求的方法参数值
  51. Object[] args = joinPoint.getArgs();
  52. // 请求的方法参数名称
  53. LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
  54. String[] paramNames = u.getParameterNames(method);
  55. if (args != null && paramNames != null) {
  56. String params = "";
  57. for (int i = 0; i < args.length; i++) {
  58. params += " " + paramNames[i] + ": " + args[i];
  59. }
  60. sysOperationLog.setMessage(params);
  61. }
  62. // 模拟一个用户名
  63. sysOperationLog.setUserId("admin");
  64. sysOperationLog.setCreateTime(new Date());
  65. // 保存系统日志
  66. sysOperationService.insert(sysOperationLog);
  67. }
  68. }

5、测试

  1. import com.ck.syscheck.annotate.Log;
  2. import org.springframework.web.servlet.ModelAndView;
  3. @RestController
  4. @RequestMapping("/api")
  5. public class SysUserController {
  6. /**
  7. * @return 测试
  8. */
  9. @Log("测试test")
  10. @RequestMapping("/test")
  11. public ModelAndView test(ModelAndView modelAndView) {
  12. modelAndView.setViewName("/front/test");
  13. return modelAndView;
  14. }
  15. @Log("添加add")
  16. @RequestMapping("/add")
  17. public ModelAndView add(ModelAndView modelAndView) {
  18. modelAndView.setViewName("/front/add");
  19. return modelAndView;
  20. }
  21. @Log("更新Update")
  22. @RequestMapping("/update")
  23. public ModelAndView update(ModelAndView modelAndView) {
  24. modelAndView.setViewName("/front/update");
  25. return modelAndView;
  26. }
  27. }

可以存到数据库
image.png

这里面是以部分字段作为参考栗子,具体的还需要看项目需求。demo源码所在位置:
GitHub:https://github.com/Tidetrace/sys-check