package handler import ( "encoding/json" "net/http" "time" "silk-server-go/internal/middleware" "silk-server-go/internal/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // RegisterCaseStudyRoutes 注册脱敏案例沉淀与审核发布路由。 func RegisterCaseStudyRoutes(rg *gin.RouterGroup, db *gorm.DB) { read := middleware.RequirePermission(db, "case:read") write := middleware.RequirePermission(db, "case:write") rg.GET("/case-studies", read, listCaseStudies(db)) rg.GET("/case-studies/:id", read, getCaseStudy(db)) rg.POST("/case-studies", write, createCaseStudy(db)) rg.POST("/case-studies/from-consultation/:id", write, createCaseStudyFromConsultation(db)) rg.PATCH("/case-studies/:id", write, updateCaseStudy(db)) rg.POST("/case-studies/:id/review", write, reviewCaseStudy(db)) } func listCaseStudies(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { q := db.Model(&model.CaseStudy{}) if status := c.Query("status"); status != "" { if !canReviewKnowledge(c) { c.JSON(http.StatusForbidden, gin.H{"error": "无权限查看非发布案例"}) return } q = q.Where("status = ?", status) if !hasGlobalAccess(c) { if userID := currentUserID(c); userID != nil { q = q.Where(` (source_room_id IS NOT NULL AND source_room_id IN ( SELECT r.id FROM rooms r JOIN organization_members om ON om.organization_id = r.org_id WHERE om.user_id = ? )) OR created_by = ?`, *userID, *userID) } else { q = q.Where("1 = 0") } } } else { q = q.Where("status = ?", "published") } if disease := c.Query("disease"); disease != "" { q = q.Where("disease = ?", disease) } var list []model.CaseStudy q.Order("published_at DESC NULLS LAST, created_at DESC").Limit(200).Find(&list) c.JSON(http.StatusOK, list) } } func getCaseStudy(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { var study model.CaseStudy if db.Where("id = ?", c.Param("id")).First(&study).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"}) return } if study.Status != "published" && !canReviewKnowledge(c) { c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"}) return } if study.Status != "published" && !hasGlobalAccess(c) && !canManageCaseStudy(db, c, study) { c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"}) return } c.JSON(http.StatusOK, study) } } func createCaseStudy(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { var body model.CaseStudy if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } body.ID = "" body.Status = "draft" body.CreatedBy = currentUserID(c) if body.Title == "" || body.Disease == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "标题和病种不能为空"}) return } if len(body.DesensitizedPayload) == 0 { body.DesensitizedPayload = json.RawMessage(`{}`) } for _, id := range []*string{body.SourceConsultationID, body.SourceDiseaseEventID, body.SourceRoomID} { if id != nil && *id != "" && !isUUID(*id) { c.JSON(http.StatusBadRequest, gin.H{"error": "关联 ID 不是合法的 UUID"}) return } } if body.SourceConsultationID != nil && !requireObjectAccess(c, db, "consultation", *body.SourceConsultationID) { return } if body.SourceDiseaseEventID != nil && !requireObjectAccess(c, db, "disease_event", *body.SourceDiseaseEventID) { return } if body.SourceRoomID != nil && !canAccessRoom(db, c, body.SourceRoomID) { c.JSON(http.StatusForbidden, gin.H{"error": "无权引用该蚕房案例"}) return } if err := db.Create(&body).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "创建案例失败"}) return } c.JSON(http.StatusCreated, body) } } func createCaseStudyFromConsultation(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") if !requireObjectAccess(c, db, "consultation", id) { return } var consultation model.Consultation if db.Where("id = ?", id).First(&consultation).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "consultation not found"}) return } payload := desensitizedConsultationPayload(db, consultation) disease := "待确认" var trace model.TraceRecord if db.Where("consultation_id = ?", id).Order("created_at DESC").First(&trace).Error == nil { disease = trace.Disease } title := "案例:" + disease if consultation.Title != "" { title = consultation.Title } study := model.CaseStudy{ Title: title, Disease: disease, SourceConsultationID: &consultation.ID, SourceRoomID: consultation.RoomID, Summary: consultation.Summary, DesensitizedPayload: payload, Status: "draft", CreatedBy: currentUserID(c), } if err := db.Create(&study).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "创建脱敏案例失败"}) return } c.JSON(http.StatusCreated, study) } } func updateCaseStudy(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") var study model.CaseStudy if db.Where("id = ?", id).First(&study).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"}) return } if !requireCaseStudyAccess(db, c, study) { return } var body struct { Title *string `json:"title"` Disease *string `json:"disease"` Region *string `json:"region"` CaseDate *time.Time `json:"caseDate"` Summary *string `json:"summary"` DesensitizedPayload json.RawMessage `json:"desensitizedPayload"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updates := map[string]interface{}{} if body.Title != nil { updates["title"] = *body.Title } if body.Disease != nil { updates["disease"] = *body.Disease } if body.Region != nil { updates["region"] = *body.Region } if body.CaseDate != nil { updates["case_date"] = *body.CaseDate } if body.Summary != nil { updates["summary"] = *body.Summary } if len(body.DesensitizedPayload) > 0 { updates["desensitized_payload"] = body.DesensitizedPayload } if len(updates) > 0 { if err := db.Model(&model.CaseStudy{}).Where("id = ?", id).Updates(updates).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "更新案例失败"}) return } } db.Where("id = ?", id).First(&study) c.JSON(http.StatusOK, study) } } func reviewCaseStudy(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") var study model.CaseStudy if db.Where("id = ?", id).First(&study).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "case study not found"}) return } if !requireCaseStudyAccess(db, c, study) { return } var body struct { Status string `json:"status"` Note *string `json:"note"` } if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if !model.ValidCaseStudyTransition(study.Status, body.Status) { c.JSON(http.StatusBadRequest, gin.H{"error": "非法的案例状态流转"}) return } now := time.Now() updates := map[string]interface{}{ "status": body.Status, "reviewer_id": currentUserID(c), "reviewed_at": now, } if body.Note != nil { updates["review_note"] = *body.Note } if body.Status == "published" { updates["published_at"] = now } if err := db.Model(&model.CaseStudy{}).Where("id = ?", id).Updates(updates).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "审核案例失败"}) return } db.Where("id = ?", id).First(&study) c.JSON(http.StatusOK, study) } } func requireCaseStudyAccess(db *gorm.DB, c *gin.Context, study model.CaseStudy) bool { if canManageCaseStudy(db, c, study) { return true } c.JSON(http.StatusForbidden, gin.H{"error": "无权管理该案例"}) return false } func canManageCaseStudy(db *gorm.DB, c *gin.Context, study model.CaseStudy) bool { if hasGlobalAccess(c) { return true } if study.SourceRoomID != nil && canAccessRoom(db, c, study.SourceRoomID) { return true } userID := currentUserID(c) if study.CreatedBy != nil && userID != nil && *study.CreatedBy == *userID { return true } return false } func desensitizedConsultationPayload(db *gorm.DB, consultation model.Consultation) json.RawMessage { data := map[string]interface{}{ "sourceStatus": consultation.Status, "createdAt": consultation.CreatedAt, } if consultation.Summary != nil { data["summary"] = *consultation.Summary } if consultation.Opinion != nil { data["opinion"] = *consultation.Opinion } if consultation.Plan != nil { data["plan"] = *consultation.Plan } if consultation.RoomID != nil { var room model.Room if db.Select("region").Where("id = ?", *consultation.RoomID).First(&room).Error == nil && room.Region != nil { data["region"] = *room.Region } } if consultation.LampTestID != nil { var lamp model.LampTest if db.Select("method", "result", "resulted_at", "status"). Where("id = ?", *consultation.LampTestID).First(&lamp).Error == nil { data["method"] = lamp.Method data["result"] = lamp.Result data["detectionStatus"] = lamp.Status data["resultedAt"] = lamp.ResultedAt } } raw, _ := json.Marshal(data) return raw }