From 13dec6c21629206452f43e90a49dad5caa2226c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com>
Date: Fri, 7 Aug 2026 12:55:04 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BA=E8=AE=BF=E9=97=AE=E4=BB=A4?=
=?UTF-8?q?=E7=89=8C=E5=A2=9E=E5=8A=A0=E5=90=8D=E7=A7=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 支持创建和编辑访问令牌名称
- 在列表首列展示名称并兼容历史数据
---
.../system/SysApiKeyController.java | 64 ++++++++++++++++++-
.../system/SysApiKeyControllerTest.java | 44 +++++++++++++
.../system/entity/base/SysApiKeyBase.java | 24 +++++++
.../mysql/V55__mysql_sys_api_key_name.sql | 6 ++
.../src/locales/langs/en-US/sysApiKey.json | 6 +-
.../src/locales/langs/zh-CN/sysApiKey.json | 6 +-
.../src/views/config/apikey/SysApiKeyList.vue | 55 +++++++++++++---
.../views/config/apikey/SysApiKeyModal.vue | 29 +++++++++
8 files changed, 220 insertions(+), 14 deletions(-)
create mode 100644 easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/mysql/V55__mysql_sys_api_key_name.sql
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() {
>
+
({
+ name: '',
apiKey: '',
status: '',
deptId: '',
@@ -66,6 +69,19 @@ const resourcePermissionList = ref([]);
// 表单校验规则(必填项校验)
const rules = ref({
+ name: [
+ {
+ required: true,
+ whitespace: true,
+ message: $t('sysApiKey.nameRequired'),
+ trigger: 'blur',
+ },
+ {
+ max: 100,
+ message: $t('sysApiKey.nameTooLong'),
+ trigger: 'change',
+ },
+ ],
status: [
{
required: true,
@@ -136,6 +152,7 @@ function createDefaultEntity(row: Partial = {}): Entity {
const knowledgeMaintenanceEnabled = Boolean(row.knowledgeMaintenanceEnabled);
const workflowApiEnabled = Boolean(row.workflowApiEnabled);
return {
+ name: '',
apiKey: '',
status: '',
deptId: '',
@@ -170,6 +187,7 @@ function save() {
saveForm.value?.validate((valid) => {
if (valid) {
btnLoading.value = true;
+ entity.value.name = entity.value.name.trim();
const url = isAdd.value
? 'api/v1/sysApiKey/save'
: 'api/v1/sysApiKey/update';
@@ -198,6 +216,7 @@ function closeDialog() {
saveForm.value?.resetFields();
// 重置表单数据
entity.value = {
+ name: '',
apiKey: '',
status: '',
deptId: '',
@@ -240,6 +259,16 @@ defineExpose({
label-position="top"
class="easyflow-modal-form easyflow-modal-form--compact form-container"
>
+
+
+
+