diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyController.java index 3042b4b6..46c25a31 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysApiKeyController.java @@ -43,6 +43,17 @@ import java.util.Set; @RestController @RequestMapping("/api/v1/sysApiKey") public class SysApiKeyController extends BaseCurdController { + + /** + * 访问令牌名称最大长度。 + */ + private static final int API_KEY_NAME_MAX_LENGTH = 100; + + /** + * 兼容旧客户端时使用的默认名称前缀。 + */ + private static final String DEFAULT_API_KEY_NAME_PREFIX = "访问令牌-"; + public SysApiKeyController(SysApiKeyService service) { super(service); } @@ -56,13 +67,19 @@ public class SysApiKeyController extends BaseCurdController save() { + public Result save(@JsonBody(value = "name", required = false) String name) { String apiKey = IdUtil.generateUUID(); + String normalizedName = normalizeCreateName(name, apiKey); + if (normalizedName.length() > API_KEY_NAME_MAX_LENGTH) { + return Result.fail("访问令牌名称不能超过100个字符", null); + } SysApiKey entity = new SysApiKey(); + entity.setName(normalizedName); entity.setApiKey(apiKey); entity.setCreated(new Date()); entity.setStatus(1); @@ -103,6 +120,10 @@ public class SysApiKeyController extends BaseCurdController nameValidationResult = normalizeAndValidateUpdateName(entity); + if (nameValidationResult != null) { + return nameValidationResult; + } if (!hasPersistentUpdateFields(entity) && !hasPermissionUpdateFields(entity)) { return Result.fail("没有可更新的访问令牌字段"); } @@ -230,7 +251,8 @@ public class SysApiKeyController extends BaseCurdController未传名称时生成可识别的兼容名称,避免旧客户端在升级期间创建空名称记录。

+ * + * @param name 客户端提交的名称 + * @param apiKey 新生成的访问令牌 + * @return 标准化后的名称 + */ + private String normalizeCreateName(String name, String apiKey) { + if (name != null && !name.trim().isEmpty()) { + return name.trim(); + } + int suffixStart = Math.max(0, apiKey.length() - 6); + return DEFAULT_API_KEY_NAME_PREFIX + apiKey.substring(suffixStart); + } + + /** + * 标准化并校验更新请求中的访问令牌名称。 + * + * @param entity 待更新的访问令牌 + * @return 校验失败结果;无需校验或校验成功时返回 {@code null} + */ + private Result normalizeAndValidateUpdateName(SysApiKey entity) { + if (entity.getName() == null) { + return null; + } + String normalizedName = entity.getName().trim(); + if (normalizedName.isEmpty()) { + return Result.fail("访问令牌名称不能为空"); + } + if (normalizedName.length() > API_KEY_NAME_MAX_LENGTH) { + return Result.fail("访问令牌名称不能超过100个字符"); + } + entity.setName(normalizedName); + return null; + } } diff --git a/easyflow-api/easyflow-api-admin/src/test/java/tech/easyflow/admin/controller/system/SysApiKeyControllerTest.java b/easyflow-api/easyflow-api-admin/src/test/java/tech/easyflow/admin/controller/system/SysApiKeyControllerTest.java index ed1b0777..4c6ef492 100644 --- a/easyflow-api/easyflow-api-admin/src/test/java/tech/easyflow/admin/controller/system/SysApiKeyControllerTest.java +++ b/easyflow-api/easyflow-api-admin/src/test/java/tech/easyflow/admin/controller/system/SysApiKeyControllerTest.java @@ -79,6 +79,50 @@ public class SysApiKeyControllerTest { verify(knowledgePermissionService).replaceApiPermissions(apiKeyId, true, true, true); } + /** + * 验证名称可以单独更新,并在持久化前移除首尾空格。 + */ + @Test + public void updateShouldPersistTrimmedNameOnly() { + BigInteger apiKeyId = BigInteger.valueOf(103); + SysApiKeyService apiKeyService = mock(SysApiKeyService.class); + KnowledgeSharePermissionService knowledgePermissionService = + mock(KnowledgeSharePermissionService.class); + SysApiKeyController controller = controller(apiKeyService, knowledgePermissionService); + SysApiKey existing = new SysApiKey(); + existing.setId(apiKeyId); + when(apiKeyService.getById(apiKeyId)).thenReturn(existing); + + SysApiKey request = new SysApiKey(); + request.setId(apiKeyId); + request.setName(" 生产环境调用 "); + + Result result = controller.update(request); + + assertEquals(result.getErrorCode(), 0); + assertEquals(request.getName(), "生产环境调用"); + verify(apiKeyService).updateById(request); + } + + /** + * 验证空白名称会被拒绝,避免覆盖为无意义内容。 + */ + @Test + public void updateShouldRejectBlankName() { + SysApiKeyService apiKeyService = mock(SysApiKeyService.class); + KnowledgeSharePermissionService knowledgePermissionService = + mock(KnowledgeSharePermissionService.class); + SysApiKeyController controller = controller(apiKeyService, knowledgePermissionService); + SysApiKey request = new SysApiKey(); + request.setId(BigInteger.valueOf(104)); + request.setName(" "); + + Result result = controller.update(request); + + assertNotEquals(result.getErrorCode(), 0); + verifyNoInteractions(apiKeyService, knowledgePermissionService); + } + /** * 验证新版知识库权限缺少字段时拒绝更新,避免遗漏字段被隐式关闭。 */ diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyBase.java index 1ee86583..030cb0d8 100644 --- a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyBase.java +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysApiKeyBase.java @@ -18,6 +18,12 @@ public class SysApiKeyBase implements Serializable { @Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "id") private BigInteger id; + /** + * 名称 + */ + @Column(comment = "名称") + private String name; + /** * apiKey */ @@ -68,6 +74,24 @@ public class SysApiKeyBase implements Serializable { this.id = id; } + /** + * 获取访问令牌名称。 + * + * @return 访问令牌名称 + */ + public String getName() { + return name; + } + + /** + * 设置访问令牌名称。 + * + * @param name 访问令牌名称 + */ + public void setName(String name) { + this.name = name; + } + public String getApiKey() { return apiKey; } diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/mysql/V55__mysql_sys_api_key_name.sql b/easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/mysql/V55__mysql_sys_api_key_name.sql new file mode 100644 index 00000000..fcf4bc5c --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/mysql/V55__mysql_sys_api_key_name.sql @@ -0,0 +1,6 @@ +ALTER TABLE `tb_sys_api_key` + ADD COLUMN `name` varchar(100) NOT NULL DEFAULT '未命名访问令牌' COMMENT '名称'; + +UPDATE `tb_sys_api_key` +SET `name` = CONCAT('访问令牌-', RIGHT(COALESCE(`api_key`, CAST(`id` AS CHAR)), 6)) +WHERE `name` = '未命名访问令牌'; diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKey.json b/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKey.json index e6bcfbaa..a0e67ae2 100644 --- a/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKey.json +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/sysApiKey.json @@ -1,5 +1,6 @@ { "id": "Id", + "name": "Name", "apiKey": "ApiKey", "created": "Created", "status": "Status", @@ -18,5 +19,8 @@ "knowledgeImportPermission": "Knowledge Import", "knowledgeMaintenancePermission": "Knowledge Maintenance", "workflowApiPermission": "Workflow API", - "addApiKeyNotice": "This operation will generate an API key. Please confirm whether to proceed" + "addApiKeyNotice": "Enter an access token name", + "namePlaceholder": "Enter a name", + "nameRequired": "Name is required", + "nameTooLong": "Name cannot exceed 100 characters" } diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKey.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKey.json index f4772299..34aa4a81 100644 --- a/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKey.json +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/sysApiKey.json @@ -1,5 +1,6 @@ { "id": "id", + "name": "名称", "apiKey": "apiKey", "created": "创建时间", "status": "数据状态", @@ -18,5 +19,8 @@ "knowledgeImportPermission": "知识导入", "knowledgeMaintenancePermission": "知识库维护", "workflowApiPermission": "工作流 API 调用授权", - "addApiKeyNotice": "该操作会生成一个apiKey,请确认是否生成" + "addApiKeyNotice": "请输入访问令牌名称", + "namePlaceholder": "请输入名称", + "nameRequired": "名称不能为空", + "nameTooLong": "名称不能超过100个字符" } diff --git a/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyList.vue b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyList.vue index 9dffbe99..c784a564 100644 --- a/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyList.vue +++ b/easyflow-ui-admin/app/src/views/config/apikey/SysApiKeyList.vue @@ -92,22 +92,51 @@ function remove(row: any) { }).catch(() => {}); } function addNewApiKey() { - ElMessageBox.confirm( + ElMessageBox.prompt( $t('sysApiKey.addApiKeyNotice'), $t('message.noticeTitle'), { confirmButtonText: $t('message.ok'), cancelButtonText: $t('message.cancel'), - type: 'warning', + inputPlaceholder: $t('sysApiKey.namePlaceholder'), + inputValidator: (value) => { + const normalizedValue = value.trim(); + if (!normalizedValue) { + return $t('sysApiKey.nameRequired'); + } + if (normalizedValue.length > 100) { + return $t('sysApiKey.nameTooLong'); + } + return true; + }, + beforeClose: (action, instance, done) => { + if (action !== 'confirm') { + done(); + return; + } + instance.confirmButtonLoading = true; + api + .post('/api/v1/sysApiKey/key/save', { + name: instance.inputValue.trim(), + }) + .then((res) => { + if (res.errorCode === 0) { + ElMessage.success($t('message.saveOkMessage')); + pageDataRef.value.setQuery({}); + done(); + } else { + ElMessage.error(res.message || $t('message.saveFailMessage')); + } + }) + .catch(() => { + ElMessage.error($t('message.saveFailMessage')); + }) + .finally(() => { + instance.confirmButtonLoading = false; + }); + }, }, - ).then(() => { - api.post('/api/v1/sysApiKey/key/save', {}).then((res) => { - if (res.errorCode === 0) { - ElMessage.success($t('message.saveOkMessage')); - pageDataRef.value.setQuery({}); - } - }); - }); + ).catch(() => {}); } @@ -129,6 +158,12 @@ function addNewApiKey() { >