package server import ( "net/http" "path/filepath" "strconv" ) func (s *Server) repoRoot() string { if s.cfg != nil && s.cfg.DataDir != "" { // DataDir 形如 ./data/dev;工作区路径形如 data/dev/workspaces/...,相对仓库根目录 return filepath.Clean(filepath.Join(s.cfg.DataDir, "..", "..")) } return "." } // handleListProjectWorkflowDocuments 列出项目工作流受控文档。 func (s *Server) handleListProjectWorkflowDocuments(w http.ResponseWriter, r *http.Request) { projectID, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil || projectID <= 0 { http.Error(w, "invalid project id", http.StatusBadRequest) return } category := r.URL.Query().Get("category") if category != "" && category != "workflow" { http.Error(w, "unsupported category", http.StatusBadRequest) return } user, ok := s.requireProjectAccess(w, r, projectID) if !ok { return } if s.svc == nil || s.svc.DocGov() == nil { http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable) return } docs, err := s.svc.DocGov().ListWorkflowDocumentsByProject(r.Context(), projectID) if err != nil { http.Error(w, "list project documents failed: "+err.Error(), http.StatusInternalServerError) return } docs = s.filterWorkflowDocsByStageAccess(r.Context(), projectID, user.ID, docs) writeJSONResponse(w, http.StatusOK, docs) } // handleDocGovGetDocumentContent 读取受控文档 Markdown 正文。 func (s *Server) handleDocGovGetDocumentContent(w http.ResponseWriter, r *http.Request) { docID := r.PathValue("doc_id") if docID == "" { http.Error(w, "doc_id required", http.StatusBadRequest) return } if s.svc == nil || s.svc.DocGov() == nil { http.Error(w, "docgov engine not initialized", http.StatusServiceUnavailable) return } docRec, err := s.svc.DocGov().GetDocument(r.Context(), docID) if err != nil || docRec == nil { http.Error(w, "document not found", http.StatusNotFound) return } user, ok := s.requireProjectAccess(w, r, docRec.ProjectID) if !ok { return } if !s.allowWorkflowDocTaskAccess(r.Context(), docRec.ProjectID, user.ID, docRec.TaskKey) { http.Error(w, `{"error":"WORKFLOW_ACCESS_DENIED","message":"无权访问该阶段文档"}`, http.StatusForbidden) return } content, err := s.svc.DocGov().ReadDocumentContent(r.Context(), docID, s.repoRoot()) if err != nil { http.Error(w, "read document content failed: "+err.Error(), http.StatusNotFound) return } if inm := r.Header.Get("If-None-Match"); inm != "" && inm == content.ContentSHA256 { w.WriteHeader(http.StatusNotModified) return } w.Header().Set("ETag", content.ContentSHA256) writeJSONResponse(w, http.StatusOK, content) }