spring data JPA 使用EntityentiListeners实现数据审计功能设计
MasonZhang 人气:1当系统中有审计需求时,特别是需要对某些数据进行动态监控时,我们可以使用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; }
然后使用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); }
代码实现:
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); } }
2.为实体添加监控注解:@EntityListeners({AuditingEntityListener.class,QuotationStausListener.class})
重写equals方法,使用 eclipse自带generator生成即可
3.创建数据表
----------------------分割线------------------------------
看一下效果:
加载全部内容