* 模板拷贝用户流程时递归复制子流程,并重写 sub_flow 节点引用 * 段级续跑改用列表位置作为段身份,移除对 segment_index 的依赖 * 段结果保存移到每段生成完成时立即落库,降低崩溃丢失风险 * 移除视频分段续跑设计与对应测试
277 lines
7.9 KiB
Go
277 lines
7.9 KiB
Go
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
|
|
}
|
|
}
|
|
}
|