- 数据审计通用功能实现
- 一、实现思路
- 二、具体实现
- 1.pom.xml
- 2.spring.factories
- 3.DataAuditAutoConfiguration
- 4.DataAuditor
- 5.DataAuditTransactionalEventListener
- 6.DataAuditApplicationEvent
- 7.DataAuditHandler
- 三、使用事例
- 1.SysUser
- 1.SysUserService
- 2.SysUserServiceImpl
- 四、扩展
- 五、参考
- 数据比对
使用开源类库
javers-core
实现
- 审计日志触发时机
二、具体实现 1.pom.xml数据库事务提交成功后,结合
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
注解实现
<dependencies>
<dependency>
<groupId>org.javersgroupId>
<artifactId>javers-coreartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
<optional>trueoptional>
dependency>
dependencies>
2.spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.audit.autoconfigure.DataAuditAutoConfiguration
3.DataAuditAutoConfiguration
package com.example.audit.autoconfigure;
import com.example.audit.DataAuditor;
import com.example.audit.listener.DataAuditTransactionalEventListener;
import org.javers.core.Javers;
import org.javers.core.JaversBuilder;
import org.javers.core.diff.ListCompareAlgorithm;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration(proxyBeanMethods = false)
@EnableAsync
public class DataAuditAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Javers javers() {
return JaversBuilder.javers().withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE).build();
}
@Bean
public DataAuditor dataAuditor(Javers javers) {
return new DataAuditor(javers);
}
@Bean
@ConditionalOnMissingBean
public DataAuditTransactionalEventListener dataAuditTransactionalEventListener(DataAuditor dataAuditor) {
return new DataAuditTransactionalEventListener(dataAuditor);
}
}
4.DataAuditor
比较器
package com.example.audit;
import org.javers.core.Javers;
import org.javers.core.diff.Change;
import java.util.List;
public class DataAuditor {
private final Javers javers;
public DataAuditor(Javers javers) {
this.javers = javers;
}
public List<Change> compare(Object obj1, Object obj2) {
return javers.compare(obj1, obj2).getChanges();
}
}
5.DataAuditTransactionalEventListener
事务提交监听
package com.example.audit.listener;
import com.example.audit.DataAuditApplicationEvent;
import com.example.audit.DataAuditor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
/**
* 数据审计触发机制
*/
public class DataAuditTransactionalEventListener {
private final DataAuditor dataAuditor;
@Autowired
public DataAuditTransactionalEventListener(DataAuditor dataAuditor) {
this.dataAuditor = dataAuditor;
}
/**
* 数据库事务提交后触发(事件的订阅逻辑交由发布方决定,可以选择存到DB、MQ或者日志文件),
* 调用数据审计回调处理函数式接口,接口参数为二元函数式接口的具体实现(Javers比较器)
* 此处较绕,注意两点就很好理解
* 1.面向接口编程,调用接口实际为调用具体实现类
* 2.二元函数式接口作为参数,实际为二元函数式接口的具体逻辑实现类(可以理解为策略类)作为参数
* >
*
* @param event 通知事件
*/
@Async
@Transactional(rollbackFor = Exception.class)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void handle(DataAuditApplicationEvent event) {
event.getDataAuditHandler().handle(dataAuditor::compare);
}
}
6.DataAuditApplicationEvent
监听事件
package com.example.audit;
import com.example.audit.handler.DataAuditHandler;
import org.springframework.context.ApplicationEvent;
public class DataAuditApplicationEvent extends ApplicationEvent {
/**
* 数据审计回调处理函数(callback函数)
*/
private final DataAuditHandler dataAuditHandler;
public DataAuditApplicationEvent(DataAuditHandler dataAuditHandler) {
super(dataAuditHandler);
this.dataAuditHandler = dataAuditHandler;
}
public DataAuditHandler getDataAuditHandler() {
return dataAuditHandler;
}
}
7.DataAuditHandler
callback函数
package com.example.audit.handler;
import org.javers.core.diff.Change;
import java.util.List;
import java.util.function.BiFunction;
@FunctionalInterface
public interface DataAuditHandler {
/**
* 处理逻辑
* @param biFunction 二元表达式
*/
void handle(BiFunction<Object, Object, List<Change>> biFunction);
}
三、使用事例
1.SysUser
实体类(省略一堆无关代码)
org.javers.core.metamodel.annotation.Id
package com.example.audit.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.javers.core.metamodel.annotation.Id;
import java.io.Serializable;
/**
* 用户信息表
*/
@TableName(value = "sys_user")
public class SysUser implements Serializable {
/**
* 用户ID
*/
@Id
@TableId(value = "user_id", type = IdType.AUTO)
private Long userId;
/**
* 用户邮箱
*/
@TableField(value = "email")
private String email;
/**
* 获取用户ID
*
* @return user_id - 用户ID
*/
public Long getUserId() {
return userId;
}
/**
* 设置用户ID
*
* @param userId 用户ID
*/
public void setUserId(Long userId) {
this.userId = userId;
}
/**
* 获取用户邮箱
*
* @return email - 用户邮箱
*/
public String getEmail() {
return email;
}
/**
* 设置用户邮箱
*
* @param email 用户邮箱
*/
public void setEmail(String email) {
this.email = email;
}
}
1.SysUserService
package com.example.audit.service;
import com.example.audit.model.SysUser;
import com.baomidou.mybatisplus.extension.service.IService;
public interface SysUserService extends IService<SysUser> {
/**
* 修改用户并记录日志
*/
void modifySysUser();
}
2.SysUserServiceImpl
package com.example.audit.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.example.audit.DataAuditApplicationEvent;
import com.example.audit.mapper.SysUserMapper;
import com.example.audit.model.ChangeHis;
import com.example.audit.model.SysUser;
import com.example.audit.service.ChangeHisService;
import com.example.audit.service.SysUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.javers.core.diff.changetype.ValueChange;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Optional;
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService, ApplicationEventPublisherAware {
private final SysUserMapper sysUserMapper;
private ApplicationEventPublisher applicationEventPublisher;
@Resource
ChangeHisService changeHisService;
@Autowired
public SysUserServiceImpl(SysUserMapper sysUserMapper) {
this.sysUserMapper = sysUserMapper;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void modifySysUser() {
SysUser sysUser = sysUserMapper.selectById(105);
// hutool工具类(SysUser必须实现Serializable接口)
SysUser clone = ObjectUtil.cloneByStream(sysUser);
sysUser.setEmail("ory001@qq.com");
sysUserMapper.updateById(sysUser);
applicationEventPublisher.publishEvent(new DataAuditApplicationEvent(t -> {
t.apply(clone, sysUser).forEach(change -> {
if (change instanceof ValueChange) {
// 此处为通用代码,可使用装配类assembly封装
ValueChange valueChange = (ValueChange) change;
ChangeHis changeHis = new ChangeHis();
// 要想正确的获取记录ID,需要在实体类中使用org.javers.core.metamodel.annotation.Id注解标记主键
Optional.ofNullable(valueChange.getAffectedLocalId()).ifPresent(e -> changeHis.setRecordId(e.toString()));
changeHis.setFieldName(valueChange.getPropertyName());
Optional.ofNullable(valueChange.getLeft()).ifPresent(e -> changeHis.setLeft(e.toString()));
Optional.ofNullable(valueChange.getRight()).ifPresent(e -> changeHis.setRight(e.toString()));
// 保存
changeHisService.save(changeHis);
// 此处异常不会回滚publish方法对应的事务,只会回滚DataAuditTransactionalEventListener.handle方法上对应的事务
// throw new RuntimeException();
}
});
}));
// 此处异常只会回滚publish方法对应的事务,不会回滚DataAuditTransactionalEventListener.handle方法上对应的事务
// throw new RuntimeException();
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
四、扩展
对象比对属性控制
class A {
@Id
private Long id;
private String foo;
@DiffIgnore
private String bar;
}
class B extends A {
@DiffIgnore
private String qux;
}
五、参考
mybatis-mate-audit
使用@TransactionalEventListener监听事务实战
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)