经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
使用Spring?AOP实现用户操作日志功能
来源:jb51  时间:2022/5/23 10:36:37  对本文有异议

我使用Spring AOP实现了用户操作日志功能

今天答辩完了,复盘了一下系统,发现还是有一些东西值得拿出来和大家分享一下。

需求分析

系统需要对用户的操作进行记录,方便未来溯源

首先想到的就是在每个方法中,去实现记录的逻辑,但是这样做肯定是不现实的,首先工作量大,其次违背了软件工程设计原则(开闭原则)

这种需求显然是对代码进行增强,首先想到的是使用 SpringBoot 提供的 AOP 结合注解的方式来实现

功能实现

1、 需要一张记录日志的 Log 表

导出的 sql 如下:

  1. -- mcams.t_log definition
  2.  
  3. CREATE TABLE `t_log` (
  4. `log_id` int NOT NULL AUTO_INCREMENT COMMENT '日志编号',
  5. `user_id` int NOT NULL COMMENT '操作人id',
  6. `operation` varchar(128) NOT NULL COMMENT '用户操作',
  7. `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '操作的方法',
  8. `params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '方法的参数',
  9. `ip` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户的ip',
  10. `create_time` timestamp NULL DEFAULT NULL COMMENT '操作时间',
  11. `cost_time` int DEFAULT NULL COMMENT '花费时间',
  12. PRIMARY KEY (`log_id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=189 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

2、我使用的是 Spring Boot 所以需要引入 spring aop 的 starter

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-aop</artifactId>
  4. </dependency>

如果是使用 spring 框架的,引入 spring-aop 即可

3、Log 实体类

  1. package com.xiaofengstu.mcams.web.entity;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. import com.baomidou.mybatisplus.annotation.TableField;
  4. import com.baomidou.mybatisplus.annotation.TableId;
  5. import com.baomidou.mybatisplus.annotation.TableName;
  6. import java.io.Serializable;
  7. import java.time.LocalDateTime;
  8. import com.fasterxml.jackson.annotation.JsonFormat;
  9. import lombok.Getter;
  10. import lombok.Setter;
  11. /**
  12. * <p>
  13. *
  14. * </p>
  15. *
  16. * @author fengzeng
  17. * @since 2022-05-21
  18. */
  19. @Getter
  20. @Setter
  21. @TableName("t_log")
  22. public class TLog implements Serializable {
  23. private static final long serialVersionUID = 1L;
  24. @TableId(value = "log_id", type = IdType.AUTO)
  25. private Integer logId;
  26. /**
  27. * 操作人id
  28. */
  29. @TableField("user_id")
  30. private Integer userId;
  31. /**
  32. * 用户操作
  33. */
  34. @TableField("operation")
  35. private String operation;
  36. @TableField("method")
  37. private String method;
  38. @TableField("params")
  39. private String params;
  40. @TableField("ip")
  41. private String ip;
  42. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  43. @TableField("create_time")
  44. private LocalDateTime createTime;
  45. @TableField("cost_time")
  46. private Long costTime;
  47. }

需要 lombok 插件(@getter && @setter 注解)

4、ILog 注解

  1. package com.xiaofengstu.mcams.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. /**
  7. * @Author FengZeng
  8. * @Date 2022-05-21 00:48
  9. * @Description TODO
  10. */
  11. @Target(ElementType.METHOD)
  12. @Retention(RetentionPolicy.RUNTIME)
  13. public @interface ILog {
  14. String value() default "";
  15. }

5、切面类 LogAspect

  1. package com.xiaofengstu.mcams.aspect;
  2. import com.xiaofengstu.mcams.annotation.ILog;
  3. import com.xiaofengstu.mcams.util.ThreadLocalUtils;
  4. import com.xiaofengstu.mcams.web.entity.TLog;
  5. import com.xiaofengstu.mcams.web.service.TLogService;
  6. import lombok.RequiredArgsConstructor;
  7. import org.aspectj.lang.ProceedingJoinPoint;
  8. import org.aspectj.lang.annotation.Around;
  9. import org.aspectj.lang.annotation.Aspect;
  10. import org.aspectj.lang.annotation.Pointcut;
  11. import org.aspectj.lang.reflect.MethodSignature;
  12. import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.web.context.request.RequestContextHolder;
  15. import org.springframework.web.context.request.ServletRequestAttributes;
  16. import javax.servlet.http.HttpServletRequest;
  17. import java.lang.reflect.Method;
  18. import java.time.LocalDateTime;
  19. /**
  20. * @Author FengZeng
  21. * @Date 2022-05-21 00:42
  22. * @Description TODO
  23. */
  24. @Aspect
  25. @Component
  26. @RequiredArgsConstructor
  27. public class LogAspect {
  28. private final TLogService logService;
  29. @Pointcut("@annotation(com.xiaofengstu.mcams.annotation.ILog)")
  30. public void pointcut() {
  31. }
  32. @Around("pointcut()")
  33. public Object around(ProceedingJoinPoint point) {
  34. Object result = null;
  35. long beginTime = System.currentTimeMillis();
  36. try {
  37. result = point.proceed();
  38. } catch (Throwable e) {
  39. e.printStackTrace();
  40. }
  41. long costTime = System.currentTimeMillis() - beginTime;
  42. saveLog(point, costTime);
  43. return result;
  44. }
  45. private void saveLog(ProceedingJoinPoint point, long costTime) {
  46. // 通过 point 拿到方法签名
  47. MethodSignature methodSignature = (MethodSignature) point.getSignature();
  48. // 通过方法签名拿到被调用的方法
  49. Method method = methodSignature.getMethod();
  50. TLog log = new TLog();
  51. // 通过方法区获取方法上的 ILog 注解
  52. ILog logAnnotation = method.getAnnotation(ILog.class);
  53. if (logAnnotation != null) {
  54. log.setOperation(logAnnotation.value());
  55. }
  56. String className = point.getTarget().getClass().getName();
  57. String methodName = methodSignature.getName();
  58. log.setMethod(className + "." + methodName + "()");
  59. // 获取方法的参数
  60. Object[] args = point.getArgs();
  61. LocalVariableTableParameterNameDiscoverer l = new LocalVariableTableParameterNameDiscoverer();
  62. String[] parameterNames = l.getParameterNames(method);
  63. if (args != null && parameterNames != null) {
  64. StringBuilder param = new StringBuilder();
  65. for (int i = 0; i < args.length; i++) {
  66. param.append(" ").append(parameterNames[i]).append(":").append(args[i]);
  67. }
  68. log.setParams(param.toString());
  69. }
  70. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  71. HttpServletRequest request = attributes.getRequest();
  72. log.setIp(request.getRemoteAddr());
  73. log.setUserId(ThreadLocalUtils.get());
  74. log.setCostTime(costTime);
  75. log.setCreateTime(LocalDateTime.now());
  76. logService.save(log);
  77. }
  78. }

因为我使用的是 Mybatis-plus,所以 logService.save(log);是 mybatis-plus 原生的 save operation

这步其实就是把 log 插入到数据库

6、使用

只需要在方法上加上 @ILog 注解,并设置它的 value 即可(value 就是描述当前 method 作用)

  1. package com.xiaofengstu.mcams.web.controller;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.xiaofengstu.mcams.annotation.ILog;
  4. import com.xiaofengstu.mcams.dto.BasicResultDTO;
  5. import com.xiaofengstu.mcams.enums.RespStatusEnum;
  6. import com.xiaofengstu.mcams.web.entity.TDept;
  7. import com.xiaofengstu.mcams.web.service.TDeptService;
  8. import lombok.RequiredArgsConstructor;
  9. import org.springframework.web.bind.annotation.GetMapping;
  10. import org.springframework.web.bind.annotation.RequestMapping;
  11. import org.springframework.web.bind.annotation.RequestParam;
  12. import org.springframework.web.bind.annotation.RestController;
  13. import java.util.List;
  14. /**
  15. * <p>
  16. * 前端控制器
  17. * </p>
  18. *
  19. * @author fengzeng
  20. * @since 2022-05-07
  21. */
  22. @RestController
  23. @RequestMapping("/web/dept")
  24. @RequiredArgsConstructor
  25. public class TDeptController {
  26. private final TDeptService deptService;
  27. @ILog("获取部门列表")
  28. @GetMapping("/list")
  29. public BasicResultDTO<List<TDept>> getDeptListByCampId(@RequestParam("campusId") Integer campusId) {
  30. return new BasicResultDTO(RespStatusEnum.SUCCESS, deptService.list(new QueryWrapper<TDept>().eq("campus_id", campusId)));
  31. }
  32. @ILog("通过角色获取部门列表")
  33. @GetMapping("/listByRole")
  34. public BasicResultDTO<List<TDept>> getDeptListByRole() {
  35. return new BasicResultDTO<>(RespStatusEnum.SUCCESS, deptService.listByRole());
  36. }
  37. }

数据库:

总结

如果要对现有代码进行功能扩展,使用 AOP + 注解不妨为一种优雅的方式

对 AOP 不熟悉的小伙伴,可以深入了解一下,毕竟是 spring 最重要的特性之一。

到此这篇关于使用Spring AOP实现了用户操作日志功能的文章就介绍到这了,更多相关Spring AOP用户操作日志内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号