背景:在erp开发中,有些用户比较敏感数据库里的数据比较敏感,系统给用户部署后,公司也不想让任何人看到数据,所以就有了数据库字段加密方案。
技术 spring boot + mybatisplus 3.3.1
mybatisplus 实际提供了 字段加密方案
 第一 他要钱
 第二 他是在实体类上加注解 满足不了我们的需求
 
我们的整体需求是
 用户可以自定义配置字段
 在系统设置里 有个 字段加密 菜单
- 点击某个表单 然后显示这个表单所需要的字段
2.勾选 某个字段 这个字段 就会激活 再提交 更新表单的时候 这个字段就会加密处理
比如 系统管理 字段加密 他点击了请假表单 然后勾选 请假事由 字段加密
然后 公司的员工再次提交表单的时候 请假事由 就会被加密 。 
这个需求 用户再页面上操作 我们是不需要改代码的 。
如果利用实体类加注解方案 肯定满足不了 因为 每个用户加密的字段不一样,
 鬼知道 加密注解要加在哪个实体类上
我们的系统 表单 和表单的字段 都定义在数据库里 所以 可以自由选择 表单和字段
 这个根据各自系统自行修改
下面直接分享 加密的代码和思路 。原理我就不再说 ,用到的知识 自行百度即可
第一步 : 首先把用户勾选需要加密的 字段 缓存到redis 减少数据库查询
 // 这段代码 就不分享了 自由编写
 根据表名 获取 需要加密的字段
 List<String> stringList= BaseDataUtil.getFieldPassword(insertSql.getTableName());
 
第二步:
 引入加密依赖
     <!--对数据库字段进行加密、脱敏-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.3</version>
        </dependency>
 
第三步:
 配置文件 配置
 
第四步 编写加密方案 利用的是 框加下的这个类 BaseTypeHandler
 对于这个类的介绍 自行百度
自定义一个类 然后继承 这个类 BaseTypeHandler
 重写 父类方法
package com.erp.init.handlers;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.erp.init.utils.BaseDataUtil;
import org.apache.ibatis.logging.jdbc.PreparedStatementLogger;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * User: Json
 * <p>
 * Date: 2023/11/14
 **/
public class EncryptHandler extends BaseTypeHandler<String> {
    /**
     * 定义好后不要修改
     */
    private static final byte[] KEYS = "shc9876543232camp".getBytes(StandardCharsets.UTF_8);
    //前缀 为了 老数据 和新数据的 处理 比如 老数据没加密 
    //新数据加密了 可以根据这个前缀判断 老数据 就不要解密了
    private static final String dataWithPrefix = "sada";
    /**
     * 设置参数
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        if (StringUtils.isEmpty(parameter)) {
            ps.setString(i, null);
            return;
        }
        // 获取动态代理类的 InvocationHandler
        PreparedStatementLogger  handler =(PreparedStatementLogger) Proxy.getInvocationHandler(ps);
        PreparedStatement preparedStatement= handler.getPreparedStatement();
        MetaObject stmtMetaObj = SystemMetaObject.forObject(preparedStatement);
        String sql =  stmtMetaObj.getValue("sql").toString();
        //System.out.println("sql:"+sql);
        SqlResult updateSql= updateSql(sql);
        if(!ObjectUtils.isEmpty(updateSql)){
//            System.out.println("更新数据:"+updateSql);
//            System.out.println(i+"===>"+updateSql.getColumnNames().get(i-1));
            List<String> stringList= BaseDataUtil.getFieldPassword(updateSql.getTableName());
            if (!CollectionUtils.isEmpty(stringList) &&
                    !CollectionUtils.isEmpty(updateSql.getColumnNames()) &&
                    stringList.contains(updateSql.getColumnNames().get(i-1))) {
                 AES aes = SecureUtil.aes(KEYS);
                 String encrypt = aes.encryptHex(parameter);
                  ps.setString(i,dataWithPrefix+ encrypt);
            } else {
                ps.setString(i, parameter);
            }
        }
        SqlResult insertSql=insetSQl(sql);
        if(!ObjectUtils.isEmpty(insertSql)){
//            System.out.println("新增数据:"+insertSql);
//            System.out.println(i+"===>"+insertSql.getColumnNames().get(i-1));
            List<String> stringList= BaseDataUtil.getFieldPassword(insertSql.getTableName());
            if (!CollectionUtils.isEmpty(stringList) &&
                    !CollectionUtils.isEmpty(insertSql.getColumnNames()) &&
                    stringList.contains(insertSql.getColumnNames().get(i-1))) {
                AES aes = SecureUtil.aes(KEYS);
                String encrypt = aes.encryptHex(parameter);
                ps.setString(i, dataWithPrefix+ encrypt);
            } else {
                ps.setString(i, parameter);
            }
        }
        if(ObjectUtils.isEmpty(insertSql) && ObjectUtils.isEmpty(updateSql) ){
            ps.setString(i, parameter);
        }
    }
    /**
     * 获取值
     */
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return decrypt(rs.getString(columnName),columnName,rs.getMetaData().getTableName(1));
    }
    /**
     * 获取值
     */
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return null;
     //   return decrypt(rs.getString(columnIndex));
    }
    /**
     * 获取值
     */
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return null;
        //return decrypt(cs.getString(columnIndex));
    }
    public String decrypt(String value,String columnName,String tableName) {
       // return  value;
        if (null == value) {
            return null;
        }
        List<String> stringList= BaseDataUtil.getFieldPassword(tableName);
        if(CollectionUtils.isEmpty(stringList)){
            return  value;
        }
        if (value.startsWith(dataWithPrefix) && stringList.contains(columnName)) {
            // 是新数据,去掉前缀
           String decryptedData = value.substring(dataWithPrefix.length());
            return SecureUtil.aes(KEYS).decryptStr(decryptedData);
        } else {
            return  value;
        }
    }
    public  SqlResult  updateSql(String sql) {
        // 假设你已经有了sql字符串
       // String sql = "UPDATE your_table SET column1 = value1, column2 = value2 WHERE condition";
        // 匹配UPDATE语句
        Pattern updatePattern = Pattern.compile("UPDATE\\s+([^\\s]+)\\s+SET\\s+([^\\s]+\\s*=\\s*[^,]+(,\\s*[^\\s]+\\s*=\\s*[^,]+)*)\\s+WHERE\\s+(.+)");
        Matcher updateMatcher = updatePattern.matcher(sql);
        // 如果是UPDATE语句
        if (updateMatcher.matches()) {
            String tableName = updateMatcher.group(1); // 获取表名
            String setClause = updateMatcher.group(2); // 获取SET子句
            // 提取更新的字段名
            List<String> columnNames = extractColumnNames(setClause);
            // 返回表名和字段名的信息
            return new SqlResult(tableName, columnNames);
        }
        return null;
    }
    private static List<String> extractColumnNames(String setClause) {
        List<String> columnNames = new ArrayList<>();
        String[] columns = setClause.split("\\s*,\\s*");
        for (String column : columns) {
            String[] parts = column.split("\\s*=\\s*");
            String columnName = parts[0].trim();
            columnNames.add(columnName);
        }
        return columnNames;
    }
    private static List<String> extractColumnNamesInsert(String columnsClause) {
        String[] columns = columnsClause.split("\\s*,\\s*");
        List<String> columnNames = new ArrayList<>();
        for (String column : columns) {
            columnNames.add(column.trim());
        }
        return columnNames;
    }
    public  SqlResult insetSQl(String sql) {
        // 假设你已经有了sql字符串
       // String sql = "INSERT INTO your_table (column1, column2) VALUES (value1, value2)";
        // 匹配INSERT语句
        Pattern insertPattern = Pattern.compile("INSERT\\s+INTO\\s+([^\\s]+)\\s*\\(([^)]+)\\)\\s*VALUES\\s*\\(([^)]+)\\)");
        Matcher insertMatcher = insertPattern.matcher(sql);
        // 如果是INSERT语句
        if (insertMatcher.matches()) {
            String tableName = insertMatcher.group(1); // 获取表名
            String columnsClause = insertMatcher.group(2); // 获取列名的部分
          //  String valuesClause = insertMatcher.group(3); // 获取值的部分
            // 提取新增的字段名和对应的值
            List<String> columnNames = extractColumnNamesInsert(columnsClause);
            // 返回表名、字段名和对应值的信息
            return new SqlResult(tableName, columnNames);
        }
        return null;
    }
}
 
第五步:
 把这个类注册到配置文件中
package com.erp.init.mybatisplus;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.erp.init.handlers.EncryptHandler;
import org.apache.ibatis.type.JdbcType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * User: Json
 * <p>
 * Date: 2023/11/15
 **/
@Configuration
public class MyBatisConfig {
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> {
        //String.class, JdbcType.VARCHAR  这个是 只对 字符串进行处理
        // int float 的加密 根据字符串再扩展一个类就行了 
            configuration.getTypeHandlerRegistry().register(String.class, JdbcType.VARCHAR, new EncryptHandler());
            // 注册其他类型处理器
        };
    }
}
 
这要编写后 每次 mybatisplus 调用 新增 和 更新 批量更新 批量操作 都会触发这里的代码
 实现字段加解密


















