per-doc 锁只串行同一文档,不同 docID 仍裸读写 memDocs 等 map。GetDocument 与另一文档写入撞上时运行时 fatal,代理把全部 API 变成 500。
113 lines
2.8 KiB
Go
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
|
|
}
|