mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-11-11 21:40:41 +00:00
fix: return model keys in admin details and allow clearing
This commit is contained in:
@@ -26,7 +26,7 @@ API Key 统一在 **ruoyi-admin → 对话管理 → 模型管理** 中直接填
|
||||
| 密钥 | 从 Dify 应用复制的真实 API Key,如 `app-...` |
|
||||
| 是否显示 | 是 |
|
||||
|
||||
直接填写真实应用 Key,不需要 `env:` 或 `Bearer ` 前缀。编辑时 Key 不回显;留空保留原 Key,填写新值会替换原值。保存后新的调用读取最新配置,无需重启后端。
|
||||
直接填写真实应用 Key,不需要 `env:` 或 `Bearer ` 前缀。编辑时回显已保存的 Key,默认隐藏,点击眼睛可查看原文。输入内容原样保存,清空后保存会清除原 Key。保存后新的调用读取最新配置,无需重启后端。
|
||||
|
||||
请求地址填写到 `/v1`,不要追加 `/chat-messages`,也不要填写 Dify 应用的网页分享链接。模型名称是本系统的选择标识,可以取一个不重复的应用名称;实际调用哪个 Dify 应用由应用 Key 决定。
|
||||
|
||||
@@ -45,6 +45,6 @@ API Key 统一在 **ruoyi-admin → 对话管理 → 模型管理** 中直接填
|
||||
| 现象 | 检查方法 |
|
||||
| --- | --- |
|
||||
| 后台无法保存 | 检查厂商是否启用,以及模型分类、名称等必填项是否齐全。 |
|
||||
| 编辑时密钥框为空 | 密钥不会回显;留空保存会保留原 Key。 |
|
||||
| 查看已保存的密钥 | 编辑时默认隐藏,点击密钥框右侧的眼睛查看原文;清空后保存会清除密钥。 |
|
||||
| Dify 返回鉴权失败 | 在模型管理中填写当前应用的有效 API Key,检查复制时是否带入空格或 `Bearer ` 前缀。 |
|
||||
| Dify 返回应用类型或输入参数错误 | 检查是否使用聊天应用,以及应用是否要求额外输入变量;先在 Dify 中验证应用可以回答。 |
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
## 修改与批量更新
|
||||
|
||||
- 编辑模型时 Key 不回显;留空保存会保留原 Key,输入新值会替换原 Key。
|
||||
- 编辑模型时回显已保存的 Key,默认以密码形式隐藏,点击眼睛可查看原文;每次重新打开编辑框都会恢复隐藏。
|
||||
- 密钥按输入内容原样保存,不限制前缀或格式,也不去除首尾空格。清空后保存会清除原 Key。
|
||||
- 按厂商批量更新接口会将提交的 Key 保存到该厂商的模型中。
|
||||
- 保存后新的模型调用会读取最新配置,无需重启后端。正在进行的请求继续使用开始时的配置。
|
||||
- 模型列表、聊天用户端和导出文件不返回 Key。
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.ruoyi.common.chat.domain.vo.chat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
/** 模型管理编辑详情,允许有查询权限的管理员回显已保存的密钥。 */
|
||||
public class ChatModelDetailVo extends ChatModelVo {
|
||||
|
||||
@Override
|
||||
@JsonProperty(access = JsonProperty.Access.READ_WRITE)
|
||||
public String getApiKey() {
|
||||
return super.getApiKey();
|
||||
}
|
||||
|
||||
public static ChatModelDetailVo from(ChatModelVo model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
ChatModelDetailVo detail = new ChatModelDetailVo();
|
||||
BeanUtils.copyProperties(model, detail);
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,7 @@ public class ChatModelVo implements Serializable {
|
||||
private String apiHost;
|
||||
|
||||
/**
|
||||
* 密钥仅用于服务端模型调用和写入配置。任何读取接口、日志字符串或 Excel 导出
|
||||
* 都不得把它带出服务端边界。
|
||||
* 列表、日志及导出不包含密钥;管理员编辑详情由 ChatModelDetailVo 回显原值。
|
||||
*/
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
@ToString.Exclude
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ModelBatchKeyBo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelDetailVo;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
|
||||
@@ -61,6 +62,20 @@ class ModelApiKeyValidationTest {
|
||||
assertFalse(view.toString().contains("test-secret"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void adminDetailReturnsExactKeyWithoutChangingListOrLogSerialization() throws Exception {
|
||||
var mapper = new ObjectMapper();
|
||||
var model = new ChatModelVo();
|
||||
model.setId(12L);
|
||||
model.setApiKey(" 任意/key: value ");
|
||||
var detail = ChatModelDetailVo.from(model);
|
||||
assertEquals(model.getApiKey(), mapper.readTree(mapper.writeValueAsString(detail)).get("apiKey").asText());
|
||||
assertEquals(model.getId(), detail.getId());
|
||||
assertFalse(mapper.writeValueAsString(model).contains("apiKey"));
|
||||
assertFalse(detail.toString().contains(model.getApiKey()));
|
||||
assertNull(ChatModelDetailVo.from(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void customEndpointsNeedNoDeploymentVariablesAndSupportLocalServices() {
|
||||
assertEquals("http://localhost:11434/v1", CustomApiCredentialPolicy.normalizeBaseUrl(
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.ruoyi.common.chat.domain.bo.chat.ChatModelSelectQuery;
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ModelBatchKeyBo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelSelectVo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelDetailVo;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.enums.ChatModeType;
|
||||
import org.ruoyi.enums.ModelType;
|
||||
@@ -105,9 +106,9 @@ public class ChatModelController extends BaseController {
|
||||
*/
|
||||
@SaCheckPermission("system:model:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<ChatModelVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
public R<ChatModelDetailVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(chatModelService.queryById(id));
|
||||
return R.ok(ChatModelDetailVo.from(chatModelService.queryById(id)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -147,10 +147,6 @@ public class ChatModelServiceImpl implements IChatModelService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean updateByBo(ChatModelBo bo) {
|
||||
ChatModel update = toEntity(bo);
|
||||
// 编辑表单不回显密钥;留空时保留数据库中的原值。
|
||||
if ("".equals(update.getApiKey())) {
|
||||
update.setApiKey(null);
|
||||
}
|
||||
ChatModel current = selectByIdForUpdate(update.getId());
|
||||
if (current == null) {
|
||||
return false;
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.NullAndEmptySource;
|
||||
import org.junit.jupiter.params.provider.NullSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
|
||||
@@ -47,7 +47,7 @@ class ChatModelApiKeyTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"sk-new-key", " env:LITERAL_VALUE ", "env:ATLAS_API_KEY"})
|
||||
@ValueSource(strings = {"", " ", "任意内容/key:123", "sk-new-key", " env:LITERAL_VALUE ", "env:ATLAS_API_KEY"})
|
||||
void editReplacesAnOldReferenceWithExactlyTheSuppliedValue(String key) {
|
||||
when(mapper.selectOne(any())).thenReturn(currentModel());
|
||||
when(mapper.updateById(any(ChatModel.class))).thenReturn(1);
|
||||
@@ -58,7 +58,7 @@ class ChatModelApiKeyTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@NullAndEmptySource
|
||||
@NullSource
|
||||
void editWithoutANewKeyDoesNotOverwriteTheExistingKey(String key) {
|
||||
when(mapper.selectOne(any())).thenReturn(currentModel());
|
||||
when(mapper.updateById(any(ChatModel.class))).thenReturn(1);
|
||||
|
||||
Reference in New Issue
Block a user