feat: 重构节点上下文与并发执行逻辑
重构GetNodeContextContent返回类型为切片,修复并发竞态与协程泄漏问题;回调改用OSS文件获取结果;调整节点输入上传时序
This commit is contained in:
@@ -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...
|
||||
|
||||
Reference in New Issue
Block a user