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 01/10] =?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; + } } From ef4abf08f02c7ec2ca758d78c8a42f47d130865b Mon Sep 17 00:00:00 2001 From: LiJinHui <166460433+stu-rgsze@users.noreply.github.com> Date: Fri, 26 Sep 2025 14:44:20 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/service/impl/ConfigServiceImpl.java | 13 +++++-------- .../model/service/impl/ModelConfigServiceImpl.java | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) 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 4e2d08e2..6dfe2a28 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 @@ -371,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; @@ -390,17 +390,14 @@ public class ConfigServiceImpl implements ConfigService { // 复制一份配置,避免修改原始数据 JSONObject configJsonCopy = new JSONObject(model.getConfigJson()); - // 对敏感数据进行隐藏处理 - JSONObject maskedConfigJson = SensitiveDataUtils.maskSensitiveFields(configJsonCopy); - - typeConfig.put(model.getId(), maskedConfigJson); + typeConfig.put(model.getId(), configJsonCopy); } 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 f5049226..15be54e2 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 @@ -138,7 +138,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl providerList = modelProviderService.getList(modelType, provideCode); if (CollectionUtil.isEmpty(providerList)) { From f345568675ef4a0e186cafd0ef36b7dfc7a997e4 Mon Sep 17 00:00:00 2001 From: LiJinHui <166460433+stu-rgsze@users.noreply.github.com> Date: Sun, 28 Sep 2025 09:32:54 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A7=98=E9=92=A5?= =?UTF-8?q?=E6=B1=A1=E6=9F=93=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/utils/SensitiveDataUtils.java | 128 ++++- .../service/impl/ConfigServiceImpl.java | 6 +- .../service/impl/ModelConfigServiceImpl.java | 538 +++++++++++------- 3 files changed, 443 insertions(+), 229 deletions(-) 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 index 045dd100..e3fc6c35 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/SensitiveDataUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/SensitiveDataUtils.java @@ -5,35 +5,39 @@ import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; 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" - )); - + "api_key", "personal_access_token", "access_token", "token", + "secret", "access_key_secret", "secret_key")); + + /** + * 检查字段是否为敏感字段 + */ + public static boolean isSensitiveField(String fieldName) { + return StringUtils.isNotBlank(fieldName) && SENSITIVE_FIELDS.contains(fieldName.toLowerCase()); + } + /** * 隐藏字符串中间部分 - * @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++) { @@ -42,43 +46,46 @@ public class SensitiveDataUtils { return value.substring(0, 4) + maskBuilder.toString() + value.substring(length - 4); } } - + + /** + * 判断字符串是否是被掩码处理过的值 + */ + public static boolean isMaskedValue(String value) { + if (StringUtils.isBlank(value)) { + return false; + } + // 掩码值至少包含4个连续的* + return value.contains("****"); + } + /** * 处理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 是否相同 + * 比较两个JSONObject的敏感字段是否相同 + * 特别针对api_key等敏感字段进行单独比较 */ public static boolean isSensitiveDataEqual(JSONObject original, JSONObject updated) { if (original == null && updated == null) { @@ -87,10 +94,69 @@ public class SensitiveDataUtils { if (original == null || updated == null) { return false; } - - JSONObject maskedOriginal = maskSensitiveFields(original); - JSONObject maskedUpdated = maskSensitiveFields(updated); - - return maskedOriginal.toString().equals(maskedUpdated.toString()); + + // 提取并比较特定敏感字段 + return compareSpecificSensitiveFields(original, updated, "api_key") && + compareSpecificSensitiveFields(original, updated, "personal_access_token") && + compareSpecificSensitiveFields(original, updated, "access_token") && + compareSpecificSensitiveFields(original, updated, "token") && + compareSpecificSensitiveFields(original, updated, "secret") && + compareSpecificSensitiveFields(original, updated, "access_key_secret") && + compareSpecificSensitiveFields(original, updated, "secret_key"); + } + + /** + * 比较两个JSON对象中特定敏感字段是否相同 + * 遍历整个JSON对象树,查找并比较指定敏感字段 + */ + private static boolean compareSpecificSensitiveFields(JSONObject original, JSONObject updated, String fieldName) { + // 提取原始对象中的指定敏感字段 + Map originalFields = new HashMap<>(); + extractSpecificSensitiveField(original, originalFields, fieldName, ""); + + // 提取更新对象中的指定敏感字段 + Map updatedFields = new HashMap<>(); + extractSpecificSensitiveField(updated, updatedFields, fieldName, ""); + + // 如果字段数量不同,说明有增删 + if (originalFields.size() != updatedFields.size()) { + return false; + } + + // 比较每个字段的值 + for (Map.Entry entry : originalFields.entrySet()) { + String key = entry.getKey(); + String originalValue = entry.getValue(); + String updatedValue = updatedFields.get(key); + + if (updatedValue == null || !updatedValue.equals(originalValue)) { + return false; + } + } + + return true; + } + + /** + * 递归提取JSON对象中指定名称的敏感字段 + */ + private static void extractSpecificSensitiveField(JSONObject jsonObject, Map fieldsMap, + String targetFieldName, String parentPath) { + if (jsonObject == null) { + return; + } + + for (String key : jsonObject.keySet()) { + String fullPath = parentPath.isEmpty() ? key : parentPath + "." + key; + Object value = jsonObject.get(key); + + if (value instanceof JSONObject) { + // 递归处理嵌套JSON对象 + extractSpecificSensitiveField((JSONObject) value, fieldsMap, targetFieldName, fullPath); + } else if (value instanceof String && key.equalsIgnoreCase(targetFieldName)) { + // 找到目标敏感字段,保存其路径和值 + fieldsMap.put(fullPath, (String) value); + } + } } } \ 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 6dfe2a28..f6779c08 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 @@ -300,7 +300,7 @@ public class ConfigServiceImpl implements ConfigService { Map voiceprintConfig = new HashMap<>(); voiceprintConfig.put("url", voiceprintUrl); voiceprintConfig.put("speakers", speakers); - + // 获取声纹识别相似度阈值,默认0.4 String thresholdStr = sysParamsService.getValue("server.voiceprint_similarity_threshold", true); if (StringUtils.isNotBlank(thresholdStr) && !"null".equals(thresholdStr)) { @@ -389,9 +389,9 @@ public class ConfigServiceImpl implements ConfigService { if (model.getConfigJson() != null) { // 复制一份配置,避免修改原始数据 JSONObject configJsonCopy = new JSONObject(model.getConfigJson()); - + typeConfig.put(model.getId(), configJsonCopy); - + } result.put(modelTypes[i], typeConfig); 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 15be54e2..3b113d41 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 @@ -1,5 +1,6 @@ package xiaozhi.modules.model.service.impl; +import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -11,8 +12,8 @@ import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.core.metadata.OrderItem; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +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; @@ -38,8 +39,6 @@ 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 @@ -69,7 +68,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl { LlmModelBasicInfoDTO dto = new LlmModelBasicInfoDTO(); dto.setId(item.getId()); @@ -82,139 +81,386 @@ public class ModelConfigServiceImpl extends BaseServiceImpl getPageList(String modelType, String modelName, String page, String limit) { - Map params = new HashMap(); + Map params = new HashMap<>(); params.put(Constant.PAGE, page); params.put(Constant.LIMIT, limit); - - // 不再使用默认的getPage方法,而是直接创建Page对象并自定义排序 + long curPage = Long.parseLong(page); long pageSize = Long.parseLong(limit); Page pageInfo = new Page<>(curPage, pageSize); - + // 添加排序规则:先按is_enabled降序,再按sort升序 pageInfo.addOrder(OrderItem.desc("is_enabled")); pageInfo.addOrder(OrderItem.asc("sort")); - - // 执行分页查询 + IPage modelConfigEntityIPage = modelConfigDao.selectPage( pageInfo, new QueryWrapper() .eq("model_type", modelType) .like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%")); - + return getPageData(modelConfigEntityIPage, ModelConfigDTO.class); } @Override public ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO) { - // 先验证有没有供应器 - if (StringUtils.isBlank(modelType) || StringUtils.isBlank(provideCode)) { - 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() - .eq(ModelConfigEntity::getId, llm)); - String selectModelType = (modelConfigEntity == null || modelConfigEntity.getModelType() == null) ? null - : modelConfigEntity.getModelType().toUpperCase(); - if (modelConfigEntity == null || !"LLM".equals(selectModelType)) { - throw new RenException(ErrorCode.LLM_NOT_EXIST); - } - String type = modelConfigEntity.getConfigJson().get("type").toString(); - // 如果查询大语言模型是openai或者ollama,意图识别选参数都可以 - if (!"openai".equals(type) && !"ollama".equals(type)) { - throw new RenException(ErrorCode.INVALID_LLM_TYPE); - } - } + // 1. 参数验证 + validateEditParameters(modelType, provideCode, id, modelConfigBodyDTO); - // 再更新供应器提供的模型 - ModelConfigEntity modelConfigEntity = ConvertUtils.sourceToTarget(modelConfigBodyDTO, ModelConfigEntity.class); - modelConfigEntity.setId(id); - modelConfigEntity.setModelType(modelType); - - // 检查敏感字段是否有变化 - if (originalEntity.getConfigJson() != null && modelConfigBodyDTO.getConfigJson() != null) { - boolean sensitiveEqual = SensitiveDataUtils.isSensitiveDataEqual( - originalEntity.getConfigJson(), - modelConfigBodyDTO.getConfigJson() - ); - - if (sensitiveEqual) { - // 敏感数据没有变化,使用原始的configJson值 - modelConfigEntity.setConfigJson(originalEntity.getConfigJson()); - } - } - + // 2. 验证模型提供者 + validateModelProvider(modelType, provideCode); + + // 3. 获取原始配置(不经过敏感数据处理) + ModelConfigEntity originalEntity = getOriginalConfigFromDb(id); + + // 4. 验证LLM配置 + validateLlmConfiguration(modelConfigBodyDTO); + + // 5. 准备更新实体并处理敏感数据 + ModelConfigEntity modelConfigEntity = prepareUpdateEntity(modelConfigBodyDTO, originalEntity, modelType, id); + + // 6. 执行数据库更新 modelConfigDao.updateById(modelConfigEntity); - - // 清除缓存 - redisUtils.delete(RedisKeys.getModelConfigById(modelConfigEntity.getId())); - - // 返回数据前处理敏感字段 - ModelConfigDTO dto = ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class); - if (dto.getConfigJson() != null) { - dto.setConfigJson(SensitiveDataUtils.maskSensitiveFields(dto.getConfigJson())); - } - - return dto; + + // 7. 清除缓存 + clearModelCache(id); + + // 8. 返回处理后的数据(包含敏感数据掩码) + return buildResponseDTO(modelConfigEntity); } @Override public ModelConfigDTO add(String modelType, String provideCode, ModelConfigBodyDTO modelConfigBodyDTO) { - // 先验证有没有供应器 - if (StringUtils.isBlank(modelType) || StringUtils.isBlank(provideCode)) { - throw new RenException(ErrorCode.MODEL_TYPE_PROVIDE_CODE_NOT_NULL); - } - List 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); + validateAddParameters(modelType, provideCode, modelConfigBodyDTO); + + validateModelProvider(modelType, provideCode); + + ModelConfigEntity modelConfigEntity = prepareAddEntity(modelConfigBodyDTO, modelType); + modelConfigDao.insert(modelConfigEntity); - - // 返回数据前处理敏感字段 - ModelConfigDTO dto = ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class); - if (dto.getConfigJson() != null) { - dto.setConfigJson(SensitiveDataUtils.maskSensitiveFields(dto.getConfigJson())); - } - - return dto; + + return buildResponseDTO(modelConfigEntity); } @Override public void delete(String id) { - // 查看是否是默认 + if (StringUtils.isBlank(id)) { + throw new RenException(ErrorCode.IDENTIFIER_NOT_NULL); + } + ModelConfigEntity modelConfig = modelConfigDao.selectById(id); if (modelConfig != null && modelConfig.getIsDefault() == 1) { throw new RenException(ErrorCode.DEFAULT_MODEL_DELETE_ERROR); } - // 验证是否有引用 + checkAgentReference(id); checkIntentConfigReference(id); modelConfigDao.deleteById(id); + + clearModelCache(id); + } + + @Override + public String getModelNameById(String id) { + if (StringUtils.isBlank(id)) { + return null; + } + + String cacheKey = RedisKeys.getModelNameById(id); + String cachedName = (String) redisUtils.get(cacheKey); + if (StringUtils.isNotBlank(cachedName)) { + return cachedName; + } + + ModelConfigEntity entity = modelConfigDao.selectById(id); + if (entity != null) { + String modelName = entity.getModelName(); + if (StringUtils.isNotBlank(modelName)) { + redisUtils.set(cacheKey, modelName); + } + return modelName; + } + + return null; + } + + @Override + public ModelConfigEntity selectById(Serializable id) { + ModelConfigEntity entity = super.selectById(id); + if (entity != null && entity.getConfigJson() != null) { + entity.setConfigJson(maskSensitiveFields(entity.getConfigJson())); + } + return entity; + } + + @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) { + entity.setConfigJson(maskSensitiveFields(entity.getConfigJson())); + } + } + } + } + return super.getPageData(page, target); + } + + @Override + public ModelConfigEntity getModelById(String id, boolean isCache) { + if (StringUtils.isBlank(id)) { + return null; + } + + ModelConfigEntity entity = null; + + if (isCache) { + String cacheKey = RedisKeys.getModelConfigById(id); + entity = (ModelConfigEntity) redisUtils.get(cacheKey); + if (entity != null) { + return entity; + } + } + + // 从数据库获取数据 + entity = modelConfigDao.selectById(id); + if (entity != null) { + if (entity.getConfigJson() != null) { + entity.setConfigJson(maskSensitiveFields(entity.getConfigJson())); + } + + if (isCache) { + redisUtils.set(RedisKeys.getModelConfigById(id), entity); + } + } + return entity; + } + + /** + * 验证编辑参数 + */ + private void validateEditParameters(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); + } + if (StringUtils.isBlank(id)) { + throw new RenException(ErrorCode.IDENTIFIER_NOT_NULL); + } + if (modelConfigBodyDTO == null) { + throw new RenException(ErrorCode.PARAMS_GET_ERROR); + } + } + + /** + * 验证添加参数 + */ + private void validateAddParameters(String modelType, String provideCode, ModelConfigBodyDTO modelConfigBodyDTO) { + if (StringUtils.isBlank(modelType) || StringUtils.isBlank(provideCode)) { + throw new RenException(ErrorCode.MODEL_TYPE_PROVIDE_CODE_NOT_NULL); + } + if (modelConfigBodyDTO == null) { + throw new RenException(ErrorCode.PARAMS_GET_ERROR); + } + } + + /** + * 设置默认模型 + */ + @Override + public void setDefaultModel(String modelType, int isDefault) { + // 参数验证 + if (StringUtils.isBlank(modelType)) { + throw new RenException(ErrorCode.MODEL_TYPE_PROVIDE_CODE_NOT_NULL); + } + + ModelConfigEntity entity = new ModelConfigEntity(); + entity.setIsDefault(isDefault); + modelConfigDao.update(entity, new QueryWrapper() + .eq("model_type", modelType)); + + // 清除相关缓存 + clearModelCacheByType(modelType); + } + + /** + * 验证模型提供者 + */ + private void validateModelProvider(String modelType, String provideCode) { + List providerList = modelProviderService.getList(modelType, provideCode); + if (CollectionUtil.isEmpty(providerList)) { + throw new RenException(ErrorCode.MODEL_PROVIDER_NOT_EXIST); + } + } + + /** + * 从数据库获取原始配置(不经过敏感数据处理) + */ + private ModelConfigEntity getOriginalConfigFromDb(String id) { + ModelConfigEntity originalEntity = modelConfigDao.selectById(id); + if (originalEntity == null) { + throw new RenException(ErrorCode.RESOURCE_NOT_FOUND); + } + return originalEntity; + } + + /** + * 验证LLM配置 + */ + private void validateLlmConfiguration(ModelConfigBodyDTO modelConfigBodyDTO) { + if (modelConfigBodyDTO.getConfigJson() != null && modelConfigBodyDTO.getConfigJson().containsKey("llm")) { + String llm = modelConfigBodyDTO.getConfigJson().get("llm").toString(); + ModelConfigEntity modelConfigEntity = modelConfigDao.selectOne(new LambdaQueryWrapper() + .eq(ModelConfigEntity::getId, llm)); + + if (modelConfigEntity == null) { + throw new RenException(ErrorCode.LLM_NOT_EXIST); + } + + String modelType = modelConfigEntity.getModelType(); + if (modelType == null || !"LLM".equals(modelType.toUpperCase())) { + throw new RenException(ErrorCode.LLM_NOT_EXIST); + } + + // 验证LLM类型 + JSONObject configJson = modelConfigEntity.getConfigJson(); + if (configJson != null && configJson.containsKey("type")) { + String type = configJson.get("type").toString(); + if (!"openai".equals(type) && !"ollama".equals(type)) { + throw new RenException(ErrorCode.INVALID_LLM_TYPE); + } + } + } + } + + /** + * 准备更新实体,处理敏感数据 + */ + private ModelConfigEntity prepareUpdateEntity(ModelConfigBodyDTO modelConfigBodyDTO, + ModelConfigEntity originalEntity, + String modelType, + String id) { + // 1. 复制原始实体,保留所有原始数据(包括敏感信息) + ModelConfigEntity modelConfigEntity = ConvertUtils.sourceToTarget(originalEntity, ModelConfigEntity.class); + modelConfigEntity.setId(id); + modelConfigEntity.setModelType(modelType); + + // 2. 只更新非敏感字段 + modelConfigEntity.setModelName(modelConfigBodyDTO.getModelName()); + modelConfigEntity.setSort(modelConfigBodyDTO.getSort()); + modelConfigEntity.setIsEnabled(modelConfigBodyDTO.getIsEnabled()); + + // 3. 处理配置JSON,仅更新非敏感字段和明确修改的敏感字段 + if (modelConfigBodyDTO.getConfigJson() != null && originalEntity.getConfigJson() != null) { + JSONObject originalJson = originalEntity.getConfigJson(); + JSONObject updatedJson = new JSONObject(originalJson); // 基于原始JSON进行修改 + + // 遍历更新的JSON,只更新非敏感字段或确实被修改的敏感字段 + for (String key : modelConfigBodyDTO.getConfigJson().keySet()) { + Object value = modelConfigBodyDTO.getConfigJson().get(key); + + // 如果是敏感字段,需要确认是否真的被修改(前端传入的可能是掩码后的值) + if (SensitiveDataUtils.isSensitiveField(key)) { + // 只有当传入的值不是掩码格式时,才认为是真实修改 + if (value instanceof String && !isMaskedValue((String) value)) { + updatedJson.put(key, value); + } + } else if (value instanceof JSONObject) { + // 递归处理嵌套JSON + mergeJson(updatedJson, key, (JSONObject) value); + } else { + // 非敏感字段直接更新 + updatedJson.put(key, value); + } + } + + modelConfigEntity.setConfigJson(updatedJson); + } + + return modelConfigEntity; + } + + // 辅助方法:判断值是否是掩码格式 + private boolean isMaskedValue(String value) { + if (value == null) + return false; + // 简单判断是否包含掩码的特征(***) + return value.contains("***"); + } + + // 辅助方法:递归合并JSON,保留原始敏感字段 + private void mergeJson(JSONObject original, String key, JSONObject updated) { + if (!original.containsKey(key)) { + original.put(key, new JSONObject()); + } + JSONObject originalChild = original.getJSONObject(key); + + for (String childKey : updated.keySet()) { + Object childValue = updated.get(childKey); + if (childValue instanceof JSONObject) { + mergeJson(originalChild, childKey, (JSONObject) childValue); + } else { + if (!SensitiveDataUtils.isSensitiveField(childKey) || + (childValue instanceof String && !isMaskedValue((String) childValue))) { + originalChild.put(childKey, childValue); + } + } + } + } + + /** + * 准备新增实体 + */ + private ModelConfigEntity prepareAddEntity(ModelConfigBodyDTO modelConfigBodyDTO, String modelType) { + ModelConfigEntity modelConfigEntity = ConvertUtils.sourceToTarget(modelConfigBodyDTO, ModelConfigEntity.class); + modelConfigEntity.setModelType(modelType); + modelConfigEntity.setIsDefault(0); + return modelConfigEntity; + } + + /** + * 构建返回的DTO,处理敏感数据 + */ + private ModelConfigDTO buildResponseDTO(ModelConfigEntity entity) { + ModelConfigDTO dto = ConvertUtils.sourceToTarget(entity, ModelConfigDTO.class); + if (dto.getConfigJson() != null) { + dto.setConfigJson(maskSensitiveFields(dto.getConfigJson())); + } + return dto; + } + + /** + * 处理敏感字段 + */ + private JSONObject maskSensitiveFields(JSONObject configJson) { + return SensitiveDataUtils.maskSensitiveFields(configJson); + } + + /** + * 清除模型缓存 + */ + private void clearModelCache(String id) { + redisUtils.delete(RedisKeys.getModelConfigById(id)); + redisUtils.delete(RedisKeys.getModelNameById(id)); + } + + /** + * 按模型类型清除缓存 + */ + private void clearModelCacheByType(String modelType) { + List entities = modelConfigDao.selectList( + new QueryWrapper().eq("model_type", modelType)); + for (ModelConfigEntity entity : entities) { + clearModelCache(entity.getId()); + } } /** * 检查智能体配置是否有引用 - * - * @param modelId 模型ID */ private void checkAgentReference(String modelId) { List agents = agentDao.selectList( @@ -242,8 +488,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; - } } From feb668c96c02903a67650a38a43e05e4c5b6dd73 Mon Sep 17 00:00:00 2001 From: LiJinHui <166460433+stu-rgsze@users.noreply.github.com> Date: Sun, 28 Sep 2025 10:28:01 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E8=BF=98=E5=8E=9FPython=E7=9A=84?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/ConfigServiceImpl.java | 70 +++++++++++++++---- 1 file changed, 57 insertions(+), 13 deletions(-) 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 f6779c08..5586b9f0 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,7 +11,6 @@ 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; @@ -20,7 +19,6 @@ 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; @@ -72,7 +70,7 @@ public class ConfigServiceImpl implements ConfigService { // 查询默认智能体 AgentTemplateEntity agent = agentTemplateService.getDefaultTemplate(); if (agent == null) { - throw new RenException(ErrorCode.DEFAULT_AGENT_NOT_FOUND); + throw new RenException("默认智能体未找到"); } // 构建模块配置 @@ -115,7 +113,7 @@ public class ConfigServiceImpl implements ConfigService { // 获取智能体信息 AgentEntity agent = agentService.getAgentById(device.getAgentId()); if (agent == null) { - throw new RenException(ErrorCode.AGENT_NOT_FOUND); + throw new RenException("智能体未找到"); } // 获取音色信息 String voice = null; @@ -300,7 +298,7 @@ public class ConfigServiceImpl implements ConfigService { Map voiceprintConfig = new HashMap<>(); voiceprintConfig.put("url", voiceprintUrl); voiceprintConfig.put("speakers", speakers); - + // 获取声纹识别相似度阈值,默认0.4 String thresholdStr = sysParamsService.getValue("server.voiceprint_similarity_threshold", true); if (StringUtils.isNotBlank(thresholdStr) && !"null".equals(thresholdStr)) { @@ -351,9 +349,6 @@ public class ConfigServiceImpl implements ConfigService { * @param intentModelId 意图模型ID * @param result 结果Map */ - /** - * 构建模块配置 - */ private void buildModuleConfig( String assistantName, String prompt, @@ -387,11 +382,60 @@ public class ConfigServiceImpl implements ConfigService { } Map typeConfig = new HashMap<>(); if (model.getConfigJson() != null) { - // 复制一份配置,避免修改原始数据 - JSONObject configJsonCopy = new JSONObject(model.getConfigJson()); - - typeConfig.put(model.getId(), configJsonCopy); - + 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()); + } + } + } } result.put(modelTypes[i], typeConfig); From 776b5fb3b4af8d614205ed23a2273bb7bbce7dca Mon Sep 17 00:00:00 2001 From: LiJinHui <166460433+stu-rgsze@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:27:46 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A0=E5=AF=86?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/utils/SensitiveDataUtils.java | 2 + .../service/impl/ConfigServiceImpl.java | 15 +++--- .../model/service/ModelConfigService.java | 20 ++++++-- .../service/impl/ModelConfigServiceImpl.java | 46 ++++++++++++------- 4 files changed, 56 insertions(+), 27 deletions(-) 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 index e3fc6c35..05f4853a 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/SensitiveDataUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/SensitiveDataUtils.java @@ -36,8 +36,10 @@ public class SensitiveDataUtils { 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++) { 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 5586b9f0..c813a398 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 @@ -350,7 +350,7 @@ public class ConfigServiceImpl implements ConfigService { * @param result 结果Map */ private void buildModuleConfig( - String assistantName, + String assistantName, String prompt, String summaryMemory, String voice, @@ -366,17 +366,18 @@ 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; } - ModelConfigEntity model = modelConfigService.getModelById(modelIds[i], isCache); + // 关键:第三个参数传false,确保获取原始密钥 + ModelConfigEntity model = modelConfigService.getModelById(modelIds[i], isCache, false); if (model == null) { continue; } @@ -424,14 +425,16 @@ public class ConfigServiceImpl implements ConfigService { if ("LLM".equals(modelTypes[i])) { if (StringUtils.isNotBlank(intentLLMModelId)) { if (!typeConfig.containsKey(intentLLMModelId)) { - ModelConfigEntity intentLLM = modelConfigService.getModelById(intentLLMModelId, isCache); + // 修改这里:添加isMaskSensitive=false参数 + ModelConfigEntity intentLLM = modelConfigService.getModelById(intentLLMModelId, isCache, false); typeConfig.put(intentLLM.getId(), intentLLM.getConfigJson()); } } if (StringUtils.isNotBlank(memLocalShortLLMModelId)) { if (!typeConfig.containsKey(memLocalShortLLMModelId)) { + // 修改这里:添加isMaskSensitive=false参数 ModelConfigEntity memLocalShortLLM = modelConfigService - .getModelById(memLocalShortLLMModelId, isCache); + .getModelById(memLocalShortLLMModelId, isCache, false); typeConfig.put(memLocalShortLLM.getId(), memLocalShortLLM.getConfigJson()); } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java index 634101e3..7788cb20 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java @@ -35,17 +35,27 @@ public interface ModelConfigService extends BaseService { /** * 根据ID获取模型配置 * - * @param id 模型ID - * @param isCache 是否缓存 + * @param id 模型ID + * @param isCache 是否缓存 + * @param isMaskSensitive 是否掩码敏感信息 + * @return 模型配置实体 + */ + ModelConfigEntity getModelById(String id, boolean isCache, boolean isMaskSensitive); + + /** + * 根据ID获取模型配置(默认掩码敏感信息) + * + * @param id 模型ID + * @param isCache 是否缓存 * @return 模型配置实体 */ ModelConfigEntity getModelById(String id, boolean isCache); - + /** * 设置默认模型 * - * @param modelType 模型类型 - * @param isDefault 是否默认 + * @param modelType 模型类型 + * @param isDefault 是否默认(1:是,0:否) */ void setDefaultModel(String modelType, int isDefault); } 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 3b113d41..cee88568 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 @@ -212,29 +212,44 @@ public class ModelConfigServiceImpl extends BaseServiceImpl Date: Sun, 28 Sep 2025 16:52:28 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ModelEditDialog.vue | 573 +++++++++++++++--- 1 file changed, 486 insertions(+), 87 deletions(-) diff --git a/main/manager-web/src/components/ModelEditDialog.vue b/main/manager-web/src/components/ModelEditDialog.vue index 7aec6da7..b1824614 100644 --- a/main/manager-web/src/components/ModelEditDialog.vue +++ b/main/manager-web/src/components/ModelEditDialog.vue @@ -1,98 +1,231 @@ @@ -385,8 +617,8 @@ export default { } .custom-close-btn:hover { - color: #409EFF; - border-color: #409EFF; + color: #409eff; + border-color: #409eff; } .custom-select .el-input__suffix { @@ -475,7 +707,7 @@ export default { background-color: white; top: 3px; left: 4px; - transition: all .3s; + transition: all 0.3s; } .custom-switch.is-checked .el-switch__core { @@ -508,4 +740,171 @@ export default { text-align: right; padding-right: 20px; } - \ No newline at end of file + + + From 4e99e176e9c61afe255887ca2dbf8fed0a25dff6 Mon Sep 17 00:00:00 2001 From: LiJinHui <166460433+stu-rgsze@users.noreply.github.com> Date: Sun, 28 Sep 2025 17:05:17 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ModelEditDialog.vue | 167 ------------------ 1 file changed, 167 deletions(-) diff --git a/main/manager-web/src/components/ModelEditDialog.vue b/main/manager-web/src/components/ModelEditDialog.vue index b1824614..fe06fd9a 100644 --- a/main/manager-web/src/components/ModelEditDialog.vue +++ b/main/manager-web/src/components/ModelEditDialog.vue @@ -741,170 +741,3 @@ export default { padding-right: 20px; } - - From 4f2b7524aa131fe318c27cc5c118a7f7594cd38d Mon Sep 17 00:00:00 2001 From: LiJinHui <166460433+stu-rgsze@users.noreply.github.com> Date: Sun, 28 Sep 2025 17:20:09 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E6=94=B9=E7=94=A8=E7=B2=BE=E5=87=86?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E7=94=9F=E6=88=90=E6=8F=90=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/components/ModelEditDialog.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main/manager-web/src/components/ModelEditDialog.vue b/main/manager-web/src/components/ModelEditDialog.vue index fe06fd9a..60b5ac70 100644 --- a/main/manager-web/src/components/ModelEditDialog.vue +++ b/main/manager-web/src/components/ModelEditDialog.vue @@ -508,7 +508,10 @@ export default { // 检测字段是否为敏感字段 isSensitiveField(fieldName) { - return this.sensitive_keys.some((key) => fieldName.toLowerCase().includes(key)); + // 将字段名转换为小写进行比较 + const lowerFieldName = fieldName.toLowerCase(); + // 精确匹配keyMap中定义的7个敏感词 + return this.sensitive_keys.includes(lowerFieldName); }, // 获取敏感字段对应的中文名称 From 8f3229dddac3c70119907dceb940c6904a320b6e Mon Sep 17 00:00:00 2001 From: LiJinHui <166460433+stu-rgsze@users.noreply.github.com> Date: Sun, 28 Sep 2025 17:37:06 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E8=B0=83=E6=95=B4=20input=20onfocus=20?= =?UTF-8?q?=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ModelEditDialog.vue | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/main/manager-web/src/components/ModelEditDialog.vue b/main/manager-web/src/components/ModelEditDialog.vue index 60b5ac70..86e6a1a0 100644 --- a/main/manager-web/src/components/ModelEditDialog.vue +++ b/main/manager-web/src/components/ModelEditDialog.vue @@ -190,10 +190,19 @@ :placeholder="$t('modelConfigDialog.enterJsonExample')" class="custom-input-bg" @change="(val) => handleJsonChange(field.prop, val)" - @focus="handleJsonInputFocus(field.prop, fieldJsonMap[field.prop])" - @blur="handleJsonInputBlur(field.prop)" + @focus=" + isSensitiveField(field.prop) + ? handleJsonInputFocus(field.prop, fieldJsonMap[field.prop]) + : undefined + " + @blur=" + isSensitiveField(field.prop) + ? handleJsonInputBlur(field.prop) + : undefined + " > + From d47664e6706766b8e76fcdacc3a2d79b54d10d79 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Sun, 28 Sep 2025 18:25:29 +0800 Subject: [PATCH 10/10] =?UTF-8?q?update:=E4=BC=98=E5=8C=96=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=A1=86=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/AddModelDialog.vue | 35 +-- .../src/components/ModelEditDialog.vue | 206 ++++-------------- 2 files changed, 64 insertions(+), 177 deletions(-) diff --git a/main/manager-web/src/components/AddModelDialog.vue b/main/manager-web/src/components/AddModelDialog.vue index b2a1adfc..c6e9486f 100644 --- a/main/manager-web/src/components/AddModelDialog.vue +++ b/main/manager-web/src/components/AddModelDialog.vue @@ -3,8 +3,8 @@ custom-class="custom-dialog" :show-close="false" class="center-dialog">
- {{ $t('modelConfigDialog.addModel') }} -
+ {{ $t('modelConfigDialog.addModel') }} +