chore: 同步本地 v9 整改与运营能力
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user