fix: 统一工作流空媒体参数处理
This commit is contained in:
@@ -139,7 +139,7 @@ public class WorkflowRunningParameterResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 归一化工作流运行时变量,确保文件参数统一为文件对象数组。
|
* 归一化工作流运行时变量,统一文件结构并移除空图片值。
|
||||||
*
|
*
|
||||||
* @param content 工作流内容
|
* @param content 工作流内容
|
||||||
* @param variables 原始运行变量
|
* @param variables 原始运行变量
|
||||||
@@ -162,7 +162,13 @@ public class WorkflowRunningParameterResolver {
|
|||||||
if (isFileParameter(parameter)) {
|
if (isFileParameter(parameter)) {
|
||||||
normalized.put(name, normalizeFileVariableValue(normalized.get(name), name));
|
normalized.put(name, normalizeFileVariableValue(normalized.get(name), name));
|
||||||
} else if (isImageParameter(parameter)) {
|
} else if (isImageParameter(parameter)) {
|
||||||
normalized.put(name, normalizeImageVariableValue(normalized.get(name), name));
|
Object imageValue = normalizeImageVariableValue(
|
||||||
|
normalized.get(name), name);
|
||||||
|
if (imageValue == null) {
|
||||||
|
normalized.remove(name);
|
||||||
|
} else {
|
||||||
|
normalized.put(name, imageValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return normalized;
|
return normalized;
|
||||||
@@ -683,6 +689,10 @@ public class WorkflowRunningParameterResolver {
|
|||||||
if (value == null) {
|
if (value == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (value instanceof String stringValue
|
||||||
|
&& !StringUtils.hasText(stringValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (value instanceof Collection<?> collection) {
|
if (value instanceof Collection<?> collection) {
|
||||||
for (Object item : collection) {
|
for (Object item : collection) {
|
||||||
collectFileValues(item, result);
|
collectFileValues(item, result);
|
||||||
|
|||||||
@@ -219,6 +219,38 @@ public class WorkflowRunningParameterResolverTest {
|
|||||||
Assert.assertTrue(((List<?>) attachments).get(0) instanceof Map<?, ?>);
|
Assert.assertTrue(((List<?>) attachments).get(0) instanceof Map<?, ?>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空文件参数应统一归一化为空数组,避免旧客户端空字符串触发格式错误。
|
||||||
|
*
|
||||||
|
* @throws Exception 反射注入失败
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNormalizeRuntimeVariablesShouldTreatBlankFileValuesAsEmptyList()
|
||||||
|
throws Exception {
|
||||||
|
WorkflowRunningParameterResolver resolver = newResolver();
|
||||||
|
Object[] emptyValues = {null, "", " ", List.of()};
|
||||||
|
|
||||||
|
for (Object emptyValue : emptyValues) {
|
||||||
|
Map<String, Object> variables = new LinkedHashMap<>();
|
||||||
|
variables.put("attachments", emptyValue);
|
||||||
|
|
||||||
|
Map<String, Object> normalized = resolver.normalizeRuntimeVariables(
|
||||||
|
workflowContentWithStartParameters(),
|
||||||
|
variables);
|
||||||
|
|
||||||
|
Assert.assertEquals(List.of(), normalized.get("attachments"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> variables = new LinkedHashMap<>();
|
||||||
|
variables.put("attachments", List.of(
|
||||||
|
" ",
|
||||||
|
"https://files.example.com/contracts/contract.docx"));
|
||||||
|
List<?> normalizedFiles = (List<?>) resolver.normalizeRuntimeVariables(
|
||||||
|
workflowContentWithStartParameters(),
|
||||||
|
variables).get("attachments");
|
||||||
|
Assert.assertEquals(1, normalizedFiles.size());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件参数应接受远程 URL 字符串数组并自动提取文件名。
|
* 文件参数应接受远程 URL 字符串数组并自动提取文件名。
|
||||||
*
|
*
|
||||||
@@ -446,6 +478,30 @@ public class WorkflowRunningParameterResolverTest {
|
|||||||
Assert.assertEquals("https://example.com/image.png", image.get("url"));
|
Assert.assertEquals("https://example.com/image.png", image.get("url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 空图片参数应从运行变量中移除,避免向执行引擎的并发 Map 写入 null。
|
||||||
|
*
|
||||||
|
* @throws Exception 反射注入失败
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testNormalizeRuntimeVariablesShouldRemoveBlankImageValues()
|
||||||
|
throws Exception {
|
||||||
|
WorkflowRunningParameterResolver resolver = newResolver();
|
||||||
|
Object[] emptyValues = {null, "", " "};
|
||||||
|
|
||||||
|
for (Object emptyValue : emptyValues) {
|
||||||
|
Map<String, Object> variables = new LinkedHashMap<>();
|
||||||
|
variables.put("image_input", emptyValue);
|
||||||
|
|
||||||
|
Map<String, Object> normalized = resolver.normalizeRuntimeVariables(
|
||||||
|
workflowContentWithImageStartParameter(),
|
||||||
|
variables);
|
||||||
|
|
||||||
|
Assert.assertFalse(normalized.containsKey("image_input"));
|
||||||
|
Assert.assertFalse(normalized.containsValue(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 运行入口不应接收 Data URI,避免 Base64 写入工作流状态和审计参数。
|
* 运行入口不应接收 Data URI,避免 Base64 写入工作流状态和审计参数。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -67,7 +67,10 @@ import {
|
|||||||
} from './workflowExecutionDetails';
|
} from './workflowExecutionDetails';
|
||||||
import WorkflowFinalOutput from './WorkflowFinalOutput.vue';
|
import WorkflowFinalOutput from './WorkflowFinalOutput.vue';
|
||||||
import WorkflowFormItem from './WorkflowFormItem.vue';
|
import WorkflowFormItem from './WorkflowFormItem.vue';
|
||||||
import { resolveWorkflowFormParameters } from './workflowFormParameters';
|
import {
|
||||||
|
buildWorkflowFormInitialValues,
|
||||||
|
resolveWorkflowFormParameters,
|
||||||
|
} from './workflowFormParameters';
|
||||||
import {
|
import {
|
||||||
buildWorkflowFormParameterSummaries,
|
buildWorkflowFormParameterSummaries,
|
||||||
buildWorkflowFormSubmissionImages,
|
buildWorkflowFormSubmissionImages,
|
||||||
@@ -349,24 +352,7 @@ async function resolveSharedWorkflowId() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initializeAdditionalValues() {
|
function initializeAdditionalValues() {
|
||||||
const values: Record<string, any> = {};
|
const values = buildWorkflowFormInitialValues(additionalParameters.value);
|
||||||
for (const parameter of additionalParameters.value) {
|
|
||||||
const defaultValue = parameter.defaultValue;
|
|
||||||
if (
|
|
||||||
parameter.contentType === 'file' &&
|
|
||||||
(defaultValue === null ||
|
|
||||||
defaultValue === undefined ||
|
|
||||||
defaultValue === '')
|
|
||||||
) {
|
|
||||||
values[parameter.name] = [];
|
|
||||||
} else if (Array.isArray(defaultValue)) {
|
|
||||||
values[parameter.name] = [...defaultValue];
|
|
||||||
} else if (defaultValue && typeof defaultValue === 'object') {
|
|
||||||
values[parameter.name] = { ...defaultValue };
|
|
||||||
} else {
|
|
||||||
values[parameter.name] = defaultValue ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defaultExtraValues.value = values;
|
defaultExtraValues.value = values;
|
||||||
extraValues.value = values;
|
extraValues.value = values;
|
||||||
parametersLocked.value = false;
|
parametersLocked.value = false;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
buildWorkflowFormInitialValues,
|
||||||
resolveWorkflowFormParameters,
|
resolveWorkflowFormParameters,
|
||||||
resolveWorkflowParameterDisplayName,
|
resolveWorkflowParameterDisplayName,
|
||||||
resolveWorkflowParameterLabel,
|
resolveWorkflowParameterLabel,
|
||||||
} from '../workflowFormParameters';
|
} from '../workflowFormParameters';
|
||||||
|
|
||||||
describe('resolveWorkflowFormParameters', () => {
|
describe('workflowFormParameters', () => {
|
||||||
it('uses the image parameter when a legacy schema still declares text', () => {
|
it('uses the image parameter when a legacy schema still declares text', () => {
|
||||||
const parameters = resolveWorkflowFormParameters({
|
const parameters = resolveWorkflowFormParameters({
|
||||||
parameters: [
|
parameters: [
|
||||||
@@ -119,4 +120,33 @@ describe('resolveWorkflowFormParameters', () => {
|
|||||||
'流程开始 > 用户问题123',
|
'流程开始 > 用户问题123',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('builds initial values without submitting blank images', () => {
|
||||||
|
const imageDefault = {
|
||||||
|
sourceType: 'url',
|
||||||
|
url: 'https://example.com/default.png',
|
||||||
|
};
|
||||||
|
const choicesDefault = ['a'];
|
||||||
|
const values = buildWorkflowFormInitialValues([
|
||||||
|
{ name: 'files', contentType: 'file', defaultValue: ' ' },
|
||||||
|
{ name: 'image', contentType: 'image', defaultValue: '' },
|
||||||
|
{
|
||||||
|
name: 'defaultImage',
|
||||||
|
contentType: 'image',
|
||||||
|
defaultValue: imageDefault,
|
||||||
|
},
|
||||||
|
{ name: 'text', contentType: 'text', defaultValue: 'hello' },
|
||||||
|
{ name: 'choices', formType: 'checkbox', defaultValue: choicesDefault },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(values).toEqual({
|
||||||
|
files: [],
|
||||||
|
defaultImage: imageDefault,
|
||||||
|
text: 'hello',
|
||||||
|
choices: ['a'],
|
||||||
|
});
|
||||||
|
expect(values).not.toHaveProperty('image');
|
||||||
|
expect(values.defaultImage).not.toBe(imageDefault);
|
||||||
|
expect(values.choices).not.toBe(choicesDefault);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -136,6 +136,48 @@ function resolveDataType(type: string, contentType: string) {
|
|||||||
return 'String';
|
return 'String';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBlankMediaValue(value: unknown) {
|
||||||
|
return (
|
||||||
|
value === null ||
|
||||||
|
value === undefined ||
|
||||||
|
(typeof value === 'string' && value.trim() === '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建发布运行表单的初始值,确保空媒体参数使用稳定的运行时语义。
|
||||||
|
*
|
||||||
|
* @param parameters 工作流运行参数
|
||||||
|
* @returns 可直接绑定到发布运行表单的初始值
|
||||||
|
*/
|
||||||
|
export function buildWorkflowFormInitialValues(parameters: any[]) {
|
||||||
|
const values: Record<string, any> = {};
|
||||||
|
for (const parameter of parameters || []) {
|
||||||
|
const defaultValue = parameter?.defaultValue;
|
||||||
|
if (
|
||||||
|
parameter?.contentType === 'file' &&
|
||||||
|
isBlankMediaValue(defaultValue)
|
||||||
|
) {
|
||||||
|
values[parameter.name] = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
parameter?.contentType === 'image' &&
|
||||||
|
isBlankMediaValue(defaultValue)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Array.isArray(defaultValue)) {
|
||||||
|
values[parameter.name] = [...defaultValue];
|
||||||
|
} else if (defaultValue && typeof defaultValue === 'object') {
|
||||||
|
values[parameter.name] = { ...defaultValue };
|
||||||
|
} else {
|
||||||
|
values[parameter.name] = defaultValue ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveWorkflowFormParameters(workflowParams: any) {
|
export function resolveWorkflowFormParameters(workflowParams: any) {
|
||||||
const schema = Array.isArray(workflowParams?.startFormSchema)
|
const schema = Array.isArray(workflowParams?.startFormSchema)
|
||||||
? workflowParams.startFormSchema
|
? workflowParams.startFormSchema
|
||||||
|
|||||||
Reference in New Issue
Block a user