|
|
@ -13,6 +13,7 @@ import org.springframework.transaction.annotation.Transactional; |
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
|
|
|
|
|
|
import java.util.*; |
|
|
import java.util.*; |
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* AI 模型配置管理服务 |
|
|
* AI 模型配置管理服务 |
|
|
@ -31,6 +32,9 @@ public class AiModelConfigService { |
|
|
/** 复用的 JSON 序列化器,避免每次调用 persistExtraConfig 都新建实例 */ |
|
|
/** 复用的 JSON 序列化器,避免每次调用 persistExtraConfig 都新建实例 */ |
|
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
|
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
|
|
|
|
|
|
|
|
|
|
|
/** 活跃配置本地缓存:appType → AiModelConfig(含完整 API Key),写操作时自动失效 */ |
|
|
|
|
|
private final ConcurrentHashMap<String, AiModelConfig> activeConfigCache = new ConcurrentHashMap<>(); |
|
|
|
|
|
|
|
|
// ==================== 分页列表 ==================== |
|
|
// ==================== 分页列表 ==================== |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -150,11 +154,32 @@ public class AiModelConfigService { |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 获取指定应用类型的活跃配置(含完整 API Key,仅供内部调用使用) |
|
|
* 获取指定应用类型的活跃配置(含完整 API Key,仅供内部调用使用) |
|
|
|
|
|
* 使用本地缓存减少对 ai_model_config 表的重复查询。 |
|
|
* |
|
|
* |
|
|
* @param appType 应用类型 |
|
|
* @param appType 应用类型 |
|
|
* @return 活跃配置(含完整 API Key) |
|
|
* @return 活跃配置(含完整 API Key) |
|
|
*/ |
|
|
*/ |
|
|
public AiModelConfig getActiveConfigWithFullKey(String appType) { |
|
|
public AiModelConfig getActiveConfigWithFullKey(String appType) { |
|
|
|
|
|
// 先查缓存,命中则直接返回(防御性拷贝,避免缓存被外部修改污染) |
|
|
|
|
|
AiModelConfig cached = activeConfigCache.get(appType); |
|
|
|
|
|
if (cached != null) { |
|
|
|
|
|
return AiModelConfig.builder() |
|
|
|
|
|
.id(cached.getId()) |
|
|
|
|
|
.name(cached.getName()) |
|
|
|
|
|
.appType(cached.getAppType()) |
|
|
|
|
|
.provider(cached.getProvider()) |
|
|
|
|
|
.apiKey(cached.getApiKey()) |
|
|
|
|
|
.modelName(cached.getModelName()) |
|
|
|
|
|
.temperature(cached.getTemperature()) |
|
|
|
|
|
.maxTokens(cached.getMaxTokens()) |
|
|
|
|
|
.baseUrl(cached.getBaseUrl()) |
|
|
|
|
|
.extraConfig(cached.getExtraConfig() != null ? new LinkedHashMap<>(cached.getExtraConfig()) : null) |
|
|
|
|
|
.isActive(cached.getIsActive()) |
|
|
|
|
|
.priority(cached.getPriority()) |
|
|
|
|
|
.description(cached.getDescription()) |
|
|
|
|
|
.build(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
LambdaQueryWrapper<AiModelConfig> wrapper = new LambdaQueryWrapper<>(); |
|
|
LambdaQueryWrapper<AiModelConfig> wrapper = new LambdaQueryWrapper<>(); |
|
|
wrapper.eq(AiModelConfig::getAppType, appType) |
|
|
wrapper.eq(AiModelConfig::getAppType, appType) |
|
|
.eq(AiModelConfig::getIsActive, true) |
|
|
.eq(AiModelConfig::getIsActive, true) |
|
|
@ -164,9 +189,31 @@ public class AiModelConfigService { |
|
|
if (config != null && config.getApiKey() != null) { |
|
|
if (config != null && config.getApiKey() != null) { |
|
|
config.setApiKey(config.getApiKey().trim()); |
|
|
config.setApiKey(config.getApiKey().trim()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 写入缓存(只缓存非 null 结果,避免缓存"查无此配置"导致后续永远不查 DB) |
|
|
|
|
|
if (config != null) { |
|
|
|
|
|
activeConfigCache.put(appType, config); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return config; |
|
|
return config; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 清除指定 appType 的活跃配置缓存(配置变更时调用) |
|
|
|
|
|
*/ |
|
|
|
|
|
public void evictActiveConfigCache(String appType) { |
|
|
|
|
|
activeConfigCache.remove(appType); |
|
|
|
|
|
log.debug("清除活跃配置缓存: appType={}", appType); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 清除所有活跃配置缓存 |
|
|
|
|
|
*/ |
|
|
|
|
|
public void evictAllActiveConfigCache() { |
|
|
|
|
|
activeConfigCache.clear(); |
|
|
|
|
|
log.debug("清除所有活跃配置缓存"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// ==================== 新建配置 ==================== |
|
|
// ==================== 新建配置 ==================== |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -191,6 +238,8 @@ public class AiModelConfigService { |
|
|
aiModelConfigMapper.insert(config); |
|
|
aiModelConfigMapper.insert(config); |
|
|
// JSONB 字段 MyBatis Plus typeHandler 可能不触发,用 JdbcTemplate 显式写入保证可靠 |
|
|
// JSONB 字段 MyBatis Plus typeHandler 可能不触发,用 JdbcTemplate 显式写入保证可靠 |
|
|
persistExtraConfig(config.getId(), config.getExtraConfig()); |
|
|
persistExtraConfig(config.getId(), config.getExtraConfig()); |
|
|
|
|
|
// 清除该 appType 的活跃配置缓存 |
|
|
|
|
|
evictActiveConfigCache(config.getAppType()); |
|
|
log.info("新建 AI 模型配置: name={}, appType={}, modelName={}", |
|
|
log.info("新建 AI 模型配置: name={}, appType={}, modelName={}", |
|
|
config.getName(), config.getAppType(), config.getModelName()); |
|
|
config.getName(), config.getAppType(), config.getModelName()); |
|
|
return config; |
|
|
return config; |
|
|
@ -245,6 +294,9 @@ public class AiModelConfigService { |
|
|
persistExtraConfig(id, config.getExtraConfig()); |
|
|
persistExtraConfig(id, config.getExtraConfig()); |
|
|
} |
|
|
} |
|
|
log.info("更新 AI 模型配置: id={}", id); |
|
|
log.info("更新 AI 模型配置: id={}", id); |
|
|
|
|
|
// 清除该 appType 的活跃配置缓存(更新后 appType 可能与原来不同) |
|
|
|
|
|
String evictAppType = config.getAppType() != null ? config.getAppType() : existing.getAppType(); |
|
|
|
|
|
evictActiveConfigCache(evictAppType); |
|
|
AiModelConfig updated = aiModelConfigMapper.selectById(id); |
|
|
AiModelConfig updated = aiModelConfigMapper.selectById(id); |
|
|
// 返回前脱敏,避免明文 api_key 暴露给前端(与列表/详情接口保持一致) |
|
|
// 返回前脱敏,避免明文 api_key 暴露给前端(与列表/详情接口保持一致) |
|
|
if (updated != null) { |
|
|
if (updated != null) { |
|
|
@ -284,6 +336,8 @@ public class AiModelConfigService { |
|
|
|
|
|
|
|
|
log.info("激活 AI 模型配置: id={}, appType={}, modelName={}, priority={}", |
|
|
log.info("激活 AI 模型配置: id={}, appType={}, modelName={}, priority={}", |
|
|
id, config.getAppType(), config.getModelName(), newPriority); |
|
|
id, config.getAppType(), config.getModelName(), newPriority); |
|
|
|
|
|
// 清除该 appType 的活跃配置缓存 |
|
|
|
|
|
evictActiveConfigCache(config.getAppType()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -491,10 +545,15 @@ public class AiModelConfigService { |
|
|
* 停用指定配置(从 Fallback 链中移除) |
|
|
* 停用指定配置(从 Fallback 链中移除) |
|
|
*/ |
|
|
*/ |
|
|
public void deactivateConfig(Long id) { |
|
|
public void deactivateConfig(Long id) { |
|
|
|
|
|
AiModelConfig config = aiModelConfigMapper.selectById(id); |
|
|
LambdaUpdateWrapper<AiModelConfig> updateWrapper = new LambdaUpdateWrapper<>(); |
|
|
LambdaUpdateWrapper<AiModelConfig> updateWrapper = new LambdaUpdateWrapper<>(); |
|
|
updateWrapper.eq(AiModelConfig::getId, id) |
|
|
updateWrapper.eq(AiModelConfig::getId, id) |
|
|
.set(AiModelConfig::getIsActive, false); |
|
|
.set(AiModelConfig::getIsActive, false); |
|
|
aiModelConfigMapper.update(null, updateWrapper); |
|
|
aiModelConfigMapper.update(null, updateWrapper); |
|
|
|
|
|
// 清除该 appType 的活跃配置缓存 |
|
|
|
|
|
if (config != null) { |
|
|
|
|
|
evictActiveConfigCache(config.getAppType()); |
|
|
|
|
|
} |
|
|
log.info("停用 AI 模型配置: id={}", id); |
|
|
log.info("停用 AI 模型配置: id={}", id); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -601,6 +660,8 @@ public class AiModelConfigService { |
|
|
result.put("errors", errors); |
|
|
result.put("errors", errors); |
|
|
log.info("导入配置完成: added={}, overwritten={}, skipped={}, errors={}", |
|
|
log.info("导入配置完成: added={}, overwritten={}, skipped={}, errors={}", |
|
|
added, overwritten, skipped, errors.size()); |
|
|
added, overwritten, skipped, errors.size()); |
|
|
|
|
|
// 导入可能改变任意 appType 的活跃配置,清除全部缓存 |
|
|
|
|
|
evictAllActiveConfigCache(); |
|
|
return result; |
|
|
return result; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |