基于MyBatis-Plus数据库存取字段加解密解法两种

基于框架中使用的MyBatis-Plus整理了两个方法,一种是MyBatis-Plus自带的TypeHandler,另一种是基于MyBatis的Intercept拦截器。方法一配置简单使用麻烦,方法二配置麻烦使用简单,自己斟酌使用即可。当然方法二适用只使用MyBatis + Springboot的架构。
MyBatis-Plus版本:3.4.0

一、基于MyBatis-Plus自定义类型处理器(TypeHandler)的方法

1、创建TypeHandler

//AES 是工具方法类,按加密需求设置
public class AESEncryptHandler extends BaseTypeHandler {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, AES.encrypt((String)parameter));
    }
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = rs.getString(columnName);
        return AES.decrypt(columnValue);
    }
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String columnValue = rs.getString(columnIndex);
        return AES.decrypt(columnValue);
    }
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        String columnValue = cs.getString(columnIndex);
        return AES.decrypt(columnValue);
    }
}

2、开启MyBatis-Plus的扫描

在配置文件中加入配置

mybatis-plus.type-handlers-package=com.demo.mybatisplusintercept.intercept

配置说明(来源官方文档):
基于MyBatis-Plus数据库存取字段加解密解法两种_第1张图片

PS:实测不加这个配置也可以使用,未深入,原因不明

3、实体类中加入注解

类上加入注解

@TableName(autoResultMap = true)

字段上加入注解

@TableField(typeHandler = AESEncryptHandler.class)

基于MyBatis-Plus数据库存取字段加解密解法两种_第2张图片

4、查询中指定TypeHandler

使用MyBatis-Plus默认方法查询时无需指定,但是当需要使用sql查询的时候,需要指定TypeHandler
注解
请添加图片描述
XML
基于MyBatis-Plus数据库存取字段加解密解法两种_第3张图片

二、基于MyBatis的方法(拦截器)

参考文章:MyBatis 插件之拦截器(Interceptor)
基于MyBatis-Plus数据库存取字段加解密解法两种_第4张图片

在官方示例(https://mybatis.org/mybatis-3/zh/configuration.html#plugins)中,是直接监听Executor,但是通过文章介绍可以看到,ParameterHandler和ResultSetHandler分别操作入参和查询,所以在此处操作字段的加解密应该更为合理。

1、自定义注解

类注解

@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptClass{

}

字段注解

@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {

}

2、工具类

package com.demo.mybatisplusintercept.intercept;

import com.demo.mybatisplusintercept.annotation.EncryptDecryptField;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Objects;

/**
 * Domain数据加密工具类
 *
 */
public class EncryptDecryptUtils {

	/**
	 * 多field加密方法
	 *
	 * @param declaredFields
	 * @param parameterObject
	 * @param 
	 * @return
	 * @throws IllegalAccessException
	 */
	public static <T> T encrypt(Field[] declaredFields, T parameterObject) throws IllegalAccessException {
		for (Field field : declaredFields) {
			EncryptDecryptField annotation = field.getAnnotation(EncryptDecryptField.class);
			if (Objects.isNull(annotation)) {
				continue;
			}
			encrypt(field, parameterObject);
		}
		return parameterObject;
	}


	/**
	 * 单个field加密方法
	 *
	 * @param field
	 * @param parameterObject
	 * @param 
	 * @return
	 * @throws IllegalAccessException
	 */
	public static <T> T encrypt(Field field, T parameterObject) throws IllegalAccessException {
		field.setAccessible(true);
		Object object = field.get(parameterObject);
		if (object instanceof BigDecimal) {
			BigDecimal value = (BigDecimal) object;
			long longValue = value.movePointRight(4).subtract(BigDecimal.valueOf(Integer.MAX_VALUE >> 3)).longValue();
			field.set(parameterObject, BigDecimal.valueOf(longValue));
		} else if (object instanceof Integer) {

		} else if (object instanceof Long) {

		} else if (object instanceof String) {
			//定制String类型的加密算法
			String value = (String) object;
			field.set(parameterObject, AES.encrypt(value));
		}
		return parameterObject;
	}

	/**
	 * 解密方法
	 *
	 * @param result
	 * @param 
	 * @return
	 * @throws IllegalAccessException
	 */
	public static <T> T decrypt(T result) throws IllegalAccessException {
		Class<?> parameterObjectClass = result.getClass();
		Field[] declaredFields = parameterObjectClass.getDeclaredFields();
		decrypt(declaredFields, result);
		return result;
	}

	/**
	 * 多个field解密方法
	 *
	 * @param declaredFields
	 * @param result
	 * @throws IllegalAccessException
	 */
	public static void decrypt(Field[] declaredFields, Object result) throws IllegalAccessException {
		for (Field field : declaredFields) {
			EncryptDecryptField annotation = field.getAnnotation(EncryptDecryptField.class);
			if (Objects.isNull(annotation)) {
				continue;
			}
			decrypt(field, result);
		}
	}

	/**
	 * 单个field解密方法
	 *
	 * @param field
	 * @param result
	 * @throws IllegalAccessException
	 */
	public static void decrypt(Field field, Object result) throws IllegalAccessException {
		field.setAccessible(true);
		Object object = field.get(result);
		if (object instanceof BigDecimal) {
			BigDecimal value = (BigDecimal) object;
			double doubleValue = value.add(BigDecimal.valueOf(Integer.MAX_VALUE >> 3)).movePointLeft(4).doubleValue();
			field.set(result, BigDecimal.valueOf(doubleValue));
		} else if (object instanceof Integer) {

		} else if (object instanceof Long) {

		} else if (object instanceof String) {
			//定制String类型的解密算法
			String value = (String) object;
			field.set(result, AES.decrypt(value));

		}
	}


}

3、两个拦截器

@Intercepts({
        @Signature(type = ParameterHandler.class,method = "setParameters",args = PreparedStatement.class)
})
@ConditionalOnProperty(value = "domain.encrypt",havingValue = "true")
@Slf4j
public class MybatisParameterIntercept implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        //拦截 ParameterHandler 的 setParameters 方法 动态设置参数
        if (invocation.getTarget() instanceof ParameterHandler) {
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();

            // 反射获取 参数对象
            Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
            parameterField.setAccessible(true);
            Object parameterObject = parameterField.get(parameterHandler);
            if (Objects.nonNull(parameterObject)){
                Class<?> parameterObjectClass = parameterObject.getClass();
                EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);
                if (Objects.nonNull(encryptDecryptClass)){
                    Field[] declaredFields = parameterObjectClass.getDeclaredFields();

                    final Object encrypt = EncryptDecryptUtils.encrypt(declaredFields, parameterObject);
                }
            }
        }
        return invocation.proceed();
    }
}
@Intercepts({
        @Signature(type = ResultSetHandler.class,method = "handleResultSets",args = Statement.class)
})
@ConditionalOnProperty(value = "domain.encrypt",havingValue = "true")
@Component
@Slf4j
public class MybatisResultSetIntercept implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        if (Objects.isNull(result)){
            return null;
        }

        if (result instanceof ArrayList) {
            ArrayList resultList = (ArrayList) result;
            if (CollectionUtils.isNotEmpty(resultList) && needToDecrypt(resultList.get(0))){
                for (int i = 0; i < resultList.size(); i++) {
                    EncryptDecryptUtils.decrypt(resultList.get(i));
                }
            }
        }else {
            if (needToDecrypt(result)){
                EncryptDecryptUtils.decrypt(result);
            }
        }
        return result;
    }

    private boolean needToDecrypt(Object object){
        Class<?> objectClass = object.getClass();
        EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptClass.class);
        if (Objects.nonNull(encryptDecryptClass)){
            return true;
        }
        return false;
    }
}

4、注册拦截器

/**
 * 注册mybatis拦截器
 */
@Configuration
@MapperScan("com.demo.mybatisplusintercept.dao")
public class MyBatisPlusConfig {

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 注册MyBatis拦截器
     * @param sqlSessionFactory
     * @return
     */
    @Bean
    public String myInterceptor(SqlSessionFactory sqlSessionFactory){

        sqlSessionFactory.getConfiguration().addInterceptor(parameterIntercept());

        sqlSessionFactory.getConfiguration().addInterceptor(resultSetIntercept());

        return "myInterceptor";
    }

    public MybatisParameterIntercept parameterIntercept(){
        return applicationContext.getAutowireCapableBeanFactory().createBean(MybatisParameterIntercept.class);
    }

    public MybatisResultSetIntercept resultSetIntercept(){
        return applicationContext.getAutowireCapableBeanFactory().createBean(MybatisResultSetIntercept.class);
    }

}

5、实体类添加注解

@Data
@TableName(value = "user",autoResultMap = true)
@EncryptDecryptClass
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;

//    @TableField(typeHandler = AESEncryptHandler.class)
    @EncryptDecryptField
    private String mobile;

//    @TableField(typeHandler = AESEncryptHandler.class)
    @EncryptDecryptField
    private String idcard;

}

你可能感兴趣的:(spring,boot,数据库,java,spring,boot,mybatis)