refactor(encrypt): 拆分字段加密、API 加密模块

This commit is contained in:
2025-08-20 21:44:40 +08:00
parent e5002b8bfc
commit e9bf92ea1f
46 changed files with 525 additions and 343 deletions

View File

@@ -0,0 +1,31 @@
<?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>top.continew.starter</groupId>
<artifactId>continew-starter-encrypt</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-encrypt-field</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>ContiNew Starter 加密模块 - 字段加密</description>
<dependencies>
<!-- 加密模块 - 核心模块 -->
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-encrypt-core</artifactId>
</dependency>
<!-- MyBatis PlusMyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,简化开发、提高效率) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 top.continew.starter.encrypt.field.annotation;
import top.continew.starter.encrypt.encryptor.IEncryptor;
import top.continew.starter.encrypt.enums.Algorithm;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 字段加密注解
*
* @author Charles7c
* @author lishuyan
* @since 1.4.0
*/
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldEncrypt {
/**
* 加密算法
*/
Algorithm value() default Algorithm.DEFAULT;
/**
* 加密处理器
* <p>
* 优先级高于加密算法
* </p>
*/
Class<? extends IEncryptor> encryptor() default IEncryptor.class;
/**
* 对称加密算法密钥
*/
String password() default "";
/**
* 非对称加密算法公钥RSA需要
*/
String publicKey() default "";
/**
* 非对称加密算法私钥RSA需要
*/
String privateKey() default "";
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 top.continew.starter.encrypt.field.autoconfigure;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.encrypt.field.interceptor.MyBatisDecryptInterceptor;
import top.continew.starter.encrypt.field.interceptor.MyBatisEncryptInterceptor;
import top.continew.starter.encrypt.field.util.EncryptHelper;
/**
* 字段加密自动配置
*
* @author Charles7c
* @author lishuyan
* @since 1.4.0
*/
@AutoConfiguration
@EnableConfigurationProperties(FieldEncryptProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.ENCRYPT_FIELD, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class FieldEncryptAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(FieldEncryptAutoConfiguration.class);
private final FieldEncryptProperties properties;
public FieldEncryptAutoConfiguration(FieldEncryptProperties properties) {
this.properties = properties;
}
/**
* MyBatis 加密拦截器配置
*/
@Bean
@ConditionalOnMissingBean
public MyBatisEncryptInterceptor mybatisEncryptInterceptor() {
return new MyBatisEncryptInterceptor();
}
/**
* MyBatis 解密拦截器配置
*/
@Bean
@ConditionalOnMissingBean(MyBatisDecryptInterceptor.class)
public MyBatisDecryptInterceptor mybatisDecryptInterceptor() {
return new MyBatisDecryptInterceptor();
}
@PostConstruct
public void postConstruct() {
EncryptHelper.init(properties);
log.debug("[ContiNew Starter] - Auto Configuration 'Encrypt-Field' completed initialization.");
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 top.continew.starter.encrypt.field.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.encrypt.autoconfigure.PasswordEncoderProperties;
import top.continew.starter.encrypt.enums.Algorithm;
/**
* 字段加密配置属性
*
* @author Charles7c
* @author lishuyan
* @since 1.4.0
*/
@ConfigurationProperties(PropertiesConstants.ENCRYPT_FIELD)
public class FieldEncryptProperties {
/**
* 是否启用
*/
private Boolean enabled;
/**
* 默认算法
*/
private Algorithm algorithm = Algorithm.AES;
/**
* 对称加密算法密钥
*/
private String password;
/**
* 非对称加密算法公钥
*/
private String publicKey;
/**
* 非对称加密算法私钥
*/
private String privateKey;
/**
* 密码编码器配置
*/
@NestedConfigurationProperty
private PasswordEncoderProperties passwordEncoder;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public Algorithm getAlgorithm() {
return algorithm;
}
public void setAlgorithm(Algorithm algorithm) {
this.algorithm = algorithm;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
public PasswordEncoderProperties getPasswordEncoder() {
return passwordEncoder;
}
public void setPasswordEncoder(PasswordEncoderProperties passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 top.continew.starter.encrypt.field.interceptor;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.MappedStatement;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BaseException;
import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
/**
* 字段解密拦截器
*
* @author Charles7c
* @since 1.4.0
*/
public abstract class AbstractMyBatisInterceptor {
private static final Map<Class<?>, List<Field>> CLASS_FIELD_CACHE = new ConcurrentHashMap<>();
private static final Map<String, Map<String, FieldEncrypt>> ENCRYPT_PARAM_CACHE = new ConcurrentHashMap<>();
/**
* 获取所有字符串类型、需要加/解密的、有值字段
*
* @param obj 对象
* @return 字段列表
*/
protected List<Field> getEncryptFields(Object obj) {
if (obj == null) {
return Collections.emptyList();
}
return this.getEncryptFields(obj.getClass());
}
/**
* 获取所有字符串类型、需要加/解密的、有值字段
*
* @param clazz 类型对象
* @return 字段列表
*/
protected List<Field> getEncryptFields(Class<?> clazz) {
return CLASS_FIELD_CACHE.computeIfAbsent(clazz, key -> Arrays.stream(ReflectUtil.getFields(clazz))
.filter(field -> String.class.equals(field.getType()))
.filter(field -> field.getAnnotation(FieldEncrypt.class) != null)
.toList());
}
/**
* 获取加密参数
*
* @param mappedStatement 映射语句
* @return 获取加密参数
*/
protected Map<String, FieldEncrypt> getEncryptParameters(MappedStatement mappedStatement) {
String mappedStatementId = mappedStatement.getId();
return ENCRYPT_PARAM_CACHE.computeIfAbsent(mappedStatementId, key -> {
Method method = this.getMethod(mappedStatementId);
if (method == null) {
return Collections.emptyMap();
}
Map<String, FieldEncrypt> encryptMap = new HashMap<>();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
FieldEncrypt fieldEncrypt = parameter.getAnnotation(FieldEncrypt.class);
if (fieldEncrypt == null) {
continue;
}
String parameterName = this.getParameterName(parameter);
encryptMap.put(parameterName, fieldEncrypt);
if (String.class.equals(parameter.getType())) {
encryptMap.put("param" + (i + 1), fieldEncrypt);
}
}
return encryptMap;
});
}
/**
* 获取映射方法
*
* @param mappedStatementId 映射语句 ID
* @return 映射方法
*/
private Method getMethod(String mappedStatementId) {
String className = CharSequenceUtil.subBefore(mappedStatementId, StringConstants.DOT, true);
String methodName = CharSequenceUtil.subAfter(mappedStatementId, StringConstants.DOT, true);
try {
Method[] methods = ReflectUtil.getMethods(Class.forName(className));
return Stream.of(methods).filter(method -> method.getName().equals(methodName)).findFirst().orElse(null);
} catch (ClassNotFoundException e) {
throw new BaseException(e);
}
}
/**
* 获取参数名称
*
* @param parameter 参数
* @return 参数名称
*/
public String getParameterName(Parameter parameter) {
Param param = parameter.getAnnotation(Param.class);
return param != null ? param.value() : parameter.getName();
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 top.continew.starter.encrypt.field.interceptor;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.type.SimpleTypeRegistry;
import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
import top.continew.starter.encrypt.field.util.EncryptHelper;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* 字段解密拦截器
*
* @author Charles7c
* @author lishuyan
* @since 1.4.0
*/
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取执行结果
Object obj = invocation.proceed();
if (ObjectUtil.isNull(obj)) {
return null;
}
// 确保目标是 ResultSetHandler
if (!(invocation.getTarget() instanceof ResultSetHandler)) {
return obj;
}
// 处理查询结果
if (obj instanceof List<?> resultList) {
// 处理列表结果
this.decryptList(resultList);
} else if (obj instanceof Map<?, ?> map) {
// 处理Map结果
this.decryptMap(map);
} else {
// 处理单个对象结果
this.decryptObject(obj);
}
return obj;
}
/**
* 解密列表结果
*
* @param resultList 结果列表
*/
private void decryptList(List<?> resultList) {
if (CollUtil.isEmpty(resultList)) {
return;
}
for (Object result : resultList) {
decryptObject(result);
}
}
/**
* 解密Map结果
*
* @param resultMap 结果Map
*/
private void decryptMap(Map<?, ?> resultMap) {
if (CollUtil.isEmpty(resultMap)) {
return;
}
new HashSet<>(resultMap.values()).forEach(this::decryptObject);
}
/**
* 解密单个对象结果
*
* @param result 结果对象
*/
private void decryptObject(Object result) {
if (ObjectUtil.isNull(result)) {
return;
}
// String、Integer、Long 等简单类型对象无需处理
if (SimpleTypeRegistry.isSimpleType(result.getClass())) {
return;
}
// 获取所有字符串类型、需要解密的、有值字段
List<Field> fieldList = super.getEncryptFields(result);
if (fieldList.isEmpty()) {
return;
}
// 解密处理
for (Field field : fieldList) {
Object fieldValue = ReflectUtil.getFieldValue(result, field);
if (fieldValue == null) {
continue;
}
String strValue = String.valueOf(fieldValue);
if (CharSequenceUtil.isBlank(strValue)) {
continue;
}
ReflectUtil.setFieldValue(result, field, EncryptHelper.decrypt(strValue, field
.getAnnotation(FieldEncrypt.class)));
}
}
}

View File

@@ -0,0 +1,225 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 top.continew.starter.encrypt.field.interceptor;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
import top.continew.starter.encrypt.field.util.EncryptHelper;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 字段加密拦截器
*
* @author Charles7c
* @author lishuyan
* @since 1.4.0
*/
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implements InnerInterceptor {
private static final Pattern PARAM_PAIRS_PATTERN = Pattern
.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");
@Override
public void beforeQuery(Executor executor,
MappedStatement mappedStatement,
Object parameterObject,
RowBounds rowBounds,
ResultHandler resultHandler,
BoundSql boundSql) {
if (parameterObject == null) {
return;
}
if (parameterObject instanceof Map parameterMap) {
this.encryptQueryParameter(parameterMap, mappedStatement);
}
}
@Override
public void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameterObject) {
if (parameterObject == null) {
return;
}
if (parameterObject instanceof Map parameterMap) {
// 带别名方法(使用 @Param 注解的场景)
this.encryptMap(parameterMap, mappedStatement);
} else {
// 无别名方法例如MP insert 等方法)
this.encryptEntity(super.getEncryptFields(parameterObject), parameterObject);
}
}
/**
* 加密查询参数(针对 Map 类型参数)
*
* @param parameterMap 参数
* @param mappedStatement 映射语句
*/
private void encryptQueryParameter(Map<String, Object> parameterMap, MappedStatement mappedStatement) {
Map<String, FieldEncrypt> encryptParameterMap = super.getEncryptParameters(mappedStatement);
for (Map.Entry<String, Object> parameterEntrySet : parameterMap.entrySet()) {
String parameterName = parameterEntrySet.getKey();
Object parameterValue = parameterEntrySet.getValue();
if (parameterValue == null || ClassUtil.isBasicType(parameterValue
.getClass()) || parameterValue instanceof AbstractWrapper) {
continue;
}
if (parameterValue instanceof String str) {
FieldEncrypt fieldEncrypt = encryptParameterMap.get(parameterName);
if (fieldEncrypt != null) {
parameterMap.put(parameterName, this.doEncrypt(str, fieldEncrypt));
}
} else {
// 实体参数
this.encryptEntity(super.getEncryptFields(parameterValue), parameterValue);
}
}
}
/**
* 处理实体加密
*
* @param fieldList 加密字段列表
* @param entity 实体
*/
private void encryptEntity(List<Field> fieldList, Object entity) {
for (Field field : fieldList) {
Object fieldValue = ReflectUtil.getFieldValue(entity, field);
if (fieldValue == null) {
continue;
}
String strValue = String.valueOf(fieldValue);
if (CharSequenceUtil.isBlank(strValue)) {
continue;
}
ReflectUtil.setFieldValue(entity, field, EncryptHelper.encrypt(strValue, field
.getAnnotation(FieldEncrypt.class)));
}
}
/**
* 加密 Map 类型数据(使用 @Param 注解的场景)
*
* @param parameterMap 参数
* @param mappedStatement 映射语句
*/
private void encryptMap(Map<String, Object> parameterMap, MappedStatement mappedStatement) {
Object parameter;
// 别名带有 et针对 MP 的 updateById、update 等方法)
if (parameterMap.containsKey(Constants.ENTITY) && (parameter = parameterMap.get(Constants.ENTITY)) != null) {
this.encryptEntity(super.getEncryptFields(parameter), parameter);
}
// 别名带有 ew针对 MP 的 UpdateWrapper、LambdaUpdateWrapper 等参数)
if (parameterMap.containsKey(Constants.WRAPPER) && (parameter = parameterMap.get(Constants.WRAPPER)) != null) {
this.encryptUpdateWrapper(parameter, mappedStatement);
}
}
/**
* 处理 UpdateWrapper 类型参数加密(针对 MP 的 UpdateWrapper、LambdaUpdateWrapper 等参数)
*
* @param parameter Wrapper 参数
* @param mappedStatement 映射语句
* @author cary
* @author wangshaopeng@talkweb.com.cn<a
* href="https://blog.csdn.net/tianmaxingkonger/article/details/130986784">基于Mybatis-Plus拦截器实现MySQL数据加解密</a>
* @since 2.1.1
*/
private void encryptUpdateWrapper(Object parameter, MappedStatement mappedStatement) {
if (parameter instanceof AbstractWrapper updateWrapper) {
String sqlSet = updateWrapper.getSqlSet();
if (CharSequenceUtil.isBlank(sqlSet)) {
return;
}
// 将 name=#{ew.paramNameValuePairs.xxx},age=#{ew.paramNameValuePairs.xxx} 切出来
String[] elArr = sqlSet.split(StringConstants.COMMA);
Map<String, String> propMap = new HashMap<>(elArr.length);
Arrays.stream(elArr).forEach(el -> {
String[] elPart = el.split(StringConstants.EQUALS);
propMap.put(elPart[0], elPart[1]);
});
// 获取加密字段
Class<?> entityClass = this.getEntityClass(updateWrapper, mappedStatement);
List<Field> encryptFieldList = super.getEncryptFields(entityClass);
for (Field field : encryptFieldList) {
FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class);
String el = propMap.get(field.getName());
if (CharSequenceUtil.isBlank(el)) {
continue;
}
Matcher matcher = PARAM_PAIRS_PATTERN.matcher(el);
if (matcher.matches()) {
String valueKey = matcher.group(1);
Object value = updateWrapper.getParamNameValuePairs().get(valueKey);
updateWrapper.getParamNameValuePairs().put(valueKey, this.doEncrypt(value, fieldEncrypt));
}
}
}
}
/**
* 处理加密
*
* @param parameterValue 参数值
* @param fieldEncrypt 字段加密注解
*/
private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) {
if (ObjectUtil.isNull(parameterValue)) {
return null;
}
String strValue = String.valueOf(parameterValue);
if (CharSequenceUtil.isBlank(strValue)) {
return null;
}
return EncryptHelper.encrypt(strValue, fieldEncrypt);
}
/**
* 获取实体类
*
* @param wrapper 查询或更新包装器
* @param mappedStatement 映射语句
* @return 实体类
*/
private Class<?> getEntityClass(AbstractWrapper wrapper, MappedStatement mappedStatement) {
// 尝试从 Wrapper 中获取实体类
Class<?> entityClass = wrapper.getEntityClass();
if (entityClass != null) {
return entityClass;
}
// 从映射语句中获取实体类
return mappedStatement.getParameterMap().getType();
}
}

View File

@@ -0,0 +1,220 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 top.continew.starter.encrypt.field.util;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.encrypt.context.CryptoContext;
import top.continew.starter.encrypt.encryptor.IEncryptor;
import top.continew.starter.encrypt.enums.Algorithm;
import top.continew.starter.encrypt.field.annotation.FieldEncrypt;
import top.continew.starter.encrypt.field.autoconfigure.FieldEncryptProperties;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 加密助手
*
* @author lishuyan
* @since 2.13.2
*/
public class EncryptHelper {
private static final Logger log = LoggerFactory.getLogger(EncryptHelper.class);
/**
* 默认加密配置
*/
private static FieldEncryptProperties defaultProperties;
/**
* 加密器缓存
*/
private static final Map<Integer, IEncryptor> ENCRYPTOR_CACHE = new ConcurrentHashMap<>();
private EncryptHelper() {
}
/**
* 初始化默认配置
*
* @param properties 加密配置
*/
public static void init(FieldEncryptProperties properties) {
defaultProperties = properties;
}
/**
* 注册加密执行者到缓存
* <p>
* 计算 CryptoContext 对象的hashCode作为缓存中的key通过hashCode查询缓存中存在则直接返回不存在则创建并缓存
* </p>
*
* @param cryptoContext 加密执行者需要的相关配置参数
* @return 加密执行者
*/
public static IEncryptor registerAndGetEncryptor(CryptoContext cryptoContext) {
int key = cryptoContext.hashCode();
return ENCRYPTOR_CACHE.computeIfAbsent(key, k -> cryptoContext.getEncryptor().equals(IEncryptor.class)
? ReflectUtil.newInstance(cryptoContext.getAlgorithm().getEncryptor(), cryptoContext)
: ReflectUtil.newInstance(cryptoContext.getEncryptor(), cryptoContext));
}
/**
* 获取字段上的 @FieldEncrypt 注解
*
* @param obj 对象
* @param fieldName 字段名称
* @return 字段上的 @FieldEncrypt 注解
* @throws NoSuchFieldException /
*/
public static FieldEncrypt getFieldEncrypt(Object obj, String fieldName) throws NoSuchFieldException {
Field field = obj.getClass().getDeclaredField(fieldName);
return field.getAnnotation(FieldEncrypt.class);
}
/**
* 加密方法
*
* @param value 待加密字符串
* @param fieldEncrypt 待加密字段注解
* @return 加密后的字符串
*/
public static String encrypt(String value, FieldEncrypt fieldEncrypt) {
if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null) {
return value;
}
String ciphertext = value;
try {
CryptoContext cryptoContext = buildCryptoContext(fieldEncrypt);
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
ciphertext = encryptor.encrypt(ciphertext);
} catch (Exception e) {
log.warn("加密失败,请检查加密配置,处理加密字段异常:{}", e.getMessage(), e);
}
return ciphertext;
}
/**
* 加密方法
*
* @param value 待加密字符串
* @return 加密后的字符串
*/
public static String encrypt(String value) {
if (CharSequenceUtil.isBlank(value)) {
return value;
}
String ciphertext = value;
try {
CryptoContext cryptoContext = buildCryptoContext();
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
ciphertext = encryptor.encrypt(ciphertext);
} catch (Exception e) {
log.warn("加密失败,请检查加密配置,处理加密字段异常:{}", e.getMessage(), e);
}
return ciphertext;
}
/**
* 解密方法
*
* @param value 待解密字符串
* @param fieldEncrypt 待解密字段注解
* @return 解密后的字符串
*/
public static String decrypt(String value, FieldEncrypt fieldEncrypt) {
if (CharSequenceUtil.isBlank(value) || fieldEncrypt == null) {
return value;
}
String plaintext = value;
try {
CryptoContext cryptoContext = buildCryptoContext(fieldEncrypt);
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
plaintext = encryptor.decrypt(plaintext);
} catch (Exception e) {
log.warn("解密失败,请检查加密配置,处理解密字段异常:{}", e.getMessage(), e);
}
return plaintext;
}
/**
* 解密方法
*
* @param value 待解密字符串
* @return 解密后的字符串
*/
public static String decrypt(String value) {
if (CharSequenceUtil.isBlank(value)) {
return value;
}
String plaintext = value;
try {
CryptoContext cryptoContext = buildCryptoContext();
IEncryptor encryptor = registerAndGetEncryptor(cryptoContext);
plaintext = encryptor.decrypt(plaintext);
} catch (Exception e) {
log.warn("解密失败,请检查加密配置,处理解密字段异常:{}", e.getMessage(), e);
}
return plaintext;
}
/**
* 构建加密上下文
*
* @param fieldEncrypt 字段加密注解
* @return 加密上下文
*/
private static CryptoContext buildCryptoContext(FieldEncrypt fieldEncrypt) {
CryptoContext cryptoContext = new CryptoContext();
cryptoContext.setAlgorithm(fieldEncrypt.value() == Algorithm.DEFAULT
? defaultProperties.getAlgorithm()
: fieldEncrypt.value());
cryptoContext.setEncryptor(fieldEncrypt.encryptor().equals(IEncryptor.class)
? IEncryptor.class
: fieldEncrypt.encryptor());
cryptoContext.setPassword(fieldEncrypt.password().isEmpty()
? defaultProperties.getPassword()
: fieldEncrypt.password());
cryptoContext.setPrivateKey(fieldEncrypt.privateKey().isEmpty()
? defaultProperties.getPrivateKey()
: fieldEncrypt.privateKey());
cryptoContext.setPublicKey(fieldEncrypt.publicKey().isEmpty()
? defaultProperties.getPublicKey()
: fieldEncrypt.publicKey());
return cryptoContext;
}
/**
* 构建加密上下文
*
* @return 加密上下文
*/
private static CryptoContext buildCryptoContext() {
CryptoContext cryptoContext = new CryptoContext();
cryptoContext.setAlgorithm(defaultProperties.getAlgorithm());
cryptoContext.setEncryptor(IEncryptor.class);
cryptoContext.setPassword(defaultProperties.getPassword());
cryptoContext.setPrivateKey(defaultProperties.getPrivateKey());
cryptoContext.setPublicKey(defaultProperties.getPublicKey());
return cryptoContext;
}
}

View File

@@ -0,0 +1 @@
top.continew.starter.encrypt.field.autoconfigure.FieldEncryptAutoConfiguration