package flow import ( "ai-agent/workflow/consts/public" "ai-agent/workflow/model/entity" "context" "fmt" "gitea.redpowerfuture.com/red-future/common/db/gfdb" ) const ( FlowAsyncStateInflight = 0 // 任务已提交,结果未取(执行中/结果已发布未取/失败) FlowAsyncStateDone = 1 // 成功,结果已缓存 FlowAsyncStateFailed = 2 // 确定失败 FlowAsyncSegSentinel = -1 // 非段异步调用的段序号哨兵值 ) var FlowAsyncTaskDao = &flowAsyncTaskDao{} type flowAsyncTaskDao struct{} // Get 查询唯一键 (execution_id, node_id, segment_index) 的记录 func (d *flowAsyncTaskDao) Get(ctx context.Context, execId int64, nodeId string, segIdx int) (res *entity.FlowAsyncTask, err error) { r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFlowAsyncTask). Where(entity.FlowAsyncTaskCol.ExecutionId, execId). Where(entity.FlowAsyncTaskCol.NodeId, nodeId). Where(entity.FlowAsyncTaskCol.SegmentIndex, segIdx). One() if err != nil { return nil, err } if r.IsEmpty() { return nil, nil } err = r.Struct(&res) return } // Upsert 提交时写入/更新 in-flight 行:唯一键冲突则更新 model_id/task_id/msg_topic/state(保留已完成结果不动)。 // 与 flow_segment_result 相同走 OnConflict().Save();两点相对初稿的调整: // 1. Result 列是 JSONB,空串会被 PG 拒绝(invalid input syntax for type json), // 提交时本无结果,统一写 '{}' 占位(与表列默认值一致)。 // 2. 必须用 OnDuplicate 限定冲突更新列——GoFrame Save 默认把 Data 里所有列写进 // ON CONFLICT DO UPDATE SET,若不限定会把已缓存的结果覆盖成 '{}',与"保留已完成结果"矛盾。 func (d *flowAsyncTaskDao) Upsert(ctx context.Context, execId int64, nodeId string, segIdx int, modelId, taskId int64, msgTopic string) error { rec := &entity.FlowAsyncTask{ ExecutionId: execId, NodeId: nodeId, SegmentIndex: segIdx, ModelId: modelId, TaskId: taskId, MsgTopic: msgTopic, State: FlowAsyncStateInflight, Result: "{}", } _, err := gfdb.DB(ctx, public.DbNameBlackDeacon). Model(ctx, public.TableNameFlowAsyncTask). Data(rec). OnConflict(entity.FlowAsyncTaskCol.ExecutionId, entity.FlowAsyncTaskCol.NodeId, entity.FlowAsyncTaskCol.SegmentIndex). OnDuplicate(entity.FlowAsyncTaskCol.ModelId, entity.FlowAsyncTaskCol.TaskId, entity.FlowAsyncTaskCol.MsgTopic, entity.FlowAsyncTaskCol.State). Save() return err } // UpdateByKey 按唯一键更新 state/result(OmitNil 丢弃 nil 字段,map 值非 nil 全写入; // state=0 也能落库)。Result 列是 JSONB,空串无法写入,统一落 '{}' 表示无结果。 func (d *flowAsyncTaskDao) UpdateByKey(ctx context.Context, execId int64, nodeId string, segIdx int, state int, result string) error { resultVal := result if resultVal == "" { resultVal = "{}" } _, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFlowAsyncTask). Where(entity.FlowAsyncTaskCol.ExecutionId, execId). Where(entity.FlowAsyncTaskCol.NodeId, nodeId). Where(entity.FlowAsyncTaskCol.SegmentIndex, segIdx). Data(map[string]any{ entity.FlowAsyncTaskCol.State: state, entity.FlowAsyncTaskCol.Result: resultVal, }). Update() return err } // DeleteByKey 物理删除:实体嵌 SQLBaseDO 软删后同键重存无法复活(ON CONFLICT 不含 deleted_at), // 与 flow_segment_result 相同约束,须 raw Exec 用物理全名 func (d *flowAsyncTaskDao) DeleteByKey(ctx context.Context, execId int64, nodeId string, segIdx int) error { const physicalTable = "black_deacon_flow_async_task" _, err := gfdb.DB(ctx, public.DbNameBlackDeacon). Exec(ctx, fmt.Sprintf("DELETE FROM %s WHERE execution_id = ? AND node_id = ? AND segment_index = ?", physicalTable), execId, nodeId, segIdx) return err } // DeleteByExecution 清理指定执行的异步任务缓存(exec 成功后调用,与段清理同处) func (d *flowAsyncTaskDao) DeleteByExecution(ctx context.Context, execId int64) error { const physicalTable = "black_deacon_flow_async_task" _, err := gfdb.DB(ctx, public.DbNameBlackDeacon). Exec(ctx, fmt.Sprintf("DELETE FROM %s WHERE execution_id = ?", physicalTable), execId) return err }