Como o projeto SpringBoot projeta a função de log de operações de negócios?

prefácio

Eu queria escrever este artigo há muito tempo e nunca estive livre, mas até agora ainda tenho uma impressão da cena naquela época. A razão pela qual tenho a impressão é que os requisitos são muito simples, a gravação e funções de consulta do log de operações de negócios, mas a implementação específica é verdadeira. É péssimo, e o método ruim específico será elaborado nos exemplos negativos. Os líderes e clientes o reconhecem muito. Estou profundamente impressionado com uma série de misteriosos operações.

Descrição e análise de requisitos

O requisito do lado do cliente é muito simples: é necessário registrar o log de operação de várias funções-chave do negócio, ou seja, quem operou qual função em que momento, qual é a mensagem de dados antes da operação e qual é a mensagem de dados após a operação. Você pode voltar com um clique.

Logs são uma função essencial em sistemas de negócios. Os mais comuns incluem logs de sistema, logs de operação, etc.:

registro do sistema

O log do sistema aqui refere-se às principais etapas no processo de execução do programa. De acordo com a cena real, as informações de registro de execução do programa de diferentes níveis, como depuração, informação, aviso e erro, são geradas. Geralmente, são para programadores ou operação e manutenção. Quando ocorre um problema anormal, você pode solucionar rapidamente a falha por meio das informações do parâmetro chave e prompts anormais registrados no log do sistema.

registro de operação

O log da operação é um registro do comportamento real da operação comercial do usuário. Essas informações geralmente são armazenadas no banco de dados, como quando e qual usuário clicou em um determinado menu, qual configuração foi modificada etc. para usuários comuns ou administradores de sistema, consulte.

Por meio da análise de requisitos, o cliente deseja uma função de gerenciamento de log de operação comercial:

1. Registre o comportamento da operação comercial do usuário. Os campos registrados incluem: operador, horário da operação, função da operação, tipo de log, descrição do conteúdo da operação, mensagem do conteúdo da operação e mensagem do conteúdo pré-operação

2. Fornecer uma página visual, que pode consultar o comportamento da operação comercial do usuário e rastrear operações importantes;

3. Forneça certas funções de gerenciamento e reverta a operação incorreta do usuário quando necessário;

implementação negativa

Depois de esclarecer os requisitos, é uma questão de como implementá-los. Aqui está um caso negativo de implementação. É também por causa desse caso negativo que estou profundamente impressionado com esse simples requisito.

Aqui tomo como exemplo uma função de gestão de pessoal para restaurar, a implementação específica naquele momento:

1. Adicionar um registro de log de operação de negócios a cada interface;

2. Cada interface deve capturar a exceção e registrar o log de operação de negócios anormal;

Aqui está o pseudocódigo:

@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;
    }
}

O maior problema com esse tipo de função de gerenciamento de log de operação de negócios realizada por meio de codificação é que a coleção de logs de operação de negócios é seriamente acoplada à lógica de negócios e é duplicada com o código. Depois que a interface recém-desenvolvida conclui a lógica de negócios, ela precisa tecer uma seção de lógica de armazenamento de log de operação de negócios. , a interface que foi desenvolvida e lançada precisa ser remodificada e testada, e a lógica de armazenamento de logs de operação de negócios que precisam ser tecidas em cada interface é a mesma.

ideias de design

Se você tem alguma impressão do AOP, a melhor maneira é usar o aop para obter:

1. Definir anotações de log de operação de negócios, que podem definir alguns atributos, como nome da função da operação, descrição da função, etc.;

2. Marque a anotação do log de operação de negócios no método que precisa ser registrado para operações de negócios (em negócios reais, alguns comportamentos de consulta de negócios simples geralmente não são necessários para registro);

3. Defina o ponto de entrada e escreva o aspecto: o ponto de entrada é o método de destino marcado com anotações do log de operações de negócios; a lógica principal do aspecto é salvar as informações do log de operações de negócios;

Primavera AOP

AOP (Aspect Orient Programming), traduzido literalmente como programação orientada a aspectos, AOP é uma ideia de programação e um suplemento à programação orientada a objetos (OOP). Programação orientada a aspectos, uma tecnologia que adiciona funções adicionais de forma dinâmica e uniforme ao programa sem modificar o código-fonte. AOP pode interceptar o método especificado e aprimorar o método sem se intrometer no código comercial, tornando o processamento comercial e não comercial separação lógica;

E o SpringAOP é uma implementação específica do AOP. O cenário mais clássico para a aplicação do SpringAOP no Spring é a transação Spring. Através da configuração das anotações da transação, o Spring abrirá e enviará automaticamente o negócio no método de negócio, e quando o processamento do negócio falhar , implemente a estratégia de reversão correspondente; em comparação com filtros e interceptores, é mais importante que seu escopo de aplicação não seja mais limitado a projetos SpringMVC, ele pode definir um pointcut em qualquer camada, tecer operações correspondentes e também O valor de retorno pode ser alterado;

Filtro e manipuladorInterceptor

A razão pela qual Filter e HandlerInterceptor não é escolhido, mas AOP para realizar a função de log de operação de negócios, é devido a algumas limitações de Filter e HandlerInterceptor:

filtro

Filter (Filter) é uma interface associada ao servlet, que é principalmente aplicável a projetos web java. Depende do container Servlet. Ele usa o mecanismo de retorno de chamada do java para implementar filtragem e interceptação de solicitações http do navegador. Ele pode interceptar o URL de acesso correspondente. A solicitação e a resposta do método (solicitação ServletRequest, resposta ServletResponse), mas o valor nas informações de solicitação e resposta não pode ser modificado; geralmente é usado para definir codificação de caracteres, operações de autenticação etc.;

Se você quiser fazer uma classe e método mais refinados ou usá-lo em um ambiente não servlet, não poderá fazê-lo, portanto, qualquer ambiente que dependa do contêiner Servlet pode usar filtros, como Struts2 e SpringMVC;

interceptor

O escopo e a função do interceptor (HandlerInterceptor) são semelhantes aos filtros, mas também existem diferenças. Em primeiro lugar, o interceptor (HandlerInterceptor) é adequado para SpringMVC, porque a interface HandlerInterceptor é uma interface relacionada ao SpringMVC e para implementar projetos da Web java, SpringMVC é a opção preferida atual, mas não a única opção, existem struts2, etc .; portanto, se forem itens não SpingMVC que o HandlerInterceptor não pode usar;

Em segundo lugar, como o filtro, o interceptador pode interceptar a solicitação e a resposta do método correspondente à URL de acesso (solicitação ServletRequest, resposta ServletResponse), mas não pode modificar o valor nas informações de solicitação e resposta; geralmente é usado para definir a codificação de caracteres , operações de autorização de autenticação, etc.; se você quiser criar uma classe e método mais refinados ou usá-los em um ambiente não servlet, não poderá fazê-lo;

Resumindo, as funções dos filtros e interceptores são muito semelhantes, mas o escopo de aplicação dos interceptores é menor que o dos filtros;

Spring AOP, filtro, comparação de interceptores

Ao combinar o mesmo destino, a prioridade de execução de filtros, interceptores e Spring AOP é: filtro > interceptador > Spring AOP, e a ordem de execução é primeiro a entrar, último a sair. As diferenças específicas são refletidas nos seguintes aspectos:

1. O escopo é diferente

  • O filtro depende do contêiner do servlet e só pode ser usado no contêiner do servlet e no ambiente da Web para filtrar e interceptar a entrada de solicitação-resposta;

  • Os interceptadores dependem do springMVC e podem ser usados ​​em projetos SpringMVC.O núcleo do SpringMVC é o DispatcherServlet, que é uma subclasse do Servlet, então o escopo é semelhante aos filtros;

  • O Spring AOP não tem restrições de escopo, desde que o ponto de corte seja definido, ele pode ser interceptado na camada de entrada de solicitação-resposta (camada do controlador) ou na camada de processamento de negócios solicitado (camada de serviço);

2. Granularidade diferente

  • A granularidade de controle do filtro é relativamente grosseira e a solicitação e a resposta só podem ser filtradas e interceptadas em doFilter();

  • O interceptador fornece um controle mais refinado, incluindo preHandle(), postHandle() e afterCompletion(), que podem entrelaçar algumas operações de negócios antes que o controlador processe a solicitação, depois que a solicitação for processada e depois que a solicitação for respondida;

  • O Spring AOP fornece pré-notificação, pós-notificação, notificação pós-retorno, notificação de exceção, notificação envolvente, controle mais granular do que interceptadores e pode até modificar o valor de retorno;

Plano de implementação

Configuração do ambiente

  • versão jdk: 1.8 Ferramentas de desenvolvimento: Intellij iDEA 2020.1

  • springboot:2.3.9.RELEASE

  • mybatis-spring-boot-starter: 2.1.4

Configuração de dependência

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

projeto de estrutura de mesa

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';

Código

1. Defina a anotação do log de negócios @BusLog, que pode ser usada em controladores ou outras classes de negócios para descrever as funções da classe atual, também pode ser usada em métodos para descrever a função do método atual;

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

2. Marque a anotação do log de operação de negócios BusLog na classe e método PersonController;

@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. Escreva a classe de aspecto BusLogAop e use @BusLog para definir o ponto de entrada. Depois de executar o método de destino na notificação surround,

Obtenha o nome da função e a descrição da função na anotação do log de negócios na classe de destino e no método de destino, grave a mensagem de parâmetro do método no arquivo e, finalmente, salve as informações do log de operação de negócios;

@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;
    }
}

teste

método de depuração

A interface de depuração do back-end geralmente usa o postman. Aqui está uma ferramenta para Amway, ou seja, o serviço web Test RESTful do Intellij IDEA. A função e o uso são semelhantes ao postman. A única vantagem é que não há necessidade de instalar um postman adicional no entrada de função: Ferramentas da barra de ferramentas-->cliente http-->Testar RESTful web

Resumir

Os registros de log de operação de negócios incluem o nome da função, descrição da função, operador, tempo de operação e mensagem de parâmetro de operação da operação do usuário. A razão pela qual a mensagem de parâmetro é selecionada para ser armazenada no arquivo é que, em circunstâncias normais, não é necessário para saber o específico A mensagem de parâmetro é usada apenas durante a operação de reversão, e a operação reversa pode ser executada de acordo com a última mensagem de parâmetro.

 
 

Acho que você gosta

Origin blog.csdn.net/2301_78586758/article/details/131405249
Recomendado
Clasificación