当前位置:   article > 正文

SpringBoot使用AOP进行日志记录

SpringBoot使用AOP进行日志记录

前言

在Spring Boot中使用AOP(面向切面编程)记录操作日志具有以下好处:

  1. 减少重复代码:利用AOP,可以将日志记录、性能统计、安全控制、事务处理、异常处理等代码从业务逻辑代码中划分出来作为公共部分,从而减少重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
  2. 提高开发效率:通过将日志记录等通用功能与核心业务功能分离,开发人员可以专注于业务逻辑的开发,而无需在每个业务逻辑中手动添加日志记录等代码。这有助于提高开发效率。
  3. 方便跟踪和统计:通过AOP记录操作日志,可以方便地跟踪和统计用户对系统的操作情况。例如,通过分析日志记录,可以了解用户常用操作,以便定点推送消息。此外,还可以统计异常出现的次数或发生的时间,方便对系统进行优化和改进。
  4. 符合开闭原则:AOP是面向切面编程的,符合开闭原则。这意味着在不修改原有代码的基础上,可以对代码进行扩展。例如,在不修改原有业务逻辑代码的情况下,可以添加新的日志记录或安全控制功能。
  5. 易于集成和上手:Spring Boot已经集成了AOP功能,使得开发者可以更容易地使用和集成AOP。同时,由于Spring Boot的广泛使用,AOP与Spring Boot的集成也使得开发者可以更容易地找到相关的资源和文档。

总之,在Spring Boot中使用AOP记录操作日志可以提高系统的可维护性、可读性和可扩展性,并降低开发成本 。

1、环境

1.所需依赖

  1. <!-- lombok-->
  2. <dependency>
  3. <groupId>org.projectlombok</groupId>
  4. <artifactId>lombok</artifactId>
  5. <scope>provided</scope>
  6. </dependency>
  7. <!-- fastjson-->
  8. <dependency>
  9. <groupId>com.alibaba</groupId>
  10. <artifactId>fastjson</artifactId>
  11. <version>1.2.73</version>
  12. </dependency>
  13. <!-- 获取ip-->
  14. <dependency>
  15. <groupId>org.lionsoul</groupId>
  16. <artifactId>ip2region</artifactId>
  17. <version>1.7.2</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>eu.bitwalker</groupId>
  21. <artifactId>UserAgentUtils</artifactId>
  22. <version>1.20</version>
  23. </dependency>
  24. <!-- knife4j-->
  25. <dependency>
  26. <groupId>com.github.xiaoymin</groupId>
  27. <artifactId>knife4j-spring-boot-starter</artifactId>
  28. <version>2.0.7</version>
  29. </dependency>
  30. <!-- aop-->
  31. <dependency>
  32. <groupId>org.springframework.boot</groupId>
  33. <artifactId>spring-boot-starter-aop</artifactId>
  34. </dependency>

2、注解AOP实现日志记录代码

1.自定义注解

  1. package com.pzg.chat.annotation;
  2. import java.lang.annotation.*;
  3. /**
  4. * 获取某些方法执行的method操作(参照包com.example.demo.constant.OptTypeConstant下的常量选择)
  5. */
  6. @Target(ElementType.METHOD)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Documented
  9. public @interface OptLog {
  10. String optType() default ""; //执行声明操作
  11. }

2.日志实体类

  1. //操作日志
  2. @Data
  3. @Builder
  4. @AllArgsConstructor
  5. @NoArgsConstructor
  6. @TableName("sys_operation_log")
  7. public class OperationLog {
  8. @TableId(value = "id", type = IdType.AUTO)
  9. private Integer id;
  10. private String optModule;
  11. private String optUri;
  12. private String optType;
  13. private String optMethod;
  14. private String optDesc;
  15. private String requestMethod;
  16. private String requestParam;
  17. private String responseData;
  18. private Integer userId;
  19. private String nickname;
  20. private String ipAddress;
  21. private String ipSource;
  22. @TableField(fill = FieldFill.INSERT)
  23. private LocalDateTime createTime;
  24. @TableField(fill = FieldFill.UPDATE)
  25. private LocalDateTime updateTime;
  26. }

3.AOP类

        负责指定切点、切面、逻辑处理,对操作日志进行收集,通过反射获取方法上的注解,并对注解进行解析,获取注解字段信息,并封装,然后通过SpringBoot的监听器功能实现异步记录

  1. @Aspect
  2. @Component
  3. @SuppressWarnings("all")
  4. public class OperationLogAspect {
  5. @Autowired
  6. private ApplicationContext applicationContext;
  7. @Pointcut("@annotation(com.pzg.chat.annotation.OptLog)")
  8. public void operationLogPointCut(){
  9. }
  10. @AfterReturning(value = "operationLogPointCut()", returning = "keys")
  11. @SuppressWarnings("unchecked")
  12. public void saveOperationLog(JoinPoint joinPoint, Object keys) {
  13. RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  14. HttpServletRequest request = (HttpServletRequest) Objects.requireNonNull(requestAttributes).resolveReference(RequestAttributes.REFERENCE_REQUEST);
  15. OperationLog operationLog = new OperationLog();
  16. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  17. Method method = signature.getMethod();
  18. Api api = (Api) signature.getDeclaringType().getAnnotation(Api.class);
  19. ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
  20. OptLog optLog = method.getAnnotation(OptLog.class);
  21. operationLog.setOptModule(api.tags()[0]);
  22. operationLog.setOptType(optLog.optType());
  23. operationLog.setOptDesc(apiOperation.value());
  24. String className = joinPoint.getTarget().getClass().getName();
  25. String methodName = method.getName();
  26. methodName = className + "." + methodName;
  27. operationLog.setRequestMethod(Objects.requireNonNull(request).getMethod());
  28. operationLog.setOptMethod(methodName);
  29. if (joinPoint.getArgs().length > 0) {
  30. if (joinPoint.getArgs()[0] instanceof MultipartFile) {
  31. operationLog.setRequestParam("file");
  32. } else {
  33. operationLog.setRequestParam(JSON.toJSONString(joinPoint.getArgs()));
  34. }
  35. }
  36. operationLog.setResponseData(JSON.toJSONString(keys));
  37. operationLog.setUserId(Optional.ofNullable(UserUtil.getUserDetailsDTO().getId()).orElse(0));
  38. operationLog.setNickname(Optional.ofNullable(UserUtil.getUserDetailsDTO().getNickName()).orElse("anonymous"));
  39. String ipAddress = IpUtil.getIpAddress(request);
  40. operationLog.setIpAddress(ipAddress);
  41. operationLog.setIpSource(IpUtil.getIpSource(ipAddress));
  42. operationLog.setOptUri(request.getRequestURI());
  43. applicationContext.publishEvent(new OperationLogEvent(operationLog));
  44. }
  45. }

4.事件类

        负责接收日志数据

  1. public class ExceptionLogEvent extends ApplicationEvent {
  2. public ExceptionLogEvent(ExceptionLog exceptionLog) {
  3. super(exceptionLog);
  4. }
  5. }

5.监听事件类

        通过在监听类中监听事件的变化,最后将数据通过mybatis plus插入到数据库当中

  1. @Component
  2. public class EventListeners {
  3. @Resource
  4. private OperationLogMapper operationLogMapper;
  5. @Autowired
  6. private ExceptionLogMapper exceptionLogMapper;
  7. @Async
  8. @EventListener(OperationLogEvent.class)
  9. public void operationLogEvent(OperationLogEvent operationLogEvent){
  10. operationLogMapper.insert((OperationLog)operationLogEvent.getSource());
  11. }
  12. }

6.IP工具类

  1. package com.aurora.util;
  2. import eu.bitwalker.useragentutils.UserAgent;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.apache.commons.lang3.StringUtils;
  5. import org.lionsoul.ip2region.DataBlock;
  6. import org.lionsoul.ip2region.DbConfig;
  7. import org.lionsoul.ip2region.DbSearcher;
  8. import org.lionsoul.ip2region.Util;
  9. import org.springframework.core.io.ClassPathResource;
  10. import org.springframework.stereotype.Component;
  11. import org.springframework.util.FileCopyUtils;
  12. import javax.annotation.PostConstruct;
  13. import javax.servlet.http.HttpServletRequest;
  14. import java.io.InputStream;
  15. import java.lang.reflect.Method;
  16. import java.net.InetAddress;
  17. import java.net.UnknownHostException;
  18. @Slf4j
  19. @Component
  20. public class IpUtil {
  21. private static DbSearcher searcher;
  22. private static Method method;
  23. public static String getIpAddress(HttpServletRequest request) {
  24. String ipAddress = request.getHeader("X-Real-IP");
  25. if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
  26. ipAddress = request.getHeader("x-forwarded-for");
  27. }
  28. if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
  29. ipAddress = request.getHeader("Proxy-Client-IP");
  30. }
  31. if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
  32. ipAddress = request.getHeader("WL-Proxy-Client-IP");
  33. }
  34. if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
  35. ipAddress = request.getHeader("HTTP_CLIENT_IP");
  36. }
  37. if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
  38. ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
  39. }
  40. if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
  41. ipAddress = request.getRemoteAddr();
  42. if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
  43. //根据网卡取本机配置的IP
  44. InetAddress inet = null;
  45. try {
  46. inet = InetAddress.getLocalHost();
  47. } catch (UnknownHostException e) {
  48. log.error("getIpAddress exception:", e);
  49. }
  50. assert inet != null;
  51. ipAddress = inet.getHostAddress();
  52. }
  53. }
  54. return StringUtils.substringBefore(ipAddress, ",");
  55. }
  56. @PostConstruct
  57. private void initIp2regionResource() throws Exception {
  58. InputStream inputStream = new ClassPathResource("/ip/ip2region.db").getInputStream();
  59. byte[] dbBinStr = FileCopyUtils.copyToByteArray(inputStream);
  60. DbConfig dbConfig = new DbConfig();
  61. searcher = new DbSearcher(dbConfig, dbBinStr);
  62. method = searcher.getClass().getMethod("memorySearch", String.class);
  63. }
  64. public static String getIpSource(String ipAddress) {
  65. if (ipAddress == null || !Util.isIpAddress(ipAddress)) {
  66. log.error("Error: Invalid ip address");
  67. return "";
  68. }
  69. try {
  70. DataBlock dataBlock = (DataBlock) method.invoke(searcher, ipAddress);
  71. String ipInfo = dataBlock.getRegion();
  72. if (!StringUtils.isEmpty(ipInfo)) {
  73. ipInfo = ipInfo.replace("|0", "");
  74. ipInfo = ipInfo.replace("0|", "");
  75. return ipInfo;
  76. }
  77. } catch (Exception e) {
  78. log.error("getCityInfo exception:", e);
  79. }
  80. return "";
  81. }
  82. public static String getIpProvince(String ipSource) {
  83. String[] strings = ipSource.split("\\|");
  84. if (strings[1].endsWith("省")) {
  85. return StringUtils.substringBefore(strings[1], "省");
  86. }
  87. return strings[1];
  88. }
  89. public static UserAgent getUserAgent(HttpServletRequest request) {
  90. return UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
  91. }
  92. }

3、使用示例

  1. @Api(tags = "操作日志模块")
  2. @RestController
  3. public class OperationLogController {
  4. @Autowired
  5. private OperationLogService operationLogService;
  6. @ApiOperation(value = "查看操作日志")
  7. @GetMapping("/admin/operation/logs")
  8. public ResultVO<PageVO<OperationLogDTO>> listOperationLogs(ConditionVO conditionVO) {
  9. return ResultVO.ok(operationLogService.listOperationLogs(conditionVO));
  10. }
  11. @OptLog(optType = DELETE)
  12. @ApiOperation(value = "删除操作日志")
  13. @DeleteMapping("/admin/operation/logs")
  14. public ResultVO<?> deleteOperationLogs(@RequestBody List<Integer> operationLogIds) {
  15. operationLogService.removeByIds(operationLogIds);
  16. return ResultVO.ok();
  17. }
  18. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/954242
推荐阅读
相关标签
  

闽ICP备14008679号