美文网首页
老大喊我用AOP记录下日志

老大喊我用AOP记录下日志

作者: zy_think123 | 来源:发表于2019-08-23 13:15 被阅读0次

老大喊我记录下API的操作日志,免得前端甩锅,主要记录新增,修改,删除等操作。我想了下就决定用AOP来实现这个功能。

由于使用的是SpringBoot,所以首先应该在依赖中引入AOP包。

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

一般引入了AOP之后,一般不用做其他特殊配置,也不用加上@EnableAspectJAutoProxy注解。但是它仍有两个属性需要我们注意

# 相当于@EnableAspectJAutoProxy
spring.aop.auto=true

# 默认使用jdk来实现AOP,如果想要使用CGLIB的话,这里要改成true
spring.aop.proxy-target-class=false

实现日志记录功能

我想要记录某个API对模块进行了什么操作,操作的key,对于修改,删除来说我们记录id,对于新增来说,最开始没有id,我们记录name即可(也可以是其他属性),所以OpLog注解是这样的

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface OpLog {

  // 模块
  String module();
  // 类型
  String opType();
  // 级别,默认一般
  String level() default OpLevel.COMMON;
  // 需要记录的key
  String key();
}

为了让这个功能更加易用,所以我要求其他开发者必须在Controller上加上注解@OpLog,为什么是Controller呢?因为API是在Controller上,我并不关心具体业务,我只要记录对应的操作即可。在Controller层你只需要这要做就行了

@RestController
@RequestMapping("/user")
public class CpGroupController {

  // 注意这里的userParam和@RequestBody的userParam名字必需要要一致
  @OpLog(module = "用户管理", opType = "新增", key = "userParam.name")
  @PostMapping("/add")
  public BaseResponse<Boolean> add(@RequestBody UserParam userParam) {
      
      return ResponseUtil.success(userService.add(userParam));
  }
}

接下来就是切面代码的编写了,其中我们要记录访问的url以及必要的操作信息。

@Component
@Aspect
@Slf4j
public class OpLogAspect {

  @Autowired
  private OperateLogService operateLogService;

  int paramNameIndex = 0;
  int propertyIndexFrom = 1;

  @Pointcut("execution(public * com.generalthink.springboot.web.controller..*.*(..))")
  public void webLogPointcut() {
  }

  @Around(value = "webLogPointcut() && @annotation(opLog)", argNames = "joinPoint,opLog")
  public Object around(ProceedingJoinPoint joinPoint, OpLog opLog) throws Throwable {

    Object objReturn = joinPoint.proceed();

    try {
        OperateLog operationLog = generateOperateLog(joinPoint, opLog);

        operateLogService.save(operationLog);
    } catch (Exception e) {
        log.error("operateLog record error", e);
    }

    return objReturn;
  }

  private OperateLog generateOperateLog(ProceedingJoinPoint joinPoint, OpLog opLog) {

    String requestUrl = extractRequestUrl();

    Object recordKey = getOpLogRecordKey(joinPoint, opLog);

    return OperateLog.builder()
            .module(opLog.module())
            .opType(opLog.opType())
            .level(opLog.level())
            .operateTimeUnix(CommonUtils.getNowTimeUnix())
            .recordKey(recordKey != null ? recordKey.toString() : null)
            .url(requestUrl)
            .operator(getCurrentUser())
            .build();
  }

  // 获取当前用户
  private String getCurrentUser() {
    Subject subject = SecurityUtils.getSubject();
    return (String) subject.getPrincipal();
  }

  // 获取想要记录的key
  private Object getOpLogRecordKey(ProceedingJoinPoint joinPoint, OpLog opLog) {
    String key = opLog.key();

    if(StringUtils.isEmpty(key)) {
        return null;
    }

    String[] keys = key.split("\\.");

    //入参  value
    Object[] args = joinPoint.getArgs();

    CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();

    // 获取Controller方法上的参数名称
    String[] paramNames = codeSignature.getParameterNames();

    Object paramArg = null;

    for (int i = 0; i < paramNames.length; i++) {
        String paramName = paramNames[i];
        if (paramName.equals(keys[paramNameIndex])) {
            paramArg = args[i];
            break;
        }
    }

    if(keys.length == 1) {
        return paramArg;
    }

    // 获取参数的值
    Object paramValue = null;
    if(null != paramArg) {
        try {
            paramValue = getParamValue(paramArg, keys, propertyIndexFrom);
        } catch (IllegalAccessException e) {
            log.error("parse field error", e);
        }
    }

    return paramValue;
  }

  public Object getParamValue(Object param, String[] keys, int idx)
          throws IllegalAccessException {
    Optional<Field> fieldOptional = getAllFields(new ArrayList<>(), param.getClass())
            .stream()
            .filter(field -> field.getName().equalsIgnoreCase(keys[idx]))
            .findFirst();

    if(!fieldOptional.isPresent()) {
        return null;
    }
    
    // 设置属性可访问,因为bean当中属性一般都是private
    Field field = fieldOptional.get();
    field.setAccessible(true);


    if(idx + 1 == keys.length) {
        return field.get(param);
    }

    return getParamValue(field.get(param), keys, idx + 1);
  }

  // 递归获取所有Field
  public List<Field> getAllFields(List<Field> fieldList, Class<?> type) {

    // 获取当前类的所有Field
    fieldList.addAll(Arrays.asList(type.getDeclaredFields()));
    
    // 获取父类的所有Field
    Class<?> superClass = type.getSuperclass();
    if(superClass != null && superClass != Object.class) {
        getAllFields(fieldList,superClass);
    }

    return fieldList;
  }
  
  // 获取访问URL
  private String extractRequestUrl() {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
            .getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();

    return request.getRequestURL().toString();
  }
}

主要就是记录了操作了什么模块,操作内容等等,对于修改和删除操作我们可以记录id,但是对于save操作,我们没有办法记录id,只能记录其他属性,比如说name,就可以记录保存的数据了

{
  name: "zhangsan",
  age: 20
}

对于上面的数据就会把"zhangsan"这个值保存下来,后面就可以通过查询日志表知道是谁操作的,修改和删除也是同样的.

相关文章

  • 老大喊我用AOP记录下日志

    老大喊我记录下API的操作日志,免得前端甩锅,主要记录新增,修改,删除等操作。我想了下就决定用AOP来实现这个功能...

  • AOP之日志记录

    IOC常有,而AOP不常有也。我很少使用AOP,学了ssm这么长时间项目中还是第一次用,据说AOP可以用来日志记录...

  • Spring AOP实现后台管理系统日志管理

    元注解方式结合AOP,灵活记录操作日志能够记录详细错误日志为运维提供支持日志记录尽可能减少性能影响 1.定义日志记...

  • 通用的记录日志注解

    通过AOP定义通用的记录日志注解 需求: 实现AOP日记记录 定义日志注解 定义日志拦截器 可扩展的日志生成规则模...

  • error at ::0 formal unbound in p

    昨天想用aop实现用户日志功能,把用户每一步操作记录下来,我的想法是把controller作为切点,然后进行切割。...

  • SpringBoot-AOP

    SpringBoot-AOP 使用AOP统一处理请求日志 1.AOP的概念 AOP:AOP是一种编程范式,与语言无...

  • Spring Boot中使用log4j实现http请求日志入mo

    之前在《使用AOP统一处理Web请求日志》一文中介绍了如何使用AOP统一记录web请求日志。基本思路是通过aop去...

  • AOP、IOC

    AOP(Aspect Oriented Programming)面向切面编程 AOP主要用于解决横切关注点(日志、...

  • 基于注解的SpringAOP切面编程实例

    1,AOP简介 面相切面编程(AOP),可以实现例如日志打印、身份认证,权限管理,安全监测。AOP的实现原理是基于...

  • SpringBoot 用AOP做日志记录

    1:自定义注解(贴到所需要记录日志的方法上) 我的注解级别设置的是METHOD 2:创建拦截类 也就是说你要做日志...

网友评论

      本文标题:老大喊我用AOP记录下日志

      本文链接:https://www.haomeiwen.com/subject/kbgzsctx.html