miketwais

work up

spring data JPA 使用EntityentiListeners实现数据审计功能设计

当系统中有审计需求时,特别是需要对某些数据进行动态监控时,我们可以使用EntityentiListeners来实现,当然这是基于使用JPA而不是mybatis的情况下。

当前我们的需求场景:

1.需要监控某一个实体的数据变化(add,update,delete)

2.需要记录:id,who,when, action, entity,condition,value分别表示id,操作人,操作时间,动作(add,update,delete),实体名称,状态(before:操作前,after:操作后),值

如何做?

1.如何识别add,update,delete操作?

  entityListenners有定义的@PreUpdate:更新前,@PostUpdate:更新后,@PrePersist:保存前,@PostPersist:保存后,@PreRemove:删除前。

  我们可以在保存前获取保存的数据,记录为add新增操作数据,我们在删除前获取数据,记录为删除的数据,我们在更新前,获取当前数据为更新后的数据,另外根据id从数据库获取之前的数据作为之前的数据,此时加以对比,若存在差异则为更新。

ps:如何区分update和add?updata时实体中是存在id的,add时不存在。

2.如何获取当前操作人:entityListenner和AOP有点不一样,需要在启动类中加上下列语句:

@Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean() {
        MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
        methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);
        methodInvokingFactoryBean.setTargetMethod("setStrategyName");
        methodInvokingFactoryBean.setArguments(new String[]{SecurityContextHolder.MODE_INHERITABLETHREADLOCAL});
        return methodInvokingFactoryBean;
    }
View Code

注意:这里存在事务问题,如果直接使用resposity查询数据不对,应该这样:

Quotation oldData = quotationService.findOldData(quotation.getId());
findOldData方法:
//@Transactional(propagation=Propagation.REQUIRES_NEW) --不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务

@Transactional(propagation = Propagation.REQUIRES_NEW)
    public Quotation findOldData(Long id) {
        Optional<Quotation> optional = quotationRepository.findById(id);
        if (optional.isPresent()) {
            return optional.get();
        }
        return null;

    }
View Code

 

然后使用spring security工具获取当前人,类似这样(具体根据本系统具体情况定制):

public Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String reString=(String)((JwtAuthenticationToken)authentication).getToken().getClaims().get("preferred_username");
        return Optional.of(reString);
      }
View Code

 

代码实现:

1.添加自己的listener类,实现监控,存日志逻辑

package com.b.pos.quotation.listeners;

import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.bmw.pos.quotation.domain.InterfaceLog;
import com.bmw.pos.quotation.domain.Quotation;
import com.bmw.pos.quotation.repository.InterfaceLogRepository;
import com.bmw.pos.quotation.security.SecurityUtils;
import com.bmw.pos.quotation.service.QuotationService;
import com.bmw.pos.quotation.util.ProfileUtil;
import com.google.common.base.Throwables;

/**
 * Application status Listener
 */
@Component
public class QuotationStausListener {
    private static final Logger log = LoggerFactory.getLogger(QuotationStausListener.class);

    private static InterfaceLogRepository interfaceLogRepository;

    private static QuotationService quotationService;


    @Autowired
    public synchronized void setInterfaceLogRepository(InterfaceLogRepository interfaceLogRepository) {
        QuotationStausListener.interfaceLogRepository = interfaceLogRepository;
    }
    
    @Autowired
    public synchronized void setQuotationService(QuotationService quotationService) {
        QuotationStausListener.quotationService = quotationService;
    }

    /**
     * after save success
     * 
     * @param object
     */
    @PostPersist
    public void postpersist(Object object) {
    }

    /**
     * after update success
     * 
     * @param object
     */
    @PostUpdate
    public void postUpdate(Object object) {
    }

    @PreRemove
    public void beforeRemove(Object object) {
        log.info("@PreRemove");
        Quotation quotation = (Quotation) object;
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                saveInterfaceLog(quotation.getId().toString(), quotation.toString(),"Quotation","Delete","Before");
            }
        });
    }

    @PreUpdate
    public void beforeUpdate(Object object) {
        log.info("@PreUpdate");
        try {
            Quotation quotation = (Quotation) object;
            log.info("@PreUpdate--------->Quotation: {}",quotation.toString());
            Quotation oldData = quotationService.findOldData(quotation.getId());
            log.info("oldData.get()------->oldData.get(): {}",oldData.toString());
            if(oldData!=null&&(!quotation.equals(oldData))) {
                cachedThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        saveInterfaceLog(quotation.getId().toString(), oldData.toString(),"Quotation","Update","Before");
                        saveInterfaceLog(quotation.getId().toString(), quotation.toString(),"Quotation","Update","After");
                    }
                });
            }
        } catch (Exception e) {
            log.info(Throwables.getStackTraceAsString(e));
        }
        
    }
    

    @PrePersist
    public void beforeSave(Object object) {
        log.info("@PrePersist");
        Quotation quotation = (Quotation) object;
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                saveInterfaceLog(quotation.getId().toString(), quotation.toString(),"Quotation","Add","");
            }
        });
    }

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    private String getCurrentUserName() {
        Optional<String> user=SecurityUtils.getCurrentUserLogin();
        return user.isPresent() ? user.get() : null;
    }
    /**
     * save log
     * 
     * @param appNo
     * @param requestData
     * @param response
     * @param exceptions
     */    
    private void saveInterfaceLog(String quotationNo, String curData,String entityName,String action,String condition) {
        // save log
        InterfaceLog interfaceLog = new InterfaceLog();
        interfaceLog.setIntType(entityName);
        interfaceLog.setReturnParam(curData);
        interfaceLog.setCalledTime(Instant.now());
        String channel = ProfileUtil.getChannelByProfile();
        interfaceLog.setEntityType(channel);
        interfaceLog.setAppGlobalId(quotationNo);
        interfaceLog.setEntityType(action);
        interfaceLog.setEntityCode(condition);
        interfaceLog.setInputParam(getCurrentUserName());
        log.info("InterfaceLog: {}",interfaceLog.toString());
        QuotationStausListener.interfaceLogRepository.saveAndFlush(interfaceLog);

    }

    

}
View Code

2.为实体添加监控注解:@EntityListeners({AuditingEntityListener.class,QuotationStausListener.class})

  重写equals方法,使用 eclipse自带generator生成即可

3.创建数据表

----------------------分割线------------------------------

看一下效果:

 

posted @ 2021-01-28 11:37  MasonZhang  阅读(1233)  评论(0编辑  收藏  举报