This is an automated email from the ASF dual-hosted git repository.

zihaoxiang pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/dev by this push:
     new 04725996ef [Improvement-17942][API&UI]Add startParams validation logic 
in both the frontend and backend. (#17956)
04725996ef is described below

commit 04725996ef1766e253e0a5e34aac7a37d77a6329
Author: njnu-seafish <[email protected]>
AuthorDate: Thu Mar 12 15:55:44 2026 +0800

    [Improvement-17942][API&UI]Add startParams validation logic in both the 
frontend and backend. (#17956)
---
 .../api/validator/StartParamListValidator.java     |  60 ++++++++++
 .../workflow/BackfillWorkflowDTOValidator.java     |  10 +-
 .../workflow/TriggerWorkflowDTOValidator.java      |  10 +-
 .../api/validator/StartParamListValidatorTest.java | 123 +++++++++++++++++++++
 dolphinscheduler-ui/src/locales/en_US/project.ts   |   4 +-
 dolphinscheduler-ui/src/locales/zh_CN/project.ts   |   4 +-
 .../workflow/components/dag/dag-save-modal.tsx     |  24 ++--
 .../workflow/definition/components/start-modal.tsx |  12 +-
 .../workflow/definition/components/use-form.ts     |  22 ++++
 9 files changed, 248 insertions(+), 21 deletions(-)

diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/StartParamListValidator.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/StartParamListValidator.java
new file mode 100644
index 0000000000..e59aacfea1
--- /dev/null
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/StartParamListValidator.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.api.validator;
+
+import org.apache.dolphinscheduler.plugin.task.api.model.Property;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * Validator for the list of start parameters (Property list).
+ * <p> If startParamList is not valid, an {@link IllegalArgumentException} 
will be thrown. </p>
+ */
+@Slf4j
+@Component
+public class StartParamListValidator implements IValidator<List<Property>> {
+
+    @Override
+    public void validate(List<Property> startParamList) {
+        if (CollectionUtils.isEmpty(startParamList)) {
+            return;
+        }
+
+        Set<String> keys = new HashSet<>();
+        for (Property param : startParamList) {
+            if (StringUtils.isBlank(param.getProp())) {
+                throw new IllegalArgumentException("Parameter key cannot be 
empty");
+            }
+
+            String key = param.getProp().trim();
+            if (keys.contains(key)) {
+                throw new IllegalArgumentException("Duplicate parameter key: " 
+ key);
+            }
+            keys.add(key);
+        }
+    }
+}
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/BackfillWorkflowDTOValidator.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/BackfillWorkflowDTOValidator.java
index 0b5da7e85a..08fa5c2b2b 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/BackfillWorkflowDTOValidator.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/BackfillWorkflowDTOValidator.java
@@ -18,6 +18,7 @@
 package org.apache.dolphinscheduler.api.validator.workflow;
 
 import org.apache.dolphinscheduler.api.validator.IValidator;
+import org.apache.dolphinscheduler.api.validator.StartParamListValidator;
 import org.apache.dolphinscheduler.api.validator.TenantExistValidator;
 import org.apache.dolphinscheduler.common.enums.CommandType;
 import org.apache.dolphinscheduler.common.enums.ReleaseState;
@@ -34,8 +35,12 @@ public class BackfillWorkflowDTOValidator implements 
IValidator<BackfillWorkflow
 
     private final TenantExistValidator tenantExistValidator;
 
-    public BackfillWorkflowDTOValidator(TenantExistValidator 
tenantExistValidator) {
+    private final StartParamListValidator startParamListValidator;
+
+    public BackfillWorkflowDTOValidator(TenantExistValidator 
tenantExistValidator,
+                                        StartParamListValidator 
startParamListValidator) {
         this.tenantExistValidator = tenantExistValidator;
+        this.startParamListValidator = startParamListValidator;
     }
 
     @Override
@@ -59,6 +64,9 @@ public class BackfillWorkflowDTOValidator implements 
IValidator<BackfillWorkflow
         if (backfillWorkflowDTO.getWorkflowDefinition().getReleaseState() != 
ReleaseState.ONLINE) {
             throw new IllegalStateException("The workflowDefinition should be 
online");
         }
+
         tenantExistValidator.validate(backfillWorkflowDTO.getTenantCode());
+
+        
startParamListValidator.validate(backfillWorkflowDTO.getStartParamList());
     }
 }
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java
index b753714d97..4837beec2a 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/validator/workflow/TriggerWorkflowDTOValidator.java
@@ -18,6 +18,7 @@
 package org.apache.dolphinscheduler.api.validator.workflow;
 
 import org.apache.dolphinscheduler.api.validator.IValidator;
+import org.apache.dolphinscheduler.api.validator.StartParamListValidator;
 import org.apache.dolphinscheduler.api.validator.TenantExistValidator;
 import org.apache.dolphinscheduler.common.enums.CommandType;
 import org.apache.dolphinscheduler.common.enums.ReleaseState;
@@ -32,8 +33,12 @@ public class TriggerWorkflowDTOValidator implements 
IValidator<TriggerWorkflowDT
 
     private final TenantExistValidator tenantExistValidator;
 
-    public TriggerWorkflowDTOValidator(TenantExistValidator 
tenantExistValidator) {
+    private final StartParamListValidator startParamListValidator;
+
+    public TriggerWorkflowDTOValidator(TenantExistValidator 
tenantExistValidator,
+                                       StartParamListValidator 
startParamListValidator) {
         this.tenantExistValidator = tenantExistValidator;
+        this.startParamListValidator = startParamListValidator;
     }
 
     @Override
@@ -47,6 +52,9 @@ public class TriggerWorkflowDTOValidator implements 
IValidator<TriggerWorkflowDT
         if (triggerWorkflowDTO.getWorkflowDefinition().getReleaseState() != 
ReleaseState.ONLINE) {
             throw new IllegalStateException("The workflowDefinition should be 
online");
         }
+
         tenantExistValidator.validate(triggerWorkflowDTO.getTenantCode());
+
+        
startParamListValidator.validate(triggerWorkflowDTO.getStartParamList());
     }
 }
diff --git 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/StartParamListValidatorTest.java
 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/StartParamListValidatorTest.java
new file mode 100644
index 0000000000..ab1d23b0e8
--- /dev/null
+++ 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/validator/StartParamListValidatorTest.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.api.validator;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.apache.dolphinscheduler.plugin.task.api.enums.DataType;
+import org.apache.dolphinscheduler.plugin.task.api.enums.Direct;
+import org.apache.dolphinscheduler.plugin.task.api.model.Property;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class StartParamListValidatorTest {
+
+    @InjectMocks
+    private StartParamListValidator startParamListValidator;
+
+    @Test
+    void testValidate_nullList() {
+        assertThatCode(() -> startParamListValidator.validate(null))
+                .doesNotThrowAnyException();
+    }
+
+    @Test
+    void testValidate_emptyList() {
+        assertThatCode(() -> 
startParamListValidator.validate(Collections.emptyList()))
+                .doesNotThrowAnyException();
+    }
+
+    @Test
+    void testValidate_validParameters() {
+        List<Property> params = Collections.singletonList(
+                new Property("workflow_id", Direct.IN, DataType.VARCHAR, 
"1001"));
+        assertThatCode(() -> startParamListValidator.validate(params))
+                .doesNotThrowAnyException();
+    }
+
+    @Test
+    void testValidate_rejectsBlankOrEmptyKeys() {
+        assertThrowsIllegalArgument("");
+
+        assertThrowsIllegalArgument("   ");
+
+        assertThrowsIllegalArgument("\t");
+
+        assertThrowsIllegalArgument("\n");
+
+        assertThrowsIllegalArgument("  \t\n  ");
+    }
+
+    private void assertThrowsIllegalArgument(String propValue) {
+        List<Property> params = Collections.singletonList(
+                new Property(propValue, Direct.IN, DataType.VARCHAR, 
"dummyValue"));
+
+        assertThatThrownBy(() -> startParamListValidator.validate(params))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage("Parameter key cannot be empty");
+    }
+
+    @Test
+    void testValidate_duplicateKeys() {
+        List<Property> params = new ArrayList<>();
+        params.add(new Property("app_name", Direct.IN, DataType.VARCHAR, "A"));
+        params.add(new Property("app_name", Direct.IN, DataType.VARCHAR, "B"));
+
+        assertThatThrownBy(() -> startParamListValidator.validate(params))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Duplicate parameter key: app_name");
+    }
+
+    @Test
+    void testValidate_duplicateKeysAfterTrim() {
+        List<Property> params = new ArrayList<>();
+        params.add(new Property(" app_name ", Direct.IN, DataType.VARCHAR, 
"A"));
+        params.add(new Property("app_name", Direct.IN, DataType.VARCHAR, "B"));
+
+        assertThatThrownBy(() -> startParamListValidator.validate(params))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Duplicate parameter key: app_name");
+    }
+
+    @Test
+    void testValidate_inTypeEmptyValueAllowed() {
+        List<Property> params = Collections.singletonList(
+                new Property("input_var", Direct.IN, DataType.VARCHAR, ""));
+
+        assertThatCode(() -> startParamListValidator.validate(params))
+                .doesNotThrowAnyException();
+    }
+
+    @Test
+    void testValidate_outTypeEmptyValueAllowed() {
+        List<Property> params = Collections.singletonList(
+                new Property("output_var", Direct.OUT, DataType.VARCHAR, ""));
+
+        assertThatCode(() -> startParamListValidator.validate(params))
+                .doesNotThrowAnyException();
+    }
+}
diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts 
b/dolphinscheduler-ui/src/locales/en_US/project.ts
index 30ea28d8f4..59edc7dfea 100644
--- a/dolphinscheduler-ui/src/locales/en_US/project.ts
+++ b/dolphinscheduler-ui/src/locales/en_US/project.ts
@@ -353,8 +353,8 @@ export default {
     update_directly: 'Whether to update the workflow definition',
     dag_name_empty: 'DAG graph name cannot be empty',
     positive_integer: 'Please enter a positive integer greater than 0',
-    prop_empty: 'prop is empty',
-    prop_repeat: 'prop is repeat',
+    prop_key_repeat: 'prop key is repeat',
+    prop_key_empty: 'prop key is empty',
     node_not_created: 'Failed to save node not created',
     copy_name: 'Copy Name',
     view_variables: 'View Variables',
diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts 
b/dolphinscheduler-ui/src/locales/zh_CN/project.ts
index 82255264bd..e6b08093c5 100644
--- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts
+++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts
@@ -347,8 +347,8 @@ export default {
     update_directly: '是否更新工作流定义',
     dag_name_empty: 'DAG图名称不能为空',
     positive_integer: '请输入大于 0 的正整数',
-    prop_empty: '自定义参数prop不能为空',
-    prop_repeat: 'prop中有重复',
+    prop_key_repeat: '自定义参数prop中key有重复',
+    prop_key_empty: '自定义参数prop中key不能为空',
     node_not_created: '未创建节点保存失败',
     copy_name: '复制名称',
     view_variables: '查看变量',
diff --git 
a/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx
 
b/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx
index a6b78cda38..1e39a6d37e 100644
--- 
a/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx
+++ 
b/dolphinscheduler-ui/src/views/projects/workflow/components/dag/dag-save-modal.tsx
@@ -98,23 +98,23 @@ export default defineComponent({
       },
       globalParams: {
         validator() {
-          const props = new Set()
+          const globalParams = formValue.value.globalParams || []
 
-          const keys = formValue.value.globalParams.map((item) => item.key)
-          const keysSet = new Set(keys)
-          if (keysSet.size !== keys.length) {
-            return new Error(t('project.dag.prop_repeat'))
-          }
+          if (!globalParams || globalParams.length === 0) return true
 
-          for (const param of formValue.value.globalParams) {
-            const prop = param.value
-            const direct = param.direct
-            if (direct === 'IN' && !prop) {
-              return new Error(t('project.dag.prop_empty'))
+          for (const param of globalParams) {
+            if (!param.key || param.key.trim() === '') {
+              return new Error(t('project.dag.prop_key_empty'))
             }
+          }
 
-            props.add(prop)
+          const keys = globalParams.map((item) => (item.key || '').trim())
+          const uniqueKeys = new Set(keys)
+          if (uniqueKeys.size !== keys.length) {
+            return new Error(t('project.dag.prop_key_repeat'))
           }
+
+          return true
         }
       }
     }
diff --git 
a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
 
b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
index bb574d2e50..eea89ad0d4 100644
--- 
a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
+++ 
b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/start-modal.tsx
@@ -290,6 +290,11 @@ export default defineComponent({
       }
     )
 
+    const formModel = computed(() => ({
+      ...startState.startForm,
+      startParamsList: variables.startParamsList
+    }))
+
     return {
       t,
       showTaskDependType,
@@ -302,6 +307,7 @@ export default defineComponent({
       removeStartParams,
       addStartParams,
       updateParamsList,
+      formModel,
       ...toRefs(variables),
       ...toRefs(startState),
       ...toRefs(props),
@@ -319,7 +325,7 @@ export default defineComponent({
         onConfirm={this.handleStart}
         confirmLoading={this.saving}
       >
-        <NForm ref='startFormRef' model={this.startForm} rules={this.rules}>
+        <NForm ref='startFormRef' model={this.formModel} rules={this.rules}>
           <NFormItem
             label={t('project.workflow.workflow_name')}
             path='workflow_name'
@@ -577,13 +583,13 @@ export default defineComponent({
             )}
           <NFormItem
             label={t('project.workflow.startup_parameter')}
-            path='startup_parameter'
+            path='startParamsList'
           >
             <NDynamicInput
               v-model:value={this.startParamsList}
               onCreate={() => {
                 return {
-                  key: '',
+                  prop: '',
                   direct: 'IN',
                   type: 'VARCHAR',
                   value: ''
diff --git 
a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts
 
b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts
index 3575311295..f4955a9d99 100644
--- 
a/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts
+++ 
b/dolphinscheduler-ui/src/views/projects/workflow/definition/components/use-form.ts
@@ -101,6 +101,28 @@ export const useForm = () => {
             return new Error(t('project.workflow.warning_group_tip'))
           }
         }
+      },
+      startParamsList: {
+        trigger: ['input', 'blur'],
+        validator: (rule: any, value: Array<any>) => {
+          const params = value || []
+
+          if (!params || params.length === 0) return true
+
+          for (const param of params) {
+            if (!param.prop || param.prop.trim() === '') {
+              return new Error(t('project.dag.prop_key_empty'))
+            }
+          }
+
+          const keys = params.map((item) => (item.prop || '').trim())
+          const uniqueKeys = new Set(keys)
+          if (uniqueKeys.size !== keys.length) {
+            return new Error(t('project.dag.prop_key_repeat'))
+          }
+
+          return true
+        }
       }
     }
   })

Reply via email to