Spring Boot + MyBatis:实现数据库字段级加密的完整解决方案
在数字化时代,数据安全已成为企业生存和发展的基石。根据《数据安全法》和《个人信息保护法》的要求,敏感个人信息必须采取相应的加密等安全措施。代码重复率高,加解密逻辑散落在各个业务方法中维护困难,新增加密字段需要修改多处代码容易遗漏,某个查询忘记解密导致数据异常测试复杂,需要同时验证业务逻辑和加密逻辑声明式加密:通过注解标记加密字段透明化处理:业务代码无需感知加密过程高性能:基于MyBatis拦截器,
·
一、前言:数据安全防护的必要性
在数字化时代,数据安全已成为企业生存和发展的基石。根据《数据安全法》和《个人信息保护法》的要求,敏感个人信息必须采取相应的加密等安全措施。然而,在实际开发中,数据加密面临着诸多挑战:
传统加密方式的痛点:
-
代码重复率高,加解密逻辑散落在各个业务方法中
-
维护困难,新增加密字段需要修改多处代码
-
容易遗漏,某个查询忘记解密导致数据异常
-
测试复杂,需要同时验证业务逻辑和加密逻辑
本文提供的解决方案优势:
-
声明式加密:通过注解标记加密字段
-
透明化处理:业务代码无需感知加密过程
-
高性能:基于MyBatis拦截器,性能损耗极小
-
易维护:集中管理加密逻辑,降低维护成本
二、解决方案架构设计
2.1 整体架构
text
业务层 (Service)
↓
数据访问层 (Mapper)
↓
MyBatis拦截器 (Interceptor)
↓ 加密/解密
数据库层 (Database)
2.2 核心组件设计
三、核心实现详解
3.1 加密注解定义
/**
* 字段加密注解
* 标记在需要加密的实体类字段上
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypted {
/**
* 是否支持模糊查询
* 如果为true,会额外存储字段的哈希值用于模糊匹配
* 默认关闭,因为会降低安全性
*/
boolean supportFuzzyQuery() default false;
/**
* 加密算法类型
* 默认使用AES-GCM算法
*/
Algorithm algorithm() default Algorithm.AES_GCM;
/**
* 字段敏感度级别
* 用于日志脱敏和权限控制
*/
SensitivityLevel level() default SensitivityLevel.HIGH;
public enum Algorithm {
AES_GCM, // AES-GCM认证加密
AES_CBC, // AES-CBC模式
SM4 // 国密SM4算法
}
public enum SensitivityLevel {
LOW, // 低敏感度,仅基础加密
MEDIUM, // 中敏感度,增加访问控制
HIGH // 高敏感度,完整安全防护
}
}
3.2 加密工具类实现
/**
* 字段加密注解
* 标记在需要加密的实体类字段上
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Encrypted {
/**
* 是否支持模糊查询
* 如果为true,会额外存储字段的哈希值用于模糊匹配
* 默认关闭,因为会降低安全性
*/
boolean supportFuzzyQuery() default false;
/**
* 加密算法类型
* 默认使用AES-GCM算法
*/
Algorithm algorithm() default Algorithm.AES_GCM;
/**
* 字段敏感度级别
* 用于日志脱敏和权限控制
*/
SensitivityLevel level() default SensitivityLevel.HIGH;
public enum Algorithm {
AES_GCM, // AES-GCM认证加密
AES_CBC, // AES-CBC模式
SM4 // 国密SM4算法
}
public enum SensitivityLevel {
LOW, // 低敏感度,仅基础加密
MEDIUM, // 中敏感度,增加访问控制
HIGH // 高敏感度,完整安全防护
}
}
3.3 MyBatis拦截器核心实现
/**
* 加密拦截器
* 自动处理字段的加密和解密操作
*/
@Slf4j
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "queryCursor", args = {MappedStatement.class, Object.class, RowBounds.class})
})
public class EncryptionInterceptor implements Interceptor {
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
public EncryptionInterceptor(ObjectFactory objectFactory,
ObjectWrapperFactory objectWrapperFactory,
ReflectorFactory reflectorFactory) {
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
Object parameter = invocation.getArgs()[1];
// 处理INSERT/UPDATE操作:加密参数
if ("update".equals(methodName)) {
processUpdateParameter(parameter);
}
// 执行原始SQL操作
Object result = invocation.proceed();
// 处理SELECT操作:解密结果
if ("query".equals(methodName) || "queryCursor".equals(methodName)) {
result = processQueryResult(result);
}
return result;
}
/**
* 处理更新操作的参数加密
*/
private void processUpdateParameter(Object parameter) {
if (parameter == null) {
return;
}
try {
// 处理不同类型的参数
if (parameter instanceof Map) {
// 处理Map参数
processMapParameter((Map<?, ?>) parameter);
} else if (isBasicType(parameter.getClass())) {
// 基本类型不处理
return;
} else if (parameter instanceof Collection) {
// 处理集合参数
for (Object item : (Collection<?>) parameter) {
encryptObjectFields(item);
}
} else {
// 处理实体对象
encryptObjectFields(parameter);
}
} catch (Exception e) {
log.error("处理更新参数失败", e);
throw new CryptoException("参数加密处理失败", e);
}
}
/**
* 处理Map类型的参数
*/
private void processMapParameter(Map<?, ?> parameterMap) {
for (Map.Entry<?, ?> entry : parameterMap.entrySet()) {
Object value = entry.getValue();
if (value != null && !isBasicType(value.getClass())) {
encryptObjectFields(value);
}
}
}
/**
* 加密对象的字段
*/
private void encryptObjectFields(Object obj) {
if (obj == null || isBasicType(obj.getClass())) {
return;
}
Class<?> clazz = obj.getClass();
List<Field> encryptedFields = getEncryptedFields(clazz);
if (encryptedFields.isEmpty()) {
return;
}
for (Field field : encryptedFields) {
encryptField(obj, field);
}
}
/**
* 获取需要加密的字段列表
*/
private List<Field> getEncryptedFields(Class<?> clazz) {
List<Field> encryptedFields = new ArrayList<>();
Class<?> currentClass = clazz;
// 遍历类继承层次,获取所有加密字段
while (currentClass != null && currentClass != Object.class) {
Field[] fields = currentClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Encrypted.class)) {
encryptedFields.add(field);
}
}
currentClass = currentClass.getSuperclass();
}
return encryptedFields;
}
/**
* 加密单个字段
*/
private void encryptField(Object obj, Field field) {
try {
field.setAccessible(true);
Object value = field.get(obj);
if (value instanceof String) {
String stringValue = (String) value;
if (StringUtils.isNotBlank(stringValue) && !CryptoUtil.isEncrypted(stringValue)) {
Encrypted encryptedAnnotation = field.getAnnotation(Encrypted.class);
String encryptedValue = CryptoUtil.encrypt(stringValue, encryptedAnnotation.algorithm());
field.set(obj, encryptedValue);
log.debug("字段加密完成: {}.{}, 原文: {}, 密文: {}",
obj.getClass().getSimpleName(),
field.getName(),
SensitiveDataLogger.maskSensitive(stringValue),
encryptedValue);
}
}
} catch (IllegalAccessException e) {
log.error("字段访问失败: {}.{}", obj.getClass().getSimpleName(), field.getName(), e);
throw new CryptoException("字段加密失败", e);
}
}
/**
* 处理查询结果解密
*/
private Object processQueryResult(Object result) {
if (result == null) {
return null;
}
try {
if (result instanceof List) {
// 处理列表结果
List<?> resultList = (List<?>) result;
for (Object item : resultList) {
decryptObjectFields(item);
}
} else if (result instanceof Map) {
// 处理Map结果
processMapResult((Map<?, ?>) result);
} else {
// 处理单个对象结果
decryptObjectFields(result);
}
} catch (Exception e) {
log.error("处理查询结果失败", e);
throw new CryptoException("结果解密处理失败", e);
}
return result;
}
/**
* 解密对象的字段
*/
private void decryptObjectFields(Object obj) {
if (obj == null || isBasicType(obj.getClass())) {
return;
}
Class<?> clazz = obj.getClass();
List<Field> encryptedFields = getEncryptedFields(clazz);
for (Field field : encryptedFields) {
decryptField(obj, field);
}
}
/**
* 加密拦截器
* 自动处理字段的加密和解密操作
*/
@Slf4j
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "queryCursor", args = {MappedStatement.class, Object.class, RowBounds.class})
})
public class EncryptionInterceptor implements Interceptor {
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
public EncryptionInterceptor(ObjectFactory objectFactory,
ObjectWrapperFactory objectWrapperFactory,
ReflectorFactory reflectorFactory) {
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
Object parameter = invocation.getArgs()[1];
// 处理INSERT/UPDATE操作:加密参数
if ("update".equals(methodName)) {
processUpdateParameter(parameter);
}
// 执行原始SQL操作
Object result = invocation.proceed();
// 处理SELECT操作:解密结果
if ("query".equals(methodName) || "queryCursor".equals(methodName)) {
result = processQueryResult(result);
}
return result;
}
/**
* 加密拦截器
* 自动处理字段的加密和解密操作
*/
@Slf4j
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "queryCursor", args = {MappedStatement.class, Object.class, RowBounds.class})
})
public class EncryptionInterceptor implements Interceptor {
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
public EncryptionInterceptor(ObjectFactory objectFactory,
ObjectWrapperFactory objectWrapperFactory,
ReflectorFactory reflectorFactory) {
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
Object parameter = invocation.getArgs()[1];
// 处理INSERT/UPDATE操作:加密参数
if ("update".equals(methodName)) {
processUpdateParameter(parameter);
}
// 执行原始SQL操作
Object result = invocation.proceed();
// 处理SELECT操作:解密结果
if ("query".equals(methodName) || "queryCursor".equals(methodName)) {
result = processQueryResult(result);
}
return result;
}
3.4 自动配置类
/**
* 加密自动配置
*/
@Configuration
@ConditionalOnProperty(name = "app.encryption.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(EncryptionProperties.class)
@AutoConfigureAfter(MybatisAutoConfiguration.class)
public class EncryptionAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public EncryptionInterceptor encryptionInterceptor(ObjectFactory objectFactory,
ObjectWrapperFactory objectWrapperFactory,
ReflectorFactory reflectorFactory) {
return new EncryptionInterceptor(objectFactory, objectWrapperFactory, reflectorFactory);
}
@Bean
public ConfigurationCustomizer encryptionConfigurationCustomizer(EncryptionInterceptor encryptionInterceptor) {
return configuration -> {
configuration.addInterceptor(encryptionInterceptor);
};
}
@Bean
@ConditionalOnMissingBean
public CryptoUtil cryptoUtil() {
return new CryptoUtil();
}
@Bean
@ConditionalOnMissingBean
public SensitiveDataLogger sensitiveDataLogger() {
return new SensitiveDataLogger();
}
}
/**
* 加密配置属性
*/
@ConfigurationProperties(prefix = "app.encryption")
@Data
public class EncryptionProperties {
/**
* 是否启用加密功能
*/
private boolean enabled = true;
/**
* 默认加密算法
*/
private Encrypted.Algorithm defaultAlgorithm = Encrypted.Algorithm.AES_GCM;
/**
* 密钥配置
*/
private KeyConfig key = new KeyConfig();
/**
* 日志配置
*/
private LogConfig log = new LogConfig();
@Data
public static class KeyConfig {
/**
* AES密钥(Base64编码)
*/
private String aesKey;
/**
* SM4密钥(Base64编码)
*/
private String sm4Key;
/**
* 密钥获取方式:CONFIG|ENV|KMS
*/
private String source = "CONFIG";
}
@Data
public static class LogConfig {
/**
* 是否启用加密日志
*/
private boolean enabled = true;
/**
* 日志级别:DEBUG|INFO|WARN|ERROR
*/
private String level = "DEBUG";
}
}
3.5 安全日志工具
/**
* 敏感数据日志处理工具
* 防止敏感信息在日志中泄露
*/
@Slf4j
public class SensitiveDataLogger {
private static final String PHONE_REGEX = "(?<=\\w{3})\\w(?=\\w{4})";
private static final String ID_CARD_REGEX = "(?<=\\w{6})\\w(?=\\w{4})";
private static final String EMAIL_REGEX = "(?<=.{2}).(?=.*@)";
private static final String BANK_CARD_REGEX = "(?<=\\w{4})\\w(?=\\w{4})";
/**
* 脱敏手机号
*/
public static String maskPhone(String phone) {
if (StringUtils.isBlank(phone)) {
return phone;
}
if (phone.length() == 11) {
return phone.replaceAll(PHONE_REGEX, "*");
}
// 其他格式的手机号处理
return maskCommon(phone, 3, 4);
}
/**
* 脱敏邮箱
*/
public static String maskEmail(String email) {
if (StringUtils.isBlank(email)) {
return email;
}
int atIndex = email.indexOf("@");
if (atIndex <= 0) {
return "***";
}
if (atIndex <= 3) {
return email.substring(0, atIndex).replaceAll(".", "*") +
email.substring(atIndex);
}
return email.substring(0, 2) +
email.substring(2, atIndex).replaceAll(".", "*") +
email.substring(atIndex);
}
/**
* 敏感数据日志处理工具
* 防止敏感信息在日志中泄露
*/
@Slf4j
public class SensitiveDataLogger {
private static final String PHONE_REGEX = "(?<=\\w{3})\\w(?=\\w{4})";
private static final String ID_CARD_REGEX = "(?<=\\w{6})\\w(?=\\w{4})";
private static final String EMAIL_REGEX = "(?<=.{2}).(?=.*@)";
private static final String BANK_CARD_REGEX = "(?<=\\w{4})\\w(?=\\w{4})";
/**
* 脱敏手机号
*/
public static String maskPhone(String phone) {
if (StringUtils.isBlank(phone)) {
return phone;
}
if (phone.length() == 11) {
return phone.replaceAll(PHONE_REGEX, "*");
}
// 其他格式的手机号处理
return maskCommon(phone, 3, 4);
}
/**
* 脱敏邮箱
*/
public static String maskEmail(String email) {
if (StringUtils.isBlank(email)) {
return email;
}
int atIndex = email.indexOf("@");
if (atIndex <= 0) {
return "***";
}
if (atIndex <= 3) {
return email.substring(0, atIndex).replaceAll(".", "*") +
email.substring(atIndex);
}
return email.substring(0, 2) +
email.substring(2, atIndex).replaceAll(".", "*") +
email.substring(atIndex);
}
/**
* 通用脱敏方法
*/
public static String maskCommon(String value, int prefixLength, int suffixLength) {
if (StringUtils.isBlank(value)) {
return value;
}
if (value.length() <= prefixLength + suffixLength) {
// 字符串太短,全部脱敏
return value.replaceAll(".", "*");
}
String prefix = value.substring(0, prefixLength);
String suffix = value.substring(value.length() - suffixLength);
String middle = value.substring(prefixLength, value.length() - suffixLength)
.replaceAll(".", "*");
return prefix + middle + suffix;
}
/**
* 智能脱敏敏感数据
*/
public static String maskSensitive(String value) {
if (StringUtils.isBlank(value)) {
return value;
}
// 根据数据特征判断类型并脱敏
if (value.matches("^1[3-9]\\d{9}$")) {
return maskPhone(value);
} else if (value.contains("@")) {
return maskEmail(value);
} else if (value.matches("^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$")) {
return maskIdCard(value);
} else if (value.matches("^[1-9]\\d{9,18}$")) {
return maskBankCard(value);
} else {
// 默认脱敏:保留前2后2
return maskCommon(value, 2, 2);
}
}
/**
* 安全日志记录
*/
public static void logSafely(String message, Object... args) {
if (log.isInfoEnabled()) {
String safeMessage = processMessage(message, args);
log.info(safeMessage);
}
}
/**
* 处理消息中的敏感参数
*/
private static String processMessage(String message, Object[] args) {
if (args == null || args.length == 0) {
return message;
}
Object[] safeArgs = Arrays.stream(args)
.map(arg -> {
if (arg instanceof String) {
return maskSensitive((String) arg);
}
return arg;
})
.toArray();
return String.format(message, safeArgs);
}
}
四、使用示例和最佳实践
4.1 实体类定义
/**
* 用户实体类
* 演示加密字段的使用
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
@Encrypted(
algorithm = Encrypted.Algorithm.AES_GCM,
level = Encrypted.SensitivityLevel.HIGH
)
private String phone;
@Encrypted(
algorithm = Encrypted.Algorithm.AES_GCM,
level = Encrypted.SensitivityLevel.MEDIUM
)
private String email;
@Encrypted(
algorithm = Encrypted.Algorithm.AES_GCM,
level = Encrypted.SensitivityLevel.HIGH,
supportFuzzyQuery = false // 身份证号不支持模糊查询
)
private String idCard;
@Encrypted(
algorithm = Encrypted.Algorithm.SM4,
level = Encrypted.SensitivityLevel.HIGH
)
private String bankCard;
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
/**
* 员工信息实体
* 演示不同加密配置
*/
@Data
@TableName("t_employee")
public class Employee {
private Long id;
private String name;
private String employeeNo;
@Encrypted(
algorithm = Encrypted.Algorithm.AES_CBC,
level = Encrypted.SensitivityLevel.MEDIUM
)
private String mobile;
@Encrypted(
algorithm = Encrypted.Algorithm.AES_GCM,
level = Encrypted.SensitivityLevel.HIGH
)
private String identityNumber;
@Encrypted(
algorithm = Encrypted.Algorithm.SM4,
level = Encrypted.SensitivityLevel.HIGH,
supportFuzzyQuery = true // 支持按邮箱前缀模糊查询
)
private String corporateEmail;
private String department;
}
4.2 业务层使用
/**
* 用户服务
* 业务代码完全无需关心加密细节
*/
@Slf4j
@Service
@Transactional
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private SensitiveDataLogger sensitiveDataLogger;
/**
* 创建用户 - 自动加密敏感字段
*/
public Long createUser(CreateUserRequest request) {
User user = User.builder()
.username(request.getUsername())
.phone(request.getPhone()) // 自动加密
.email(request.getEmail()) // 自动加密
.idCard(request.getIdCard()) // 自动加密
.bankCard(request.getBankCard()) // 自动加密
.status(1)
.build();
userMapper.insert(user);
// 安全日志记录
sensitiveDataLogger.logSafely("创建用户成功, 用户名: {}, 手机号: {}",
user.getUsername(), user.getPhone());
return user.getId();
}
/**
* 用户服务
* 业务代码完全无需关心加密细节
*/
@Slf4j
@Service
@Transactional
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private SensitiveDataLogger sensitiveDataLogger;
/**
* 创建用户 - 自动加密敏感字段
*/
public Long createUser(CreateUserRequest request) {
User user = User.builder()
.username(request.getUsername())
.phone(request.getPhone()) // 自动加密
.email(request.getEmail()) // 自动加密
.idCard(request.getIdCard()) // 自动加密
.bankCard(request.getBankCard()) // 自动加密
.status(1)
.build();
userMapper.insert(user);
// 安全日志记录
sensitiveDataLogger.logSafely("创建用户成功, 用户名: {}, 手机号: {}",
user.getUsername(), user.getPhone());
return user.getId();
}
/**
* 批量查询用户
*/
public List<User> batchGetUsers(List<Long> ids) {
List<User> users = userMapper.selectBatchIds(ids);
// 所有用户的敏感字段都已被自动解密
users.forEach(user -> {
sensitiveDataLogger.logSafely("批量查询用户: {}, 手机: {}",
user.getUsername(), user.getPhone());
});
return users;
}
/**
* 条件查询示例
*/
public List<User> queryUsers(UserQuery query) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(query.getUsername())) {
wrapper.like(User::getUsername, query.getUsername());
}
// 注意:加密字段不能直接用于条件查询
// 如果需要查询加密字段,需要特殊处理
return userMapper.selectList(wrapper);
}
}
/**
* 用户查询参数
*/
@Data
public class UserQuery {
private String username;
private String phone; // 加密字段,不能直接用于查询条件
private String email; // 加密字段,不能直接用于查询条件
private Integer status;
}
4.3 配置文件
# application.yml
app:
encryption:
enabled: true
default-algorithm: AES_GCM
key:
source: CONFIG
aes-key: "your-aes-base64-key-here" # 实际项目中从安全配置获取
sm4-key: "your-sm4-base64-key-here" # 实际项目中从安全配置获取
log:
enabled: true
level: DEBUG
# MyBatis配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:mapper/**/*.xml
# 数据源配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
# 日志配置
logging:
level:
com.example.encryption: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
五、高级特性与优化
5.1 模糊查询支持
/**
* 模糊查询支持工具
* 为支持模糊查询的字段创建哈希索引
*/
@Component
public class FuzzyQuerySupport {
/**
* 为支持模糊查询的字段生成哈希值
*/
public static String generateFuzzyHash(String value) {
if (StringUtils.isBlank(value)) {
return null;
}
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(value.getBytes(StandardCharsets.UTF_8));
// 取前8字节作为哈希值
byte[] shortHash = Arrays.copyOf(hash, 8);
return Base64.getEncoder().encodeToString(shortHash);
} catch (Exception e) {
throw new CryptoException("生成模糊查询哈希失败", e);
}
}
/**
* 扩展的加密拦截器,支持模糊查询
*/
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public static class FuzzyQueryEncryptionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object parameter = invocation.getArgs()[1];
if (parameter != null && !isBasicType(parameter.getClass())) {
processFuzzyQueryFields(parameter);
}
return invocation.proceed();
}
private void processFuzzyQueryFields(Object obj) {
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Encrypted encrypted = field.getAnnotation(Encrypted.class);
if (encrypted != null && encrypted.supportFuzzyQuery()) {
processFuzzyQueryField(obj, field);
}
}
}
private void processFuzzyQueryField(Object obj, Field field) {
try {
field.setAccessible(true);
Object value = field.get(obj);
if (value instanceof String && StringUtils.isNotBlank((String) value)) {
// 生成哈希字段名:原字段名 + "Hash"
String hashFieldName = field.getName() + "Hash";
Field hashField = obj.getClass().getDeclaredField(hashFieldName);
hashField.setAccessible(true);
String hashValue = generateFuzzyHash((String) value);
hashField.set(obj, hashValue);
}
} catch (Exception e) {
log.error("处理模糊查询字段失败: {}.{}",
obj.getClass().getSimpleName(), field.getName(), e);
}
}
}
}
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)