package projectworkflow import ( "context" "crypto/rand" "encoding/hex" "fmt" "strings" "time" "chat-agent-system/internal/auth" ) // Service 项目多工作流与血缘领域服务 type Service struct { store Store catalog Catalog } // NewService 构造项目工作流服务 func NewService(store Store, catalog Catalog) *Service { if catalog == nil { catalog = NewDefaultCatalog() } return &Service{ store: store, catalog: catalog, } } // Catalog 获取静态工作流规范目录 func (s *Service) Catalog() Catalog { return s.catalog } // Store 获取持久化存储接口 func (s *Service) Store() Store { return s.store } // CreateProject 创建新项目,严格从认证令牌注入权威创建人,并自动初始化挂载工作流 func (s *Service) CreateProject(ctx context.Context, user *auth.UserAccount, in CreateProjectInput) (*Project, error) { if user == nil || user.ID <= 0 { return nil, ErrUnauthenticated } name := strings.TrimSpace(in.Name) if name == "" { return nil, fmt.Errorf("%w: 项目名称为必填项", ErrInvalidInput) } code := strings.TrimSpace(in.Code) if code == "" { code = generateProjectCode(name) } initialWorkflow := strings.TrimSpace(in.InitialWorkflowKey) if initialWorkflow != "" { if _, ok := s.catalog.Get(initialWorkflow); !ok { return nil, fmt.Errorf("%w: 初始工作流 %s 不存在", ErrWorkflowNotFound, initialWorkflow) } } p := &Project{ Code: code, Name: name, CustomerName: in.CustomerName, DeviceType: in.DeviceType, IntendedUse: in.IntendedUse, TargetMarket: in.TargetMarket, CurrentStage: "V0.1_triage", Environment: "dev", Status: "active", CreatorUserID: user.ID, LifecycleStatus: "planning", ActiveWorkflowKey: "", WorkflowSelectionVersion: 1, WorkflowPlanRevision: 0, } if initialWorkflow != "" { p.LifecycleStatus = "active" p.ActiveWorkflowKey = initialWorkflow } created, err := s.store.CreateProject(ctx, p) if err != nil { return nil, err } // 自动挂载目录中的工作流实例 var allKeys []string for _, def := range s.catalog.List() { allKeys = append(allKeys, def.Key) } if err := s.store.EnsureProjectWorkflows(ctx, created.ID, allKeys); err != nil { return nil, fmt.Errorf("initialize project workflows failed: %w", err) } return created, nil } // GetProject 根据项目 ID 获取项目详情 func (s *Service) GetProject(ctx context.Context, projectID int64, userID int64) (*Project, error) { if projectID <= 0 { return nil, ErrProjectNotFound } p, err := s.store.GetProjectByID(ctx, projectID) if err != nil { return nil, err } return p, nil } // GetProjectByCode 根据项目唯一编码获取项目详情 func (s *Service) GetProjectByCode(ctx context.Context, code string) (*Project, error) { return s.store.GetProjectByCode(ctx, code) } // ListProjects 查询指定用户可见的项目列表 func (s *Service) ListProjects(ctx context.Context, userID int64) ([]*Project, error) { return s.store.ListProjectsForUser(ctx, userID) } // ListAllProjects 查询全部项目,仅供已完成系统管理员鉴权的调用方使用。 func (s *Service) ListAllProjects(ctx context.Context) ([]*Project, error) { return s.store.ListAllProjects(ctx) } // IsProjectCreator 判断用户是否为指定项目的权威创建人 func (s *Service) IsProjectCreator(ctx context.Context, projectID int64, userID int64) (bool, error) { p, err := s.store.GetProjectByID(ctx, projectID) if err != nil { return false, err } return p.CreatorUserID == userID, nil } // ListProjectWorkflows 查询项目已挂载的工作流清单 func (s *Service) ListProjectWorkflows(ctx context.Context, projectID int64) ([]*ProjectWorkflow, error) { // 确保默认工作流已初始化 var allKeys []string for _, def := range s.catalog.List() { allKeys = append(allKeys, def.Key) } _ = s.store.EnsureProjectWorkflows(ctx, projectID, allKeys) return s.store.ListProjectWorkflows(ctx, projectID) } // ActivateWorkflow 原子切换项目当前激活的工作流焦点(仅限创建人) func (s *Service) ActivateWorkflow( ctx context.Context, projectID int64, userID int64, in ActivateWorkflowInput, ) (*ActivateWorkflowResult, error) { if userID <= 0 { return nil, ErrUnauthenticated } targetKey := strings.TrimSpace(in.WorkflowKey) if targetKey == "" { return nil, fmt.Errorf("%w: target workflow_key cannot be empty", ErrInvalidInput) } def, ok := s.catalog.Get(targetKey) if !ok || !def.Enabled { return nil, fmt.Errorf("%w: workflow '%s' is not supported or disabled", ErrWorkflowNotFound, targetKey) } updatedProject, audit, err := s.store.UpdateActiveWorkflow( ctx, projectID, targetKey, in.ExpectedVersion, userID, in.Reason, ) if err != nil { return nil, err } now := time.Now() if audit != nil { now = audit.CreatedAt } else if updatedProject.ActiveWorkflowChangedAt != nil { now = *updatedProject.ActiveWorkflowChangedAt } return &ActivateWorkflowResult{ ProjectID: updatedProject.ID, ActiveWorkflowKey: updatedProject.ActiveWorkflowKey, Version: updatedProject.WorkflowSelectionVersion, ChangedAt: now, ChangedBy: userID, }, nil } // CreateWorkflowRun 创建工作流运行实例 func (s *Service) CreateWorkflowRun(ctx context.Context, run *WorkflowRun) (*WorkflowRun, error) { if run.RunID == "" { run.RunID = generateRunID(run.WorkflowKey) } if run.Status == "" { run.Status = "running" } return s.store.CreateWorkflowRun(ctx, run) } // UpdateWorkflowRun 结束/更新工作流运行实例 func (s *Service) UpdateWorkflowRun( ctx context.Context, runID string, status string, outputDocID string, summary string, errMsg string, ) error { return s.store.UpdateWorkflowRun(ctx, runID, status, outputDocID, summary, errMsg) } // GetWorkflowRun 获取运行记录详情 func (s *Service) GetWorkflowRun(ctx context.Context, runID string) (*WorkflowRun, error) { return s.store.GetWorkflowRun(ctx, runID) } // ListWorkflowRuns 列出项目的运行历史 func (s *Service) ListWorkflowRuns(ctx context.Context, projectID int64, workflowKey string, limit int) ([]*WorkflowRun, error) { return s.store.ListWorkflowRuns(ctx, projectID, workflowKey, limit) } // RecordLineage 记录文档版本血缘 func (s *Service) RecordLineage(ctx context.Context, lineage *DocumentLineage) error { return s.store.RecordLineage(ctx, lineage) } // GetLineageForDownstream 查询下游文档所依赖的上游版本信息 func (s *Service) GetLineageForDownstream(ctx context.Context, docID string, version int) ([]*DocumentLineage, error) { return s.store.GetLineageForDownstream(ctx, docID, version) } // CheckAndPropagateStale 当上游文档更新且内容哈希变化时,通知并将关联下游文档标记为 stale func (s *Service) CheckAndPropagateStale( ctx context.Context, upstreamDocID string, newSHA256 string, reason string, ) ([]*DocumentLineage, error) { lineages, err := s.store.GetLineagesForUpstream(ctx, upstreamDocID) if err != nil { return nil, err } needsStale := false for _, l := range lineages { if l.UpstreamSHA256 != "" && l.UpstreamSHA256 != newSHA256 && !l.IsStale { needsStale = true break } } if !needsStale { return nil, nil } if reason == "" { reason = fmt.Sprintf("上游文档 %s 生成了新版本,内容已发生实质变化", upstreamDocID) } return s.store.MarkDownstreamStale(ctx, upstreamDocID, reason) } // 辅助方法:生成项目短代码(按 rune 截断,避免切断 UTF-8 多字节字符导致 PG 插入失败) func generateProjectCode(name string) string { b := make([]byte, 4) _, _ = rand.Read(b) hash := hex.EncodeToString(b) cleanName := strings.ToLower(strings.ReplaceAll(strings.TrimSpace(name), " ", "-")) runes := []rune(cleanName) if len(runes) > 12 { runes = runes[:12] cleanName = string(runes) } if cleanName == "" { cleanName = "proj" } return fmt.Sprintf("%s-%s", cleanName, hash) } // 辅助方法:生成运行实例 ID func generateRunID(workflowKey string) string { b := make([]byte, 6) _, _ = rand.Read(b) return fmt.Sprintf("run_%s_%s", workflowKey, hex.EncodeToString(b)) }