perf: 收敛工作流状态与高 IO 节点开销

- 落地 Redis 版本状态、触发租约和定义缓存

- 优化数据批写、插件请求、文件下载与审计日志

- 补齐循环范围校验、轮询兼容和专项测试
This commit is contained in:
2026-07-29 00:47:48 +08:00
parent 5ee6065017
commit 1ae8a22afe
103 changed files with 15600 additions and 532 deletions

View File

@@ -84,6 +84,53 @@
let currentNodeId = getCurrentNodeId();
let wrapperElement: HTMLDivElement | null = null;
const nodeSizeObserver = useTinyflowNodeSizeObserver();
const MIN_LOOP_COUNT = 1;
const MAX_LOOP_COUNT = 300;
let loopCountHint = $state('');
const normalizeLoopCount = (value: unknown) => {
const parsed = Number(value);
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < MIN_LOOP_COUNT) {
return MIN_LOOP_COUNT;
}
return Math.min(parsed, MAX_LOOP_COUNT);
};
const updateLoopEnabled = (event: Event) => {
const loopEnable = (event.target as HTMLInputElement).checked;
updateNodeData(currentNodeId, {
loopEnable,
...(loopEnable ? {maxLoopCount: normalizeLoopCount(data.maxLoopCount)} : {})
});
};
const updateMaxLoopCount = (event: Event) => {
const input = event.target as HTMLInputElement;
const parsed = Number(input.value);
const normalized = normalizeLoopCount(input.value);
if (Number.isFinite(parsed) && parsed > MAX_LOOP_COUNT) {
loopCountHint = `最大支持 ${MAX_LOOP_COUNT} 次,已调整为 ${MAX_LOOP_COUNT}`;
} else if (!Number.isInteger(parsed) || parsed < MIN_LOOP_COUNT) {
loopCountHint = `请输入 ${MIN_LOOP_COUNT}${MAX_LOOP_COUNT} 的整数`;
} else {
loopCountHint = '';
}
updateNodeData(currentNodeId, {maxLoopCount: normalized});
};
$effect(() => {
if (!data.loopEnable) {
loopCountHint = '';
return;
}
const normalized = normalizeLoopCount(data.maxLoopCount);
if (Number(data.maxLoopCount) !== normalized) {
loopCountHint = Number(data.maxLoopCount) > MAX_LOOP_COUNT
? `最大支持 ${MAX_LOOP_COUNT} 次,已调整为 ${MAX_LOOP_COUNT}`
: `请输入 ${MIN_LOOP_COUNT}${MAX_LOOP_COUNT} 的整数`;
updateNodeData(currentNodeId, {maxLoopCount: normalized});
}
});
onMount(() => {
if (!wrapperElement) {
@@ -187,12 +234,7 @@
<label class="input-item-inline">
<span>循环执行:</span>
<input type="checkbox" checked={!!data.loopEnable} onchange={(event)=>{
const value = (event.target as any).checked;
updateNodeData(currentNodeId,{
loopEnable: value
})
}} />
<input type="checkbox" checked={!!data.loopEnable} onchange={updateLoopEnabled} />
</label>
{#if !!data.loopEnable}
@@ -207,13 +249,19 @@
</div>
<div class="input-item">
最大循环次数0 表示不限制)
<Textarea rows={1} style="width: 100%;" onchange={(event)=>{
const value = (event.target as any).value;
updateNodeData(currentNodeId,{
maxLoopCount: value
})
}} value={data.maxLoopCount || '0'} />
循环次数:
<Input
type="number"
min={MIN_LOOP_COUNT}
max={MAX_LOOP_COUNT}
step="1"
style="width: 100%;"
onchange={updateMaxLoopCount}
value={data.maxLoopCount ?? MIN_LOOP_COUNT}
/>
{#if loopCountHint}
<span class="input-hint" role="status" aria-live="polite">{loopCountHint}</span>
{/if}
</div>
<div class="input-item">
@@ -337,6 +385,11 @@
color: var(--tf-text-secondary);
}
.input-hint {
color: var(--tf-warning-soft-text);
line-height: 1.4;
}
input[type='checkbox'] {
width: 14px;
height: 14px;

View File

@@ -14,12 +14,22 @@
}
});
const { parameter, index, dataKeyName, useChildrenOnly, showContentType = false }: {
const {
parameter,
index,
dataKeyName,
useChildrenOnly,
showContentType = false,
fixedNumberMin,
fixedNumberMax
}: {
parameter: Parameter,
index: number,
dataKeyName: string,
useChildrenOnly?: boolean,
showContentType?: boolean,
fixedNumberMin?: number,
fixedNumberMax?: number,
} = $props();
@@ -53,6 +63,39 @@
updateParam(name, newValue);
};
let fixedValueHint = $state('');
const updateFixedValue = (event: Event) => {
const value = (event.target as HTMLInputElement).value;
if (fixedNumberMin === undefined && fixedNumberMax === undefined) {
updateParam('value', value);
return;
}
if (value.trim() === '') {
fixedValueHint = '';
updateParam('value', value);
return;
}
const parsed = Number(value);
if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) {
fixedValueHint = '请输入整数';
updateParam('value', value);
return;
}
if (fixedNumberMax !== undefined && parsed > fixedNumberMax) {
fixedValueHint = `最大支持 ${fixedNumberMax} 次,已调整为 ${fixedNumberMax}`;
updateParam('value', String(fixedNumberMax));
return;
}
if (fixedNumberMin !== undefined && parsed < fixedNumberMin) {
fixedValueHint = `最少执行 ${fixedNumberMin} 次`;
updateParam('value', String(fixedNumberMin));
return;
}
fixedValueHint = '';
updateParam('value', value);
};
const updateRef = (item: any) => {
const newValue = item.value;
@@ -93,7 +136,21 @@
</div>
<div class="input-item">
{#if param.refType === 'fixed'}
<Input value={param.value} placeholder="请输入参数值" oninput={(event)=>updateParamByEvent('value', event)} />
<div class="fixed-value">
<Input
type={fixedNumberMin !== undefined || fixedNumberMax !== undefined ? 'number' : 'text'}
min={fixedNumberMin}
max={fixedNumberMax}
step={fixedNumberMin !== undefined || fixedNumberMax !== undefined ? 1 : undefined}
style="width: 100%;"
value={param.value}
placeholder="请输入参数值"
oninput={updateFixedValue}
/>
{#if fixedValueHint}
<span class="input-hint" role="status" aria-live="polite">{fixedValueHint}</span>
{/if}
</div>
{:else if (param.refType !== 'input')}
<Select items={selectItems.current} style="width: 100%" defaultValue={["ref"]} value={[param.ref]} variant="reference"
expandAll
@@ -152,6 +209,20 @@
min-width: 0;
}
.fixed-value {
display: flex;
flex-direction: column;
gap: 3px;
width: 100%;
min-width: 0;
}
.input-hint {
color: var(--tf-warning-soft-text);
font-size: 11px;
line-height: 1.4;
}
.input-more-setting {
display: flex;
flex-direction: column;

View File

@@ -7,12 +7,16 @@
noneParameterText = '无输入参数',
dataKeyName = 'parameters',
useChildrenOnly,
showContentType = false
showContentType = false,
fixedNumberMin,
fixedNumberMax
}: {
noneParameterText?: string;
dataKeyName?: string;
useChildrenOnly?: boolean,
showContentType?: boolean,
fixedNumberMin?: number,
fixedNumberMax?: number,
} = $props();
let currentNodeId = getCurrentNodeId();
@@ -31,7 +35,15 @@
<div class="input-header"></div>
{/if}
{#each parameters as param, index (param.id)}
<RefParameterItem parameter={param} index={index} {dataKeyName} {useChildrenOnly} {showContentType} />
<RefParameterItem
parameter={param}
index={index}
{dataKeyName}
{useChildrenOnly}
{showContentType}
{fixedNumberMin}
{fixedNumberMax}
/>
{:else }
<div class="none-params">{noneParameterText}</div>
{/each}

View File

@@ -55,7 +55,8 @@
<!-- </svg>-->
<!-- </Button>-->
</div>
<RefParameterList dataKeyName="loopVars" />
<RefParameterList dataKeyName="loopVars" fixedNumberMin={1} fixedNumberMax={300} />
<div class="loop-limit-hint">单次最多循环 300 次</div>
<div class="heading">
<Heading level={3}>输出参数</Heading>
@@ -78,6 +79,13 @@
align-items: center;
}
.loop-limit-hint {
margin-top: 6px;
color: var(--tf-text-secondary);
font-size: 12px;
line-height: 1.4;
}
:global(.loop-handle-wrapper) {
&::after {
//display: none;