Напишите плагин Mybatis самостоятельно: Плагин десенсибилизации Mybatis

Spring Boot Java

1. Введение

В повседневной разработке личная информация, такая как идентификационный номер, номер мобильного телефона, номер карты и номер клиента, должна быть десенсибилизирована. В противном случае легко получить утечку личной информации и информации о клиентах, что даст преступникам возможность. Но десенсибилизация данных заключается не в том, чтобы скрыть секретную информацию, а в том, чтобы выглядеть как настоящая, но на самом деле она не может быть настоящей. Поскольку моя предыдущая компания не обращала внимания на десенсибилизацию, сотрудник экспортировал основные данные о клиентах через функцию экспорта в фоновом режиме и продавал их конкурирующему продукту, когда уходил, что нанесло компании большие убытки. Конечно, есть причины для управления данными, но десенсибилизация по-прежнему является частью, которую нельзя игнорировать, Десенсибилизация может в определенной степени обеспечить надлежащее использование данных. Ниже приведены десенсибилизированные данные:

脱敏之后的数据

2. Плагин десенсибилизации Mybatis

исследую недавноMybatisплагин, так что подумайте, может ли он бытьORMЯ занимался десенсибилизацией, вот и попробовал, вот и делюсь своими идеями. также поделитесь этимMybatisИдея разработки плагина.

2.1 Интерфейс плагина 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
  }

}

Самое главное здесьObject intercept(Invocation invocation)метод, это метод, который нам нужно реализовать.

2.2 Объекты вызова

Затем в основном методеInvocation Что такое концепция?

public class Invocation {

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

Эта штука содержит четыре понятия:

  • targetперехваченный объект
  • methodперехватыватьtargetконкретные методы в , т.е.MybatisДетализация плагинов точна вплоть до уровня метода.
  • argsперехваченные параметры.
  • proceedВыполните перехваченный метод, вы можете сделать некоторые вещи до и после выполнения.

2.3 Подпись перехвата

Теперь, когда мы знаемMybatisДетализация плагинов точна до уровня метода, поэтому возникает вопрос, как плагин узнает, что пришла его очередь работать?

такMybatisМеханизм подписи предназначен для решения этой проблемы с помощью аннотаций в интерфейсе плагина.@Interceptsаннотация для решения этой проблемы.

@Intercepts(@Signature(type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class}))

Точно так же, как и выше, фактически это эквивалентно настройкеInvocation.

2.4 Объем плагинов

Потом снова возникает проблемаMybatisКакие объекты может перехватывать плагин или на каком этапе жизненного цикла работает плагин? Он может перехватывать следующие четыре основных объекта:

  • ExecutorдаSQLИсполнитель, который содержит параметры сборки, собирает результирующий набор в возвращаемое значение и выполняетSQLпроцесс, зернистость относительно грубая.
  • StatementHandlerПроцесс выполнения, используемый для обработки SQL, мы можем переписать его здесьSQLОчень часто используется.
  • ParameterHandlerиспользуется для обработки входящихSQLпараметры, мы можем переопределить правила обработки для параметров.
  • ResultSetHandlerДля обработки наборов результатов мы можем переопределить правила сборки для наборов результатов.

Все, что вам нужно сделать, это указать, какую стадию обработки вышеуказанных четырех объектов ваш бизнес должен перехватить и обработать.

2.5 MetaObject

Mybatisпредоставляет служебный классorg.apache.ibatis.reflection.MetaObject. Он использует отражение для чтения и изменения свойств некоторых важных объектов. Мы можем использовать его для работы с некоторыми свойствами четырех основных объектов, которыеMybatisОбщий класс инструментов для разработки плагинов.

  • Object getValue(String name)Получить значение свойства объекта по имени, поддержкаOGNLвыражение.
  • void setValue(String name, Object value)Установить значение свойства.
  • Class<?> getSetterType(String name)ПолучатьsetterТип параметра метода.
  • Class<?> getGetterType(String name)ПолучатьgetterТип возвращаемого значения метода.

Обычно мы используемSystemMetaObject.forObject(Object object)создавать экземплярMetaObjectобъект. Вы увидите, как я использую его в последующих демонстрациях.

3. Плагин десенсибилизации Mybatis в действии

Далее я реализую требования десенсибилизации в самом начале. Сначала вам нужно пометить поля десенсибилизации и определить стратегию десенсибилизации, которую следует использовать.

Напишите функцию десенсибилизации:

/**
 * 具体策略的函数
 * @author felord.cn
 * @since 11:24
 **/
public interface Desensitizer  extends Function<String,String>  {

}

Напишите перечисление стратегии десенсибилизации:

/**
 * 脱敏策略.
 *
 * @author felord.cn
 * @since 11 :25
 */
public enum SensitiveStrategy {
    /**
     * Username sensitive strategy.
     */
    USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
    /**
     * Id card sensitive type.
     */
    ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
    /**
     * Phone sensitive type.
     */
    PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),

    /**
     * Address sensitive type.
     */
    ADDRESS(s -> s.replaceAll("(\\S{8})\\S{4}(\\S*)\\S{4}", "$1****$2****"));


    private final Desensitizer desensitizer;

    SensitiveStrategy(Desensitizer desensitizer) {
        this.desensitizer = desensitizer;
    }

    /**
     * Gets desensitizer.
     *
     * @return the desensitizer
     */
    public Desensitizer getDesensitizer() {
        return desensitizer;
    }
}

Напишите аннотации разметки для десенсибилизированных полей:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sensitive {
    SensitiveStrategy strategy();
}

Если поле в нашем возвращаемом объекте должно быть десенсибилизировано, его нужно только пометить. Например следующее:

@Data
public class UserInfo {

    private static final long serialVersionUID = -8938650956516110149L;
    private Long userId;
    @Sensitive(strategy = SensitiveStrategy.USERNAME)
    private String name;
    private Integer age;
}

Тогда пришло время написать плагин. Что я могу подтвердить, так это то, что нужно перехватитьResultSetHandlerобъектhandleResultSetsметод, нам нужно только реализовать интерфейс плагинаInterceptorИ поставить на нем подпись. Вся логика такова:

@Slf4j
@Intercepts(@Signature(type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class}))
public class SensitivePlugin implements Interceptor {
    @SuppressWarnings("unchecked")
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        List<Object> records = (List<Object>) invocation.proceed();
        // 对结果集脱敏
        records.forEach(this::sensitive);
        return records;
    }


    private void sensitive(Object source) {
        // 拿到返回值类型
        Class<?> sourceClass = source.getClass();
        // 初始化返回值类型的 MetaObject
        MetaObject metaObject = SystemMetaObject.forObject(source);
        // 捕捉到属性上的标记注解 @Sensitive 并进行对应的脱敏处理
        Stream.of(sourceClass.getDeclaredFields())
                .filter(field -> field.isAnnotationPresent(Sensitive.class))
                .forEach(field -> doSensitive(metaObject, field));
    }


    private void doSensitive(MetaObject metaObject, Field field) {
        // 拿到属性名
        String name = field.getName();
        // 获取属性值
        Object value = metaObject.getValue(name);
        // 只有字符串类型才能脱敏  而且不能为null
        if (String.class == metaObject.getGetterType(name) && value != null) {
            Sensitive annotation = field.getAnnotation(Sensitive.class);
            // 获取对应的脱敏策略 并进行脱敏
            SensitiveStrategy type = annotation.strategy();
            Object o = type.getDesensitizer().apply((String) value);
            // 把脱敏后的值塞回去
            metaObject.setValue(name, o);
        }
    }
}

Затем настройте плагин десенсибилизации, чтобы он вступил в силу:

@Bean
public SensitivePlugin sensitivePlugin(){
    return new SensitivePlugin();
}

Оперативный запрос для получения результатовUserInfo(userId=123123, name=李*龙, age=28), успешно десенсибилизировано указанное поле.

Кроме того, на самом деле десенсибилизация также может бытьJSONво время сериализации.

4. Резюме

Пишу сегодняMybatisОбъясняются некоторые моменты работы плагина, и по инструкции реализуется плагин десенсибилизации. Но учтите, что вы должны быть знакомы с жизненным циклом четырех основных объектов, иначе самописные плагины могут привести к неожиданным результатам. Плагин может подписаться на общедоступную учетную запись WeChat:Код Фермер Маленький Толстый Братключевое слово ответаsensitiveчтобы получить. Если вы найдете это полезным, пожалуйста, безжалостно лайкните, перечитайте и вперед.

关注公众号:Felordcn获取更多资讯

Личный блог: https://felord.cn