The boss asked me to record the API operation log, so as to avoid front-end dumping, mainly record new, modify, delete and other operations. I thought about it and decided to use AOP to achieve this function.

Since you are using SpringBoot, you should first introduce AOP packages into your dependencies.

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

Typically, when AOP is introduced, you don’t need to do any special configuration or add the @enableAspectJAutoProxy annotation. But it still has two properties that need our attention

# is equivalent to @enableAspectJAutoProxy
spring.aop.auto=true

Use the JDK to implement AOP by default. If you want to use CGLIB, change this to true
spring.aop.proxy-target-class=false
Copy the code

Implement the logging function

I want to record what an API does to the module, the key of the operation, for modification, delete we record the ID, for addition, we don’t have the ID, we just record the name (or any other property), so the OpLog annotation looks like this, right

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

  / / module
  String module(a);
  / / type
  String opType(a);
  // Level, normal by default
  String level(a) default OpLevel.COMMON;
  // The key to record
  String key(a);
}
Copy the code

To make this feature easier to use, I require other developers to annotate Controller with @oplog. Why Controller? Because the API is on the Controller, I don’t care about the specific business, I just need to record the corresponding operation. That’s all you need to do in the Controller layer

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

  // Note that userParam and @requestBody userParam names must be the same
  @OpLog(module = "User Management", opType = "New", key = "userParam.name")
  @PostMapping("/add")
  public BaseResponse<Boolean> add(@RequestBody UserParam userParam) {
      
      returnResponseUtil.success(userService.add(userParam)); }}Copy the code

The next step is to write the section code, in which we need to record the URL to visit and the necessary operation information.

@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(a) {}@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();
  }

  // Get the current user
  private String getCurrentUser(a) {
    Subject subject = SecurityUtils.getSubject();
    return (String) subject.getPrincipal();
  }

  // Get the key you want to record
  private Object getOpLogRecordKey(ProceedingJoinPoint joinPoint, OpLog opLog) {
    String key = opLog.key();

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

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

    / / into the reference value
    Object[] args = joinPoint.getArgs();

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

    // Get the parameter name on the Controller method
    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;
    }

    // Get the value of the parameter
    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;
    }
    
    // Make properties accessible, since properties in beans are generally 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);
  }

  // Get all fields recursively
  public List<Field> getAllFields(List
       
         fieldList, Class
         type)
        {

    // Get all fields of the current class
    fieldList.addAll(Arrays.asList(type.getDeclaredFields()));
    
    // Get all fields of the parent classClass<? > superClass = type.getSuperclass();if(superClass ! =null&& superClass ! = Object.class) { getAllFields(fieldList,superClass); }return fieldList;
  }
  
  // Get the access URL
  private String extractRequestUrl(a) {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
            .getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();

    returnrequest.getRequestURL().toString(); }}Copy the code

It basically records what module was operated on, what was operated on, and so on. For the modify and delete operation, we can record the ID, but for the save operation, we can’t record the ID, we can only record other properties, like name, so we can record the saved data, right

{
  name: "zhangsan",
  age: 20
}

Copy the code

For the above data “zhangsan” will be saved, later you can query the log table to know who is the operation, modify and delete the same.