From 0c99c66172720dcc48616cb8aa14ad955503d39d Mon Sep 17 00:00:00 2001 From: zhangwenzan Date: Thu, 4 Sep 2025 09:31:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/security/util/EsDataEncryptUtil.java | 25 ++- .../security/util/SensitiveDataMaskUtil.java | 150 ++++++++++++++++++ .../crm/common/InitEsIndexRunner.java | 12 +- 3 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/com/kakarote/core/security/util/SensitiveDataMaskUtil.java diff --git a/core/src/main/java/com/kakarote/core/security/util/EsDataEncryptUtil.java b/core/src/main/java/com/kakarote/core/security/util/EsDataEncryptUtil.java index 2d8d3e9..0fb46cd 100644 --- a/core/src/main/java/com/kakarote/core/security/util/EsDataEncryptUtil.java +++ b/core/src/main/java/com/kakarote/core/security/util/EsDataEncryptUtil.java @@ -21,7 +21,6 @@ public class EsDataEncryptUtil { private static final Set DATE_FIELD_PREFIXES = new HashSet<>(Arrays.asList( "createTime", "updateTime", "lastTime", "startTime", "endTime", "birthDate", "nextContactTime" )); - private static EncryptionService getEncryptionService() { if (encryptionService == null) { encryptionService = ApplicationContextHolder.getBean(EncryptionService.class); @@ -63,7 +62,7 @@ public class EsDataEncryptUtil { for (Map.Entry entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); - decryptedMap.put(key, decryptValue(value)); + decryptedMap.put(key, decryptValue(key,value)); } return decryptedMap; } @@ -133,7 +132,7 @@ public class EsDataEncryptUtil { * 根据值类型进行解密 */ @SuppressWarnings("unchecked") - private static Object decryptValue(Object value) { + private static Object decryptValue(String key,Object value) { if (value == null) { return null; } @@ -142,7 +141,8 @@ public class EsDataEncryptUtil { String strValue = (String) value; // 对已加密的数据进行解密 if (strValue.startsWith(Const.ENCRYPTED_PREFIX)) { - return getEncryptionService().decryptAes(strValue.substring(Const.ENCRYPTED_PREFIX.length())); + String newValue = getEncryptionService().decryptAes(strValue.substring(Const.ENCRYPTED_PREFIX.length())); + return desensitization(key,newValue); } return strValue; } else if (value instanceof Map) { @@ -152,14 +152,27 @@ public class EsDataEncryptUtil { // 递归处理List中的每个元素 List decryptedList = new ArrayList<>(); for (Object item : (List) value) { - decryptedList.add(decryptValue(item)); + decryptedList.add(decryptValue(key,item)); } return decryptedList; } // 其他类型保持不变 return value; } - + + // 数据脱敏 + private static String desensitization(String key,String value){ + if ("mobile".equals(key)) { + return SensitiveDataMaskUtil.maskPhone(value); + }else if ("email".equals(key)) { + return SensitiveDataMaskUtil.maskEmail(value); + }else if ("ownerDeptName".equals(key)) { + return SensitiveDataMaskUtil.maskName(value); + }else if ("address".equals(key)) { + return SensitiveDataMaskUtil.maskAddress(value); + } + return value; + } /** * 判断字段是否为日期类型字段 * @param fieldName 字段名 diff --git a/core/src/main/java/com/kakarote/core/security/util/SensitiveDataMaskUtil.java b/core/src/main/java/com/kakarote/core/security/util/SensitiveDataMaskUtil.java new file mode 100644 index 0000000..512015a --- /dev/null +++ b/core/src/main/java/com/kakarote/core/security/util/SensitiveDataMaskUtil.java @@ -0,0 +1,150 @@ +package com.kakarote.core.security.util; + +import org.apache.commons.lang3.StringUtils; + +/** + * 敏感数据脱敏工具类 + * 提供姓名、手机号、地址等敏感信息的脱敏处理 + */ +public class SensitiveDataMaskUtil { + + /** + * 对姓名进行脱敏处理 + * 规则: + * 1. 单字名:显示姓氏,其余用*代替 + * 2. 双字名:显示姓氏,名用*代替 + * 3. 多字名:显示姓氏和最后一个字,中间用*代替 + * 4. 长度不足的情况:直接返回原字符串 + * + * @param name 姓名 + * @return 脱敏后的姓名 + */ + public static String maskName(String name) { + if (StringUtils.isBlank(name)) { + return StringUtils.EMPTY; + } + + int length = name.length(); + if (length == 1) { + // 单字名,直接返回 + return name; + } else if (length == 2) { + // 双字名,显示姓氏 + return name.charAt(0) + "*"; + } else if (length == 3) { + // 三字名,显示姓氏和最后一个字 + return name.charAt(0) + "*" + name.charAt(2); + } else { + // 多字名,显示姓氏和最后一个字,中间用*代替 + return name.charAt(0) + StringUtils.repeat("*", length - 2) + name.charAt(length - 1); + } + } + + /** + * 对手机号码进行脱敏处理 + * 规则:保留前3位和后4位,中间用*代替 + * + * @param phone 手机号码 + * @return 脱敏后的手机号码 + */ + public static String maskPhone(String phone) { + if (StringUtils.isBlank(phone) || phone.length() < 11) { + return phone; + } + return phone.substring(0, 3) + "****" + phone.substring(7); + } + + /** + * 对地址进行脱敏处理 + * 规则: + * 1. 保留省、市、区,后面的详细地址用*代替 + * 2. 如果地址较短,保留前8个字符,后面用*代替 + * + * @param address 地址 + * @return 脱敏后的地址 + */ + public static String maskAddress(String address) { + if (StringUtils.isBlank(address)) { + return StringUtils.EMPTY; + } + + int length = address.length(); + if (length <= 8) { + // 地址较短,保留前半部分 + int keepLength = Math.max(1, length / 2); + return address.substring(0, keepLength) + StringUtils.repeat("*", length - keepLength); + } else { + // 地址较长,保留前8个字符 + return address.substring(0, 8) + "****"; + } + } + + /** + * 对邮箱进行脱敏处理 + * 规则:保留前2位和域名,中间用*代替 + * + * @param email 邮箱地址 + * @return 脱敏后的邮箱 + */ + public static String maskEmail(String email) { + if (StringUtils.isBlank(email) || !email.contains("@")) { + return email; + } + + String[] parts = email.split("@"); + if (parts.length != 2) { + return email; + } + + String username = parts[0]; + String domain = parts[1]; + + if (username.length() <= 2) { + // 用户名较短,全部用*代替 + return StringUtils.repeat("*", username.length()) + "@" + domain; + } else { + // 保留前2位,其余用*代替 + return username.substring(0, 2) + StringUtils.repeat("*", username.length() - 2) + "@" + domain; + } + } + + /** + * 对身份证号进行脱敏处理 + * 规则:保留前6位和后4位,中间用*代替 + * + * @param idCard 身份证号 + * @return 脱敏后的身份证号 + */ + public static String maskIdCard(String idCard) { + if (StringUtils.isBlank(idCard) || idCard.length() < 18) { + return idCard; + } + return idCard.substring(0, 6) + StringUtils.repeat("*", 8) + idCard.substring(14); + } + + /** + * 通用脱敏方法 + * + * @param str 原始字符串 + * @param start 保留的开始位置(包含) + * @param end 保留的结束位置(包含) + * @return 脱敏后的字符串 + */ + public static String maskGeneral(String str, int start, int end) { + if (StringUtils.isBlank(str)) { + return StringUtils.EMPTY; + } + + int length = str.length(); + if (start < 0 || end < 0 || start >= length || end >= length || start >= end) { + return str; + } + + StringBuilder sb = new StringBuilder(); + sb.append(str.substring(0, start)); + sb.append(StringUtils.repeat("*", end - start + 1)); + sb.append(str.substring(end + 1)); + + return sb.toString(); + } +} \ No newline at end of file diff --git a/crm/src/main/java/com/kakarote/crm/common/InitEsIndexRunner.java b/crm/src/main/java/com/kakarote/crm/common/InitEsIndexRunner.java index 3009be5..4858a7a 100644 --- a/crm/src/main/java/com/kakarote/crm/common/InitEsIndexRunner.java +++ b/crm/src/main/java/com/kakarote/crm/common/InitEsIndexRunner.java @@ -12,6 +12,7 @@ import com.kakarote.core.common.SystemCodeEnum; import com.kakarote.core.exception.CrmException; import com.kakarote.core.feign.admin.entity.SimpleUser; import com.kakarote.core.field.FieldService; +import com.kakarote.core.security.util.EsDataEncryptUtil; import com.kakarote.core.servlet.ApplicationContextHolder; import com.kakarote.core.utils.UserCacheUtil; import com.kakarote.crm.constant.CrmEnum; @@ -458,10 +459,12 @@ public class InitEsIndexRunner implements ApplicationRunner { } } }); + // 在创建IndexRequest之前加密数据 + Map encryptedMap = EsDataEncryptUtil.encryptMap(map); IndexRequest request = new IndexRequest(crmEnum.getIndex(), "_doc"); - request.id(map.get(crmEnum.getPrimaryKey()).toString()); - request.source(map); + request.id(encryptedMap.get(crmEnum.getPrimaryKey()).toString()); + request.source(encryptedMap); bulkRequest.add(request); if (bulkRequest.requests().size() >= 1000) { bulk(bulkRequest); @@ -510,7 +513,10 @@ public class InitEsIndexRunner implements ApplicationRunner { } } } - request.doc(map); + // 在创建UpdateRequest之前加密数据 + Map encryptedMap = EsDataEncryptUtil.encryptMap(map); + + request.doc(encryptedMap); bulkRequest.add(request); if (bulkRequest.requests().size() >= 1000) { bulk(bulkRequest);