feat: 重构节点上下文与并发执行逻辑

重构GetNodeContextContent返回类型为切片,修复并发竞态与协程泄漏问题;回调改用OSS文件获取结果;调整节点输入上传时序
This commit is contained in:
2026-06-18 14:24:48 +08:00
parent fba7d032ae
commit 4df45069e0
5 changed files with 287 additions and 162 deletions

View File

@@ -44,16 +44,18 @@ func JudgeLambda(ctx context.Context, input any) (string, error) {
// 1. 直接用你原来的方法(返回两个 map
inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
var outputResult []node.NodeFormField
for _, valueAny := range inputMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
}
}
for _, valueAny := range outputMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
}
}
outputResult = append(outputResult, inputMap...)
outputResult = append(outputResult, outputMap...)
//for _, valueAny := range inputMap {
// if field, ok := valueAny.(node.NodeFormField); ok {
// outputResult = append(outputResult, field)
// }
//}
//for _, valueAny := range outputMap {
// if field, ok := valueAny.(node.NodeFormField); ok {
// outputResult = append(outputResult, field)
// }
//}
for _, valueAny := range modelMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
@@ -123,62 +125,75 @@ func BatchModelLambda(ctx context.Context, input any) (any, error) {
}
}
}
// 结果按索引存放,保证顺序
// 结果按索引存放,切片不同下标并发写无竞争,不用锁
res := make([][]node.NodeFormField, len(reqMap))
var wg sync.WaitGroup
// 用一个通道标记是否完成
done := make(chan struct{})
// 错误只存一个
var execErr error
// 并发执行
subCtx, cancel := context.WithCancel(ctx)
defer cancel()
// 缓冲1错误通道仅接收第一个错误
errCh := make(chan error, 1)
// 并发执行任务
for idx, item := range reqMap {
wg.Add(1)
go func(idx int, userItem map[string]any) {
defer wg.Done()
// 上下文已取消则直接退出
select {
case <-subCtx.Done():
return
default:
}
singleUserFrom := []map[string]any{userItem}
output, err := TextNode(ctx, nodeInput, skillName, from, singleUserFrom)
output, err := TextNode(subCtx, nodeInput, skillName, from, singleUserFrom)
if err != nil {
// 并发安全赋值错误
if execErr == nil {
execErr = err
// 仅第一个错误写入通道
select {
case errCh <- err:
cancel() // 触发全局取消,其他协程快速退出
default:
}
return
}
// 直接按原索引写,顺序绝对正确
res[idx] = output
}(idx, item)
}
// 后台等待所有协程完成,然后关闭 done 通道
// 任务全部结束后关闭错误通道
go func() {
wg.Wait()
close(done)
close(errCh)
}()
// 等待全部完成
<-done
// 如果有错误,直接返回
if execErr != nil {
return nil, execErr
// ========== 修正后的等待逻辑 ==========
var execErr error
select {
// 优先捕获业务错误
case execErr = <-errCh:
if execErr != nil {
// 收到真实业务错误,等待剩余协程收尾后返回
wg.Wait()
return nil, execErr
}
// execErr == nil 代表通道关闭、无任何错误,走到下方返回完整结果
case <-subCtx.Done():
// 上下文被取消阻塞读完errCh确认是否存在业务错误
execErr = <-errCh
}
// 全局自增 i
// 拼接输出结果
var globalIndex int
var outputRes []node.NodeFormField
for _, items := range res {
for _, item := range items {
// 1. 拿到原来的 Field例如 "text_content:2:0"
oldField := item.Field
// 2. 找到最后一个 : 的位置
if idx := strings.LastIndex(oldField, ":"); idx != -1 {
// 3. 截断前面部分,拼接上新的 globalIndex
item.Field = oldField[:idx+1] + fmt.Sprint(globalIndex)
}
// Label 同理
oldLabel := item.Label
if idx := strings.LastIndex(oldLabel, ":"); idx != -1 {
item.Label = oldLabel[:idx+1] + fmt.Sprint(globalIndex)
@@ -437,11 +452,14 @@ func MergeLambda(ctx context.Context, input any) (res any, err error) {
// 1. 把所有节点输出拍平成 字段名->内容 的map
dataMap := make(map[string]node.NodeFormField)
_, outputMap, _ := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
for _, valueAny := range outputMap {
field := node.NodeFormField{}
if field, ok = valueAny.(node.NodeFormField); ok {
dataMap[field.Field] = field
}
//for _, valueAny := range outputMap {
// field := node.NodeFormField{}
// if field, ok = valueAny.(node.NodeFormField); ok {
// dataMap[field.Field] = field
// }
//}
for _, field := range outputMap {
dataMap[field.Field] = field
}
// 2. 提取所有文案text_content_0,1,2...