The Spring Boot project designs the business operation log function, which is very well written!

foreword

I wanted to write this article a long time ago, and I have never been free, but until now I still have an impression of the scene at that time. The reason why I have an impression is that the requirements are very simple, the recording and query functions of the business operation log, but the specific implementation is true. It sucks, and the specific bad method will be elaborated in the negative examples. The leaders and customers recognize it very much. I am deeply impressed by a series of mysterious operations.

Requirements description and analysis

The requirement on the client side is very simple: it is necessary to record the operation log of several key business functions, that is, who operated which function at what time, what is the data message before the operation, and what is the data message after the operation. You can go back with one click.

Logs are an essential function in business systems. Common ones include system logs, operation logs, etc.:

system log

The system log here refers to the key steps in the program execution process. According to the actual scene, the program execution record information of different levels such as debug, info, warn, and error is output. These are generally for programmers or operation and maintenance. When an abnormal problem occurs, you can quickly troubleshoot the fault through the key parameter information and abnormal prompts recorded in the system log.

operation log

The operation log is a record of the user's actual business operation behavior. This information is generally stored in the database, such as when and which user clicked a certain menu, which configuration was modified, etc. Such business operation behaviors, these log information is for ordinary users or System administrators see.

Through the analysis of requirements, the customer wants a function of business operation log management:

1. Record the user's business operation behavior. The recorded fields include: operator, operation time, operation function, log type, operation content description, operation content message, and pre-operation content message

2. Provide a visual page, which can query the user's business operation behavior and backtrack important operations;

3. Provide certain management functions, and roll back the user's misoperation when necessary;

Negative implementation

After clarifying the requirements, it is a question of how to implement them. Here is a negative implementation case. It is also because of this negative case that I am deeply impressed by this simple requirement.

Here I take a personnel management function as an example to restore, the specific implementation at that time:

1. Add a record of business operation log to each interface;

2. Every interface must capture the exception and record the abnormal business operation log;

Here is the pseudocode:

@RestController
@Slf4j
@BusLog(name = "人员管理")
@RequestMapping("/person")
public class PersonController2 {
    @Autowired
    private IPersonService personService;
    @Autowired
    private IBusLogService busLogService;
    //添加人员信息
    @PostMapping
    public Person add(@RequestBody Person person) {
       try{
           //添加信息信息
        Person result = this.personService.registe(person);
        //保存业务日志
        this.saveLog(person);
        log.info("//增加person执行完成");        
       }catch(Exception e){
           //保存异常操作日志
           this.saveExceptionLog(e);       
       }
        return result;
    }
}

The biggest problem with this kind of business operation log management function realized by hard coding is that the collection of business operation logs is seriously coupled with business logic, and it is duplicated with the code. After the newly developed interface completes the business logic, it needs to weave a section of business operation log storage logic. , the interface that has been developed and launched needs to be re-modified and tested, and the logic of storing business operation logs that need to be woven into each interface is the same.

Recommend an open source and free Spring Boot practical project:

https://github.com/javastacks/spring-boot-best-practice

Design ideas

If you have some impression of AOP, the best way is to use aop to achieve:

1. Define business operation log annotations, which can define some attributes, such as operation function name, function description, etc.;

2. Mark the business operation log annotation on the method that needs to be recorded for business operations (in actual business, some simple business query behaviors are usually not necessary to record);

3. Define the entry point and write the aspect: the entry point is the target method marked with business operation log annotations; the main logic of the aspect is to save the business operation log information;

Spring AOP

AOP (Aspect Orient Programming), literally translated as aspect-oriented programming, AOP is a programming idea and a supplement to object-oriented programming (OOP). Aspect-oriented programming, a technology that dynamically and uniformly adds additional functions to the program without modifying the source code. AOP can intercept the specified method and enhance the method without intruding into the business code, making business and non-business processing logical separation;

And SpringAOP is a specific implementation of AOP. The most classic scenario for the application of SpringAOP in Spring is the Spring transaction. Through the configuration of transaction annotations, Spring will automatically open and submit the business in the business method, and when the business processing fails , implement the corresponding rollback strategy; compared with filters and interceptors, it is more important that its scope of application is no longer limited to SpringMVC projects, it can define a pointcut at any layer, weave corresponding operations, and also The return value can be changed;

Filter and HandlerInterceptor

The reason why Filter and HandlerInterceptor is not chosen, but AOP to realize the business operation log function, is because of some limitations of Filter and HandlerInterceptor:

filter

Filter (Filter) is an interface associated with servlet, which is mainly applicable to java web projects. It depends on the Servlet container. It uses the callback mechanism of java to implement filtering and interception of http requests from the browser. It can intercept the corresponding access URL. The request and response of the method (ServletRequest request, ServletResponse response), but the value in the request and response information cannot be modified; it is generally used to set character encoding, authentication operations, etc.;

If you want to make a finer class and method or use it in a non-servlet environment, you can't do it; so any environment that depends on the Servlet container can use filters, such as Struts2 and SpringMVC;

interceptor

The scope and function of the interceptor (HandlerInterceptor) are similar to filters, but there are also differences. First of all, the interceptor (HandlerInterceptor) is suitable for SpringMVC, because the HandlerInterceptor interface is an interface related to SpringMVC, and to implement java Web projects, SpringMVC is the current preferred option, but not the only option, there are struts2, etc.; therefore, if it is non-SpingMVC Items that HandlerInterceptor cannot use;

Secondly, like the filter, the interceptor can intercept the request and response of the method corresponding to the access URL (ServletRequest request, ServletResponse response), but cannot modify the value in the request and response information; it is generally used to set character encoding, authentication Authorization operations, etc.; if you want to make a finer class and method or use it in a non-servlet environment, you can't do it;

In short, the functions of filters and interceptors are very similar, but the scope of application of interceptors is smaller than that of filters;

Spring AOP, filter, interceptor comparison

When matching the same target, the execution priority of filters, interceptors, and Spring AOP is: filter > interceptor > Spring AOP, and the execution order is first-in-last-out. The specific differences are reflected in the following aspects:

1. The scope is different

  • The filter depends on the servlet container and can only be used in the servlet container and web environment to filter and intercept the request-response entry;
  • Interceptors depend on springMVC and can be used in SpringMVC projects. The core of SpringMVC is DispatcherServlet, which is a subclass of Servlet, so the scope is similar to filters;
  • Spring AOP has no restrictions on the scope. As long as the cut point is defined, it can be intercepted at the request-response entry layer (controller layer), or at the requested business processing layer (service layer);

2. Different granularity

  • The control granularity of the filter is relatively coarse, and the request and response can only be filtered and intercepted in doFilter();
  • The interceptor provides finer-grained control, including preHandle(), postHandle(), and afterCompletion(), which can weave some business operations before the controller processes the request, after the request is processed, and after the request is responded to;
  • Spring AOP provides pre-notification, post-notification, post-return notification, exception notification, surround notification, more granular control than interceptors, and can even modify the return value;

Implementation plan

Environment configuration

  • jdk version: 1.8 Development tools: Intellij iDEA 2020.1
  • springboot:2.3.9.RELEASE
  • mybatis-spring-boot-starter:2.1.4

Dependency configuration

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

table structure design

create table if not exists bus_log
(
   id bigint auto_increment comment '自增id'
      primary key,
   bus_name varchar(100) null comment '业务名称',
   bus_descrip varchar(255) null comment '业务操作描述',
   oper_person varchar(100) null comment '操作人',
   oper_time datetime null comment '操作时间',
   ip_from varchar(50) null comment '操作来源ip',
   param_file varchar(255) null comment '操作参数报文文件'
)
comment '业务操作日志' default charset ='utf8';

Code

1. Define the business log annotation @BusLog, which can be used on controllers or other business classes to describe the functions of the current class; it can also be used on methods to describe the function of the current method;

/**
 * 业务日志注解
 * 可以作用在控制器或其他业务类上,用于描述当前类的功能;
 * 也可以用于方法上,用于描述当前方法的作用;
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BusLog {


    /**
     * 功能名称
     * @return
     */
    String name() default "";

    /**
     * 功能描述
     * @return
     */
    String descrip() default "";

}

2. Mark the business operation log annotation BusLog on the PersonController class and method;

@RestController
@Slf4j
@BusLog(name = "人员管理")
@RequestMapping("/person")
public class PersonController {
    @Autowired
    private IPersonService personService;
    private Integer maxCount=100;

    @PostMapping
    @NeedEncrypt
    @BusLog(descrip = "添加单条人员信息")
    public Person add(@RequestBody Person person) {
        Person result = this.personService.registe(person);
        log.info("//增加person执行完成");
        return result;
    }
    @PostMapping("/batch")
    @BusLog(descrip = "批量添加人员信息")
    public String addBatch(@RequestBody List<Person> personList){
        this.personService.addBatch(personList);
        return String.valueOf(System.currentTimeMillis());
    }

    @GetMapping
    @NeedDecrypt
    @BusLog(descrip = "人员信息列表查询")
    public PageInfo<Person> list(Integer page, Integer limit, String searchValue) {
       PageInfo<Person> pageInfo = this.personService.getPersonList(page,limit,searchValue);
        log.info("//查询person列表执行完成");
        return pageInfo;
    }
    @GetMapping("/{loginNo}")
    @NeedDecrypt
    @BusLog(descrip = "人员信息详情查询")
    public Person info(@PathVariable String loginNo,String phoneVal) {
        Person person= this.personService.get(loginNo);
        log.info("//查询person详情执行完成");
        return person;
    }
    @PutMapping
    @NeedEncrypt
    @BusLog(descrip = "修改人员信息")
    public String edit(@RequestBody Person person) {
         this.personService.update(person);
        log.info("//查询person详情执行完成");
        return String.valueOf(System.currentTimeMillis());
    }
    @DeleteMapping
    @BusLog(descrip = "删除人员信息")
    public String edit(@PathVariable(name = "id") Integer id) {
         this.personService.delete(id);
        log.info("//查询person详情执行完成");
        return String.valueOf(System.currentTimeMillis());
    }
}

3. Write the aspect class BusLogAop, and use @BusLog to define the entry point. After executing the target method in the surrounding notification, obtain the function name and function description on the business log annotation on the target class and target method, and send the parameter message of the method Write to the file, and finally save the business operation log information;

@Component
@Aspect
@Slf4j
public class BusLogAop implements Ordered {
    @Autowired
    private BusLogDao busLogDao;

    /**
     * 定义BusLogAop的切入点为标记@BusLog注解的方法
     */
    @Pointcut(value = "@annotation(com.fanfu.anno.BusLog)")
    public void pointcut() {
    }

    /**
     * 业务操作环绕通知
     *
     * @param proceedingJoinPoint
     * @retur
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {
        log.info("----BusAop 环绕通知 start");
        //执行目标方法
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        //目标方法执行完成后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述
        Object target = proceedingJoinPoint.getTarget();
        Object[] args = proceedingJoinPoint.getArgs();
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        BusLog anno1 = target.getClass().getAnnotation(BusLog.class);
        BusLog anno2 = signature.getMethod().getAnnotation(BusLog.class);
        BusLogBean busLogBean = new BusLogBean();
        String logName = anno1.name();
        String logDescrip = anno2.descrip();
        busLogBean.setBusName(logName);
        busLogBean.setBusDescrip(logDescrip);
        busLogBean.setOperPerson("fanfu");
        busLogBean.setOperTime(new Date());
        JsonMapper jsonMapper = new JsonMapper();
        String json = null;
        try {
            json = jsonMapper.writeValueAsString(args);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //把参数报文写入到文件中
        OutputStream outputStream = null;
        try {
            String paramFilePath = System.getProperty("user.dir") + File.separator + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN) + ".log";
            outputStream = new FileOutputStream(paramFilePath);
            outputStream.write(json.getBytes(StandardCharsets.UTF_8));
            busLogBean.setParamFile(paramFilePath);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
        //保存业务操作日志信息
        this.busLogDao.insert(busLogBean);
        log.info("----BusAop 环绕通知 end");
        return result;
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

test

debugging method

The backend debugging interface usually uses postman. Here is a tool for Amway, that is, Intellij IDEA’s Test RESTful web service. The function and use are similar to postman. The only advantage is that there is no need to install an additional postman on the computer. Function entry: Toolbar Tools-->http client-->Test RESTful web

img

In addition, there is another usage, which I prefer to use. It can initiate an http request with a few simple sentences, and it can also be executed in batches at one time;

Validation results

Summarize

The business operation log records include the function name, function description, operator, operation time and operation parameter message of the user operation. The reason why the parameter message is selected to be stored in the file is that under normal circumstances, it is not necessary to know the specific The parameter message is only used during the rollback operation, and the reverse operation can be performed according to the last parameter message.

Copyright statement: This article is an original article of CSDN blogger "Fanfufanfu", which follows the CC 4.0 BY-SA copyright agreement. For reprinting, please attach the original source link and this statement. Original link: https://blog.csdn.net/fox9916/article/details/130175379

Recent hot article recommendation:

1. 1,000+ Java interview questions and answers (2022 latest version)

2. Brilliant! Java coroutines are coming. . .

3. Spring Boot 2.x tutorial, too comprehensive!

4. Don't fill the screen with explosions and explosions, try the decorator mode, this is the elegant way! !

5. The latest release of "Java Development Manual (Songshan Edition)", download quickly!

Feel good, don't forget to like + forward!

Guess you like

Origin blog.csdn.net/youanyyou/article/details/131450468