package handler import ( "net/http" "silk-server-go/internal/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) const defaultOrgCode = "default" // currentUserRole 从 JWT context 中取当前角色。 func currentUserRole(c *gin.Context) string { user, ok := c.Get("user") if !ok { return "" } m, ok := user.(map[string]interface{}) if !ok { return "" } role, _ := m["role"].(string) return role } // hasGlobalAccess admin 拥有跨组织全部对象访问权。 func hasGlobalAccess(c *gin.Context) bool { return currentUserRole(c) == model.RoleAdmin } // applyOrgScope 对组织归属列(如 rooms.org_id)追加当前用户可见组织过滤。 func applyOrgScope(q *gorm.DB, c *gin.Context, column string) *gorm.DB { if hasGlobalAccess(c) { return q } userID := currentUserID(c) if userID == nil { return q.Where("1 = 0") } return q.Where("CAST("+column+" AS text) IN (SELECT organization_id::text FROM organization_members WHERE user_id = ?)", *userID) } // applyRoomScope 对业务记录的房间列追加当前用户可见房间过滤。 func applyRoomScope(q *gorm.DB, c *gin.Context, column string) *gorm.DB { if hasGlobalAccess(c) { return q } userID := currentUserID(c) if userID == nil { return q.Where("1 = 0") } return q.Where("CAST("+column+" AS text) IN ("+ "SELECT r.id::text FROM rooms r "+ "JOIN organization_members om ON om.organization_id = r.org_id "+ "WHERE om.user_id = ?)", *userID) } // canAccessOrganization 判断当前用户是否属于指定组织。 func canAccessOrganization(db *gorm.DB, c *gin.Context, orgID string) bool { if hasGlobalAccess(c) { return true } userID := currentUserID(c) if userID == nil { return false } var count int64 db.Model(&model.OrganizationMember{}). Where("organization_id = ? AND user_id = ?", orgID, *userID). Count(&count) return count > 0 } // canAccessRoom 判断当前用户是否能访问指定房间。 func canAccessRoom(db *gorm.DB, c *gin.Context, roomID *string) bool { if roomID == nil || *roomID == "" { return false } if hasGlobalAccess(c) { return true } userID := currentUserID(c) if userID == nil { return false } var count int64 db.Model(&model.Room{}). Joins("JOIN organization_members om ON om.organization_id = rooms.org_id"). Where("rooms.id = ? AND om.user_id = ?", *roomID, *userID). Count(&count) return count > 0 } // canAccessDeviceKey 判断当前用户是否能访问指定 deviceKey 对应的设备。 func canAccessDeviceKey(db *gorm.DB, c *gin.Context, deviceKey string) bool { if hasGlobalAccess(c) { return true } if deviceKey == "" { return false } if currentUserID(c) == nil { return false } var device model.Device if db.Where("device_key = ?", deviceKey).First(&device).Error != nil { return false } return canAccessRoom(db, c, &device.RoomID) } // requireDeviceKeyAccess 统一校验 deviceKey 对象授权;无权返回 403。 func requireDeviceKeyAccess(c *gin.Context, db *gorm.DB, deviceKey string) bool { if canAccessDeviceKey(db, c, deviceKey) { return true } if deviceKey == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "deviceKey 不能为空"}) return false } var count int64 db.Model(&model.Device{}).Where("device_key = ?", deviceKey).Count(&count) if count == 0 { c.JSON(http.StatusNotFound, gin.H{"error": "device not found"}) return false } c.JSON(http.StatusForbidden, gin.H{"error": "无权访问该设备"}) return false } // requireObjectAccess 统一对象授权检查;无权返回 403。 func requireObjectAccess(c *gin.Context, db *gorm.DB, entityType, id string) bool { if hasGlobalAccess(c) { return true } roomID, ok := objectRoomID(db, entityType, id) if !ok { c.JSON(http.StatusNotFound, gin.H{"error": entityType + " not found"}) return false } if !canAccessRoom(db, c, roomID) { c.JSON(http.StatusForbidden, gin.H{"error": "无权访问该对象"}) return false } return true } // objectRoomID 返回实体归属房间;实体不存在时返回 false。 func objectRoomID(db *gorm.DB, entityType, id string) (*string, bool) { switch entityType { case "room": var room model.Room if db.Where("id = ?", id).First(&room).Error != nil { return nil, false } return &room.ID, true case "device": var device model.Device if db.Where("id = ?", id).First(&device).Error != nil { return nil, false } return &device.RoomID, true case "sensor": var sensor model.Sensor if db.Where("id = ?", id).First(&sensor).Error != nil { return nil, false } var device model.Device if db.Where("id = ?", sensor.DeviceID).First(&device).Error != nil { return nil, false } return &device.RoomID, true case "threshold": var threshold model.Threshold if db.Where("id = ?", id).First(&threshold).Error != nil { return nil, false } var sensor model.Sensor if db.Where("id = ?", threshold.SensorID).First(&sensor).Error != nil { return nil, false } var device model.Device if db.Where("id = ?", sensor.DeviceID).First(&device).Error != nil { return nil, false } return &device.RoomID, true case "alarm": var alarm model.Alarm if db.Where("id = ?", id).First(&alarm).Error != nil { return nil, false } if alarm.DeviceKey == nil || *alarm.DeviceKey == "" { return nil, false } var device model.Device if db.Where("device_key = ?", *alarm.DeviceKey).First(&device).Error != nil { return nil, false } return &device.RoomID, true case "device_maintenance_record": var rec model.DeviceMaintenanceRecord if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } var device model.Device if db.Where("id = ?", rec.DeviceID).First(&device).Error != nil { return nil, false } return &device.RoomID, true case "camera": var camera model.Camera if db.Where("id = ?", id).First(&camera).Error != nil { return nil, false } return camera.RoomID, true case "video_clip": var clip model.VideoClip if db.Where("id = ?", id).First(&clip).Error != nil { return nil, false } return clip.RoomID, true case "tray": var tray model.Tray if db.Where("id = ?", id).First(&tray).Error != nil { return nil, false } return &tray.RoomID, true case "batch": var batch model.Batch if db.Where("id = ?", id).First(&batch).Error != nil { return nil, false } return &batch.RoomID, true case "rearing_record": var rec model.RearingRecord if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } return batchRoomID(db, &rec.BatchID) case "inspection": var rec model.InspectionRecord if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } return rec.RoomID, true case "lamp_test": var rec model.LampTest if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } return rec.RoomID, true case "detection_task": var rec model.DetectionTask if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } return rec.RoomID, true case "consultation": var rec model.Consultation if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } return rec.RoomID, true case "trace_record": var rec model.TraceRecord if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } return rec.RoomID, true case "disease_event": var rec model.DiseaseEvent if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } return rec.RoomID, true case "sample": var rec model.Sample if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } return rec.RoomID, true case "seed_source": var rec model.SeedSource if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } return batchRoomID(db, rec.BatchID) case "disinfection_record": var rec model.DisinfectionRecord if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } if rec.RoomID != nil { return rec.RoomID, true } return batchRoomID(db, rec.BatchID) case "production_loss_record": var rec model.ProductionLossRecord if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } if rec.RoomID != nil { return rec.RoomID, true } return batchRoomID(db, rec.BatchID) case "laboratory_result": var rec model.LaboratoryResult if db.Where("id = ?", id).First(&rec).Error != nil { return nil, false } if rec.RoomID != nil { return rec.RoomID, true } if rec.BatchID != nil { return batchRoomID(db, rec.BatchID) } if rec.TraceRecordID != nil { var trace model.TraceRecord if db.Where("id = ?", *rec.TraceRecordID).First(&trace).Error == nil { return trace.RoomID, true } } return nil, true default: return nil, false } } func batchRoomID(db *gorm.DB, batchID *string) (*string, bool) { if batchID == nil { return nil, false } var batch model.Batch if db.Where("id = ?", *batchID).First(&batch).Error != nil { return nil, false } return &batch.RoomID, true } // defaultOrgID 返回当前用户第一个组织;管理员未指定时返回默认组织。 func defaultOrgID(db *gorm.DB, c *gin.Context) *string { if userID := currentUserID(c); userID != nil { var member model.OrganizationMember if db.Where("user_id = ?", *userID).Order("created_at ASC").First(&member).Error == nil { return &member.OrganizationID } } var org model.Organization if db.Where("code = ?", defaultOrgCode).First(&org).Error == nil { return &org.ID } return nil } // firstAccessibleRoomID 返回当前用户可见的第一个房间;用于无显式 roomId 的历史创建流程。 func firstAccessibleRoomID(db *gorm.DB, c *gin.Context) *string { var room model.Room q := db.Model(&model.Room{}) if !hasGlobalAccess(c) { q = applyOrgScope(q, c, "org_id") } if q.Order("created_at ASC").First(&room).Error == nil { return &room.ID } return nil } // assignDefaultOrganization 注册新用户时补齐默认组织成员关系。 func assignDefaultOrganization(db *gorm.DB, userID string) { var org model.Organization if db.Where("code = ?", defaultOrgCode).First(&org).Error != nil { return } var count int64 db.Model(&model.OrganizationMember{}). Where("organization_id = ? AND user_id = ?", org.ID, userID). Count(&count) if count == 0 { _ = db.Create(&model.OrganizationMember{OrganizationID: org.ID, UserID: userID, Role: "member"}).Error } }