红酒网站建设策划书,淮南 搭建一个企业展示网站,北京网站设计十年乐云seo,seo是什么平台目录1.场景介绍2.Maven依赖2.AESUtil.java 加解密工具类3.字段处理类4.修改 MyBatis Plus 查询4.1 修改表对应实体类4.2 修改加密字段对应属性4.3 修改 xml 使用 ResultMap4.4 修改 xml 中 el 表达式5.测试结果6.MyBatis Plus 缺陷补充#xff1a;测试实例1 查询测试1.1 查询信…
目录1.场景介绍2.Maven依赖2.AESUtil.java 加解密工具类3.字段处理类4.修改 MyBatis Plus 查询4.1 修改表对应实体类4.2 修改加密字段对应属性4.3 修改 xml 使用 ResultMap4.4 修改 xml 中 el 表达式5.测试结果6.MyBatis Plus 缺陷补充测试实例1 查询测试1.1 查询信息SQL实现1.2 查询信息QueryWrapper实现1.3 查询信息根据加密字段查询SQL实现1.4 查询信息根据加密字段查询QueryWrapper实现2.测试更新2.1 更新信息SQL实现2.2 更新信息UpdateWrapper实现2.3 更新信息LambdaUpdateWrapper实现2.4 更新信息updateById实现3.测试插入7.3.1 插入信息SQL实现3.2 插入信息Service实现1.场景介绍
当项目开发到一半可能突然客户会要求对数据库里面比如手机号、身份证号的字段进行加密在保证开发最快、影响范围最小的情况下我们需要选择一种介于数据库和代码之间的工具来帮我们实现自动加解密
2.Maven依赖
!-- mybatis-plus --
dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.3/version
/dependency!-- mybatis的分页插件 --
dependencygroupIdcom.github.pagehelper/groupIdartifactIdpagehelper/artifactIdversion5.1.11/version!-- pagehelper 包含该依赖存在版本冲突因此不建议和 mp 一起混用 --exclusionsexclusiongroupIdcom.github.jsqlparser/groupIdartifactIdjsqlparser/artifactId/exclusion/exclusions
/dependency2.AESUtil.java 加解密工具类
这里我们选用AES对称加密算法因为它是可逆算法。
AES加密介绍 https://blog.csdn.net/qq_33204709/article/details/126930720
具体实现代码如下
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;/*** AES加密工具类** author ACGkaka* since 2021-06-18 19:11:03*/
public class AESUtil {/*** 日志相关*/private static final Logger LOGGER LoggerFactory.getLogger(AESUtil.class);/*** 编码*/private static final String ENCODING UTF-8;/*** 算法定义*/private static final String AES_ALGORITHM AES;/*** 指定填充方式*/private static final String CIPHER_PADDING AES/ECB/PKCS5Padding;private static final String CIPHER_CBC_PADDING AES/CBC/PKCS5Padding;/*** 偏移量(CBC中使用增强加密算法强度)*/private static final String IV_SEED 1234567812345678;/*** AES加密* param content 待加密内容* param aesKey 密码* return*/public static String encrypt(String content, String aesKey){if(StringUtils.isBlank(content)){LOGGER.info(AES encrypt: the content is null!);return null;}//判断秘钥是否为16位if(StringUtils.isNotBlank(aesKey) aesKey.length() 16){try {//对密码进行编码byte[] bytes aesKey.getBytes(ENCODING);//设置加密算法生成秘钥SecretKeySpec skeySpec new SecretKeySpec(bytes, AES_ALGORITHM);// 算法/模式/补码方式Cipher cipher Cipher.getInstance(CIPHER_PADDING);//选择加密cipher.init(Cipher.ENCRYPT_MODE, skeySpec);//根据待加密内容生成字节数组byte[] encrypted cipher.doFinal(content.getBytes(ENCODING));//返回base64字符串return Base64Utils.encodeToString(encrypted);} catch (Exception e) {LOGGER.info(AES encrypt exception: e.getMessage());throw new RuntimeException(e);}}else {LOGGER.info(AES encrypt: the aesKey is null or error!);return null;}}/*** 解密* * param content 待解密内容* param aesKey 密码* return*/public static String decrypt(String content, String aesKey){if(StringUtils.isBlank(content)){LOGGER.info(AES decrypt: the content is null!);return null;}//判断秘钥是否为16位if(StringUtils.isNotBlank(aesKey) aesKey.length() 16){try {//对密码进行编码byte[] bytes aesKey.getBytes(ENCODING);//设置解密算法生成秘钥SecretKeySpec skeySpec new SecretKeySpec(bytes, AES_ALGORITHM);// 算法/模式/补码方式Cipher cipher Cipher.getInstance(CIPHER_PADDING);//选择解密cipher.init(Cipher.DECRYPT_MODE, skeySpec);//先进行Base64解码byte[] decodeBase64 Base64Utils.decodeFromString(content);//根据待解密内容进行解密byte[] decrypted cipher.doFinal(decodeBase64);//将字节数组转成字符串return new String(decrypted, ENCODING);} catch (Exception e) {LOGGER.info(AES decrypt exception: e.getMessage());throw new RuntimeException(e);}}else {LOGGER.info(AES decrypt: the aesKey is null or error!);return null;}}/*** AES_CBC加密* * param content 待加密内容* param aesKey 密码* return*/public static String encryptCBC(String content, String aesKey){if(StringUtils.isBlank(content)){LOGGER.info(AES_CBC encrypt: the content is null!);return null;}//判断秘钥是否为16位if(StringUtils.isNotBlank(aesKey) aesKey.length() 16){try {//对密码进行编码byte[] bytes aesKey.getBytes(ENCODING);//设置加密算法生成秘钥SecretKeySpec skeySpec new SecretKeySpec(bytes, AES_ALGORITHM);// 算法/模式/补码方式Cipher cipher Cipher.getInstance(CIPHER_CBC_PADDING);//偏移IvParameterSpec iv new IvParameterSpec(IV_SEED.getBytes(ENCODING));//选择加密cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);//根据待加密内容生成字节数组byte[] encrypted cipher.doFinal(content.getBytes(ENCODING));//返回base64字符串return Base64Utils.encodeToString(encrypted);} catch (Exception e) {LOGGER.info(AES_CBC encrypt exception: e.getMessage());throw new RuntimeException(e);}}else {LOGGER.info(AES_CBC encrypt: the aesKey is null or error!);return null;}}/*** AES_CBC解密* * param content 待解密内容* param aesKey 密码* return*/public static String decryptCBC(String content, String aesKey){if(StringUtils.isBlank(content)){LOGGER.info(AES_CBC decrypt: the content is null!);return null;}//判断秘钥是否为16位if(StringUtils.isNotBlank(aesKey) aesKey.length() 16){try {//对密码进行编码byte[] bytes aesKey.getBytes(ENCODING);//设置解密算法生成秘钥SecretKeySpec skeySpec new SecretKeySpec(bytes, AES_ALGORITHM);//偏移IvParameterSpec iv new IvParameterSpec(IV_SEED.getBytes(ENCODING));// 算法/模式/补码方式Cipher cipher Cipher.getInstance(CIPHER_CBC_PADDING);//选择解密cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);//先进行Base64解码byte[] decodeBase64 Base64Utils.decodeFromString(content);//根据待解密内容进行解密byte[] decrypted cipher.doFinal(decodeBase64);//将字节数组转成字符串return new String(decrypted, ENCODING);} catch (Exception e) {LOGGER.info(AES_CBC decrypt exception: e.getMessage());throw new RuntimeException(e);}}else {LOGGER.info(AES_CBC decrypt: the aesKey is null or error!);return null;}}public static void main(String[] args) {// AES支持三种长度的密钥128位、192位、256位。// 代码中这种就是128位的加密密钥16字节 * 8位/字节 128位。String random RandomStringUtils.random(16, abcdefghijklmnopqrstuvwxyz1234567890);System.out.println(随机key: random);System.out.println();System.out.println(---------加密---------);String aesResult encrypt(测试AES加密12, random);System.out.println(aes加密结果: aesResult);System.out.println();System.out.println(---------解密---------);String decrypt decrypt(aesResult, random);System.out.println(aes解密结果: decrypt);System.out.println();System.out.println(--------AES_CBC加密解密---------);String cbcResult encryptCBC(测试AES加密12456, random);System.out.println(aes_cbc加密结果: cbcResult);System.out.println();System.out.println(---------解密CBC---------);String cbcDecrypt decryptCBC(cbcResult, random);System.out.println(aes解密结果: cbcDecrypt);System.out.println();}
}3.字段处理类
import com.demo.util.AESUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;/*** p Title MyEncryptTypeHandler* p Description 字段加密处理** author ACGkaka* date 2023/2/21 17:20*/
public class MyEncryptTypeHandler extends BaseTypeHandlerString {Overridepublic void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, AESUtil.defaultEncrypt(parameter));}Overridepublic String getNullableResult(ResultSet rs, String column) throws SQLException {return AESUtil.defaultDecrypt(rs.getString(column));}Overridepublic String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return AESUtil.defaultDecrypt(rs.getString(columnIndex));}Overridepublic String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return AESUtil.defaultDecrypt(cs.getString(columnIndex));}
}4.修改 MyBatis Plus 查询
4.1 修改表对应实体类
设置 TableName 注解的 autoResultMap 为 true默认 false。
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;/*** 用户表** author ACGkaka* date 2023/2/21 17:20*/
Data
TableName(value t_user_info, autoResultMap true)
public class UserInfo implements Serializable {}4.2 修改加密字段对应属性
设置 TableField 注解的 typeHandler 为 MyEncryptTypeHandler.class。
import com.demo.encrypt.MyEncryptTypeHandler;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;/*** 用户表** author ACGkaka* date 2023/2/21 17:20*/
Data
TableName(value t_user_info, autoResultMap true)
public class UserInfo implements Serializable {/*** 手机号码*/TableField(value PHONE, typeHandler MyEncryptTypeHandler.class)private String phone;
}4.3 修改 xml 使用 ResultMap
1创建 ResultMap 映射指定 typeHandler
2查询语句使用 ResultMap 返回。
!-- 通用查询映射结果 --
resultMap idBaseResultMap typecom.demo.model.UserInfoid columnID propertyid /result columnACCOUNT propertystaffCode /result columnPHONE propertyphone typeHandlercom.demo.encrypt.MyEncryptTypeHandler /
/resultMap!-- 查询全部 --
select idfindAll resultMapBaseResultMapSELECT * FROM t_user_info
/select4.4 修改 xml 中 el 表达式 设置好 4.1 和 4.2 就可以保证 修改前
!-- 更新手机号 --
update idupdatePhoneByIdupdate t_user_info set phone #{phone} where id #{id}
/update!-- 根据手机号查询 --
select idfindByPhone resultMapBaseResultMapSELECT * FROM t_user_info where phone #{phone}
/select修改后
!-- 更新手机号 --
update idupdatePhoneByIdupdate t_user_info set phone #{phone, typeHandlercom.demo.encrypt.MyEncryptTypeHandler} where id #{id}
/update!-- 根据手机号查询 --
select idfindByPhone resultMapBaseResultMapSELECT * FROM t_user_info where phone #{phone, typeHandlercom.demo.encrypt.MyEncryptTypeHandler}
/select5.测试结果
由于测试内容较多这里先直接展示测试结果具体测试示例可以看 补充测试实例
操作实现方式入参测试结果SELECT原生SQL非加密字段出参解密成功SELECTQueryWrapper非加密字段出参解密成功SELECT原生SQL加密字段入参加密成功SELECTQueryWrapper加密字段入参加密失败UPDATE原生SQL加密字段入参加密成功UPDATEUpdateWrapper加密字段入参加密失败UPDATELambdaUpdateWrapper加密字段入参加密成功UPDATEupdateById加密字段入参加密成功INSERTService加密字段入参加密成功
说明
官方的解答是 QueryWrapper、UpdateWrapper 底层是通过 Param 来实现的目前没有做到入参支持 typeHandler如果做的话会影响性能。
6.MyBatis Plus 缺陷 QueryWrapper 不支持入参加密 UpdateWrapper 不支持入参加密 加密字段不支持模糊查询。
补充测试实例
1 查询测试
1.1 查询信息SQL实现
Test
public void getUserInfoTest1() {UserInfo userInfo userInfoService.findByAccount(testAccount);System.out.println(userInfo: userInfo);System.out.println(phone: userInfo.getPhone());
}测试结果出参解密成功
1.2 查询信息QueryWrapper实现
Test
public void getUserInfoTest2() {QueryWrapperUserInfo wrapper new QueryWrapper();wrapper.eq(account, testAccount);ListUserInfo users userInfoService.list(wrapper);System.out.println(userInfo: users);System.out.println(phone: users.get(0).getPhone());
}测试结果出参解密成功
1.3 查询信息根据加密字段查询SQL实现
Test
public void getUserInfoTest3() {UserInfo user userInfoService.findByPhone(13888888888);System.out.println(userInfo: user);System.out.println(phone: user.getPhone());
}注意入参需要使用el表达式指定 typeHandler
测试结果入参加密成功
1.4 查询信息根据加密字段查询QueryWrapper实现
Test
public void getUserInfoTest3() {QueryWrapperUserInfo wrapper new QueryWrapper();wrapper.lambda().eq(UserInfo::getPhone, 13888888888);ListUserInfo users userInfoService.list(wrapper);System.out.println(userInfo: users);System.out.println(phone: users.get(0).getPhone());
}测试结果入参加密失败QueryWrapper底层使用 Param 实现无法像 SQL 实现一样指定 typeHandler。
2.测试更新
2.1 更新信息SQL实现
Test
public void updateUserInfoTest1() {userInfoService.updatePhoneByAccount(testAccount, 13888888888);
}测试结果入参加密成功
2.2 更新信息UpdateWrapper实现
Test
public void updateUserInfoTest2() {UpdateWrapperUserInfo wrapper new UpdateWrapper();wrapper.set(phone, 13888888888);wrapper.eq(account, testAccount);userInfoService.update(wrapper);getUserInfoTest1();
}测试结果入参加密失败UpdateWrapper底层使用 Param 实现无法像 SQL 实现一样指定 typeHandler。
2.3 更新信息LambdaUpdateWrapper实现
Test
public void updateUserInfoTest3() {LambdaUpdateWrapperUserInfo wrapper Wrappers.UserInfolambdaUpdate().set(UserInfo::getPhone, 13888888888, typeHandlercom.demo.encrypt.MyEncryptTypeHandler);wrapper.eq(UserInfo::getAccount, testAccount);userInfoService.update(wrapper);getUserInfoTest1();
}测试结果入参加密成功
2.4 更新信息updateById实现
Test
public void updateUserInfoTest4() {UserInfo userInfo userInfoService.findByAccount(testAccount);userInfo.setPhone(13888888888);userInfoService.updateById(userInfo);
}测试结果入参加密成功
3.测试插入
7.3.1 插入信息SQL实现
Test
public void insertUserInfoTest1() {UserInfo userInfo userInfoService.findByAccount(testAccount);userInfo.setAccount(testAccount_002);userInfo.setPhone(13888888888);userInfoService.save(userInfo);UserInfo newUserInfo userInfoService.findByAccount(testAccount_002);System.out.println(userInfo: newUserInfo);System.out.println(phone: newUserInfo.getPhone());
}测试结果入参加密成功
3.2 插入信息Service实现
Test
public void insertUserInfoTest1() {UserInfo userInfo userInfoService.findByAccount(testAccount);userInfo.setAccount(testAccount_002);userInfo.setPhone(13888888888);userInfoService.save(userInfo);UserInfo newUserInfo userInfoService.findByAccount(testAccount_002);System.out.println(userInfo: newUserInfo);System.out.println(phone: newUserInfo.getPhone());
}测试结果入参加密成功
整理完毕完结撒花~ 参考地址
1.mybaits plus 字段加密与解密https://blog.csdn.net/qq_21134059/article/details/121752978
2.mybatis plus 官方问题页面https://github.com/baomidou/mybatis-plus/issues
3.更新时自定义的TypeHandler不生效https://github.com/baomidou/mybatis-plus/issues/794
4.lambdaUpdate() 无法更新Json对象字段https://github.com/baomidou/mybatis-plus/issues/5031
5.LambdaUpdateWrapper不支持自定义BaseTypeHandlerhttps://github.com/baomidou/mybatis-plus/issues/3317