• JAVA自定义注解记录操作日志


    背景:

    系统的操作日志、审计日志。在日常的管理还是维护中都会起到很大的作用。

    解决办法:

    可以在需要的方法中对日志进行保存操作,但是对业务代码入侵性大。

    或者使用切面针对控制类进行处理,但是灵活度不高。

    ==》因此决定使用自定义注解 + 切面来针对方法进行日志记录

    目前日志主要记录的有三方面:

    1. 请求的入参,出参
    2. 关于业务上的操作
    3. 异常日常日志的打印

    一、自定义注解

    创建自定义注解 @AuditLog 

    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. @Retention(RetentionPolicy.RUNTIME)
    6. @Target(ElementType.METHOD)
    7. public @interface AuditLog {
    8. // 1.操作描述
    9. String action() default "";
    10. // 2.操作类型(增删改查)
    11. OperateEnum type() default OperateEnum.MODIFY;
    12. // 3.是否记录参数
    13. boolean isRecord() default true;
    14. }

    操作类型枚举类:新增、删除、修改、查询。

    1. public enum OperateEnum {
    2. ADD,
    3. DELETE,
    4. MODIFY,
    5. SAVE_OR_MODIFY,
    6. SELECT;
    7. }

    二、定义一个切面

    创建一个切面类AuditAspect ,用于捕获带有@AuditLog 注解的方法调用并记录日志:

    1. import cn.hutool.json.JSONUtil;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.aspectj.lang.ProceedingJoinPoint;
    4. import org.aspectj.lang.annotation.Around;
    5. import org.aspectj.lang.annotation.Aspect;
    6. import org.aspectj.lang.annotation.Pointcut;
    7. import org.aspectj.lang.reflect.MethodSignature;
    8. import org.springframework.stereotype.Component;
    9. import javax.servlet.ServletRequest;
    10. import javax.servlet.ServletResponse;
    11. import java.lang.reflect.Method;
    12. import java.lang.reflect.Parameter;
    13. import java.util.HashMap;
    14. /**
    15. * 日志记录切面
    16. */
    17. @Slf4j
    18. @Aspect
    19. @Component
    20. public class AuditAspect {
    21. @Pointcut(value = "@annotation(com.bocloud.devops.eaas.api.log.AuditLog)")
    22. public void logMethodPointCut() {
    23. }
    24. @Around("logMethodPointCut()")
    25. public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
    26. UserLogRecordDO userLogRecordDO = bulidRequestParams(point);
    27. //先执行业务
    28. try {
    29. Object result = point.proceed();
    30. if (result != null) {
    31. userLogRecordDO.setResultMsg(JSONUtil.toJsonStr(result));
    32. }
    33. return result;
    34. } catch (BusinessException e) {
    35. userLogRecordDO.setResultCode(e.getCode());
    36. userLogRecordDO.setResultMsg(e.getMessage());
    37. throw e;
    38. } catch (Exception e) {
    39. userLogRecordDO.setResultCode("");
    40. userLogRecordDO.setResultMsg("后端未知异常");
    41. throw e;
    42. } finally {
    43. //操作日志入库
    44. try {
    45. //XXXService.save(userLogRecordDO);
    46. } catch (Exception e) {
    47. log.warn("记录用户操作异常:{}", e.getMessage());
    48. }
    49. }
    50. }
    51. /**
    52. * 记录方法和方法入参
    53. * @param point
    54. * @return
    55. */
    56. protected UserLogRecordDO bulidRequestParams(ProceedingJoinPoint point) {
    57. MethodSignature methodSignature = (MethodSignature) point.getSignature();
    58. Method method = methodSignature.getMethod();
    59. AuditLog annotation = method.getAnnotation(AuditLog.class);
    60. UserLogRecordDO userLogRecordDO = new UserLogRecordDO();
    61. userLogRecordDO.setAction(annotation.action());
    62. userLogRecordDO.setType(annotation.type().name());
    63. userLogRecordDO.setMethodName(method.getDeclaringClass().getSimpleName() + "." + method.getName());
    64. try {
    65. // 处理入参
    66. Parameter[] parameters = methodSignature.getMethod().getParameters();
    67. HashMap paramMap = new HashMap<>();
    68. Object[] args = point.getArgs();
    69. for (int i = 0; i < parameters.length; i++) {
    70. AuditLog auditLog = parameters[i].getAnnotation(AuditLog.class);
    71. if (auditLog != null) {
    72. continue;
    73. }
    74. Class type = parameters[i].getType();
    75. if (ServletResponse.class.isAssignableFrom(type) || ServletRequest.class.isAssignableFrom(type)) {
    76. continue;
    77. }
    78. String name = parameters[i].getName();
    79. paramMap.put(name, args[i]);
    80. }
    81. userLogRecordDO.setMethodParams(JSONUtil.toJsonStr(paramMap));
    82. return userLogRecordDO;
    83. } catch (Exception e) {
    84. log.warn("构建入参异常:{}", e.getMessage());
    85. }
    86. return userLogRecordDO;
    87. }
    88. }
    • logMethodPointCut(): 这个方法定义了一个切入点(pointcut),它使用@annotation注解来匹配带有com.bocloud.devops.eaas.api.log.AuditLog注解的方法。这意味着切面将在这些被标记为@AuditLog的方法执行前后生效。

    • recordSysLog(ProceedingJoinPoint point): 这是一个环绕通知方法,用于包围切入点方法的执行。它负责记录系统日志和审计信息。具体的操作包括:

      • 构建请求参数并创建一个UserLogRecordDO对象,其中包括操作描述、操作类型、方法名等信息。
      • 调用point.proceed()来执行切入点方法,捕获方法的执行结果,将结果信息记录到UserLogRecordDO中。
      • 处理可能抛出的BusinessException异常,记录异常信息。
      • finally块中尝试将操作日志入库,但在此示例中,入库的部分被注释掉。
    • bulidRequestParams(ProceedingJoinPoint point): 这个方法用于构建方法的参数信息,并将其记录到UserLogRecordDO对象中。它包括方法名、操作描述、操作类型以及方法的入参信息。此方法通过反射分析方法参数和参数上的AuditLog注解,以构建参数信息的JSON表示。

    此切面的主要目的是在标记了@AuditLog注解的方法执行前后记录操作日志信息。在实际应用中,您需要确保数据库操作以及记录日志的部分(在finally块中)按照实际需求进行配置。此示例代码中的入库部分被注释掉,您需要根据自己的需求实现相应的数据持久化逻辑。

    三、记录日志的实体类

    1. import io.swagger.annotations.ApiModelProperty;
    2. import io.swagger.annotations.ApiOperation;
    3. import lombok.Data;
    4. /**
    5. * @program: eaas-center
    6. * @description:
    7. * @author: yanchao
    8. * @create: 2023-10-10 23:09
    9. **/
    10. @Data
    11. @ApiOperation("操作日志记录表")
    12. public class UserLogRecordDO {
    13. @ApiModelProperty("主键")
    14. private String id;
    15. @ApiModelProperty("行为描述")
    16. private String action;
    17. @ApiModelProperty("执行方法")
    18. private String methodName;
    19. @ApiModelProperty("执行入参")
    20. private String methodParams;
    21. @ApiModelProperty("操作类型")
    22. private String type;
    23. @ApiModelProperty("响应编码")
    24. private String resultCode;
    25. @ApiModelProperty("结果描述")
    26. private String resultMsg;
    27. }

    最后:使用该注解

    1. /**
    2. * 通过id查询中心维护表
    3. *
    4. * @param id 主键
    5. * @return 单条数据
    6. */
    7. @GetMapping("{id}")
    8. @AuditLog(action = "通过id查询中心维护表", type = OperateEnum.SELECT)
    9. public Result detail(@PathVariable("id") Integer id{
    10. return Result.success(this.eaasCentralMaintainService.detail(id));
    11. }

    这样即可在数据库查看相应的操作日志记录。 

  • 相关阅读:
    无线通信与编码_新型OFDM波形集_使用MATLAB仿真实现UFMC并与OFDM作对比_含实现代码
    30 个 Python 技巧,加速你的数据分析处理速度
    PicGo+Gitee中的404错误解决方案
    JLINK掉固件后重刷
    架构师的 36 项修炼第08讲:高可用系统架构设计
    微软AI量化平台Qlib:你需要知道的核心知识点
    作为编程的我们,针对我们的职业,初入职场要如何规划呢?
    数据库与MPP数仓(二十五):Vertica的优化/运维高频使用语句
    工业机器人国产替代,数字化转型是关键
    行情订阅&分钟合成
  • 原文地址:https://blog.csdn.net/m0_52767002/article/details/133793285