package server import ( "context" "fmt" "strings" "unicode" "chat-agent-system/internal/auth" "chat-agent-system/internal/chat" "chat-agent-system/internal/coordinator" "chat-agent-system/internal/docgov" ) // bindSessionDocumentSummaryReader 将正文访问绑定到已认证的用户与项目。 // 读取前先应用与文档列表/正文 API 相同的阶段权限过滤。 func (s *Server) bindSessionDocumentSummaryReader(sess *chat.Session, user *auth.UserAccount) { if sess == nil || sess.UserCtx == nil { return } sess.UserCtx.DocumentSummaryReader = nil projectID := sess.ProjectID userID := int64(0) if user != nil { userID = user.ID } if projectID <= 0 || userID <= 0 || s.svc == nil || s.svc.DocGov() == nil { return } sess.UserCtx.DocumentSummaryReader = func(ctx context.Context, target string) (*coordinator.ReadableDocument, error) { if s.authorizer == nil { return nil, fmt.Errorf("project authorization unavailable") } if err := s.authorizer.RequireProjectAccessOrAdmin(ctx, projectID, userID, s.isSystemAdminUser(ctx, user)); err != nil { return nil, err } docs, err := s.svc.DocGov().ListWorkflowDocumentsByProject(ctx, projectID) if err != nil { return nil, err } docs = s.filterWorkflowDocsByStageAccess(ctx, projectID, userID, docs) doc, err := selectReadableWorkflowDocument(docs, target) if err != nil { return nil, err } content, err := s.svc.DocGov().ReadDocumentContent(ctx, doc.DocID, s.repoRoot()) if err != nil { return nil, err } return &coordinator.ReadableDocument{ DocID: doc.DocID, Title: doc.Title, TaskKey: doc.TaskKey, LifecycleState: string(doc.LifecycleState), Version: doc.Version, Markdown: content.Markdown, IsTemplateOnly: doc.IsTemplateOnly || content.IsTemplateOnly, }, nil } } func selectReadableWorkflowDocument(docs []docgov.WorkflowDocumentSummary, target string) (*docgov.WorkflowDocumentSummary, error) { if len(docs) == 0 { return nil, fmt.Errorf("no readable workflow documents") } targetKey := normalizeDocumentLookupKey(target) if targetKey == "" { if len(docs) == 1 { return &docs[0], nil } return nil, fmt.Errorf("document target required") } for i := range docs { if target == docs[i].DocID || target == docs[i].TaskKey || target == docs[i].Title { return &docs[i], nil } } for i := range docs { keys := []string{ normalizeDocumentLookupKey(docs[i].DocID), normalizeDocumentLookupKey(docs[i].TaskKey), normalizeDocumentLookupKey(docs[i].Title), } for _, key := range keys { if key != "" && (key == targetKey || strings.Contains(key, targetKey) || strings.Contains(targetKey, key)) { return &docs[i], nil } } } return nil, fmt.Errorf("document not found in readable scope") } func normalizeDocumentLookupKey(value string) string { value = strings.ToLower(strings.TrimSpace(value)) value = strings.NewReplacer("收集", "", "采集", "", "正文", "", "文档", "", "《", "", "》", "").Replace(value) return strings.Map(func(r rune) rune { if unicode.IsSpace(r) || unicode.IsPunct(r) { return -1 } return r }, value) }