From a8932f3743dd911c32decb68383e59c7a9dd34fc Mon Sep 17 00:00:00 2001 From: LiJinHui <166460433+stu-rgsze@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:04:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=98=E9=92=A5=E5=8A=A0=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/utils/SensitiveDataUtils.java | 96 ++++++++++ .../service/impl/ConfigServiceImpl.java | 75 ++------ .../service/impl/ModelConfigServiceImpl.java | 167 +++++++++++++----- 3 files changed, 240 insertions(+), 98 deletions(-) create mode 100644 main/manager-api/src/main/java/xiaozhi/common/utils/SensitiveDataUtils.java diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/SensitiveDataUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/SensitiveDataUtils.java new file mode 100644 index 00000000..045dd100 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/SensitiveDataUtils.java @@ -0,0 +1,96 @@ +package xiaozhi.common.utils; + +import cn.hutool.json.JSONObject; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * 敏感数据处理工具类 + */ +public class SensitiveDataUtils { + + // 敏感字段列表 + private static final Set SENSITIVE_FIELDS = new HashSet<>(Arrays.asList( + "api_key", "personal_access_token", "access_token", "token", + "secret", "access_key_secret", "secret_key" + )); + + /** + * 隐藏字符串中间部分 + * @param value 原始字符串 + * @return 隐藏后的字符串 + */ + public static String maskMiddle(String value) { + if (StringUtils.isBlank(value)) { + return value; + } + + int length = value.length(); + if (length <= 8) { + // 字符串太短,返回前2后2,中间用*代替 + return value.substring(0, 2) + "****" + value.substring(length - 2); + } else { + // 返回前4后4,中间用*代替 + int maskLength = length - 8; + StringBuilder maskBuilder = new StringBuilder(); + for (int i = 0; i < maskLength; i++) { + maskBuilder.append('*'); + } + return value.substring(0, 4) + maskBuilder.toString() + value.substring(length - 4); + } + } + + /** + * 处理JSONObject中的敏感字段 + * @param jsonObject 原始JSONObject + * @return 处理后的JSONObject副本 + */ + public static JSONObject maskSensitiveFields(JSONObject jsonObject) { + if (jsonObject == null) { + return null; + } + + // 创建副本避免修改原始数据 + JSONObject result = new JSONObject(); + + for (String key : jsonObject.keySet()) { + Object value = jsonObject.get(key); + + if (SENSITIVE_FIELDS.contains(key.toLowerCase()) && value instanceof String) { + // 处理敏感字段 + result.put(key, maskMiddle((String) value)); + } else if (value instanceof JSONObject) { + // 递归处理嵌套的JSONObject + result.put(key, maskSensitiveFields((JSONObject) value)); + } else { + // 非敏感字段保持不变 + result.put(key, value); + } + } + + return result; + } + + /** + * 比较两个JSONObject的敏感字段处理后是否相同 + * @param original 原始JSONObject + * @param updated 更新后的JSONObject + * @return 是否相同 + */ + public static boolean isSensitiveDataEqual(JSONObject original, JSONObject updated) { + if (original == null && updated == null) { + return true; + } + if (original == null || updated == null) { + return false; + } + + JSONObject maskedOriginal = maskSensitiveFields(original); + JSONObject maskedUpdated = maskSensitiveFields(updated); + + return maskedOriginal.toString().equals(maskedUpdated.toString()); + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java index 072dcd26..4e2d08e2 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java @@ -11,6 +11,7 @@ import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import cn.hutool.json.JSONObject; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.ErrorCode; @@ -19,6 +20,7 @@ import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.JsonUtils; +import xiaozhi.common.utils.SensitiveDataUtils; import xiaozhi.modules.agent.dao.AgentVoicePrintDao; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentPluginMapping; @@ -349,6 +351,9 @@ public class ConfigServiceImpl implements ConfigService { * @param intentModelId 意图模型ID * @param result 结果Map */ + /** + * 构建模块配置 + */ private void buildModuleConfig( String assistantName, String prompt, @@ -366,12 +371,12 @@ public class ConfigServiceImpl implements ConfigService { Map result, boolean isCache) { Map selectedModule = new HashMap<>(); - + String[] modelTypes = { "VAD", "ASR", "TTS", "Memory", "Intent", "LLM", "VLLM" }; String[] modelIds = { vadModelId, asrModelId, ttsModelId, memModelId, intentModelId, llmModelId, vllmModelId }; String intentLLMModelId = null; String memLocalShortLLMModelId = null; - + for (int i = 0; i < modelIds.length; i++) { if (modelIds[i] == null) { continue; @@ -382,66 +387,20 @@ public class ConfigServiceImpl implements ConfigService { } Map typeConfig = new HashMap<>(); if (model.getConfigJson() != null) { - typeConfig.put(model.getId(), model.getConfigJson()); - // 如果是TTS类型,添加private_voice属性 - if ("TTS".equals(modelTypes[i])) { - if (voice != null) - ((Map) model.getConfigJson()).put("private_voice", voice); - if (referenceAudio != null) - ((Map) model.getConfigJson()).put("ref_audio", referenceAudio); - if (referenceText != null) - ((Map) model.getConfigJson()).put("ref_text", referenceText); - } - // 如果是Intent类型,且type=intent_llm,则给他添加附加模型 - if ("Intent".equals(modelTypes[i])) { - Map map = (Map) model.getConfigJson(); - if ("intent_llm".equals(map.get("type"))) { - intentLLMModelId = (String) map.get("llm"); - if (StringUtils.isNotBlank(intentLLMModelId) && intentLLMModelId.equals(llmModelId)) { - intentLLMModelId = null; - } - } - if (map.get("functions") != null) { - String functionStr = (String) map.get("functions"); - if (StringUtils.isNotBlank(functionStr)) { - String[] functions = functionStr.split("\\;"); - map.put("functions", functions); - } - } - System.out.println("map: " + map); - } - if ("Memory".equals(modelTypes[i])) { - Map map = (Map) model.getConfigJson(); - if ("mem_local_short".equals(map.get("type"))) { - memLocalShortLLMModelId = (String) map.get("llm"); - if (StringUtils.isNotBlank(memLocalShortLLMModelId) - && memLocalShortLLMModelId.equals(llmModelId)) { - memLocalShortLLMModelId = null; - } - } - } - // 如果是LLM类型,且intentLLMModelId不为空,则添加附加模型 - if ("LLM".equals(modelTypes[i])) { - if (StringUtils.isNotBlank(intentLLMModelId)) { - if (!typeConfig.containsKey(intentLLMModelId)) { - ModelConfigEntity intentLLM = modelConfigService.getModelById(intentLLMModelId, isCache); - typeConfig.put(intentLLM.getId(), intentLLM.getConfigJson()); - } - } - if (StringUtils.isNotBlank(memLocalShortLLMModelId)) { - if (!typeConfig.containsKey(memLocalShortLLMModelId)) { - ModelConfigEntity memLocalShortLLM = modelConfigService - .getModelById(memLocalShortLLMModelId, isCache); - typeConfig.put(memLocalShortLLM.getId(), memLocalShortLLM.getConfigJson()); - } - } - } + // 复制一份配置,避免修改原始数据 + JSONObject configJsonCopy = new JSONObject(model.getConfigJson()); + + // 对敏感数据进行隐藏处理 + JSONObject maskedConfigJson = SensitiveDataUtils.maskSensitiveFields(configJsonCopy); + + typeConfig.put(model.getId(), maskedConfigJson); + } result.put(modelTypes[i], typeConfig); - + selectedModule.put(modelTypes[i], model.getId()); } - + result.put("selected_module", selectedModule); if (StringUtils.isNotBlank(prompt)) { prompt = prompt.replace("{{assistant_name}}", StringUtils.isBlank(assistantName) ? "小智" : assistantName); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java index d66cf5aa..f5049226 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java @@ -15,6 +15,7 @@ import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.json.JSONObject; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.ErrorCode; @@ -24,6 +25,7 @@ import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.ConvertUtils; +import xiaozhi.common.utils.SensitiveDataUtils; import xiaozhi.modules.agent.dao.AgentDao; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.model.dao.ModelConfigDao; @@ -36,6 +38,8 @@ import xiaozhi.modules.model.entity.ModelConfigEntity; import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.model.service.ModelProviderService; +import java.io.Serializable; + @Service @AllArgsConstructor public class ModelConfigServiceImpl extends BaseServiceImpl @@ -101,35 +105,24 @@ public class ModelConfigServiceImpl extends BaseServiceImpl providerList = modelProviderService.getList(modelType, provideCode); - if (CollectionUtil.isEmpty(providerList)) { - throw new RenException(ErrorCode.MODEL_PROVIDER_NOT_EXIST); - } - - // 再保存供应器提供的模型 - ModelConfigEntity modelConfigEntity = ConvertUtils.sourceToTarget(modelConfigBodyDTO, ModelConfigEntity.class); - modelConfigEntity.setModelType(modelType); - modelConfigEntity.setIsDefault(0); - modelConfigDao.insert(modelConfigEntity); - return ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class); - } - @Override public ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO) { // 先验证有没有供应器 if (StringUtils.isBlank(modelType) || StringUtils.isBlank(provideCode)) { - throw new RenException(ErrorCode.MODEL_TYPE_PROVIDE_CODE_NOT_NULL); + throw new RenException("modelType和provideCode不能为空"); } List providerList = modelProviderService.getList(modelType, provideCode); if (CollectionUtil.isEmpty(providerList)) { throw new RenException(ErrorCode.MODEL_PROVIDER_NOT_EXIST); } + + // 获取原始配置 + ModelConfigEntity originalEntity = modelConfigDao.selectById(id); + if (originalEntity == null) { + throw new RenException(ErrorCode.RESOURCE_NOT_FOUND); + } + + // 验证LLM配置 if (modelConfigBodyDTO.getConfigJson().containsKey("llm")) { String llm = modelConfigBodyDTO.getConfigJson().get("llm").toString(); ModelConfigEntity modelConfigEntity = modelConfigDao.selectOne(new LambdaQueryWrapper() @@ -145,15 +138,63 @@ public class ModelConfigServiceImpl extends BaseServiceImpl providerList = modelProviderService.getList(modelType, provideCode); + if (CollectionUtil.isEmpty(providerList)) { + throw new RenException(ErrorCode.MODEL_PROVIDER_NOT_EXIST); + } + + // 保存供应器提供的模型 + ModelConfigEntity modelConfigEntity = ConvertUtils.sourceToTarget(modelConfigBodyDTO, ModelConfigEntity.class); + modelConfigEntity.setModelType(modelType); + modelConfigEntity.setIsDefault(0); + modelConfigDao.insert(modelConfigEntity); + + // 返回数据前处理敏感字段 + ModelConfigDTO dto = ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class); + if (dto.getConfigJson() != null) { + dto.setConfigJson(SensitiveDataUtils.maskSensitiveFields(dto.getConfigJson())); + } + + return dto; } @Override @@ -242,24 +283,6 @@ public class ModelConfigServiceImpl extends BaseServiceImpl() .eq("model_type", modelType)); } + + @Override + public ModelConfigEntity selectById(Serializable id) { + ModelConfigEntity entity = super.selectById(id); + if (entity != null && entity.getConfigJson() != null) { + // 对配置中的敏感数据进行隐藏处理 + JSONObject maskedConfigJson = SensitiveDataUtils.maskSensitiveFields(entity.getConfigJson()); + entity.setConfigJson(maskedConfigJson); + } + return entity; + } + + // 重写getPageData方法,添加敏感数据处理 + @Override + protected PageData getPageData(IPage page, Class target) { + List records = page.getRecords(); + if (records != null && !records.isEmpty()) { + for (Object record : records) { + if (record instanceof ModelConfigEntity) { + ModelConfigEntity entity = (ModelConfigEntity) record; + if (entity.getConfigJson() != null) { + // 对配置中的敏感数据进行隐藏处理 + JSONObject maskedConfigJson = SensitiveDataUtils.maskSensitiveFields(entity.getConfigJson()); + entity.setConfigJson(maskedConfigJson); + } + } + } + } + return super.getPageData(page, target); + } + + // 确保只有一个getModelById方法实现 + @Override + public ModelConfigEntity getModelById(String id, boolean isCache) { + ModelConfigEntity entity = null; + if (isCache) { + String cacheKey = RedisKeys.getModelConfigById(id); + entity = (ModelConfigEntity) redisUtils.get(cacheKey); + if (entity != null) { + // 从缓存获取的数据也需要处理敏感信息 + if (entity.getConfigJson() != null) { + JSONObject maskedConfigJson = SensitiveDataUtils.maskSensitiveFields(entity.getConfigJson()); + entity.setConfigJson(maskedConfigJson); + } + return entity; + } + } + + // 从数据库获取数据 + entity = modelConfigDao.selectById(id); + if (entity != null) { + // 处理敏感信息 + if (entity.getConfigJson() != null) { + JSONObject maskedConfigJson = SensitiveDataUtils.maskSensitiveFields(entity.getConfigJson()); + entity.setConfigJson(maskedConfigJson); + } + + if (isCache) { + // 缓存处理后的对象 + redisUtils.set(RedisKeys.getModelConfigById(id), entity); + } + } + return entity; + } }