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..05f4853a --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/SensitiveDataUtils.java @@ -0,0 +1,164 @@ +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.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")); + + /** + * 检查字段是否为敏感字段 + */ + public static boolean isSensitiveField(String fieldName) { + return StringUtils.isNotBlank(fieldName) && SENSITIVE_FIELDS.contains(fieldName.toLowerCase()); + } + + /** + * 隐藏字符串中间部分 + */ + 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); + } + } + + /** + * 判断字符串是否是被掩码处理过的值 + */ + public static boolean isMaskedValue(String value) { + if (StringUtils.isBlank(value)) { + return false; + } + // 掩码值至少包含4个连续的* + return value.contains("****"); + } + + /** + * 处理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) { + result.put(key, maskSensitiveFields((JSONObject) value)); + } else { + result.put(key, value); + } + } + + return result; + } + + /** + * 比较两个JSONObject的敏感字段是否相同 + * 特别针对api_key等敏感字段进行单独比较 + */ + public static boolean isSensitiveDataEqual(JSONObject original, JSONObject updated) { + if (original == null && updated == null) { + return true; + } + if (original == null || updated == null) { + return false; + } + + // 提取并比较特定敏感字段 + 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 072dcd26..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 @@ -70,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("默认智能体未找到"); } // 构建模块配置 @@ -113,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; @@ -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 d66cf5aa..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 @@ -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,10 +12,11 @@ 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; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.ErrorCode; @@ -24,6 +26,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; @@ -65,7 +68,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl { LlmModelBasicInfoDTO dto = new LlmModelBasicInfoDTO(); dto.setId(item.getId()); @@ -78,102 +81,400 @@ 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 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); - } + public ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO) { + // 1. 参数验证 + validateEditParameters(modelType, provideCode, id, modelConfigBodyDTO); - // 再保存供应器提供的模型 - ModelConfigEntity modelConfigEntity = ConvertUtils.sourceToTarget(modelConfigBodyDTO, ModelConfigEntity.class); - modelConfigEntity.setModelType(modelType); - modelConfigEntity.setIsDefault(0); - modelConfigDao.insert(modelConfigEntity); - return ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class); + // 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); + + // 7. 清除缓存 + clearModelCache(id); + + // 8. 返回处理后的数据(包含敏感数据掩码) + return buildResponseDTO(modelConfigEntity); } @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); - } - List providerList = modelProviderService.getList(modelType, provideCode); - if (CollectionUtil.isEmpty(providerList)) { - throw new RenException(ErrorCode.MODEL_PROVIDER_NOT_EXIST); - } - 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); - } - } + public ModelConfigDTO add(String modelType, String provideCode, ModelConfigBodyDTO modelConfigBodyDTO) { + validateAddParameters(modelType, provideCode, modelConfigBodyDTO); - // 再更新供应器提供的模型 - ModelConfigEntity modelConfigEntity = ConvertUtils.sourceToTarget(modelConfigBodyDTO, ModelConfigEntity.class); - modelConfigEntity.setId(id); - modelConfigEntity.setModelType(modelType); - modelConfigDao.updateById(modelConfigEntity); - // 清除缓存 - redisUtils.delete(RedisKeys.getModelConfigById(modelConfigEntity.getId())); - return ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class); + validateModelProvider(modelType, provideCode); + + ModelConfigEntity modelConfigEntity = prepareAddEntity(modelConfigBodyDTO, modelType); + + modelConfigDao.insert(modelConfigEntity); + + 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) { + return getModelById(id, isCache, true); + } + + @Override + public ModelConfigEntity getModelById(String id, boolean isCache, boolean isMaskSensitive) { + if (StringUtils.isBlank(id)) { + return null; + } + + ModelConfigEntity entity = null; + String cacheKey = RedisKeys.getModelConfigById(id); + + if (isCache) { + // 从缓存获取 + entity = (ModelConfigEntity) redisUtils.get(cacheKey); + if (entity != null) { + // 修改:根据isMaskSensitive参数重新处理敏感信息 + if (!isMaskSensitive && entity.getConfigJson() != null) { + // 如果需要获取原始配置,但缓存的是掩码后的配置,则从数据库重新获取 + ModelConfigEntity originalEntity = modelConfigDao.selectById(id); + if (originalEntity != null) { + entity.setConfigJson(originalEntity.getConfigJson()); + } + } + return entity; + } + } + + // 从数据库获取数据 + entity = modelConfigDao.selectById(id); + if (entity != null) { + // 根据isMaskSensitive参数决定是否掩码敏感信息 + if (isMaskSensitive && entity.getConfigJson() != null) { + entity.setConfigJson(maskSensitiveFields(entity.getConfigJson())); + } + + if (isCache) { + redisUtils.set(cacheKey, 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 && !SensitiveDataUtils.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( @@ -201,8 +502,6 @@ public class ModelConfigServiceImpl extends BaseServiceImpl() - .eq("model_type", modelType)); - } } 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') }} +