This commit is contained in:
kk
2026-07-07 18:01:21 +08:00
commit b259f94d4b
1088 changed files with 121778 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springblade</groupId>
<artifactId>BladeX-Tool</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-starter-mybatis-encrypt</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<properties>
<module.name>org.springblade.blade.starter.mybatis.encrypt</module.name>
</properties>
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-mybatis</artifactId>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,59 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.algorithm;
/**
* 加密类型枚举
*
* @author BladeX
*/
public enum Algorithm {
/**
* SM4国密加密
*/
SM4,
/**
* AES加密
*/
AES,
/**
* DES加密
*/
DES,
/**
* Base64编码(非加密,不建议用于安全要求高的场景)
*/
BASE64,
/**
* 自定义加密
*/
CUSTOM
}
@@ -0,0 +1,59 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.algorithm;
/**
* 加密器统一接口
*
* @author BladeX
*/
public interface EncryptAlgorithm {
/**
* 获取加密器类型
*
* @return 加密算法类型
*/
String type();
/**
* 加密
*
* @param plainText 明文
* @param secretKey 密钥
* @return 密文
*/
String encrypt(String plainText, String secretKey);
/**
* 解密
*
* @param cipherText 密文
* @param secretKey 密钥
* @return 明文
*/
String decrypt(String cipherText, String secretKey);
}
@@ -0,0 +1,105 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.algorithm;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.encrypt.props.EncryptProperties;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 加密器工厂
*
* @author BladeX
*/
@AllArgsConstructor
public class EncryptAlgorithmFactory {
/**
* 加密器集合
*/
private final List<EncryptAlgorithm> algorithms;
/**
* 配置属性
*/
private final EncryptProperties properties;
/**
* 加密器缓存池,使用 ConcurrentHashMap 保证线程安全
*/
private static final Map<String, EncryptAlgorithm> ENCRYPTOR_POOL = new ConcurrentHashMap<>();
/**
* 根据算法类型获取加密器,如果缓存中不存在,则尝试创建
*
* @param algorithm 算法类型
* @return 加密器实例
*/
public EncryptAlgorithm create(Algorithm algorithm) {
return create(algorithm.name());
}
/**
* 根据算法类型获取加密器
*
* @param algorithmType 算法类型字符串
* @return 加密器实例
*/
public EncryptAlgorithm create(String algorithmType) {
// 使用 computeIfAbsent 实现延迟加载
return ENCRYPTOR_POOL.computeIfAbsent(algorithmType, this::initializeAlgorithm);
}
/**
* 根据配置获取默认加密器
*
* @return 默认加密器实例
*/
public EncryptAlgorithm create() {
Algorithm algorithm = properties.getAlgorithm();
if (algorithm == null) {
algorithm = Algorithm.AES; // 默认使用AES
}
return create(algorithm);
}
/**
* 初始化加密器
*
* @param algorithmType 算法类型
* @return 初始化的加密器
*/
private EncryptAlgorithm initializeAlgorithm(String algorithmType) {
return algorithms.stream()
.filter(algorithm -> algorithm.type().equals(algorithmType))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(
String.format("Unsupported encryption algorithm: %s", algorithmType)));
}
}
@@ -0,0 +1,69 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.algorithm.provider;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.mp.encrypt.algorithm.Algorithm;
import org.springblade.core.mp.encrypt.algorithm.EncryptAlgorithm;
import org.springblade.core.tool.utils.AesUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
/**
* AES加密器
*
* @author BladeX
*/
@Slf4j
@Component
public class AesAlgorithm implements EncryptAlgorithm {
@Override
public String type() {
return Algorithm.AES.name();
}
@Override
public String encrypt(String plainText, String secretKey) {
if (StringUtil.isBlank(plainText)) {
return plainText;
}
return AesUtil.encryptToHex(plainText, secretKey);
}
@Override
public String decrypt(String cipherText, String secretKey) {
if (StringUtil.isBlank(cipherText)) {
return cipherText;
}
try {
return AesUtil.decryptFormHexToString(cipherText, secretKey);
} catch (Exception e) {
log.error("AES decryption failed, returning original value: {}", e.getMessage());
return cipherText;
}
}
}
@@ -0,0 +1,71 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.algorithm.provider;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.mp.encrypt.algorithm.Algorithm;
import org.springblade.core.mp.encrypt.algorithm.EncryptAlgorithm;
import org.springblade.core.tool.utils.Base64Util;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
/**
* Base64编码器(非真正加密,仅用于编码)
*
* @author BladeX
*/
@Slf4j
@Component
public class Base64Algorithm implements EncryptAlgorithm {
@Override
public String type() {
return Algorithm.BASE64.name();
}
@Override
public String encrypt(String plainText, String secretKey) {
if (StringUtil.isBlank(plainText)) {
return plainText;
}
// Base64不需要密钥
return Base64Util.encode(plainText);
}
@Override
public String decrypt(String cipherText, String secretKey) {
if (StringUtil.isBlank(cipherText)) {
return cipherText;
}
try {
// Base64不需要密钥
return Base64Util.decode(cipherText);
} catch (Exception e) {
log.error("Base64 decoding failed, returning original value: {}", e.getMessage());
return cipherText;
}
}
}
@@ -0,0 +1,69 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.algorithm.provider;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.mp.encrypt.algorithm.Algorithm;
import org.springblade.core.mp.encrypt.algorithm.EncryptAlgorithm;
import org.springblade.core.tool.utils.DesUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
/**
* DES加密器
*
* @author BladeX
*/
@Slf4j
@Component
public class DesAlgorithm implements EncryptAlgorithm {
@Override
public String type() {
return Algorithm.DES.name();
}
@Override
public String encrypt(String plainText, String secretKey) {
if (StringUtil.isBlank(plainText)) {
return plainText;
}
return DesUtil.encryptToHex(plainText, secretKey);
}
@Override
public String decrypt(String cipherText, String secretKey) {
if (StringUtil.isBlank(cipherText)) {
return cipherText;
}
try {
return DesUtil.decryptFormHex(cipherText, secretKey);
} catch (Exception e) {
log.error("DES decryption failed, returning original value: {}", e.getMessage());
return cipherText;
}
}
}
@@ -0,0 +1,69 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.algorithm.provider;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.mp.encrypt.algorithm.Algorithm;
import org.springblade.core.mp.encrypt.algorithm.EncryptAlgorithm;
import org.springblade.core.tool.utils.SM4Util;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
/**
* SM4国密加密器
*
* @author BladeX
*/
@Slf4j
@Component
public class Sm4Algorithm implements EncryptAlgorithm {
@Override
public String type() {
return Algorithm.SM4.name();
}
@Override
public String encrypt(String plainText, String secretKey) {
if (StringUtil.isBlank(plainText)) {
return plainText;
}
return SM4Util.encrypt(plainText, secretKey);
}
@Override
public String decrypt(String cipherText, String secretKey) {
if (StringUtil.isBlank(cipherText)) {
return cipherText;
}
try {
return SM4Util.decrypt(cipherText, secretKey);
} catch (Exception e) {
log.error("SM4 decryption failed, returning original value: {}", e.getMessage());
return cipherText;
}
}
}
@@ -0,0 +1,44 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.annotation;
import com.baomidou.mybatisplus.annotation.TableField;
import org.springblade.core.mp.encrypt.handler.EncryptTypeHandler;
import java.lang.annotation.*;
/**
* Mybatis字段加密注解
*
* @author BladeX
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@TableField(typeHandler = EncryptTypeHandler.class)
public @interface FieldEncrypt {
}
@@ -0,0 +1,59 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.annotation;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import java.lang.annotation.*;
/**
* Mybatis字段加密模糊查询注解
*
* @author BladeX
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@TableField(fill = FieldFill.INSERT_UPDATE)
public @interface SearchableFieldEncrypt {
/**
* 是否启用模糊查询支持
* 当设置为true时,字段值会使用滑动窗口加密存储,支持like查询
*
* @return true-支持模糊查询,false-不支持模糊查询
*/
boolean enabled() default true;
/**
* 模糊查询时,Entity的加密字段名称后缀
*
* @return 加密字段名称后缀,默认"Enc"
*/
String encName() default "Enc";
}
@@ -0,0 +1,75 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.config;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.mp.encrypt.algorithm.EncryptAlgorithm;
import org.springblade.core.mp.encrypt.algorithm.EncryptAlgorithmFactory;
import org.springblade.core.mp.encrypt.exception.EncryptException;
import org.springblade.core.mp.encrypt.props.EncryptProperties;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import java.util.List;
/**
* 字段加密自动配置
*
* @author BladeX
*/
@Slf4j
@AutoConfiguration
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = EncryptProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class EncryptAutoConfiguration {
private final EncryptProperties properties;
@PostConstruct
public void init() {
// 启动时验证配置
if (StringUtil.isBlank(properties.getSecretKey())) {
throw new EncryptException(
"MyBatis field encryption is enabled but no secret key is configured. " +
"Please configure 'blade.mybatis-plus.encrypt.secret-key' in application.yml " +
"or disable encryption by setting 'blade.mybatis-plus.encrypt.enabled=false'");
}
log.info("MyBatis field encryption enabled successfully with algorithm: {}", properties.getAlgorithm());
}
@Bean
@ConditionalOnMissingBean
public EncryptAlgorithmFactory encryptAlgorithmFactory(List<EncryptAlgorithm> encryptors) {
return new EncryptAlgorithmFactory(encryptors, properties);
}
}
@@ -0,0 +1,44 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.config;
import org.springblade.core.launch.props.BladePropertySource;
import org.springblade.core.mp.encrypt.props.EncryptProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* 字段加密自动配置
*
* @author BladeX
*/
@AutoConfiguration
@EnableConfigurationProperties(EncryptProperties.class)
@BladePropertySource(value = "classpath:/blade-mybatis-encrypt.yml")
public class EncryptConfiguration {
}
@@ -0,0 +1,58 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.exception;
import java.io.Serial;
/**
* 加密异常
*
* @author BladeX
*/
public class EncryptException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 构造函数
*
* @param message 错误消息
*/
public EncryptException(String message) {
super(message);
}
/**
* 构造函数
*
* @param message 错误消息
* @param cause 原因
*/
public EncryptException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,102 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springblade.core.mp.encrypt.annotation.SearchableFieldEncrypt;
import org.springblade.core.mp.encrypt.utils.SlidingUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Optional;
/**
* 加密搜索字段自定义填充
*
* @author Chill
*/
@Slf4j
@Component
public class EncryptMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
processEncryptFields(metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
processEncryptFields(metaObject);
}
/**
* 处理带有SearchableEncryptField注解且开启模糊查询的字段
*
* @param metaObject 元对象
*/
private void processEncryptFields(MetaObject metaObject) {
if (metaObject == null || metaObject.getOriginalObject() == null) {
return;
}
Field[] declaredFields = metaObject.getOriginalObject().getClass().getDeclaredFields();
Arrays.stream(declaredFields).forEach(field -> processField(field, metaObject));
}
/**
* 处理单个字段
*
* @param field 字段
* @param metaObject 元对象
*/
private void processField(Field field, MetaObject metaObject) {
Optional.ofNullable(field.getAnnotation(SearchableFieldEncrypt.class))
.filter(SearchableFieldEncrypt::enabled)
.ifPresent(annotation -> processSearchableField(field, annotation, metaObject));
}
/**
* 处理可搜索的加密字段
*
* @param field 字段
* @param annotation 注解
* @param metaObject 元对象
*/
private void processSearchableField(Field field, SearchableFieldEncrypt annotation, MetaObject metaObject) {
String sourceFieldName = StringUtil.replace(field.getName(), annotation.encName(), StringPool.EMPTY);
Optional.ofNullable(this.getFieldValByName(sourceFieldName, metaObject))
.filter(String.class::isInstance)
.map(String.class::cast)
.map(SlidingUtil::segment)
.ifPresent(segmentedValue -> this.setFieldValByName(field.getName(), segmentedValue, metaObject));
}
}
@@ -0,0 +1,78 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.handler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.springblade.core.mp.encrypt.utils.EncryptUtil;
import org.springblade.core.tool.utils.StringUtil;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* MyBatis字段加密TypeHandler
*
* @author BladeX
*/
@Slf4j
public class EncryptTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
if (StringUtil.isBlank(parameter)) {
ps.setString(i, parameter);
return;
}
try {
ps.setString(i, EncryptUtil.encrypt(parameter));
} catch (Exception e) {
log.error("Field encryption failed: {}", e.getMessage(), e);
throw new SQLException("Field encryption failed", e);
}
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return EncryptUtil.decrypt(value);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return EncryptUtil.decrypt(value);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
return EncryptUtil.decrypt(value);
}
}
@@ -0,0 +1,62 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.props;
import lombok.Data;
import org.springblade.core.mp.encrypt.algorithm.Algorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 加密配置属性
*
* @author BladeX
*/
@Data
@ConfigurationProperties(prefix = EncryptProperties.PREFIX)
public class EncryptProperties {
public static final String PREFIX = "blade.mybatis-plus.encrypt";
/**
* 是否启用字段加密功能
*/
private boolean enabled = true;
/**
* 滑动窗口大小,用于字段加密的模糊查询
*/
private int windowSize = 3;
/**
* 加密算法类型
*/
private Algorithm algorithm = Algorithm.AES;
/**
* 加密算法密钥
*/
private String secretKey;
}
@@ -0,0 +1,97 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.support;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.mp.encrypt.wrapper.EncryptQueryWrapper;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.SqlKeyword;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.ClassUtil;
import org.springblade.core.tool.utils.StringPool;
import java.util.Map;
import java.util.Set;
/**
* 加密分页工具
*
* @author Chill
*/
public class EncryptCondition extends Condition {
/**
* 获取mybatis plus中的QueryWrapper
*
* @param entity 实体
* @param <T> 类型
* @return QueryWrapper
*/
public static <T> EncryptQueryWrapper<T> getEncryptQueryWrapper(T entity) {
return new EncryptQueryWrapper<>(entity);
}
/**
* 获取mybatis plus中的QueryWrapper
*
* @param query 查询条件
* @param clazz 实体类
* @param <T> 类型
* @return QueryWrapper
*/
public static <T> EncryptQueryWrapper<T> getEncryptQueryWrapper(Map<String, Object> query, Class<T> clazz) {
Kv exclude = Kv.create().set(TokenConstant.AUTH_HEADER, TokenConstant.AUTH_HEADER)
.set("current", "current").set("size", "size").set("ascs", "ascs").set("descs", "descs");
return getEncryptQueryWrapper(query, exclude, clazz);
}
/**
* 获取mybatis plus中的QueryWrapper
*
* @param query 查询条件
* @param exclude 排除的查询条件
* @param clazz 实体类
* @param <T> 类型
* @return QueryWrapper
*/
public static <T> EncryptQueryWrapper<T> getEncryptQueryWrapper(Map<String, Object> query, Map<String, Object> exclude, Class<T> clazz) {
// 移除 query 中在 exclude 里有的字段
exclude.forEach((k, v) -> query.remove(k));
// 获取 clazz 的所有字段名,构造成 Set
Set<String> fieldNames = ClassUtil.getClassFieldNames(clazz);
// 移除 query 中不属于 clazz 字段的键
query.keySet().removeIf(key -> !fieldNames.contains(key.split(StringPool.UNDERSCORE)[0]));
// 构造 QueryWrapper
EncryptQueryWrapper<T> qw = new EncryptQueryWrapper<>();
qw.setEntity(BeanUtil.newInstance(clazz));
// 使用 SQL 关键字条件构建
SqlKeyword.buildCondition(query, qw);
// 返回构造类
return qw;
}
}
@@ -0,0 +1,179 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.utils;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.mp.encrypt.algorithm.Algorithm;
import org.springblade.core.mp.encrypt.algorithm.EncryptAlgorithm;
import org.springblade.core.mp.encrypt.algorithm.EncryptAlgorithmFactory;
import org.springblade.core.mp.encrypt.exception.EncryptException;
import org.springblade.core.mp.encrypt.props.EncryptProperties;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringUtil;
/**
* 加密工具类 - 提供统一的密钥管理和加密解密方法
*
* @author BladeX
*/
@Slf4j
public class EncryptUtil {
private static volatile EncryptProperties encryptProperties;
private static volatile EncryptAlgorithmFactory encryptAlgorithmFactory;
private static final int DEFAULT_WINDOW_SIZE = 3;
/**
* 延迟加载获取加密配置属性
*
* @return 加密配置属性
*/
private static EncryptProperties getEncryptProperties() {
if (encryptProperties == null) {
synchronized (EncryptUtil.class) {
if (encryptProperties == null) {
encryptProperties = SpringUtil.getBean(EncryptProperties.class);
}
}
}
return encryptProperties;
}
/**
* 延迟加载获取加密器工厂
*
* @return 加密器工厂
*/
private static EncryptAlgorithmFactory getEncryptFactory() {
if (encryptAlgorithmFactory == null) {
synchronized (EncryptUtil.class) {
if (encryptAlgorithmFactory == null) {
encryptAlgorithmFactory = SpringUtil.getBean(EncryptAlgorithmFactory.class);
}
}
}
return encryptAlgorithmFactory;
}
/**
* 获取加密算法
*
* @return 加密算法
*/
public static EncryptAlgorithm getAlgorithm() {
EncryptProperties properties = getEncryptProperties();
Algorithm algorithm = properties.getAlgorithm();
return getEncryptFactory().create(algorithm.name());
}
/**
* 获取加密密钥
*
* @return 加密密钥
*/
public static String getSecretKey() {
EncryptProperties properties = getEncryptProperties();
String secretKey = properties.getSecretKey();
if (StringUtil.isBlank(secretKey)) {
throw new EncryptException(
"Encryption key not configured. Please set 'blade.mybatis-plus.encrypt.secret-key' in configuration file");
}
if (secretKey.length() != 32) {
throw new EncryptException(
"Encryption key must be exactly 32 characters long, current length: " + secretKey.length());
}
return secretKey;
}
/**
* 获取滑动窗口大小
*
* @return 滑动窗口大小
*/
public static int getWindowSize() {
EncryptProperties properties = getEncryptProperties();
return Func.toInt(properties.getWindowSize(), DEFAULT_WINDOW_SIZE);
}
/**
* 加密字符串为Base64格式(使用配置的算法)
*
* @param plainText 明文
* @return 加密后的Base64字符串
*/
public static String encrypt(String plainText) {
if (StringUtil.isBlank(plainText)) {
return plainText;
}
EncryptAlgorithm algorithm = getAlgorithm();
return algorithm.encrypt(plainText, getSecretKey());
}
/**
* 加密字符串为Base64格式(指定密钥)
*
* @param plainText 明文
* @param secretKey 密钥
* @return 加密后的Base64字符串
*/
public static String encrypt(String plainText, String secretKey) {
if (StringUtil.isBlank(plainText)) {
return plainText;
}
EncryptAlgorithm algorithm = getAlgorithm();
return algorithm.encrypt(plainText, secretKey);
}
/**
* 解密Base64格式的密文(使用配置的算法)
*
* @param cipherText Base64格式的密文
* @return 解密后的明文
*/
public static String decrypt(String cipherText) {
if (StringUtil.isBlank(cipherText)) {
return cipherText;
}
EncryptAlgorithm algorithm = getAlgorithm();
return algorithm.decrypt(cipherText, getSecretKey());
}
/**
* 解密Base64格式的密文(指定密钥)
*
* @param cipherText Base64格式的密文
* @param secretKey 密钥
* @return 解密后的明文
*/
public static String decrypt(String cipherText, String secretKey) {
if (StringUtil.isBlank(cipherText)) {
return cipherText;
}
EncryptAlgorithm algorithm = getAlgorithm();
return algorithm.decrypt(cipherText, secretKey);
}
}
@@ -0,0 +1,100 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.mp.encrypt.utils;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* 滑动窗口加密工具类
* 用于支持加密字段的模糊查询
*
* @author BladeX
*/
public class SlidingUtil {
/**
* 使用默认窗口大小进行滑动窗口加密
*
* @param text 原始文本
* @param secretKey 加密密钥
* @return 加密后的拼接字符串
*/
public static String segment(String text, String secretKey) {
return segment(text, EncryptUtil.getWindowSize(), secretKey);
}
/**
* 使用默认窗口大小和默认密钥进行滑动窗口加密
*
* @param text 原始文本
* @return 加密后的拼接字符串
*/
public static String segment(String text) {
return segment(text, EncryptUtil.getWindowSize(), EncryptUtil.getSecretKey());
}
/**
* 使用指定窗口大小和默认密钥进行滑动窗口加密
*
* @param text 原始文本
* @param windowSize 窗口大小
* @return 加密后的拼接字符串
*/
public static String segment(String text, int windowSize) {
return segment(text, windowSize, EncryptUtil.getSecretKey());
}
/**
* 按滑动窗口方式将字符串分割为固定长度的子串并加密
*
* @param text 原始文本
* @param windowSize 窗口大小
* @param secretKey 加密密钥
* @return 加密后的拼接字符串
*/
public static String segment(String text, int windowSize, String secretKey) {
if (StringUtil.isBlank(text) || StringUtil.isBlank(secretKey)) {
return null;
}
// 如果文本长度小于窗口大小,直接加密整个文本
if (text.length() < windowSize || windowSize <= 0) {
return EncryptUtil.encrypt(text, secretKey);
}
int maxStartIndex = text.length() - windowSize;
// 使用 Stream 进行滑动窗口处理
return IntStream.rangeClosed(0, maxStartIndex)
.mapToObj(i -> text.substring(i, i + windowSize))
.map(subStr -> EncryptUtil.encrypt(subStr, secretKey))
.collect(Collectors.joining(StringPool.COMMA));
}
}
@@ -0,0 +1,336 @@
/*
* Copyright (c) 2011-2025, baomidou (jobob@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.mp.encrypt.wrapper;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.SharedString;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import org.springblade.core.mp.encrypt.annotation.SearchableFieldEncrypt;
import org.springblade.core.mp.encrypt.handler.EncryptTypeHandler;
import org.springblade.core.mp.encrypt.utils.EncryptUtil;
import org.springblade.core.mp.encrypt.utils.SlidingUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
/**
* 自定义加密查询Wrapper
* 自动对查询条件进行加密处理
*
* @author hubin miemie HCL BladeX
*/
public class EncryptLambdaQueryWrapper<T> extends LambdaQueryWrapper<T> {
private Class<T> entityClass;
private SharedString sqlSelect = new SharedString();
/**
* 缓存字段是否需要加密的信息
*/
private static final Map<String, Boolean> FIELD_ENCRYPT_CACHE = new ConcurrentHashMap<>();
public EncryptLambdaQueryWrapper() {
super();
}
public EncryptLambdaQueryWrapper(T entity) {
super.setEntity(entity);
super.initNeed();
}
public EncryptLambdaQueryWrapper(Class<T> entityClass) {
super.setEntityClass(entityClass);
super.initNeed();
}
/**
* 用于从 EncryptQueryWrapper 转换的构造方法
*/
EncryptLambdaQueryWrapper(T entity, Class<T> entityClass, SharedString sqlSelect, AtomicInteger paramNameSeq,
Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments, SharedString paramAlias,
SharedString lastSql, SharedString sqlComment, SharedString sqlFirst) {
super.setEntity(entity);
super.setEntityClass(entityClass);
this.paramNameSeq = paramNameSeq;
this.paramNameValuePairs = paramNameValuePairs;
this.expression = mergeSegments;
this.sqlSelect = sqlSelect;
this.paramAlias = paramAlias;
this.lastSql = lastSql;
this.sqlComment = sqlComment;
this.sqlFirst = sqlFirst;
this.entityClass = entityClass;
}
@Override
public LambdaQueryWrapper<T> select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) {
if (entityClass == null) {
entityClass = getEntityClass();
} else {
setEntityClass(entityClass);
}
this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(entityClass).chooseSelect(predicate));
return typedThis;
}
protected LambdaQueryWrapper<T> doSelect(boolean condition, List<SFunction<T, ?>> columns) {
if (condition && CollectionUtils.isNotEmpty(columns)) {
this.sqlSelect.setStringValue(columnsToString(false, columns));
}
return typedThis;
}
@Override
public String getSqlSelect() {
return sqlSelect.getStringValue();
}
@Override
public LambdaQueryWrapper<T> eq(boolean condition, SFunction<T, ?> column, Object val) {
if (condition && val != null) {
Object encryptedVal = applyConditionalEncryption(column, val);
return super.eq(true, column, encryptedVal);
}
return super.eq(condition, column, val);
}
@Override
public LambdaQueryWrapper<T> ne(boolean condition, SFunction<T, ?> column, Object val) {
if (condition && val != null) {
Object encryptedVal = applyConditionalEncryption(column, val);
return super.ne(true, column, encryptedVal);
}
return super.ne(condition, column, val);
}
@Override
public LambdaQueryWrapper<T> in(boolean condition, SFunction<T, ?> column, Object... values) {
if (condition && values != null && values.length > 0) {
Object[] encryptedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
encryptedValues[i] = applyConditionalEncryption(column, values[i]);
}
return super.in(true, column, encryptedValues);
}
return super.in(condition, column, values);
}
/**
* 模糊查询(使用滑动窗口加密)
* 注意:需要配合特殊的数据库字段存储方式使用
*/
@Override
public LambdaQueryWrapper<T> like(SFunction<T, ?> column, Object val) {
return like(true, column, val, EncryptUtil.getWindowSize());
}
/**
* 模糊查询(使用滑动窗口加密)
* 注意:需要配合特殊的数据库字段存储方式使用
*/
@Override
public LambdaQueryWrapper<T> like(boolean condition, SFunction<T, ?> column, Object val) {
return like(condition, column, val, EncryptUtil.getWindowSize());
}
/**
* 模糊查询(使用滑动窗口加密)
*
* @param condition 条件
* @param column 列
* @param val 值
* @param windowSize 窗口大小
*/
public LambdaQueryWrapper<T> like(boolean condition, SFunction<T, ?> column, Object val, int windowSize) {
if (condition && val != null && requiresEncryption(column)) {
String strVal = val.toString();
String encryptedVal = SlidingUtil.segment(strVal, windowSize);
return super.like(true, column, encryptedVal);
}
return super.like(condition, column, val);
}
@Override
public Class<T> getEntityClass() {
if (this.entityClass != null) {
return this.entityClass;
}
return super.getEntityClass();
}
/**
* 用于生成嵌套 sql
* <p>故 sqlSelect 不向下传递</p>
*/
@Override
protected EncryptLambdaQueryWrapper<T> instance() {
return new EncryptLambdaQueryWrapper<>(getEntity(), getEntityClass(), null, paramNameSeq, paramNameValuePairs,
new MergeSegments(), paramAlias, SharedString.emptyString(), SharedString.emptyString(), SharedString.emptyString());
}
@Override
public void clear() {
super.clear();
sqlSelect.toNull();
}
/**
* 对字段值进行条件性加密处理
*
* @param column 字段函数引用
* @param value 待处理的值
* @return 加密后的值或原值
*/
private Object applyConditionalEncryption(SFunction<T, ?> column, Object value) {
if (value == null || StringUtil.isBlank(value.toString())) {
return value;
}
Class<T> currentEntityClass = getEntityClassFromColumn(column);
if (currentEntityClass == null) {
return value;
}
return requiresEncryption(column)
? EncryptUtil.encrypt(value.toString())
: value;
}
/**
* 检查字段是否启用加密功能
*
* @param column 字段函数引用
* @return true 如果字段需要加密,否则返回 false
*/
private boolean requiresEncryption(SFunction<T, ?> column) {
Class<T> currentEntityClass = getEntityClassFromColumn(column);
if (currentEntityClass == null) {
return false;
}
String fieldName = resolveLambdaFieldName(column);
if (fieldName == null) {
return false;
}
String cacheKey = buildCacheKey(fieldName);
return FIELD_ENCRYPT_CACHE.computeIfAbsent(cacheKey, key ->
detectEncryptionHandler(fieldName) || detectEncryptFieldAnnotation(fieldName));
}
/**
* 构建缓存键
*/
private String buildCacheKey(String fieldName) {
return getEntityClass().getName() + StringPool.DOT + fieldName;
}
/**
* 检测字段是否配置了加密处理器
*/
private boolean detectEncryptionHandler(String fieldName) {
try {
Field field = getEntityClass().getDeclaredField(fieldName);
TableField tableField = field.getAnnotation(TableField.class);
return tableField != null
&& tableField.typeHandler() != null
&& tableField.typeHandler().getName().contains(EncryptTypeHandler.class.getName());
} catch (NoSuchFieldException | SecurityException e) {
// 字段不存在或访问受限,默认不加密
return false;
}
}
/**
* 检测字段是否配置了EncryptField注解且开启了模糊查询
*
* @param fieldName 字段名称
* @return true 如果字段配置了EncryptField注解且开启了模糊查询,否则返回 false
*/
private boolean detectEncryptFieldAnnotation(String fieldName) {
try {
Field field = getEntityClass().getDeclaredField(fieldName);
SearchableFieldEncrypt searchableFieldEncrypt = field.getAnnotation(SearchableFieldEncrypt.class);
return searchableFieldEncrypt != null && searchableFieldEncrypt.enabled();
} catch (NoSuchFieldException | SecurityException e) {
// 字段不存在或访问受限,默认不启用模糊查询
return false;
}
}
/**
* 解析Lambda表达式获取字段名称
*
* @param column Lambda表达式函数引用
* @return 字段名称,解析失败返回null
*/
private String resolveLambdaFieldName(SFunction<T, ?> column) {
try {
Method method = column.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(true);
SerializedLambda serializedLambda = (SerializedLambda) method.invoke(column);
String methodName = serializedLambda.getImplMethodName();
return extractFieldFromAccessor(methodName);
} catch (ReflectiveOperationException e) {
// Lambda表达式解析失败
return null;
}
}
/**
* 从访问器方法名中提取字段名
*
* @param accessorName getter/setter方法名
* @return 字段名
*/
private String extractFieldFromAccessor(String accessorName) {
if (accessorName.startsWith("get") && accessorName.length() > 3) {
return StringUtil.firstCharToLower(accessorName.substring(3));
} else if (accessorName.startsWith("is") && accessorName.length() > 2) {
return StringUtil.firstCharToLower(accessorName.substring(2));
}
return accessorName;
}
/**
* 通过SFunction解析出实体类的Class
*
* @param column 字段函数引用
* @return 实体类Class
*/
@SuppressWarnings("unchecked")
private Class<T> getEntityClassFromColumn(SFunction<T, ?> column) {
if (this.entityClass != null) {
return this.entityClass;
}
this.entityClass = (Class<T>) LambdaUtils.extract(column).getInstantiatedClass();
return this.entityClass;
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2011-2025, baomidou (jobob@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.mp.encrypt.wrapper;
import com.baomidou.mybatisplus.core.conditions.SharedString;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 自定义加密查询Wrapper
* 主要用于提供lambda()方法转换到EncryptLambdaQueryWrapper
*
* @author hubin miemie HCL BladeX
*/
public class EncryptQueryWrapper<T> extends QueryWrapper<T> {
public EncryptQueryWrapper() {
super();
}
public EncryptQueryWrapper(T entity) {
super.setEntity(entity);
super.initNeed();
}
public EncryptQueryWrapper(Class<T> entityClass) {
super.setEntityClass(entityClass);
super.initNeed();
}
public EncryptQueryWrapper(T entity, String... columns) {
super.setEntity(entity);
super.initNeed();
this.select(columns);
}
/**
* 非对外公开的构造方法,只用于生产嵌套 sql
*/
private EncryptQueryWrapper(T entity, Class<T> entityClass, AtomicInteger paramNameSeq,
Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments, SharedString paramAlias,
SharedString lastSql, SharedString sqlComment, SharedString sqlFirst) {
super.setEntity(entity);
super.setEntityClass(entityClass);
this.paramNameSeq = paramNameSeq;
this.paramNameValuePairs = paramNameValuePairs;
this.expression = mergeSegments;
this.paramAlias = paramAlias;
this.lastSql = lastSql;
this.sqlComment = sqlComment;
this.sqlFirst = sqlFirst;
}
/**
* 返回一个支持 lambda 函数写法的加密 wrapper
*/
@Override
public EncryptLambdaQueryWrapper<T> lambda() {
return new EncryptLambdaQueryWrapper<>(getEntity(), getEntityClass(), sqlSelect, paramNameSeq, paramNameValuePairs,
expression, paramAlias, lastSql, sqlComment, sqlFirst);
}
/**
* 用于生成嵌套 sql
* <p>
* 故 sqlSelect 不向下传递
* </p>
*/
@Override
protected EncryptQueryWrapper<T> instance() {
return new EncryptQueryWrapper<>(getEntity(), getEntityClass(), paramNameSeq, paramNameValuePairs, new MergeSegments(),
paramAlias, SharedString.emptyString(), SharedString.emptyString(), SharedString.emptyString());
}
}
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2011-2025, baomidou (jobob@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.mp.encrypt.wrapper;
/**
* EncryptWrappers
*
* @author hubin miemie HCL BladeX
*/
public final class EncryptWrappers {
/**
* 获取 QueryWrapper&lt;T&gt;
*
* @param <T> 实体类泛型
* @return QueryWrapper&lt;T&gt;
*/
public static <T> EncryptQueryWrapper<T> query() {
return new EncryptQueryWrapper<>();
}
/**
* 获取 QueryWrapper&lt;T&gt;
*
* @param entity 实体类
* @param <T> 实体类泛型
* @return QueryWrapper&lt;T&gt;
*/
public static <T> EncryptQueryWrapper<T> query(T entity) {
return new EncryptQueryWrapper<>(entity);
}
/**
* 获取 QueryWrapper&lt;T&gt;
*
* @param entityClass 实体类class
* @param <T> 实体类泛型
* @return QueryWrapper&lt;T&gt;
*/
public static <T> EncryptQueryWrapper<T> query(Class<T> entityClass) {
return new EncryptQueryWrapper<>(entityClass);
}
/**
* 获取 LambdaQueryWrapper&lt;T&gt;
*
* @return LambdaQueryWrapper&lt;T&gt;
*/
public static <T> EncryptLambdaQueryWrapper<T> lambdaQuery() {
return new EncryptLambdaQueryWrapper<>();
}
/**
* 获取 LambdaQueryWrapper&lt;T&gt;
*
* @param entity 实体类
* @param <T> 实体类泛型
* @return LambdaQueryWrapper&lt;T&gt;
*/
public static <T> EncryptLambdaQueryWrapper<T> lambdaQuery(T entity) {
return new EncryptLambdaQueryWrapper<>(entity);
}
/**
* 获取 LambdaQueryWrapper&lt;T&gt;
*
* @param entityClass 实体类class
* @param <T> 实体类泛型
* @return LambdaQueryWrapper&lt;T&gt;
*/
public static <T> EncryptLambdaQueryWrapper<T> lambdaQuery(Class<T> entityClass) {
return new EncryptLambdaQueryWrapper<>(entityClass);
}
}
@@ -0,0 +1,11 @@
blade:
mybatis-plus:
encrypt:
# 是否启用加密功能
enabled: true
# 滑动窗口大小,用于加密字段模糊搜索
window-size: 3
# 加密算法类型
algorithm: sm4
# 加密算法密钥,必须配置
secret-key: ${BLADE_MYBATIS_PLUS_ENCRYPT_SECRET_KEY:}