Files
Agent-System/internal/docgov/duty_project.go
T
weichen a90cce73b9 fix(docgov): 保护共享内存 map,避免并发读写把进程打崩
per-doc 锁只串行同一文档,不同 docID 仍裸读写 memDocs 等 map。GetDocument 与另一文档写入撞上时运行时 fatal,代理把全部 API 变成 500。
2026-09-28 08:52:44 +00:00

113 lines
2.8 KiB
Go

package docgov
import (
"context"
"fmt"
"strings"
"time"
)
// FindByProjectTaskKey 按项目 + task_key 查找文档(无则 ErrDocumentNotFound)。
func (e *GovernanceEngine) FindByProjectTaskKey(ctx context.Context, projectID int64, taskKey string) (*DocumentRecord, error) {
if e == nil || projectID <= 0 {
return nil, ErrDocumentNotFound
}
taskKey = strings.TrimSpace(taskKey)
if taskKey == "" {
return nil, ErrDocumentNotFound
}
e.mu.Lock()
defer e.mu.Unlock()
if e.pool != nil {
var docID string
err := e.pool.QueryRow(ctx, `
SELECT doc_id FROM document_records
WHERE project_id = $1 AND task_key = $2
ORDER BY updated_at DESC
LIMIT 1
`, projectID, taskKey).Scan(&docID)
if err != nil {
// fall through to mem
} else if docID != "" {
return e.ensureDocumentLocked(ctx, docID)
}
}
doc := e.memDocsFind(func(doc *DocumentRecord) bool {
return doc.ProjectID == projectID && doc.TaskKey == taskKey
})
if doc != nil {
cp := *doc
return &cp, nil
}
return nil, ErrDocumentNotFound
}
// ProjectDocTaskDuties 受控改派:更新 document_records 三职投影(含 duty_locked)。
func (e *GovernanceEngine) ProjectDocTaskDuties(
ctx context.Context,
projectID int64,
taskKey string,
authorID, reviewerID, approverID int64,
dutyPolicy string,
authorName, reviewerName, approverName string,
) error {
if e == nil || projectID <= 0 {
return nil
}
taskKey = strings.TrimSpace(taskKey)
if taskKey == "" {
return nil
}
if authorName == "" {
authorName = fmt.Sprintf("user-%d", authorID)
}
if reviewerName == "" {
reviewerName = fmt.Sprintf("user-%d", reviewerID)
}
if approverName == "" {
approverName = fmt.Sprintf("user-%d", approverID)
}
e.mu.Lock()
defer e.mu.Unlock()
now := time.Now()
if e.pool != nil {
tag, err := e.pool.Exec(ctx, `
UPDATE document_records SET
author_id = NULLIF($3, 0), author_name = $4,
reviewer_id = NULLIF($5, 0), reviewer_name = $6,
approver_id = NULLIF($7, 0), approver_name = $8,
duty_policy = $9,
updated_at = $10
WHERE project_id = $1 AND task_key = $2
`, projectID, taskKey,
authorID, authorName, reviewerID, reviewerName, approverID, approverName,
strings.TrimSpace(dutyPolicy), now)
if err != nil {
return fmt.Errorf("docgov: project doc task duties: %w", err)
}
if tag.RowsAffected() == 0 {
return nil // 文档尚未生成,仅实例侧已更新
}
}
e.forEachMemDoc(func(doc *DocumentRecord) {
if doc == nil || doc.ProjectID != projectID || doc.TaskKey != taskKey {
return
}
doc.AuthorID = authorID
doc.AuthorName = authorName
doc.ReviewerID = reviewerID
doc.ReviewerName = reviewerName
doc.ApproverID = approverID
doc.ApproverName = approverName
doc.DutyPolicy = strings.TrimSpace(dutyPolicy)
doc.UpdatedAt = now
})
return nil
}