package flow import ( "ai-agent/workflow/consts/flow" "ai-agent/workflow/consts/public" flowDao "ai-agent/workflow/dao/flow" flowDto "ai-agent/workflow/model/dto/flow" "ai-agent/workflow/model/entity" "ai-agent/workflow/service" "context" "gitea.redpowerfuture.com/red-future/common/beans" "gitea.redpowerfuture.com/red-future/common/db/gfdb" "gitea.redpowerfuture.com/red-future/common/utils" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) var FlowUserService = &flowUserService{} type flowUserService struct{} func (s *flowUserService) Create(ctx context.Context, req *flowDto.CreateFlowUserReq) (res *flowDto.CreateFlowUserRes, err error) { admin, err := service.UtilService.IsAdmin(ctx) if err != nil { return } req.NodeInputParams = ExtractFlowNodeFrom(req.FlowContent) var id int64 if admin { id, err = flowDao.FlowTemplateDao.Insert(ctx, &flowDto.CreateFlowTemplateReq{ FlowTemplateName: req.FlowName, Description: req.Description, FlowContent: req.FlowContent, NodeInputParams: req.NodeInputParams, Status: flow.FlowTemplateStatusEnable.Code(), }) } else { id, err = flowDao.FlowUserDao.Insert(ctx, req) } return &flowDto.CreateFlowUserRes{Id: id}, err } func (s *flowUserService) Update(ctx context.Context, req *flowDto.UpdateFlowUserReq) (res *flowDto.CreateFlowUserRes, err error) { id := req.Id admin, err := service.UtilService.IsAdmin(ctx) if err != nil { return } req.NodeInputParams = ExtractFlowNodeFrom(req.FlowContent) if admin { _, err = flowDao.FlowTemplateDao.Update(ctx, &flowDto.UpdateFlowTemplateReq{ Id: req.Id, FlowTemplateName: req.FlowName, Description: req.Description, FlowContent: req.FlowContent, NodeInputParams: req.NodeInputParams, Status: flow.FlowTemplateStatusEnable.Code(), }) } else { var get *entity.FlowTemplate get, err = flowDao.FlowTemplateDao.Get(ctx, &flowDto.GetFlowTemplateReq{ Id: req.Id, }) if err != nil { return nil, err } if !g.IsEmpty(get) { // 模版 → 用户流程拷贝(含递归子流程拷贝)整体放一个事务:任一拷贝失败则整体回滚, // 避免主流程已落库而子流程缺失/引用错乱的脏数据。 // Transaction 会把 tx 注入回调 ctx,回调内用该 ctx 调 DAO 即自动进入事务。 txErr := gfdb.DB(ctx, public.DbNameBlackDeacon).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { // 子流程引用重写:模版里的 sub_flow 节点指向子模版/子流程 id, // 拷贝成用户自己的流程时要把子工作流也复制一份(子流程可能再嵌套子流程,递归), // 并把主流程 SubConfig.WorkflowId 改写为新拷贝的 id subIdMap, copyErr := copySubFlows(ctx, req.SubFlows) if copyErr != nil { return copyErr } rewriteSubFlowWorkflowIds(req.FlowContent, subIdMap) // SubConfig 改写后重新提取节点参数,保证落库的 NodeInputParams 与 FlowContent 一致 req.NodeInputParams = ExtractFlowNodeFrom(req.FlowContent) newId, insertErr := flowDao.FlowUserDao.Insert(ctx, &flowDto.CreateFlowUserReq{ FlowName: req.FlowName, Description: req.Description, FlowContent: req.FlowContent, NodeInputParams: req.NodeInputParams, SourceFlowTemplateId: get.Id, }) if insertErr != nil { return insertErr } id = newId return nil }) if txErr != nil { return nil, txErr } } else { _, err = flowDao.FlowUserDao.Update(ctx, req) } } return &flowDto.CreateFlowUserRes{Id: id}, err } func (s *flowUserService) Delete(ctx context.Context, req *flowDto.DeleteFlowUserReq) (err error) { admin, err := service.UtilService.IsAdmin(ctx) if err != nil { return } if admin { _, err = flowDao.FlowTemplateDao.Delete(ctx, &flowDto.DeleteFlowTemplateReq{ Id: req.Id, }) } else { _, err = flowDao.FlowUserDao.Delete(ctx, req) } return } func (s *flowUserService) Get(ctx context.Context, req *flowDto.GetFlowUserReq) (res *flowDto.FlowUserVO, err error) { var flowInfo *entity.FlowTemplate flowInfo, err = flowDao.FlowTemplateDao.Get(ctx, &flowDto.GetFlowTemplateReq{ Id: req.Id, }) if err != nil { return } if flowInfo != nil { res = new(flowDto.FlowUserVO) res.FlowName = flowInfo.FlowTemplateName err = gconv.Struct(flowInfo, res) return } var flowUserInfo *entity.FlowUser flowUserInfo, err = flowDao.FlowUserDao.Get(ctx, req) if err != nil { return } res = new(flowDto.FlowUserVO) err = gconv.Struct(flowUserInfo, res) return } func (s *flowUserService) List(ctx context.Context, req *flowDto.ListFlowUserReq) (res *flowDto.ListFlowRes, err error) { admin, err := service.UtilService.IsAdmin(ctx) if err != nil { return } if admin { var t int var l []*entity.FlowTemplate l, t, err = flowDao.FlowTemplateDao.List(ctx, &flowDto.ListFlowTemplateReq{ Keyword: req.Keyword, Page: req.Page, }) if err != nil { return } r := &flowDto.ListFlowTemplateRes{ Total: t, } err = gconv.Struct(l, &r.List) if err != nil { return } res = &flowDto.ListFlowRes{ ListFlowUserRes: nil, ListFlowTemplateRes: r, IsAdmin: admin, } return } res = &flowDto.ListFlowRes{ IsAdmin: admin, } if !req.IsOwn { var t int var l []*entity.FlowTemplate l, t, err = flowDao.FlowTemplateDao.List(ctx, &flowDto.ListFlowTemplateReq{ Keyword: req.Keyword, Page: req.Page, }) if err != nil { return } r := &flowDto.ListFlowTemplateRes{ Total: t, } err = gconv.Struct(l, &r.List) if err != nil { return } res.ListFlowTemplateRes = r } var user *beans.User user, err = utils.GetUserInfo(ctx) if err != nil { return nil, err } req.Creator = user.UserName list, total, err := flowDao.FlowUserDao.List(ctx, req) if err != nil { return } re := &flowDto.ListFlowUserRes{ Total: total, } err = gconv.Struct(list, &re.List) if err != nil { return } res.ListFlowUserRes = re return } // copySubFlows 递归拷贝子工作流(子流程可能再包含子流程),返回 旧id→新id 映射。 // 映射键是子工作流在来源(模版)中的 id,即父流程 sub_flow 节点 SubConfig.WorkflowId 指向的值; // 拷贝时把子工作流落为当前用户自己的流程(SourceFlowTemplateId 记录来源)。 // 同一子流程被多个节点引用时只拷贝一份,避免产生孤儿副本。 func copySubFlows(ctx context.Context, subs []flowDto.UpdateFlowUserReq) (map[int64]int64, error) { idMap := make(map[int64]int64) for i := range subs { sub := subs[i] if _, done := idMap[sub.Id]; done { continue } nestedMap, err := copySubFlows(ctx, sub.SubFlows) if err != nil { return nil, err } for k, v := range nestedMap { idMap[k] = v } // 改写本子流程对更深层子流程的引用后,再落库为新的用户流程 rewriteSubFlowWorkflowIds(sub.FlowContent, idMap) var nodeInputParams []*entity.FlowNode if sub.FlowContent != nil { nodeInputParams = ExtractFlowNodeFrom(sub.FlowContent) } newId, err := flowDao.FlowUserDao.Insert(ctx, &flowDto.CreateFlowUserReq{ FlowName: sub.FlowName, Description: sub.Description, FlowContent: sub.FlowContent, NodeInputParams: nodeInputParams, SourceFlowTemplateId: sub.Id, }) if err != nil { return nil, err } idMap[sub.Id] = newId } return idMap, nil } // rewriteSubFlowWorkflowIds 把 flowContent 中 sub_flow 节点的 WorkflowId 按 idMap 改写为新拷贝的流程 id func rewriteSubFlowWorkflowIds(flowContent *entity.FlowInfo, idMap map[int64]int64) { if flowContent == nil { return } for i := range flowContent.Nodes { n := &flowContent.Nodes[i] if n.SubConfig == nil || n.SubConfig.WorkflowId == 0 { continue } if newId, ok := idMap[n.SubConfig.WorkflowId]; ok { n.SubConfig.WorkflowId = newId } } }