山西建設(shè)執(zhí)業(yè)注冊管理中心網(wǎng)站怎么推廣比較好
目錄
- 1.場景介紹
- 2.Maven依賴
- 2.AESUtil.java 加解密工具類
- 3.字段處理類
- 4.修改 MyBatis Plus 查詢
- 4.1 修改表對應(yīng)實(shí)體類
- 4.2 修改加密字段對應(yīng)屬性
- 4.3 修改 xml 使用 ResultMap
- 4.4 修改 xml 中 el 表達(dá)式
- 5.測試結(jié)果
- 6.MyBatis Plus 缺陷
- 補(bǔ)充:測試實(shí)例
- 1 查詢測試
- 1.1 查詢信息,SQL實(shí)現(xiàn)
- 1.2 查詢信息,QueryWrapper實(shí)現(xiàn)
- 1.3 查詢信息,根據(jù)加密字段查詢,SQL實(shí)現(xiàn)
- 1.4 查詢信息,根據(jù)加密字段查詢,QueryWrapper實(shí)現(xiàn)
- 2.測試更新
- 2.1 更新信息,SQL實(shí)現(xiàn)
- 2.2 更新信息,UpdateWrapper實(shí)現(xiàn)
- 2.3 更新信息,LambdaUpdateWrapper實(shí)現(xiàn)
- 2.4 更新信息,updateById實(shí)現(xiàn)
- 3.測試插入
- 7.3.1 插入信息,SQL實(shí)現(xiàn)
- 3.2 插入信息,Service實(shí)現(xiàn)
1.場景介紹
- 當(dāng)項(xiàng)目開發(fā)到一半,可能突然客戶會(huì)要求對數(shù)據(jù)庫里面比如手機(jī)號、身份證號的字段進(jìn)行加密;
- 在保證開發(fā)最快、影響范圍最小的情況下,我們需要選擇一種介于數(shù)據(jù)庫和代碼之間的工具來幫我們實(shí)現(xiàn)自動(dòng)加解密;
2.Maven依賴
<!-- mybatis-plus -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3</version>
</dependency><!-- mybatis的分頁插件 -->
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.1.11</version><!-- pagehelper 包含該依賴存在版本沖突,因此不建議和 mp 一起混用 --><exclusions><exclusion><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId></exclusion></exclusions>
</dependency>
2.AESUtil.java 加解密工具類
這里我們選用AES對稱加密算法,因?yàn)樗强赡嫠惴ā?/p>
AES加密介紹: https://blog.csdn.net/qq_33204709/article/details/126930720
具體實(shí)現(xiàn)代碼如下:
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 {/*** 日志相關(guān)*/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中使用,增強(qiáng)加密算法強(qiáng)度)*/private static final String IV_SEED = "1234567812345678";/*** AES加密* @param content 待加密內(nèi)容* @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 {//對密碼進(jìn)行編碼byte[] bytes = aesKey.getBytes(ENCODING);//設(shè)置加密算法,生成秘鑰SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);// "算法/模式/補(bǔ)碼方式"Cipher cipher = Cipher.getInstance(CIPHER_PADDING);//選擇加密cipher.init(Cipher.ENCRYPT_MODE, skeySpec);//根據(jù)待加密內(nèi)容生成字節(jié)數(shù)組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 待解密內(nèi)容* @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 {//對密碼進(jìn)行編碼byte[] bytes = aesKey.getBytes(ENCODING);//設(shè)置解密算法,生成秘鑰SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);// "算法/模式/補(bǔ)碼方式"Cipher cipher = Cipher.getInstance(CIPHER_PADDING);//選擇解密cipher.init(Cipher.DECRYPT_MODE, skeySpec);//先進(jìn)行Base64解碼byte[] decodeBase64 = Base64Utils.decodeFromString(content);//根據(jù)待解密內(nèi)容進(jìn)行解密byte[] decrypted = cipher.doFinal(decodeBase64);//將字節(jié)數(shù)組轉(zhuǎn)成字符串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 待加密內(nèi)容* @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 {//對密碼進(jìn)行編碼byte[] bytes = aesKey.getBytes(ENCODING);//設(shè)置加密算法,生成秘鑰SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);// "算法/模式/補(bǔ)碼方式"Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);//偏移IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));//選擇加密cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);//根據(jù)待加密內(nèi)容生成字節(jié)數(shù)組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 待解密內(nèi)容* @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 {//對密碼進(jìn)行編碼byte[] bytes = aesKey.getBytes(ENCODING);//設(shè)置解密算法,生成秘鑰SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);//偏移IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));// "算法/模式/補(bǔ)碼方式"Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);//選擇解密cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);//先進(jìn)行Base64解碼byte[] decodeBase64 = Base64Utils.decodeFromString(content);//根據(jù)待解密內(nèi)容進(jìn)行解密byte[] decrypted = cipher.doFinal(decodeBase64);//將字節(jié)數(shù)組轉(zhuǎn)成字符串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字節(jié) * 8位/字節(jié) = 128位。String random = RandomStringUtils.random(16, "abcdefghijklmnopqrstuvwxyz1234567890");System.out.println("隨機(jī)key:" + random);System.out.println();System.out.println("---------加密---------");String aesResult = encrypt("測試AES加密12", random);System.out.println("aes加密結(jié)果:" + aesResult);System.out.println();System.out.println("---------解密---------");String decrypt = decrypt(aesResult, random);System.out.println("aes解密結(jié)果:" + decrypt);System.out.println();System.out.println("--------AES_CBC加密解密---------");String cbcResult = encryptCBC("測試AES加密12456", random);System.out.println("aes_cbc加密結(jié)果:" + cbcResult);System.out.println();System.out.println("---------解密CBC---------");String cbcDecrypt = decryptCBC(cbcResult, random);System.out.println("aes解密結(jié)果:" + 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 BaseTypeHandler<String> {@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 修改表對應(yīng)實(shí)體類
設(shè)置 @TableName
注解的 autoResultMap
為 true,默認(rèn) 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 修改加密字段對應(yīng)屬性
設(shè)置 @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 {/*** 手機(jī)號碼*/@TableField(value = "PHONE", typeHandler = MyEncryptTypeHandler.class)private String phone;
}
4.3 修改 xml 使用 ResultMap
1)創(chuàng)建 ResultMap
映射,指定 typeHandler
;
2)查詢語句使用 ResultMap
返回。
<!-- 通用查詢映射結(jié)果 -->
<resultMap id="BaseResultMap" type="com.demo.model.UserInfo"><id column="ID" property="id" /><result column="ACCOUNT" property="staffCode" /><result column="PHONE" property="phone" typeHandler="com.demo.encrypt.MyEncryptTypeHandler" />
</resultMap><!-- 查詢?nèi)?-->
<select id="findAll" resultMap="BaseResultMap">SELECT * FROM t_user_info
</select>
4.4 修改 xml 中 el 表達(dá)式
設(shè)置好 4.1 和 4.2 就可以保證
修改前:
<!-- 更新手機(jī)號 -->
<update id="updatePhoneById">update t_user_info set phone = #{phone} where id = #{id}
</update><!-- 根據(jù)手機(jī)號查詢 -->
<select id="findByPhone" resultMap="BaseResultMap">SELECT * FROM t_user_info where phone = #{phone}
</select>
修改后:
<!-- 更新手機(jī)號 -->
<update id="updatePhoneById">update t_user_info set phone = #{phone, typeHandler=com.demo.encrypt.MyEncryptTypeHandler} where id = #{id}
</update><!-- 根據(jù)手機(jī)號查詢 -->
<select id="findByPhone" resultMap="BaseResultMap">SELECT * FROM t_user_info where phone = #{phone, typeHandler=com.demo.encrypt.MyEncryptTypeHandler}
</select>
5.測試結(jié)果
由于測試內(nèi)容較多,這里先直接展示測試結(jié)果,具體測試示例可以看 補(bǔ)充:測試實(shí)例
操作 | 實(shí)現(xiàn)方式 | 入?yún)?/th> | 測試結(jié)果 |
---|---|---|---|
SELECT | 原生SQL | 非加密字段 | 出參解密成功 |
SELECT | QueryWrapper | 非加密字段 | 出參解密成功 |
SELECT | 原生SQL | 加密字段 | 入?yún)⒓用艹晒?/font> |
SELECT | QueryWrapper | 加密字段 | 入?yún)⒓用苁?/font> |
UPDATE | 原生SQL | 加密字段 | 入?yún)⒓用艹晒?/font> |
UPDATE | UpdateWrapper | 加密字段 | 入?yún)⒓用苁?/font> |
UPDATE | LambdaUpdateWrapper | 加密字段 | 入?yún)⒓用艹晒?/font> |
UPDATE | updateById | 加密字段 | 入?yún)⒓用艹晒?/font> |
INSERT | Service | 加密字段 | 入?yún)⒓用艹晒?/font> |
說明:
- 官方的解答是 QueryWrapper、UpdateWrapper 底層是通過 @Param 來實(shí)現(xiàn)的,目前沒有做到入?yún)⒅С?typeHandler,如果做的話會(huì)影響性能。
6.MyBatis Plus 缺陷
-
QueryWrapper 不支持入?yún)⒓用?#xff1b;
-
UpdateWrapper 不支持入?yún)⒓用?#xff1b;
-
加密字段不支持模糊查詢。
補(bǔ)充:測試實(shí)例
1 查詢測試
1.1 查詢信息,SQL實(shí)現(xiàn)
@Test
public void getUserInfoTest1() {UserInfo userInfo = userInfoService.findByAccount("testAccount");System.out.println("userInfo:" + userInfo);System.out.println("phone:" + userInfo.getPhone());
}
測試結(jié)果:出參解密成功
1.2 查詢信息,QueryWrapper實(shí)現(xiàn)
@Test
public void getUserInfoTest2() {QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();wrapper.eq("account", "testAccount");List<UserInfo> users = userInfoService.list(wrapper);System.out.println("userInfo:" + users);System.out.println("phone:" + users.get(0).getPhone());
}
測試結(jié)果:出參解密成功
1.3 查詢信息,根據(jù)加密字段查詢,SQL實(shí)現(xiàn)
@Test
public void getUserInfoTest3() {UserInfo user = userInfoService.findByPhone("13888888888");System.out.println("userInfo:" + user);System.out.println("phone:" + user.getPhone());
}
(注意:入?yún)⑿枰褂胑l表達(dá)式指定 typeHandler)
測試結(jié)果:入?yún)⒓用艹晒?/font>
1.4 查詢信息,根據(jù)加密字段查詢,QueryWrapper實(shí)現(xiàn)
@Test
public void getUserInfoTest3() {QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();wrapper.lambda().eq(UserInfo::getPhone, "13888888888");List<UserInfo> users = userInfoService.list(wrapper);System.out.println("userInfo:" + users);System.out.println("phone:" + users.get(0).getPhone());
}
測試結(jié)果:入?yún)⒓用苁?#xff0c;QueryWrapper底層使用 @Param 實(shí)現(xiàn),無法像 SQL 實(shí)現(xiàn)一樣指定 typeHandler。
2.測試更新
2.1 更新信息,SQL實(shí)現(xiàn)
@Test
public void updateUserInfoTest1() {userInfoService.updatePhoneByAccount("testAccount", "13888888888");
}
測試結(jié)果:入?yún)⒓用艹晒?/font>
2.2 更新信息,UpdateWrapper實(shí)現(xiàn)
@Test
public void updateUserInfoTest2() {UpdateWrapper<UserInfo> wrapper = new UpdateWrapper<>();wrapper.set("phone", "13888888888");wrapper.eq("account", "testAccount");userInfoService.update(wrapper);getUserInfoTest1();
}
測試結(jié)果:入?yún)⒓用苁?#xff0c;UpdateWrapper底層使用 @Param 實(shí)現(xiàn),無法像 SQL 實(shí)現(xiàn)一樣指定 typeHandler。
2.3 更新信息,LambdaUpdateWrapper實(shí)現(xiàn)
@Test
public void updateUserInfoTest3() {LambdaUpdateWrapper<UserInfo> wrapper = Wrappers.<UserInfo>lambdaUpdate().set(UserInfo::getPhone, "13888888888", "typeHandler=com.demo.encrypt.MyEncryptTypeHandler");wrapper.eq(UserInfo::getAccount, "testAccount");userInfoService.update(wrapper);getUserInfoTest1();
}
測試結(jié)果:入?yún)⒓用艹晒?/font>
2.4 更新信息,updateById實(shí)現(xiàn)
@Test
public void updateUserInfoTest4() {UserInfo userInfo = userInfoService.findByAccount("testAccount");userInfo.setPhone("13888888888");userInfoService.updateById(userInfo);
}
測試結(jié)果:入?yún)⒓用艹晒?/font>
3.測試插入
7.3.1 插入信息,SQL實(shí)現(xiàn)
@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());
}
測試結(jié)果:入?yún)⒓用艹晒?/font>
3.2 插入信息,Service實(shí)現(xiàn)
@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());
}
測試結(jié)果:入?yún)⒓用艹晒?/font>
整理完畢,完結(jié)撒花~
參考地址:
1.mybaits plus 字段加密與解密,https://blog.csdn.net/qq_21134059/article/details/121752978
2.mybatis plus 官方問題頁面,https://github.com/baomidou/mybatis-plus/issues
3.更新時(shí)自定義的TypeHandler不生效,https://github.com/baomidou/mybatis-plus/issues/794
4.lambdaUpdate() 無法更新Json對象字段,https://github.com/baomidou/mybatis-plus/issues/5031
5.LambdaUpdateWrapper不支持自定義BaseTypeHandler,https://github.com/baomidou/mybatis-plus/issues/3317