【MyBatis】脱敏插件

多么希望压得我喘不过气的不是生活,而是一个成熟御姐

MyBatis 插件

前置知识

最好了解或回顾一下以下知识点:

  • 动态代理模式
  • 责任链模式

为啥要了解这两个设计模式?

插件的原理:责任链模式 + JDK动态代理(接口,代理对象、代理类 implement InvocationHandler)

如果不了解也没啥问题,学会咋用就行(反正我也没写插件原理,太耗时间了,下次一定),就像下面这样:

image-20211031092113883

Interceptor

先上个官网地址:配置_MyBatis中文网

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用,自定义拦截器需要的接口:org.apache.ibatis.plugin.Interceptor

默认情况下,MyBatis 允许使用插件来拦截的对象有以下:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) SQL执行器,用于执行增删改操作
  • ParameterHandler (getParameterObject, setParameters) 处理 SQL 的参数对象
  • ResultSetHandler (handleResultSets, handleOutputParameters) 处理 SQL 返回的结果集
  • StatementHandler (prepare, parameterize, batch, update, query) 拦截 SQL 语句构建的处理

了解 mybatis 插件的接口

如果你要自定义 mybatis 插件,你必须要实现 org.apache.ibatis.plugin.Interceptor 接口,我们看下这个接口的方法:

public interface Interceptor {
  //拦截逻辑
  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}
  • intercept 方法: 如果自定义插件实现 Interceptor 覆盖 intercept 方法,这个方法是一个核心方法,里面的参数 Invocation 对象,可以通过这个对象反射调度原来对象的方法。

    插件的核心:其实会拦截四个接口的子对象,拦截以后会进入到 intercept 方法中进行业务的处理,Invocation 对象可以获取到四个接口的具体

  • plugin 方法: 参数 target 指的就是代理对象。它的作用:把拦截的 target 对象变成一个代理对象,并且返回它

    动态代理规则:当一个代理对象,执行某个方法的时候,就进入代理类的 invoke 方法中

  • setProperties 方法: 允许 plugin 在注册的时候,配置插件需要的参数,这个参数可以在 mybatis 的核心配置文件中注册插件的时候,一起配置到文件中,如下:

    <plugins>
      <plugin interceptor="org.mybatis.example.ExamplePlugin">
        <property name="someProperty" value="100"/>
      </plugin>
    </plugins>
    

这四个接口分别是什么?

  • Executor:SQL执行器,用于执行增删改操作
  • ParameterHandler:处理 SQL 的参数对象
  • ResultSetHandler:处理 SQL 返回的结果集
  • StatementHandler :拦截 SQL 语句构建的处理

插件小案例

数据脱敏插件

什么是数据脱敏?

举个例子,查询用户信息:

脱敏前:

  • 张三达美

脱敏后:

  • 张 * * 美

其实就是对敏感数据做点处理

用图片举个例子,这是数据脱敏前:

image-20211030101225619

这个是数据脱敏后:

image-20211030085729311

好的,简单了解了数据脱敏后,就开始使用 mybatis 插件特性来完成数据脱敏的实现。

思路

先理一下思路:

  1. 首先我们需要对数据的结果集进行拦截,也就是说需要拦截这个接口的方法

    image-20211030112308412

  2. 得到数据返回的结果集后,对结果集转换成 List 进行遍历脱敏

  3. 脱敏的时候我们还需要判断一下哪些字段需要进行脱敏,这里我们定义注解来标识需要脱敏的字段(在这个注解里面可以定义一些脱敏策略,比如对手机号的脱敏规则、身份证的脱敏规则等等

  4. 遍历的时候通过反射,获取所有属性

  5. 我这里的脱敏有三个条件

    • 1、必须带有 脱敏标识注解
    • 2、必须是 String 类型
    • 3、不得为空,这里直接使用工具类来判断 StringUtils.isEmpty()
  6. 脱敏条件达成后,就获取该注解上的 脱敏策略,根据脱敏策略 对属性进行脱敏

  7. 将脱敏后的结果重新设置到属性上

就这样,大功告成!

先看一下我这的最终目录结构:

image-20211030114118071

编码

首先新建一个 TongMinPlugin 类

这个就是我们的脱敏插件主要类

继承了 Interceptor 这个拦截器接口,这里会让你实现 intercept(Invocation invocation) 这个方法,拦截逻辑主要也是在这里编写的

package com.yuan.plugin;

@Component //需要放入到 ioc 容器中拦截器才会生效哦
//指定要拦截的目标方法的注解
//<h1>1、这个就是指定需要拦截的方法,这里我们指定拦截返回结果集的方法</h1>
@Intercepts(@Signature(type = ResultSetHandler.class,method = "handleResultSets",args= Statement.class)) 
public class TongMinPlugin implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
       	//<h1>2、得到数据返回的结果集,对结果集转换成 List<Object> 进行遍历脱敏</h1>
        List<Object> records = (List<Object>) invocation.proceed();
        //遍历数据 调用 tuoMin 这个方法
        records.forEach(this::tuoMin);
        return records;
    }

    private void tuoMin(Object source) {
       
    }
}

到这里需要先定义一下注解和策略:

//这里的Function是jdk8新特性,自己了解一哈子
//提示:传入一个函数执行
public interface Desensitizer extends Function<String,String> {

}
//这里定义脱敏策略
public enum TuoMinStrategy {
    
    /*手机号*/
    PHONE(s->s.replaceAll("^(\\\\d{3})\\\\d{4}(\\\\d{4})$","$1****$2")),
    /*用户名 匹配中文全部替换*/
    USERNAME(s->s.replaceAll("[\\u4e00-\\u9fa5]","*"));

    private final Desensitizer desensitizer;

    TuoMinStrategy(Desensitizer d) {
        desensitizer = d;
    }

    public Desensitizer getDesensitizer() {
        return desensitizer;
    }
}

然后,我们的注解:

//作用于属性上
@Target(ElementType.FIELD)
//运行时生效
@Retention(RetentionPolicy.RUNTIME)
public @interface TuoMin {

    /**
     * 脱敏策略
     * @return
     */
    TuoMinStrategy strategy();

}

最后,给需要脱敏的字段加上注解:

@Data
public class User implements Serializable {
    private static final long serialVersionUID = 415277636701148394L;

    private Integer id;

    @TuoMin(strategy = TuoMinStrategy.USERNAME) //加上注解并指定脱敏策略
    private String username;

    private String password;

}

接下来就可以开始写脱敏方法的逻辑了

private void tuoMin(Object source) {
    Class<?> sourceClass = source.getClass();
    MetaObject metaObject = SystemMetaObject.forObject(source);//这个东东是 mybatis 提供的,通过这个玩意我们可以得到属性的值
    
    //使用 stream 流
    /**
     * 1、得到所有属性
     * 2、筛选出带有 TuoMin.class 注解的属性
     * 3、调用doTuoMin方法将这些属性脱敏
     */
    Stream.of(sourceClass.getDeclaredFields())
            .filter(field -> field.isAnnotationPresent(TuoMin.class))
            .forEach(field->doTuoMin(metaObject,field));
}

哈哈,其实里面还有一层方法哒!

private void doTuoMin(MetaObject metaObject, Field field) {
    String name = field.getName();
    Object value = metaObject.getValue(name);//根据属性名得到属性值
    //脱敏条件:必须为String类型,值不等于空
    if (String.class == metaObject.getGetterType(name) && !StringUtils.isEmpty(value)) {
        //获取脱敏策略
        TuoMin tuoMin = field.getAnnotation(TuoMin.class);
        TuoMinStrategy type = tuoMin.strategy();
        //调用策略正则表达式脱敏,得到结果
        Object apply = type.getDesensitizer().apply((String) value);
        //将脱敏后的结果重新设置到属性上
        metaObject.setValue(name,apply);
    }
}

到这里,我们的小型数据脱敏插件就已经编码完毕了,接下来进行测试:

原本的数据:

image-20211030121530265

拦截脱敏后的数据:

image-20211030121547150

嗯,看来测试的没毛病

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值