自定义脱敏字段、脱敏规则,不能像普通的脱敏一样在字段上加注解的实现方式,需要对方法返回每个属性进行判断,还要考虑对象嵌套问题
方案一:
/**
* 数据权限注解拦截
* @Author: zz
**/
@Slf4j
@Aspect
@Component
public class DataPermissionAspect {
/**
* PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
* 切面最主要的就是切点,所有的故事都围绕切点发生
* logPointCut()代表切点名称
*/
@Pointcut("@annotation(DataPermission)")
private void logPointCut() {
}
/**
* 目标方法调用之前执行
* 注意这里不能使用 ProceedingJoinPoint
*
* @param joinPoint
*/
@Before("logPointCut()")
public void doBefore(JoinPoint joinPoint) {
}
/**
* 目标方法调用之后执行
* 注意这里不能使用 ProceedingJoinPoint
*
* @param joinPoint
*/
@After("logPointCut()")
public void doAfter(JoinPoint joinPoint) {
}
/**
* 环绕通知
*
* @param proceedingJoinPoint
*/
@Around("logPointCut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// roleId 查询角色脱敏规则
List<UcRoleColumnMask> columnMaskList = null;
//继续执行方法
Object result = proceedingJoinPoint.proceed();
if (result != null && !CollectionUtils.isEmpty(columnMaskList)) {
// 脱敏字段对应脱敏规则
Map<String, UcRoleColumnMask> columnMaskMap = columnMaskList.stream().collect(Collectors.toMap(UcRoleColumnMask::getColumnAliss, UcRoleColumnMask -> UcRoleColumnMask));
// 数据字段脱敏
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = null;
// 分页结构数据处理
if (result instanceof IPage) {
IPage page = (IPage) result;
if (!CollectionUtils.isEmpty(page.getRecords())) {
List<Object> list = new ArrayList<>();
for (Object record : page.getRecords()) {
jsonNode = objectMapper.valueToTree(record);
for (Map.Entry<String, UcRoleColumnMask> entry : columnMaskMap.entrySet()) {
if (jsonNode.has(entry.getKey())) {
// 节点处理
this.nodeDis((ObjectNode) jsonNode, entry);
}
}
list.add(objectMapper.convertValue(jsonNode, record.getClass()));
}
page.setRecords(list);
return page;
}
} else {
// 对象处理,或者讲对象转为map也可
jsonNode = objectMapper.valueToTree(result);
// 最外层
for (Map.Entry<String, UcRoleColumnMask> entry : columnMaskMap.entrySet()) {
if (jsonNode.has(entry.getKey())) {
// 节点处理
this.nodeDis((ObjectNode) jsonNode, entry);
}
}
return objectMapper.convertValue(jsonNode, result.getClass());
}
}
return result;
}
/**
* 节点嵌套处理
* @Param: [nextNode, columnMaskMap]
* @Return: void
* @Author: zz
*/
private void nodeDis(ObjectNode jsonNode, Map.Entry<String, UcRoleColumnMask> entry) {
// nextNode 只能拿到数据
JsonNode nextNode = jsonNode.get(entry.getKey());
// 根据数据类型处理
if (nextNode instanceof ArrayNode) {
Iterator<JsonNode> it = nextNode.iterator();
while (it.hasNext()) {
JsonNode arrayNode = it.next();
// 递归
nodeDis((ObjectNode) arrayNode, entry);
}
} else if (nextNode instanceof ObjectNode) {
cloumnMask((ObjectNode) nextNode, entry);
} else {
cloumnMask(jsonNode, entry);
}
}
/**
* 敏感字段 规则处理
* @param jsonNode
* @param entry
*/
private void cloumnMask(ObjectNode jsonNode, Map.Entry<String, UcRoleColumnMask> entry) {
try {
// 脱敏规则
UcRoleColumnMask mask = entry.getValue();
String maskStr = null;
String value = jsonNode.get(entry.getKey()).textValue();
if (value != null) {
switch (mask.getType()) {
// todo 自己的脱敏规则
}
jsonNode.put(entry.getKey(), maskStr);
} else {
jsonNode.putNull(entry.getKey());
}
} catch (IllegalArgumentException e) {
log.error("无脱敏字段{}:{}", entry.getKey(), e.getMessage());
}
}
}
方案二:
无意中看到MyBatis-Flex框架,他的“字段权限”实现方式,通过重写mybatis 的 BeanWrapper.set()方法实现,
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.reflection.wrapper.BeanWrapper;
import org.apache.ibatis.reflection.wrapper.ObjectWrapper;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import java.util.Collection;
import java.util.Map;
public class EntityWrapperFactory implements ObjectWrapperFactory {
@Override
public boolean hasWrapperFor(Object object) {
Class<?> objectClass = object.getClass();
if (Map.class.isAssignableFrom(objectClass) ||
Collection.class.isAssignableFrom(objectClass)) {
return false;
}
// entityTableMap tableName: 列名、@SetListener[] (listener:BeanWrapper.set时调用类对应的listenr方法)
return TableInfoFactory.ofEntityClass(objectClass) != null;
}
@Override
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
return new FlexBeanWrapper(metaObject, object);
}
static class FlexBeanWrapper extends BeanWrapper {
private final Object entity;
private final TableInfo tableInfo;
public FlexBeanWrapper(MetaObject metaObject, Object object) {
super(metaObject, object);
this.entity = object;
this.tableInfo = TableInfoFactory.ofEntityClass(object.getClass());
}
@Override
public void set(PropertyTokenizer prop, Object value) {
// 根据自己的业务去处理value,flex是使用了设计模式:监听模式(具体实现自己请引包看源码)
Object v = tableInfo.invokeOnSetListener(entity, prop.getName(), value);
// mybatis 值设置
super.set(prop, v);
}
}
}
以上代码有个问题,就是你只实现了逻辑,他怎么才能生效呢?需要将 EntityWrapperFactory 注入到spring 容器
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
/**
* @Author: zz
* mybatis-plus:ObjectWrapperFactory 设置
**/
//@Component
public class Z {
// @Bean
public ConfigurationCustomizer mybatisConfigurationCustomizer(){
return new ConfigurationCustomizer() {
@Override
public void customize(MybatisConfiguration configuration) {
configuration.setObjectWrapperFactory(new EntityWrapperFactory());
}
};
}
}